#!/usr/bin/env python3
"""Agent for hunting Windows persistence mechanisms across registry, services, and scheduled tasks."""

import json
import argparse
import subprocess
import re
from datetime import datetime

REGISTRY_PERSISTENCE_KEYS = [
    r"HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Run",
    r"HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\RunOnce",
    r"HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\Run",
    r"HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\RunOnce",
    r"HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon",
    r"HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\Explorer\Run",
    r"HKLM\SOFTWARE\Microsoft\Active Setup\Installed Components",
    r"HKLM\SYSTEM\CurrentControlSet\Services",
    r"HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options",
    r"HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\User Shell Folders",
    r"HKCU\Environment",
]

SUSPICIOUS_INDICATORS = [
    r"\\temp\\", r"\\tmp\\", r"\\appdata\\local\\temp\\",
    r"powershell.*-enc", r"cmd\.exe.*/c\s+",
    r"\\users\\public\\", r"\\programdata\\",
    r"mshta\.exe", r"rundll32\.exe", r"regsvr32\.exe",
    r"wscript\.exe", r"cscript\.exe",
    r"base64", r"iex\s*\(", r"downloadstring",
]


def enumerate_registry_persistence():
    """Enumerate common Windows registry persistence locations using reg query."""
    findings = []
    for key in REGISTRY_PERSISTENCE_KEYS:
        try:
            result = subprocess.run(
                ["reg", "query", key], capture_output=True, text=True, timeout=10
            )
            if result.returncode == 0:
                entries = parse_reg_output(result.stdout, key)
                for entry in entries:
                    entry["suspicious"] = any(
                        re.search(p, entry.get("value", ""), re.I)
                        for p in SUSPICIOUS_INDICATORS
                    )
                    findings.append(entry)
        except (subprocess.TimeoutExpired, FileNotFoundError):
            continue
    return {
        "timestamp": datetime.utcnow().isoformat(),
        "total_entries": len(findings),
        "suspicious_entries": sum(1 for f in findings if f.get("suspicious")),
        "findings": findings,
    }


def parse_reg_output(output, parent_key):
    """Parse reg query output into structured entries."""
    entries = []
    current_key = parent_key
    for line in output.strip().split("\n"):
        line = line.strip()
        if not line:
            continue
        if line.startswith("HK"):
            current_key = line
            continue
        parts = re.split(r"\s{2,}", line, maxsplit=2)
        if len(parts) >= 3:
            entries.append({
                "key": current_key,
                "name": parts[0],
                "type": parts[1],
                "value": parts[2],
            })
    return entries


def enumerate_scheduled_tasks():
    """List scheduled tasks and flag suspicious ones."""
    try:
        result = subprocess.run(
            ["schtasks", "/query", "/fo", "CSV", "/v"],
            capture_output=True, text=True, timeout=30
        )
    except (subprocess.TimeoutExpired, FileNotFoundError):
        return {"error": "schtasks not available"}
    findings = []
    import csv
    from io import StringIO
    reader = csv.DictReader(StringIO(result.stdout))
    for row in reader:
        task_name = row.get("TaskName", "")
        action = row.get("Task To Run", "")
        author = row.get("Author", "")
        suspicious = any(re.search(p, action, re.I) for p in SUSPICIOUS_INDICATORS)
        if suspicious or "\\Microsoft\\" not in task_name:
            findings.append({
                "task_name": task_name,
                "action": action[:500],
                "author": author,
                "status": row.get("Status", ""),
                "next_run": row.get("Next Run Time", ""),
                "suspicious": suspicious,
            })
    return {
        "total_tasks": len(findings),
        "suspicious_tasks": sum(1 for f in findings if f["suspicious"]),
        "findings": findings,
    }


def enumerate_services():
    """List Windows services and flag those running from unusual paths."""
    try:
        result = subprocess.run(
            ["wmic", "service", "get", "Name,PathName,StartMode,State", "/format:csv"],
            capture_output=True, text=True, timeout=30
        )
    except (subprocess.TimeoutExpired, FileNotFoundError):
        return {"error": "wmic not available"}
    findings = []
    for line in result.stdout.strip().split("\n")[1:]:
        parts = line.strip().split(",")
        if len(parts) >= 5:
            path = parts[3]
            suspicious = any(re.search(p, path, re.I) for p in SUSPICIOUS_INDICATORS)
            findings.append({
                "name": parts[1], "path": path,
                "start_mode": parts[4] if len(parts) > 4 else "",
                "state": parts[2], "suspicious": suspicious,
            })
    return {
        "total_services": len(findings),
        "suspicious_services": sum(1 for f in findings if f["suspicious"]),
        "findings": [f for f in findings if f["suspicious"]],
    }


def main():
    parser = argparse.ArgumentParser(description="Hunt for Windows persistence mechanisms")
    sub = parser.add_subparsers(dest="command")
    sub.add_parser("registry", help="Enumerate registry persistence keys")
    sub.add_parser("tasks", help="Enumerate scheduled tasks")
    sub.add_parser("services", help="Enumerate suspicious services")
    sub.add_parser("all", help="Run all persistence hunts")
    args = parser.parse_args()
    if args.command == "registry":
        result = enumerate_registry_persistence()
    elif args.command == "tasks":
        result = enumerate_scheduled_tasks()
    elif args.command == "services":
        result = enumerate_services()
    elif args.command == "all":
        result = {
            "registry": enumerate_registry_persistence(),
            "scheduled_tasks": enumerate_scheduled_tasks(),
            "services": enumerate_services(),
            "timestamp": datetime.utcnow().isoformat(),
        }
    else:
        parser.print_help()
        return
    print(json.dumps(result, indent=2, default=str))


if __name__ == "__main__":
    main()
