Stellt euch dieses Skript als das Gehirn und Herz des gesamten Projekts vor. Wenn ein Operator das Tool im Terminal startet, ist live_recon.py das erste, was zum Leben erweckt wird. Es ist der Dirigent, der das Orchester aus verschiedenen Scan-Tools steuert. Seine Aufgabe ist es, Befehle entgegenzunehmen, die Mission zu planen, die einzelnen Soldaten (Scans) loszuschicken, ihre Berichte (Live-Funde) in Echtzeit zu sammeln und am Ende das gesamte Schlachtfeld zu überblicken.
diff_intern_extern(target)
def diff_intern_extern(target):
try:
ip = ipaddress.ip_address(target)
return "internal" if ip.is_private or ip.is_loopback else "external"
except ValueError:
return "external"
Ziel: Diese Funktion ist unser erster Aufklärer. Sie muss eine kritische Entscheidung treffen: Greifen wir ein Ziel im internen Netzwerk (z.B. in einem Firmennetz) oder im externen, öffentlichen Internet an? Davon hängen später Scan-Strategien ab.
Zeile 2: try: - Dies leitet einen "sicheren" Block ein. Der Code hier wird versucht, aber wenn ein Fehler auftritt, stürzt das Programm nicht ab, sondern springt zum except-Block. Es ist unser Sicherheitsnetz.
Zeile 3: ip = ipaddress.ip_address(target) - Die Bibliothek ipaddress ist wie ein Ausweisprüfer für IP-Adressen. Sie versucht, den Text (target) in ein spezielles IP-Objekt umzuwandeln. Das funktioniert nur, wenn der Text eine gültige IP-Adresse wie "192.168.1.1" oder "8.8.8.8" ist.
Zeile 4: return "internal" if ip.is_private ... - Das ist das Herzstück. Das IP-Objekt hat eingebaute Superkräfte. ip.is_private erkennt automatisch, ob es eine private IP (wie 192.168.x.x, 10.x.x.x) ist. ip.is_loopback erkennt die "sich selbst"-Adresse (127.0.0.1). Wenn eine der beiden zutrifft, ist es ein internes Ziel. Sonst ist es extern.
Zeile 5-6: except ValueError: return "external" - Das ist der Plan B. Wenn der Ausweisprüfer in Zeile 3 fehlschlägt (weil der target z.B. "google.com" ist), löst er einen ValueError aus. Wir fangen diesen Fehler ab und gehen als Sicherheitsmaßnahme immer davon aus, dass ein Domainname ein externes Ziel ist.
if __name__ == "__main__":
if __name__ == "__main__":
os.system("clear")
print_mega_banner()
check_dependencies()
time.sleep(1)
parser = argparse.ArgumentParser(description="Recon Monster – Autonomous Recon Framework")
parser.add_argument("--ip", required=True, help="Target IP address or hostname")
args = parser.parse_args()
target = args.ip
sudo = [] if os.getuid() == 0 else ["sudo"]
scan_mode = diff_intern_extern(target)
log_dir = f"logs/{target}"
os.makedirs(log_dir, exist_ok=True)
live_findings = {
"ipv6": set(),
"nmap": set(),
"webserver": set(),
"methods": set(),
"cookie": set(),
"nikto": set(),
"ferox": set(),
}
Grundlagen: Der Block if __name__ == "__main__": ist ein Standard in Python. Er sorgt dafür, dass dieser Code nur dann ausgeführt wird, wenn wir das Skript direkt starten (python live_recon.py), aber nicht, wenn wir es als Modul in ein anderes Skript importieren.
Vorbereitung (Zeile 2-5): Bevor der Kampf beginnt, wird das Schlachtfeld vorbereitet. os.system("clear") säubert den Bildschirm. print_mega_banner() sorgt für den epischen ersten Eindruck. check_dependencies() ist der Waffen-Check – sind alle externen Tools (Soldaten) wie Nmap anwesend?
Befehle entgegennehmen (Zeile 7-10): argparse ist wie ein Bestellformular für die Kommandozeile. Wir definieren, welche Informationen wir vom Nutzer brauchen (--ip) und machen es zu einer Pflicht (required=True). parser.parse_args() liest dann die Eingabe des Nutzers und speichert sie.
Root-Check (Zeile 12): os.getuid() == 0 ist der Weg in Linux/Unix, um zu fragen: "Bin ich der Gott-Kaiser (root)?". Die User-ID von root ist immer 0. Wenn ja, ist die sudo-Liste leer. Wenn nein, wird die Liste mit dem Wort "sudo" gefüllt, damit wir es später vor Befehle setzen können, die Admin-Rechte benötigen.
Datenstruktur (Zeile 18-26): live_findings ist unser zentrales Notizbuch. Ein set ist wie eine Liste, hat aber eine Superkraft: Es kann jeden Eintrag nur einmal enthalten. Wenn wir 10x "http" hinzufügen, steht am Ende trotzdem nur einmal "http" drin. Das ist perfekt für unseren Live-Banner, der keine Duplikate anzeigen soll.
for scan in SCANS_TO_RUN:
for scan in SCANS_TO_RUN:
proc = None
try:
print_live_banner(scan_mode, live_findings)
print_scan_title(scan["name"])
cmd = [p.replace("{TARGET}", target) for p in scan["command"]]
if cmd[0] not in ["curl", "ping6"]:
cmd = sudo + cmd
proc = subprocess.Popen(
cmd,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
text=True,
errors="ignore"
)
for line in proc.stdout:
is_ferox = "ferox" in scan["name"].lower()
if is_ferox:
try:
if line.strip().startswith("{"):
data = json.loads(line)
url = data.get("url", "")
status = data.get("status", 200)
lime_color = "\033[92m"
reset_color = "\033[0m"
print(f"{lime_color}[+] Found: {url} (Status: {status}){reset_color}")
else:
print(line.rstrip())
except json.JSONDecodeError:
print(line.rstrip())
else:
print(line.rstrip())
parse_line(scan["name"], line, live_findings)
proc.wait()
print_scan_end()
if "Nmap" in scan["name"]:
jf = f"{log_dir}/nmap_full.json"
if os.path.exists(jf):
tasks = NmapRunner(jf).run_analysis()
for t in tasks:
live_findings["nmap"].add(t)
except KeyboardInterrupt:
print(f"\n\n[!] SCAN SKIPPED: {scan['name']} interrupted by user.")
if proc:
proc.terminate()
try:
proc.wait(timeout=2)
except subprocess.TimeoutExpired:
proc.kill()
print_scan_end()
time.sleep(1)
continue
Ziel: Das ist die Haupt-Engine. Sie arbeitet die Liste der Scans aus scans.py systematisch ab.
Befehl bauen (Zeile 7): [p.replace("{TARGET}", target) for p in scan["command"]] ist eine elegante Python-Kurzschreibweise ("List Comprehension"). Stellt es euch wie eine Mini-Fabrik in einer Zeile vor: Sie nimmt die Befehls-Vorlage, stanzt das echte Ziel an die richtige Stelle und legt das fertige Teil in eine neue Liste namens cmd.
Prozess starten (Zeile 11): subprocess.Popen ist der Befehl, um einen neuen, unabhängigen Prozess (unseren Scan) zu starten. Die Parameter sind entscheidend:
stdout=subprocess.PIPE: Sagt dem Prozess: "Schick mir deinen Output nicht direkt ins Terminal, sondern durch eine unsichtbare Röhre (Pipe), damit ich ihn lesen kann."stderr=subprocess.STDOUT: Leitet auch alle Fehlermeldungen in dieselbe Röhre um. So entgeht uns nichts.Live-Parsing (Zeile 21): for line in proc.stdout: ist die Magie. Diese Schleife wartet nicht, bis der Scan fertig ist. Sie lauscht an der "Röhre" und schnappt sich **jede Zeile einzeln**, sobald sie ankommt. Das ermöglicht die Echtzeit-Analyse.
Der Feroxbuster-Filter (Zeile 23-38): Dein Schüler hat gefragt, was line.strip().startswith("{") bedeutet. Hier ist die Erklärung: Das ist eine Kette von Befehlen.
line.strip(): Ist wie eine Putzfrau. Sie nimmt die Zeile und entfernt alle unsichtbaren Leerzeichen oder Zeilenumbrüche am Anfang und Ende..startswith("{"): Nach dem Putzen schaut sie sich das erste Zeichen an. Ist es eine öffnende geschweifte Klammer {?{. Diese Zeile ist unser Goldfilter, der den normalen "Arbeitslärm" von den wertvollen JSON-Funden trennt.
Nmap Nach-Analyse (Zeile 43-48): Nmap ist unser wichtigster Spion. Nachdem er seinen Bericht abgegeben hat (die JSON-Datei), wird hier ein Spezialist (NmapRunner) darauf angesetzt, um aus den Rohdaten konkrete Folge-Missionen (Subroutinen) abzuleiten.
Nach der Schleife
print_live_banner(scan_mode, live_findings)
for task in live_findings["nmap"]:
fn = getattr(nmap_subroutines, task, None)
if fn:
fn(target)
show_detailed_logs(log_dir)
Ziel: Nachdem alle primären Scans durch sind, beginnt die Phase der Auswertung und des finalen Angriffs.
Dynamische Ausführung (Zeile 4-7): Das ist eine Technik für Fortgeschrittene und extrem mächtig. getattr(nmap_subroutines, task, None) ist wie ein magischer Griff in eine Werkzeugkiste. Es sagt: "Gib mir das Werkzeug (die Funktion) aus der nmap_subroutines-Kiste, dessen Name genau im Text task steht." Wenn der Text "ftp_brute" ist, holt es die ftp_brute-Funktion. Das erlaubt uns, Funktionen dynamisch aufzurufen, ohne eine riesige if/elif/else-Kette bauen zu müssen. Das macht den Code unglaublich flexibel für zukünftige Erweiterungen.
Debriefing (Zeile 9): show_detailed_logs(log_dir) ist der letzte Schritt. Der Operator bekommt die Möglichkeit, sich alle originalen, ungefilterten Berichte seiner Soldaten (der Scan-Tools) anzusehen.
Jede Armee braucht Pioniere und Logistiker. Das utils.py-Modul ist genau das: unser Schweizer Taschenmesser. Es enthält fundamentale Helfer-Funktionen, die von anderen Teilen des Programms immer wieder aufgerufen werden. Die wichtigste Mission dieses Moduls ist der Waffen-Check (check_dependencies), der sicherstellt, dass alle unsere "Soldaten" (externe Tools wie Nmap) vor dem Kampf anwesend und einsatzbereit sind.
check_dependencies()
import shutil
import sys
import subprocess
import os
def check_dependencies():
print("[+] Checking toolchain...")
required_tools = [
"nmap", "nikto", "feroxbuster", "curl", "wpscan",
"joomscan", "hydra", "rpcclient", "enum4linux", "wget"
]
missing_tools = []
for tool in required_tools:
if shutil.which(tool) is None:
missing_tools.append(tool)
if missing_tools:
print(f"[ERROR] The following tools are missing: {', '.join(missing_tools)}")
answer = input(
"Should Recon Monster attempt to install them automatically? "
"(apt-get will be used) [y/N]: "
)
if answer.lower() == 'y':
print("[+] Attempting to install missing tools...")
sudo_prefix = [] if os.getuid() == 0 else ["sudo"]
try:
update_command = sudo_prefix + ["apt-get", "update"]
subprocess.run(update_command, check=True)
install_command = sudo_prefix + ["apt-get", "install", "-y"] + missing_tools
subprocess.run(install_command, check=True)
print("[+] Installation successful! Re-checking dependencies...")
check_dependencies()
except subprocess.CalledProcessError:
print("[ERROR] Automatic installation failed. Please install the tools manually.")
sys.exit(1)
except FileNotFoundError:
print("[ERROR] 'sudo' or 'apt-get' not found. Please install dependencies manually.")
sys.exit(1)
else:
print("[ERROR] Aborted by user. Please install the missing tools manually.")
sys.exit(1)
else:
print("[+] All required tools are installed and ready.")
Ziel: Diese Funktion ist unser Waffenmeister. Sie stellt sicher, dass das Tool nicht mitten im Kampf abstürzt, weil ein externes Programm fehlt. Sie prüft das gesamte Arsenal und bietet sogar an, fehlende Waffen automatisch nachzurüsten.
Zeile 9: required_tools = [...] - Das ist die offizielle Ausrüstungsliste. Jedes Tool, das wir für unsere Scans benötigen, wird hier aufgeführt.
High-End-Befehl: shutil.which(tool) (Zeile 16)
shutil.which() wie einen Späher vor, der durch das gesamte Betriebssystem läuft und fragt: "Gibt es hier irgendwo ein ausführbares Programm mit dem Namen 'nmap'?"./usr/bin/nmap).None (Nichts) zurück.Automatische Installation (Zeile 27-46): Das ist ein Feature für Fortgeschrittene und extrem benutzerfreundlich.
sudo benötigt wird.subprocess.run() die Befehle apt-get update und apt-get install ... ausgeführt. Der Parameter check=True ist eine eingebaute Sicherung: Wenn einer dieser Befehle fehlschlägt (z.B. weil das Paket nicht gefunden wurde), löst er sofort einen CalledProcessError aus, den wir im except-Block abfangen können.check_dependencies() - Ein genialer, rekursiver Schachzug. Nach der Installation ruft sich die Funktion selbst erneut auf, um zu verifizieren, dass jetzt wirklich alle Waffen vorhanden sind.Der Notausgang (Zeile 48): sys.exit(1) - Wenn Werkzeuge fehlen und der Nutzer die Installation ablehnt (oder sie fehlschlägt), ist das der rote Knopf. sys.exit(1) beendet das Programm sofort und kompromisslos. Die Zahl 1 ist ein Standard-Code, um dem Betriebssystem zu signalisieren: "Die Mission wurde mit einem Fehler abgebrochen."
Wenn live_recon.py der Dirigent ist, dann ist scans.py das Notenblatt. Dieses Modul ist keine aktive Waffe, sondern der strategische Schlachtplan unserer gesamten Operation. Es enthält eine einzige, aber entscheidende Variable: SCANS_TO_RUN. Das ist eine geordnete Liste, die exakt definiert, welche Scans in welcher Reihenfolge mit welchen Parametern ausgeführt werden sollen. Die Schönheit dieses Ansatzes ist die extreme Flexibilität: Um einen Scan zu ändern, hinzuzufügen oder zu entfernen, müssen wir nur dieses eine "Notenblatt" anpassen, nicht die komplexe Logik des Dirigenten.
Dieses Modul ist reine Konfiguration. Es benötigt keine externen Bibliotheken, um seine Mission zu erfüllen. Es ist ein reines Datenmodul.
SCANS_TO_RUN
SCANS_TO_RUN = [
{
"name": "IPv6 Discovery Scan",
"command": ["ping6", "-c", "3", "ff02::1"]
},
{
"name": "Curl - HTTP Headers",
"command": ["curl", "-I", "-L", "-v", "http://{TARGET}", "-s"]
},
# ... weitere Scan-Definitionen ...
{
"name": "Nmap Full Scan",
"command": ["nmap", "-sS", "-sC", "-sV", "-p-", "-Pn",
"--min-rate", "5000", "-oA", "logs/{TARGET}/nmap_full",
"-oJ", "logs/{TARGET}/nmap_full.json", "{TARGET}"]
},
{
"name": "Nikto Scan",
"command": ["nikto", "-h", "{TARGET}", "-nointeractive",
"-Format", "json", "-output", "logs/{TARGET}/nikto.json",
"-Format", "txt", "-output", "logs/{TARGET}/nikto.txt"]
},
# ... etc. ...
]
Ziel: Diese eine Variable ist das Gehirn der gesamten Operation. Sie ist eine Liste von Dictionaries.
Die Struktur (High-End-Konzept): Jedes Element in der Liste ist ein Dictionary (ein "Karteikasten"), das einen einzelnen Scan beschreibt. Dieser Karteikasten hat immer zwei Schubladen:
"name": Ein menschenlesbarer Name für den Scan, der im Live-Banner angezeigt wird (z.B. "Nmap Full Scan")."command": Eine Liste von Strings, die den exakten Befehl und seine Parameter darstellt.Der Platzhalter {TARGET} (Architektonische Meisterleistung): Anstatt das Ziel in jedem Befehl fest einzuprogrammieren, wird der generische Platzhalter {TARGET} verwendet. Die Kommandozentrale (live_recon.py) ist dafür verantwortlich, diesen Platzhalter zur Laufzeit durch das tatsächliche Ziel zu ersetzen. Das macht den Schlachtplan wiederverwendbar und extrem flexibel.
Analyse der Befehle: Die Befehle selbst sind die eines Profis.
--min-rate 5000 und -T5 (in anderen Scans) befehlen Nmap, extrem aggressiv und schnell zu scannen. -oA und -oJ sind Befehle an Nmap, die Ergebnisse in allen wichtigen Formaten (normal, XML, grep-bar) UND als JSON-Datei zu speichern. Die JSON-Datei ist die Grundlage für unsere spätere Tiefenanalyse.-nointeractive ist entscheidend für die Automatisierung, da er verhindert, dass Nikto auf Nutzereingaben wartet. Die Ergebnisse werden ebenfalls in mehreren Formaten (JSON und TXT) gespeichert.Die Reihenfolge ist entscheidend: Die Scans werden exakt in der Reihenfolge ausgeführt, in der sie in dieser Liste stehen. Leichte, schnelle Scans wie curl stehen am Anfang, um schnell erste Ergebnisse zu liefern, während die langen, intensiven Scans wie der Nmap Full Scan später kommen.
Stellt euch dieses Modul als das Munitionslager und die Feind-Datenbank unserer Operation vor. Ein Soldat im Feld, der einen Panzer sieht, meldet nicht "großes graues Ding", er meldet "T-72 Panzer". Er braucht dafür eine Datenbank im Kopf. Dieses Modul liefert genau das: Es enthält die "Intelligenz" in Form von Python-Dictionaries (unseren Datenbanken), die es dem live_parser ermöglichen, aus rohen Scan-Ergebnissen (wie "Port 80 ist offen") konkrete, wertvolle Informationen (wie "HTTP-Webserver gefunden") zu machen. Es ist reines, hartcodiertes Wissen.
Dieses Modul ist, ähnlich wie scans.py, ein reines Datenmodul. Es ist eine Sammlung von Wissen und benötigt keine aktiven Bibliotheken, um seine Mission zu erfüllen.
NMAP_SPECIALS
NMAP_SPECIALS = {
"anonymous ftp login allowed": "ftp_anon",
"wp-config": "wpconfig",
"robots.txt": "robots",
"id_rsa": "ssh_key",
"microsoft windows": "windows_host",
"guest login": "smb_guest",
"sql server": "mssql_db",
"php version": "php_info",
"apache tomcat": "tomcat",
"nagios": "nagios",
"webmin": "webmin",
"moodle": "moodle",
"joomla": "joomla",
"wordpress": "wordpress"
}
Ziel: NMAP_SPECIALS ist unsere "Most Wanted"-Liste für textbasierte Funde. Es ist ein Python-Dictionary, eine Art intelligenter Karteikasten.
Die Architektur (High-End-Konzept: Key-Value Store): Ein Dictionary speichert Daten immer in Schlüssel-Wert-Paaren (Key-Value).
"anonymous ftp login allowed". Der Schlüssel ist immer einzigartig."ftp_anon".Herleitung der Funktion (Warum wurde das so gebaut?):
Stellen wir uns vor, wir hätten dieses Dictionary nicht. Der live_parser müsste dann einen riesigen, unübersichtlichen Block aus if/elif/else-Anweisungen haben:
if "anonymous ftp login allowed" in line:
live_findings["nmap"].add("ftp_anon")
elif "wp-config" in line:
live_findings["nmap"].add("wpconfig")
...und so weiter. Das wäre ein Albtraum zu warten und zu erweitern.
Die geniale Lösung: Wir lagern die gesamte "Intelligenz" in dieses Dictionary aus. Der live_parser kann jetzt eine einzige, elegante Schleife verwenden, um die gesamte Liste abzuarbeiten: for key, value in NMAP_SPECIALS.items(): if key in line: .... Um eine neue Signatur hinzuzufügen, müssen wir nur eine neue Zeile in dieses Dictionary einfügen, anstatt die komplexe Logik des Parsers zu ändern. Das entkoppelt die Daten von der Logik – ein Kernprinzip guter Software-Architektur.
Stellt euch dieses Modul als einen hochspezialisierten Signal-Offizier und Spion vor. Seine Mission ist es, den ununterbrochenen Strom an rohen Daten (line), der von unseren Soldaten (den Scan-Tools) hereinkommt, in Echtzeit abzuhören. Er muss jede einzelne Nachricht analysieren, den "Lärm" (unwichtige Informationen) herausfiltern und nur die kritischen "Live Hits" identifizieren. Diese wertvollen Informationen meldet er sofort an die Kommandozentrale (live_findings), damit das Lagebild im Live-Banner aktualisiert werden kann. Ohne diesen Spion wäre unser Live-Banner nutzlos.
remove_ansi(text)
import re
import json
from nmap_services_loader import NMAP_SPECIALS
def remove_ansi(text):
ansi_escape = re.compile(r'\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])')
return ansi_escape.sub('', text)
Ziel: Diese Funktion ist unsere "Dekontaminations-Einheit". Viele Kommandozeilen-Tools (wie Feroxbuster) benutzen unsichtbare ANSI-Escape-Codes, um ihren Output farbig zu machen. Für unser Skript ist das aber nur störender Datenmüll. Diese Funktion reinigt den Text von all diesen unsichtbaren Steuerzeichen.
High-End-Befehl: re.compile(...)
re.compile() zu Python: "Hey, dieses Muster werden wir oft benutzen. Analysiere es einmal, kompiliere es zu super-schnellem Maschinencode und speichere es." Das ist eine Performance-Optimierung. Bei hunderten oder tausenden von Zeilen, die wir parsen, macht das einen spürbaren Unterschied.Zeile 8: return ansi_escape.sub('', text) - Der .sub()-Befehl (substitute/ersetzen) ist die Exekution. Er nimmt den kompilierten Regex, sucht im text nach allen Treffern und ersetzt sie durch einen leeren String ('') – er löscht sie also effektiv.
parse_line(tool_name, line, live_findings)
def parse_line(tool_name, line, live_findings):
clean_line = remove_ansi(line).strip()
l = clean_line.lower()
if "nmap" in tool_name.lower():
# ... Nmap Parsing Logik ...
if "ferox" in tool_name.lower() or "gobuster" in tool_name.lower():
# ... Feroxbuster/Gobuster Parsing Logik ...
if "ipv6" in tool_name.lower():
# ... IPv6 Parsing Logik ...
# ... und so weiter für Nikto und Curl
Ziel: Das ist das Gehirn des Spions. Diese Funktion ist eine große Weiche, die entscheidet, wie eine Zeile analysiert wird, basierend darauf, von welchem Tool sie kommt.
Zeile 2-3 (Vorbereitung): Zuerst wird die Zeile durch unsere Dekontaminations-Einheit (remove_ansi) geschickt. .strip() entfernt dann noch eventuelle Leerzeichen am Anfang und Ende. Erst dann wird die saubere Zeile für die Analyse in Kleinbuchstaben umgewandelt.
Die if-Kaskade (Die Spezialabteilungen): Die Funktion ist wie eine Geheimdienst-Zentrale mit verschiedenen Abteilungen. if "nmap" in tool_name.lower(): fragt: "Ist diese Nachricht von unserem Nmap-Agenten?". Wenn ja, wird die Zeile an die Nmap-Spezialisten weitergegeben. Wenn nicht, wird die nächste Abteilung gefragt. Jeder if-Block ist eine hochspezialisierte Analyseeinheit nur für ein bestimmtes Tool.
High-End-Konzept (Herleitung der Funktion): Anstatt eine riesige, unlesbare Funktion zu schreiben, die alles kann, haben wir hier eine saubere "Single Responsibility" (Einzelverantwortung)-Architektur. Die parse_line-Funktion ist nur der Verteiler. Die eigentliche, komplexe Logik für jedes Tool ist in einem eigenen, isolierten if-Block gekapselt. Wenn wir in Zukunft einen neuen Parser für ein neues Tool (z.B. "sqlmap") hinzufügen wollen, müssen wir nur einen neuen if "sqlmap" in tool_name.lower():-Block hinzufügen, ohne den Rest des Codes anzufassen. Das ist modular, sauber und wartbar.
nmap-Block
#... (innerhalb des nmap-if-blocks)
if "openssh" in full_service_line:
live_findings["nmap"].add("openssh")
live_findings["nmap"].discard("ssh")
specific_found = True
#... (ähnliche Blöcke für apache, nginx, etc.)
ignore_list = ["unknown", "tcpwrapped", "service"]
if not specific_found and base_service not in ignore_list:
live_findings["nmap"].add(base_service)
Ziel: Dieser Block verfeinert die Nmap-Ergebnisse. Statt nur "http" zu melden, wollen wir wissen, ob es "http-apache" oder "http-nginx" ist.
High-End-Befehl: .discard("ssh") - Das ist der subtile, aber wichtige Bruder von .remove(). Wenn wir "openssh" finden, wissen wir, dass es spezifischer ist als das generische "ssh". Wir wollen das generische Label also aus unserem Notizbuch (dem set) entfernen. .remove("ssh") würde abstürzen, wenn "ssh" nicht im Set wäre. .discard("ssh") ist der Profi-Move: Es versucht, "ssh" zu entfernen. Wenn es da ist, ist es weg. Wenn nicht, **passiert einfach nichts.** Kein Fehler, kein Absturz. Das macht den Code extrem robust.
Herleitung der Logik: Die specific_found-Variable ist unser Gedächtnis. Sie merkt sich, ob wir schon einen sehr spezifischen Treffer hatten. Nur wenn am Ende kein spezifischer Treffer gefunden wurde (if not specific_found), fügen wir den allgemeinen Dienst (base_service) hinzu. Das verhindert, dass der Banner mit überflüssigen, generischen Labels wie "http" zugemüllt wird, wenn wir schon das viel wertvollere "http-apache" kennen.
Wenn der Live-Parser der Spion an der Front ist, dann ist der NmapRunner der Geheimdienst-Analyst im Hauptquartier. Seine Mission beginnt, *nachdem* der große Nmap-Scan abgeschlossen ist. Er nimmt den vollständigen, extrem detaillierten JSON-Bericht von Nmap, breitet ihn auf seinem Analysetisch aus und sucht nach strategischen Zielen für die zweite Angriffswelle. Er empfiehlt dann der Kommandozentrale eine Liste von gezielten Folge-Missionen (Subroutinen), die von unseren Spezialkräften (nmap_subroutines.py) ausgeführt werden sollen.
import json
class NmapRunner:
def __init__(self, json_file_path):
self.nmap_data = self._load_json(json_file_path)
self.tasks_to_run = []
def _load_json(self, file_path):
try:
with open(file_path, 'r') as f:
return json.load(f)
except (json.JSONDecodeError, FileNotFoundError):
return None
Ziel: Das ist die Blaupause für unseren Analysten. Eine Klasse ist wie ein Bauplan für ein Objekt. Jedes Mal, wenn wir den NmapRunner aufrufen, erschaffen wir eine neue, frische Instanz dieses Analysten, der seinen eigenen Satz an Daten und Aufgaben hat.
High-End-Konzept: __init__(self, ...) - Das ist der "Konstruktor" der Klasse, die Funktion, die beim Erschaffen des Objekts automatisch aufgerufen wird. Stellt es euch wie das "Boot-Up-Protokoll" des Analysten vor.
self: Ist das wichtigste Wort in einer Klasse. Es ist der Verweis des Objekts auf sich selbst. Damit kann es seine eigenen Daten (wie self.nmap_data) speichern und darauf zugreifen.self.nmap_data = self._load_json(...): Sobald der Analyst "geboren" wird, ist seine erste Amtshandlung, die JSON-Beweismittel-Datei zu laden.self.tasks_to_run = []: Er legt sich einen leeren Notizblock an, um die identifizierten Folge-Missionen aufzuschreiben.Funktion _load_json (Die Beweisaufnahme): Das _ am Anfang des Namens ist eine Konvention in Python. Es signalisiert anderen Entwicklern: "Dies ist eine interne Hilfsfunktion, die nicht von außen aufgerufen werden soll." Die Funktion selbst ist robust gebaut: Sie fängt mit try...except die beiden häufigsten Fehler ab: Die Datei existiert nicht (FileNotFoundError) oder sie enthält kaputtes JSON (json.JSONDecodeError).
run_analysis(self)
def run_analysis(self):
if not self.nmap_data:
print("[ERROR] Nmap JSON log could not be loaded or parsed.")
return []
for host in self.nmap_data.get('hosts', []):
for port in host.get('ports', []):
service = port.get('service', {})
service_name = service.get('name', 'unknown')
scripts = port.get('scripts', [])
if service_name == 'ftp':
is_anon = False
for script in scripts:
if (
script.get('id') == 'ftp-anon'
and 'Anonymous FTP login allowed' in script.get('output', '')
):
self.tasks_to_run.append('ftp_anon_get')
is_anon = True
break
if not is_anon:
self.tasks_to_run.append('ftp_brute')
# ... (ähnliche Logik für telnet, domain, smb) ...
return list(set(self.tasks_to_run))
Ziel: Das ist das Gehirn des Analysten. Diese Funktion durchkämmt die geladenen JSON-Daten und wendet eine Reihe von "Wenn-Dann"-Regeln an, um Folge-Missionen zu identifizieren.
High-End-Konzept (Sichere Navigation in Dictionaries): Der Ausdruck self.nmap_data.get('hosts', []) ist der Profi-Weg, um durch komplexe JSON-Daten zu navigieren. .get('key', default) versucht, den Schlüssel 'hosts' zu finden. Wenn der Schlüssel nicht existiert (z.B. weil der Nmap-Scan fehlgeschlagen ist), gibt der Befehl keinen Fehler, sondern einfach eine leere Liste [] zurück. Die Schleife läuft dann einfach null Mal durch und das Programm stürzt nicht ab. Das macht den Code extrem fehlertolerant.
Herleitung der Logik (Beispiel FTP):
scripts). Er sucht speziell nach dem Ergebnis des ftp-anon-Skripts. Wenn dieses Skript meldet, dass ein anonymer Login erlaubt ist, wird die hochspezialisierte Mission 'ftp_anon_get' zum Notizblock hinzugefügt.if not is_anon), wird die Brute-Force-Mission 'ftp_brute' hinzugefügt. Das ist eine kluge Priorisierung: Wir versuchen erst den einfachen, leisen Weg, bevor wir die laute Artillerie (Hydra) auspacken.High-End-Befehl (Zeile 28): return list(set(self.tasks_to_run))
set(self.tasks_to_run): Wir wandeln unsere Liste in ein set um. Die Superkraft des Sets tritt in Aktion und wirft sofort alle Duplikate raus.list(...): Danach wandeln wir das saubere Set wieder in eine normale Liste um, die wir an die Kommandozentrale zurückgeben.
Wenn NmapRunner der Planer ist, dann ist dieses Modul die ausführende Spezialeinheit (Task Force). Jede Funktion in dieser Datei ist eine "Subroutine" – eine eigenständige, hochspezialisierte Mission, die für ein ganz bestimmtes Szenario entwickelt wurde. Diese Missionen werden nicht standardmäßig ausgeführt, sondern nur dann, wenn der NmapRunner-Analyst grünes Licht gibt. Das ist der Kern unserer "intelligenten" Aufklärung: Wir verschwenden keine Munition, sondern setzen unsere besten Waffen nur dann ein, wenn wir ein lohnendes Ziel identifiziert haben.
In der aktuellen Version enthalten diese Funktionen nur print-Anweisungen als Platzhalter. In der finalen Version würden hier die nötigen Bibliotheken (wie subprocess, um Hydra oder Wget zu starten) importiert werden.
def ftp_anon_get(target):
print(f"\n>>> SUBROUTINE GESTARTET: ftp_anon_get auf {target}")
print(">>> AKTION: Versuche, den gesamten FTP-Server rekursiv herunterzuladen...")
# Hier käme der wget -m ... Befehl
def ftp_brute(target):
print(f"\n>>> SUBROUTINE GESTARTET: ftp_brute auf {target}")
print(">>> AKTION: Starte Hydra Brute-Force gegen den FTP-Dienst...")
def dns_axfr(target):
print(f"\n>>> SUBROUTINE GESTARTET: dns_axfr auf {target}")
print(">>> AKTION: Versuche einen DNS Zone Transfer...")
def smb_enum(target):
print(f"\n>>> SUBROUTINE GESTARTET: smb_enum auf {target}")
print(">>> AKTION: Starte enum4linux für eine umfassende SMB-Enumeration...")
... und so weiter für telnet_brute, smb_nullsession etc.
Ziel: Jede dieser Funktionen ist ein eigenständiger Angriffsplan für ein spezifisches Szenario.
High-End-Konzept (Dynamische Funktionsaufrufe): Die Magie liegt nicht in den Funktionen selbst, sondern darin, wie sie aufgerufen werden. Schauen wir zurück auf Kapitel 1 (live_recon.py):
fn = getattr(nmap_subroutines, task, None)
if fn: fn(target)
Herleitung der Architektur: Stellt euch vor, wir hätten dieses System nicht. Die Kommandozentrale müsste eine riesige, hässliche if/elif/else-Struktur haben:
if task == "ftp_anon_get":
nmap_subroutines.ftp_anon_get(target)
elif task == "ftp_brute":
nmap_subroutines.ftp_brute(target)
...und so weiter. Das wäre ein Albtraum. Für jede neue Subroutine müssten wir die Kommandozentrale umbauen.
Die geniale Lösung: Unsere Architektur ist viel schlauer. Der NmapRunner-Analyst muss nur den exakten Namen der Funktion (z.B. den String "ftp_brute") zurückgeben. Die Kommandozentrale benutzt dann getattr, um diese Funktion dynamisch zu finden und auszuführen.
Der strategische Vorteil: Um eine neue Spezial-Mission hinzuzufügen (z.B. einen SSH Brute-Force), müssen wir nur zwei Dinge tun:
NmapRunner hinzufügen, die den Task "ssh_brute" zurückgibt, wenn Port 22 offen ist.def ssh_brute(target): ... in dieser nmap_subroutines.py-Datei erstellen.live_recon.py) müssen wir niemals wieder anfassen. Sie passt sich automatisch an unser wachsendes Arsenal an. Das ist der Inbegriff von modularer, erweiterbarer und wartbarer Software-Architektur.
Jede Operation endet mit einem Debriefing. Dieses Modul ist der Archivar, der dem Operator am Ende der Mission die Möglichkeit gibt, die vollständigen, ungefilterten Original-Berichte aller eingesetzten Agenten (Scan-Tools) einzusehen. Es fragt den Nutzer, ob er die Details sehen möchte, und präsentiert dann die rohen Log-Dateien in einer sauberen, lesbaren Form direkt im Terminal. Das ist entscheidend für die Tiefenanalyse und das Verständnis des gesamten Schlachtfelds.
show_detailed_logs(log_directory)
import os
import json
import shutil
# ... (LogColors Klasse und Hilfsfunktionen wie get_terminal_width, print_file_header)
def show_detailed_logs(log_directory):
answer = input(
"\n[?] If you want to display all scan results in detail, "
"press 'J' and hit Enter: "
)
if answer.lower() == 'j':
try:
log_files = sorted(os.listdir(log_directory))
if not log_files:
print(f"[INFO] No log files found in directory '{log_directory}'.")
return
# Zuerst alle textbasierten Logs (.nmap, .txt) anzeigen
for filename in log_files:
if filename.endswith(".nmap") or filename.endswith(".txt"):
filepath = os.path.join(log_directory, filename)
print_file_header(filename)
with open(filepath, 'r', errors='ignore') as f:
print(f.read().strip())
# Danach alle JSON-basierten Logs anzeigen
for filename in log_files:
if filename.endswith(".json") and "nmap" not in filename:
filepath = os.path.join(log_directory, filename)
print_file_header(filename)
with open(filepath, 'r', errors='ignore') as f:
print(f"{LogColors.BOLD}STATUS SIZE URL{LogColors.RESET}")
print(f"{LogColors.TURQ}{'-' * 60}{LogColors.RESET}")
for line in f:
formatted = format_ferox_line(line)
if formatted:
print(formatted)
# ... (weitere Logik für generisches JSON-Parsing)
except FileNotFoundError:
print(f"[ERROR] Log directory '{log_directory}' not found.")
Ziel: Diese Funktion ist der Kern des Debriefings. Sie interagiert mit dem Nutzer und präsentiert die Daten.
Zeile 8: answer = input(...) - Das ist der Dialog mit dem Operator. Das Programm hält an und wartet auf eine Eingabe. Nur wenn der Nutzer explizit 'J' (oder 'j') eingibt, wird der Rest ausgeführt. Das verhindert, dass der Bildschirm mit riesigen Log-Dateien überflutet wird, wenn der Nutzer das nicht wünscht.
Zeile 14: log_files = sorted(os.listdir(log_directory)) - Der Archivar geht in den Log-Ordner (log_directory), listet alle darin enthaltenen Dateien auf (os.listdir) und sortiert sie alphabetisch (sorted). Das sorgt für eine konsistente und geordnete Anzeige.
High-End-Konzept (Getrennte Verarbeitungsschleifen): Statt alle Dateien in einer Schleife zu verarbeiten, wird die Logik clever aufgeteilt.
.nmap, .txt). Diese können einfach ausgelesen und direkt ins Terminal gedruckt werden (f.read()).format_ferox_line) übergeben, die sie in eine hübsche, tabellarische Form bringt.format_ferox_line(line)
def format_ferox_line(line):
try:
data = json.loads(line)
if data.get("type") != "response":
return None
status = data.get("status", 0)
url = data.get("url", "")
length = data.get("content_length", 0)
s_color = LogColors.GREEN
if 300 <= status < 400:
s_color = LogColors.YELLOW
if status >= 400:
s_color = LogColors.RED
return (
f"{s_color}[{status}]{LogColors.RESET} "
f"({LogColors.BLUE}{length:>6}b{LogColors.RESET}) {url}"
)
except json.JSONDecodeError:
return None
Ziel: Diese Funktion ist ein Übersetzer. Sie nimmt eine einzelne, kryptische JSON-Zeile von Feroxbuster und verwandelt sie in eine einzelne, farbige, menschenlesbare Zeile für den Report.
Zeile 3: data = json.loads(line) - Der json-Übersetzer nimmt den JSON-Text und verwandelt ihn in ein Python-Dictionary, auf das wir einfach zugreifen können.
Zeile 5: if data.get("type") != "response": return None - Das ist ein intelligenter Filter. Feroxbuster produziert verschiedene Arten von JSON-Nachrichten. Wir interessieren uns aber nur für die tatsächlichen Server-Antworten ("response"). Alle anderen Nachrichten werden ignoriert (return None).
Farb-Logik (Zeile 12-16): Hier wird die Farbe basierend auf dem HTTP-Status-Code ausgewählt. Erfolgreiche Anfragen (2xx) sind grün, Weiterleitungen (3xx) sind gelb und Fehler (4xx/5xx) sind rot. Das gibt dem Operator sofort einen visuellen Hinweis auf die Wichtigkeit eines Fundes.
High-End-Befehl (Format-String): f"({LogColors.BLUE}{length:>6}b{LogColors.RESET})"
{length:>6}: Das ist die geheime Waffe für saubere Tabellen. Es sagt Python: "Nimm die Variable length, aber reserviere dafür immer 6 Zeichen Platz. Wenn die Zahl kleiner ist, fülle den Rest mit Leerzeichen auf (> bedeutet rechtsbündig)."