mirror of
https://github.com/infinition/Bjorn.git
synced 2026-03-09 06:01:59 +00:00
- Implemented methods for fetching AI stats, training history, and recent experiences. - Added functionality to set operation mode (MANUAL, AUTO, AI) with appropriate handling. - Included helper methods for querying the database and sending JSON responses. - Integrated model metadata extraction for visualization purposes.
192 lines
6.5 KiB
Python
192 lines
6.5 KiB
Python
#!/usr/bin/env python3
|
|
# -*- coding: utf-8 -*-
|
|
"""
|
|
thor_hammer.py — Service fingerprinting (Pi Zero friendly, orchestrator compatible).
|
|
|
|
What it does:
|
|
- For a given target (ip, port), tries a fast TCP connect + banner grab.
|
|
- Optionally stores a service fingerprint into DB.port_services via db.upsert_port_service.
|
|
- Updates EPD fields: bjorn_orch_status, bjorn_status_text2, comment_params, bjorn_progress.
|
|
|
|
Notes:
|
|
- Avoids spawning nmap per-port (too heavy). If you want nmap, add a dedicated action.
|
|
"""
|
|
|
|
import logging
|
|
import socket
|
|
import time
|
|
from typing import Dict, Optional, Tuple
|
|
|
|
from logger import Logger
|
|
from actions.bruteforce_common import ProgressTracker
|
|
|
|
logger = Logger(name="thor_hammer.py", level=logging.DEBUG)
|
|
|
|
# -------------------- Action metadata (AST-friendly) --------------------
|
|
b_class = "ThorHammer"
|
|
b_module = "thor_hammer"
|
|
b_status = "ThorHammer"
|
|
b_port = None
|
|
b_parent = None
|
|
b_service = '["ssh","ftp","telnet","http","https","smb","mysql","postgres","mssql","rdp","vnc"]'
|
|
b_trigger = "on_port_change"
|
|
b_priority = 35
|
|
b_action = "normal"
|
|
b_cooldown = 1200
|
|
b_rate_limit = "24/86400"
|
|
b_enabled = 0 # keep disabled by default; enable via Actions UI/DB when ready.
|
|
|
|
|
|
def _guess_service_from_port(port: int) -> str:
|
|
mapping = {
|
|
21: "ftp",
|
|
22: "ssh",
|
|
23: "telnet",
|
|
25: "smtp",
|
|
53: "dns",
|
|
80: "http",
|
|
110: "pop3",
|
|
139: "netbios-ssn",
|
|
143: "imap",
|
|
443: "https",
|
|
445: "smb",
|
|
1433: "mssql",
|
|
3306: "mysql",
|
|
3389: "rdp",
|
|
5432: "postgres",
|
|
5900: "vnc",
|
|
8080: "http",
|
|
}
|
|
return mapping.get(int(port), "")
|
|
|
|
|
|
class ThorHammer:
|
|
def __init__(self, shared_data):
|
|
self.shared_data = shared_data
|
|
|
|
def _connect_and_banner(self, ip: str, port: int, timeout_s: float, max_bytes: int) -> Tuple[bool, str]:
|
|
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
s.settimeout(timeout_s)
|
|
try:
|
|
if s.connect_ex((ip, int(port))) != 0:
|
|
return False, ""
|
|
try:
|
|
data = s.recv(max_bytes)
|
|
banner = (data or b"").decode("utf-8", errors="ignore").strip()
|
|
except Exception:
|
|
banner = ""
|
|
return True, banner
|
|
finally:
|
|
try:
|
|
s.close()
|
|
except Exception:
|
|
pass
|
|
|
|
def execute(self, ip, port, row, status_key) -> str:
|
|
if self.shared_data.orchestrator_should_exit:
|
|
return "interrupted"
|
|
|
|
try:
|
|
port_i = int(port) if str(port).strip() else None
|
|
except Exception:
|
|
port_i = None
|
|
|
|
# If port is missing, try to infer from row 'Ports' and fingerprint a few.
|
|
ports_to_check = []
|
|
if port_i:
|
|
ports_to_check = [port_i]
|
|
else:
|
|
ports_txt = str(row.get("Ports") or row.get("ports") or "")
|
|
for p in ports_txt.split(";"):
|
|
p = p.strip()
|
|
if p.isdigit():
|
|
ports_to_check.append(int(p))
|
|
ports_to_check = ports_to_check[:12] # Pi Zero guard
|
|
|
|
if not ports_to_check:
|
|
return "failed"
|
|
|
|
timeout_s = float(getattr(self.shared_data, "thor_connect_timeout_s", 1.5))
|
|
max_bytes = int(getattr(self.shared_data, "thor_banner_max_bytes", 1024))
|
|
source = str(getattr(self.shared_data, "thor_source", "thor_hammer"))
|
|
|
|
mac = (row.get("MAC Address") or row.get("mac_address") or row.get("mac") or "").strip()
|
|
hostname = (row.get("Hostname") or row.get("hostname") or "").strip()
|
|
if ";" in hostname:
|
|
hostname = hostname.split(";", 1)[0].strip()
|
|
|
|
self.shared_data.bjorn_orch_status = "ThorHammer"
|
|
self.shared_data.bjorn_status_text2 = ip
|
|
self.shared_data.comment_params = {"ip": ip, "port": str(ports_to_check[0])}
|
|
|
|
progress = ProgressTracker(self.shared_data, len(ports_to_check))
|
|
|
|
try:
|
|
any_open = False
|
|
for p in ports_to_check:
|
|
if self.shared_data.orchestrator_should_exit:
|
|
return "interrupted"
|
|
|
|
ok, banner = self._connect_and_banner(ip, p, timeout_s=timeout_s, max_bytes=max_bytes)
|
|
any_open = any_open or ok
|
|
|
|
service = _guess_service_from_port(p)
|
|
product = ""
|
|
version = ""
|
|
fingerprint = banner[:200] if banner else ""
|
|
confidence = 0.4 if ok else 0.1
|
|
state = "open" if ok else "closed"
|
|
|
|
self.shared_data.comment_params = {
|
|
"ip": ip,
|
|
"port": str(p),
|
|
"open": str(int(ok)),
|
|
"svc": service or "?",
|
|
}
|
|
|
|
# Persist to DB if method exists.
|
|
try:
|
|
if hasattr(self.shared_data, "db") and hasattr(self.shared_data.db, "upsert_port_service"):
|
|
self.shared_data.db.upsert_port_service(
|
|
mac_address=mac or "",
|
|
ip=ip,
|
|
port=int(p),
|
|
protocol="tcp",
|
|
state=state,
|
|
service=service or None,
|
|
product=product or None,
|
|
version=version or None,
|
|
banner=banner or None,
|
|
fingerprint=fingerprint or None,
|
|
confidence=float(confidence),
|
|
source=source,
|
|
)
|
|
except Exception as e:
|
|
logger.error(f"DB upsert_port_service failed for {ip}:{p}: {e}")
|
|
|
|
progress.advance(1)
|
|
|
|
progress.set_complete()
|
|
return "success" if any_open else "failed"
|
|
finally:
|
|
self.shared_data.bjorn_progress = ""
|
|
self.shared_data.comment_params = {}
|
|
self.shared_data.bjorn_status_text2 = ""
|
|
|
|
|
|
# -------------------- Optional CLI (debug/manual) --------------------
|
|
if __name__ == "__main__":
|
|
import argparse
|
|
from shared import SharedData
|
|
|
|
parser = argparse.ArgumentParser(description="ThorHammer (service fingerprint)")
|
|
parser.add_argument("--ip", required=True)
|
|
parser.add_argument("--port", default="22")
|
|
args = parser.parse_args()
|
|
|
|
sd = SharedData()
|
|
act = ThorHammer(sd)
|
|
row = {"MAC Address": sd.get_raspberry_mac() or "__GLOBAL__", "Hostname": "", "Ports": args.port}
|
|
print(act.execute(args.ip, args.port, row, "ThorHammer"))
|
|
|