#!/usr/bin/env python3
"""Detect credential dumping techniques via Sysmon/Windows event log analysis."""

import json
import re
import argparse
import xml.etree.ElementTree as ET
from datetime import datetime


LSASS_GRANTED_ACCESS_SUSPICIOUS = {
    "0x1010": "PROCESS_VM_READ | PROCESS_QUERY_LIMITED_INFORMATION (Mimikatz-style)",
    "0x1410": "PROCESS_VM_READ | PROCESS_QUERY_INFORMATION (procdump-style)",
    "0x1FFFFF": "PROCESS_ALL_ACCESS (full access to LSASS)",
    "0x1438": "PROCESS_VM_READ | PROCESS_QUERY_INFORMATION | PROCESS_DUP_HANDLE",
    "0x40": "PROCESS_DUP_HANDLE (handle duplication for indirect access)",
}

SUSPICIOUS_CALLERS = [
    "mimikatz", "procdump", "rundll32.exe", "taskmgr.exe",
    "powershell.exe", "cmd.exe", "wmic.exe", "cscript.exe", "wscript.exe",
]

SAM_EXPORT_PATTERNS = [
    r"reg\s+save\s+hklm\\sam",
    r"reg\s+save\s+hklm\\security",
    r"reg\s+save\s+hklm\\system",
    r"esentutl.*ntds\.dit",
    r"ntdsutil.*\"activate instance ntds\"",
    r"vssadmin\s+create\s+shadow",
    r"copy\s+\\\\.*\\c\$.*ntds\.dit",
    r"secretsdump",
]

COMSVCS_PATTERNS = [
    r"comsvcs\.dll.*MiniDump",
    r"comsvcs\.dll.*#24",
    r"rundll32.*comsvcs",
]


def parse_sysmon_xml(xml_path):
    """Parse Sysmon event log XML export for Event ID 10 (ProcessAccess)."""
    tree = ET.parse(xml_path)
    root = tree.getroot()
    ns = {"e": "http://schemas.microsoft.com/win/2004/08/events/event"}
    events = []
    for event_el in root.findall(".//e:Event", ns):
        sys_el = event_el.find("e:System", ns)
        event_id = int(sys_el.find("e:EventID", ns).text)
        time_created = sys_el.find("e:TimeCreated", ns).attrib.get("SystemTime", "")
        data_el = event_el.find("e:EventData", ns)
        fields = {}
        for d in data_el.findall("e:Data", ns):
            fields[d.attrib.get("Name", "")] = d.text or ""
        events.append({"event_id": event_id, "timestamp": time_created, **fields})
    return events


def detect_lsass_access(events):
    """Detect suspicious ProcessAccess (Event ID 10) targeting lsass.exe."""
    alerts = []
    for ev in events:
        if ev["event_id"] != 10:
            continue
        target = ev.get("TargetImage", "").lower()
        if "lsass.exe" not in target:
            continue
        granted = ev.get("GrantedAccess", "")
        source_image = ev.get("SourceImage", "").lower()
        source_name = source_image.split("\\")[-1] if source_image else ""
        severity = "medium"
        reasons = []
        if granted in LSASS_GRANTED_ACCESS_SUSPICIOUS:
            reasons.append(f"Suspicious GrantedAccess {granted}: {LSASS_GRANTED_ACCESS_SUSPICIOUS[granted]}")
            severity = "critical" if granted == "0x1FFFFF" else "high"
        if any(s in source_name for s in SUSPICIOUS_CALLERS):
            reasons.append(f"Suspicious calling process: {source_name}")
            severity = "critical"
        if not reasons:
            continue
        alerts.append({
            "detection": "LSASS Memory Access",
            "mitre_technique": "T1003.001",
            "timestamp": ev["timestamp"],
            "source_process": ev.get("SourceImage", ""),
            "source_pid": ev.get("SourceProcessId", ""),
            "target_process": ev.get("TargetImage", ""),
            "granted_access": granted,
            "call_trace": ev.get("CallTrace", "")[:200],
            "severity": severity,
            "reasons": reasons,
            "user": ev.get("SourceUser", ev.get("User", "")),
            "host": ev.get("Computer", ""),
        })
    return alerts


