#!/usr/bin/env python3
"""Pass-the-Hash attack detection agent.
Detects NTLM hash reuse attacks by analyzing Windows Security Event ID 4624
for Type 3 NTLM logons with anomalous patterns across multiple targets.
"""
import argparse
import json
import re
from collections import defaultdict
from datetime import datetime
try:
import Evtx.Evtx as evtx
except ImportError:
evtx = None
LEGITIMATE_SOURCES = {"127.0.0.1", "::1", "-", ""}
def parse_logon_events(filepath):
if evtx is None:
return {"error": "python-evtx not installed: pip install python-evtx"}
events = []
with evtx.Evtx(filepath) as log:
for record in log.records():
xml = record.xml()
if "4624" not in xml:
continue
logon_type = re.search(r'(\d+)', xml)
auth_pkg = re.search(r'([^<]+)', xml)
account = re.search(r'([^<]+)', xml)
domain = re.search(r'([^<]+)', xml)
src_ip = re.search(r'([^<]+)', xml)
computer = re.search(r'([^<]+)', xml)
time_match = re.search(r'SystemTime="([^"]+)"', xml)
lt = logon_type.group(1) if logon_type else ""
ap = auth_pkg.group(1) if auth_pkg else ""
if lt == "3" and "NTLM" in ap.upper():
events.append({
"timestamp": time_match.group(1) if time_match else "",
"logon_type": int(lt), "auth_package": ap.strip(),
"account": account.group(1) if account else "",
"domain": domain.group(1) if domain else "",
"source_ip": src_ip.group(1) if src_ip else "",
"computer": computer.group(1) if computer else "",
})
return events
def detect_pth_patterns(events, target_threshold=3):
if isinstance(events, dict) and "error" in events:
return [events]
findings = []
src_targets = defaultdict(lambda: {"computers": set(), "count": 0,
"source_ip": "", "account": ""})
for evt in events:
src = evt.get("source_ip", "")
if src in LEGITIMATE_SOURCES:
continue
key = f"{src}|{evt.get('account', '')}"
src_targets[key]["computers"].add(evt.get("computer", ""))
src_targets[key]["count"] += 1
src_targets[key]["source_ip"] = src
src_targets[key]["account"] = evt.get("account", "")
for key, data in src_targets.items():
target_count = len(data["computers"])
if target_count >= target_threshold:
findings.append({
"type": "ntlm_type3_multi_target",
"source_ip": data["source_ip"],
"account": data["account"],
"target_count": target_count,
"targets": list(data["computers"])[:20],
"total_logons": data["count"],
"severity": "CRITICAL" if target_count >= 10 else "HIGH",
"mitre": "T1550.002",
})
admin_sources = defaultdict(int)
for evt in events:
if evt.get("account", "").lower() in ("administrator", "admin"):
admin_sources[evt.get("source_ip", "")] += 1
for src, count in admin_sources.items():
if count >= 2 and src not in LEGITIMATE_SOURCES:
findings.append({
"type": "admin_ntlm", "source_ip": src,
"logon_count": count, "severity": "HIGH", "mitre": "T1550.002",
})
return findings
def main():
parser = argparse.ArgumentParser(description="Pass-the-Hash Detector")
parser.add_argument("--security-log", required=True, help="Windows Security EVTX")
parser.add_argument("--target-threshold", type=int, default=3)
args = parser.parse_args()
events = parse_logon_events(args.security_log)
findings = detect_pth_patterns(events, args.target_threshold)
ntlm_count = len(events) if isinstance(events, list) else 0
results = {
"timestamp": datetime.utcnow().isoformat() + "Z",
"total_ntlm_type3_logons": ntlm_count,
"findings": findings, "total_findings": len(findings),
}
print(json.dumps(results, indent=2))
if __name__ == "__main__":
main()