def detect_credential_commands(events):
    """Detect SAM/NTDS.dit export and comsvcs.dll dump commands from Event ID 1 or 4688."""
    alerts = []
    for ev in events:
        if ev["event_id"] not in (1, 4688):
            continue
        cmdline = ev.get("CommandLine", ev.get("ProcessCommandLine", "")).lower()
        if not cmdline:
            continue
        for pattern in SAM_EXPORT_PATTERNS:
            if re.search(pattern, cmdline, re.IGNORECASE):
                technique = "T1003.002" if "sam" in cmdline or "security" in cmdline else "T1003.003"
                alerts.append({
                    "detection": "Registry Hive / NTDS.dit Export",
                    "mitre_technique": technique,
                    "timestamp": ev["timestamp"],
                    "command_line": ev.get("CommandLine", ev.get("ProcessCommandLine", "")),
                    "process": ev.get("Image", ev.get("NewProcessName", "")),
                    "user": ev.get("User", ev.get("SubjectUserName", "")),
                    "severity": "critical",
                })
                break
        for pattern in COMSVCS_PATTERNS:
            if re.search(pattern, cmdline, re.IGNORECASE):
                alerts.append({
                    "detection": "LSASS Dump via comsvcs.dll",
                    "mitre_technique": "T1003.001",
                    "timestamp": ev["timestamp"],
                    "command_line": ev.get("CommandLine", ev.get("ProcessCommandLine", "")),
                    "process": ev.get("Image", ev.get("NewProcessName", "")),
                    "user": ev.get("User", ev.get("SubjectUserName", "")),
                    "severity": "critical",
                })
                break
    return alerts


def detect_ntdll_access(events):
    """Detect suspicious ntdll.dll access patterns in CallTrace (Mimikatz signature)."""
    alerts = []
    for ev in events:
        if ev["event_id"] != 10:
            continue
        call_trace = ev.get("CallTrace", "")
        if "ntdll.dll" in call_trace.lower() and "UNKNOWN" in call_trace:
            target = ev.get("TargetImage", "").lower()
            if "lsass.exe" in target:
                alerts.append({
                    "detection": "NTDLL Suspicious CallTrace (Mimikatz Signature)",
                    "mitre_technique": "T1003.001",
                    "timestamp": ev["timestamp"],
                    "source_process": ev.get("SourceImage", ""),
                    "call_trace_snippet": call_trace[:300],
                    "severity": "critical",
                })
    return alerts


def generate_splunk_queries():
    """Return SPL detection queries for credential dumping."""
    return {
        "lsass_access": (
            'index=sysmon EventCode=10 TargetImage="*\\\\lsass.exe" '
            'GrantedAccess IN ("0x1010","0x1FFFFF","0x1410","0x1438") '
            '| stats count by SourceImage, GrantedAccess, Computer'
        ),
        "comsvcs_dump": (
            'index=sysmon EventCode=1 CommandLine="*comsvcs*MiniDump*" '
            'OR CommandLine="*comsvcs*#24*" | table _time, Computer, User, CommandLine'
        ),
        "sam_export": (
            'index=sysmon EventCode=1 (CommandLine="*reg*save*hklm\\\\sam*" '
            'OR CommandLine="*reg*save*hklm\\\\security*") '
            '| table _time, Computer, User, CommandLine'
        ),
        "ntds_extraction": (
            'index=sysmon EventCode=1 (CommandLine="*ntdsutil*" '
            'OR CommandLine="*vssadmin*create*shadow*") '
            '| table _time, Computer, User, CommandLine'
        ),
    }


def main():
    parser = argparse.ArgumentParser(description="Credential Dumping Detection Agent")
    parser.add_argument("--sysmon-xml", help="Path to Sysmon event log XML export")
    parser.add_argument("--output", default="credential_dump_report.json", help="Output report path")
    parser.add_argument("--show-splunk", action="store_true", help="Print Splunk detection queries")
    args = parser.parse_args()

    if args.show_splunk:
        for name, spl in generate_splunk_queries().items():
            print(f"\n--- {name} ---\n{spl}")
        return

    if not args.sysmon_xml:
        print("[!] Provide --sysmon-xml path or use --show-splunk for detection queries")
        return

    events = parse_sysmon_xml(args.sysmon_xml)
    print(f"[+] Parsed {len(events)} Sysmon/Security events")

    lsass_alerts = detect_lsass_access(events)
    cmd_alerts = detect_credential_commands(events)
    ntdll_alerts = detect_ntdll_access(events)

    report = {
        "analysis_time": datetime.utcnow().isoformat() + "Z",
        "total_events": len(events),
        "detections": {
            "lsass_memory_access": lsass_alerts,
            "credential_export_commands": cmd_alerts,
            "ntdll_suspicious_calltrace": ntdll_alerts,
        },
        "total_alerts": len(lsass_alerts) + len(cmd_alerts) + len(ntdll_alerts),
        "mitre_techniques": ["T1003.001", "T1003.002", "T1003.003"],
        "splunk_queries": generate_splunk_queries(),
    }

    with open(args.output, "w") as f:
        json.dump(report, f, indent=2)
    print(f"[+] LSASS access alerts: {len(lsass_alerts)}")
    print(f"[+] Credential export commands: {len(cmd_alerts)}")
    print(f"[+] NTDLL suspicious traces: {len(ntdll_alerts)}")
    print(f"[+] Report saved to {args.output}")


if __name__ == "__main__":
    main()
