#!/usr/bin/env python3
"""Agent for hunting T1547.001 startup folder persistence on Windows."""

import json
import os
import hashlib
import argparse
import time
from datetime import datetime
from pathlib import Path

try:
    from watchdog.observers import Observer
    from watchdog.events import FileSystemEventHandler
except ImportError:
    Observer = None
    FileSystemEventHandler = object

SUSPICIOUS_EXTENSIONS = {
    ".exe": 30, ".bat": 35, ".cmd": 35, ".vbs": 40, ".vbe": 40,
    ".js": 40, ".jse": 40, ".wsf": 40, ".wsh": 35, ".ps1": 45,
    ".pif": 45, ".scr": 40, ".hta": 45, ".lnk": 15, ".url": 20,
}

LEGITIMATE_ENTRIES = [
    "desktop.ini", "Send to OneNote.lnk", "OneNote 2016.lnk",
    "Microsoft Teams.lnk", "Outlook.lnk", "OneDrive.lnk",
    "Cisco AnyConnect Secure Mobility Client.lnk",
    "Skype for Business.lnk", "Zoom.lnk",
]


def get_startup_paths():
    """Return user-specific and all-users startup folder paths."""
    paths = []
    user_startup = os.path.join(
        os.environ.get("APPDATA", ""),
        "Microsoft", "Windows", "Start Menu", "Programs", "Startup"
    )
    if os.path.isdir(user_startup):
        paths.append({"path": user_startup, "scope": "current_user"})
    all_users_startup = os.path.join(
        os.environ.get("PROGRAMDATA", r"C:\ProgramData"),
        "Microsoft", "Windows", "Start Menu", "Programs", "Startup"
    )
    if os.path.isdir(all_users_startup):
        paths.append({"path": all_users_startup, "scope": "all_users"})
    return paths


def compute_file_hash(filepath):
    """Compute SHA-256 hash of a file."""
    sha256 = hashlib.sha256()
    try:
        with open(filepath, "rb") as f:
            for chunk in iter(lambda: f.read(8192), b""):
                sha256.update(chunk)
        return sha256.hexdigest()
    except (PermissionError, OSError):
        return "access_denied"


def analyze_file(filepath, scope="unknown"):
    """Analyze a single file in a startup directory."""
    path = Path(filepath)
    name = path.name
    ext = path.suffix.lower()
    try:
        stat = path.stat()
        created = datetime.fromtimestamp(stat.st_ctime)
        modified = datetime.fromtimestamp(stat.st_mtime)
        size = stat.st_size
    except (PermissionError, OSError):
        return {"file": str(filepath), "error": "access_denied"}

    is_legitimate = name in LEGITIMATE_ENTRIES
    age_days = (datetime.now() - created).days

    risk = 0
    indicators = []
    risk += SUSPICIOUS_EXTENSIONS.get(ext, 0)
    if ext in SUSPICIOUS_EXTENSIONS and ext != ".lnk":
        indicators.append(f"suspicious_extension_{ext}")
    if age_days < 7:
        risk += 25
        indicators.append("recently_created")
    if age_days < 1:
        risk += 15
        indicators.append("created_within_24h")
    if size == 0:
        risk += 10
        indicators.append("zero_byte_file")
    if size > 10 * 1024 * 1024:
        risk += 10
        indicators.append("large_file_over_10mb")
    if not is_legitimate:
        risk += 10
        indicators.append("not_in_baseline")
    if scope == "all_users" and ext in SUSPICIOUS_EXTENSIONS:
        risk += 10
        indicators.append("all_users_startup")

    risk = min(risk, 100)

    return {
        "file": str(filepath),
        "filename": name,
        "extension": ext,
        "scope": scope,
        "size_bytes": size,
        "created": created.isoformat(),
        "modified": modified.isoformat(),
        "age_days": age_days,
        "sha256": compute_file_hash(filepath),
        "is_legitimate_baseline": is_legitimate,
        "suspicious_indicators": indicators,
        "risk_score": risk,
        "risk_level": "CRITICAL" if risk >= 70 else "HIGH" if risk >= 50 else "MEDIUM" if risk >= 25 else "LOW",
    }


def scan_startup_folders():
    """Scan all startup directories and analyze contents."""
    startup_paths = get_startup_paths()
    results = []
    for sp in startup_paths:
        folder = sp["path"]
        scope = sp["scope"]
        try:
            for entry in os.listdir(folder):
                full_path = os.path.join(folder, entry)
                if os.path.isfile(full_path):
                    analysis = analyze_file(full_path, scope)
                    results.append(analysis)
        except PermissionError:
            results.append({"path": folder, "error": "access_denied"})
    results.sort(key=lambda x: x.get("risk_score", 0), reverse=True)
    return results


def check_registry_run_keys():
    """Check Registry Run keys for autostart entries."""
    import subprocess
    run_keys = [
        r"HKCU\Software\Microsoft\Windows\CurrentVersion\Run",
        r"HKCU\Software\Microsoft\Windows\CurrentVersion\RunOnce",
        r"HKLM\Software\Microsoft\Windows\CurrentVersion\Run",
        r"HKLM\Software\Microsoft\Windows\CurrentVersion\RunOnce",
    ]
    entries = []
    for key in run_keys:
        try:
            result = subprocess.run(
                ["reg", "query", key],
                capture_output=True, text=True, timeout=10
            )
            if result.returncode == 0:
                for line in result.stdout.strip().split("\n"):
                    line = line.strip()
                    if line and not line.startswith("HK") and "REG_" in line:
                        parts = line.split("REG_SZ", 1) if "REG_SZ" in line else line.split("REG_EXPAND_SZ", 1)
                        name = parts[0].strip() if parts else line
                        value = parts[1].strip() if len(parts) > 1 else ""
                        entries.append({
                            "registry_key": key,
                            "name": name,
                            "value": value,
                            "suspicious": any(p in value.lower() for p in
                                             ["powershell", "cmd.exe", "\\temp\\", "\\appdata\\",
                                              "mshta", "-enc", "downloadstring"]),
                        })
        except Exception:
            pass
    return entries


class StartupMonitorHandler(FileSystemEventHandler):
    """Watchdog handler for monitoring startup folder changes."""

    def __init__(self):
        self.events = []

    def on_created(self, event):
        if not event.is_directory:
            analysis = analyze_file(event.src_path)
            alert = {
                "event": "FILE_CREATED",
                "timestamp": datetime.now().isoformat(),
                "file": event.src_path,
                "risk_score": analysis.get("risk_score", 0),
                "risk_level": analysis.get("risk_level", "UNKNOWN"),
                "indicators": analysis.get("suspicious_indicators", []),
            }
            self.events.append(alert)
            print(json.dumps(alert, indent=2))

    def on_modified(self, event):
        if not event.is_directory:
            alert = {
                "event": "FILE_MODIFIED",
                "timestamp": datetime.now().isoformat(),
                "file": event.src_path,
            }
            self.events.append(alert)
            print(json.dumps(alert, indent=2))

    def on_deleted(self, event):
        if not event.is_directory:
            alert = {
                "event": "FILE_DELETED",
                "timestamp": datetime.now().isoformat(),
                "file": event.src_path,
            }
            self.events.append(alert)
            print(json.dumps(alert, indent=2))


def monitor_startup(duration_seconds=60):
    """Monitor startup folders in real-time using watchdog."""
    if not Observer:
        return {"error": "watchdog not installed: pip install watchdog"}
    handler = StartupMonitorHandler()
    observer = Observer()
    startup_paths = get_startup_paths()
    for sp in startup_paths:
        observer.schedule(handler, sp["path"], recursive=False)
    observer.start()
    try:
        time.sleep(duration_seconds)
    except KeyboardInterrupt:
        pass
    observer.stop()
    observer.join()
    return {"monitored_seconds": duration_seconds, "events_detected": handler.events}


def full_hunt():
    """Run comprehensive startup persistence threat hunt."""
    scan_results = scan_startup_folders()
    registry_entries = check_registry_run_keys()
    suspicious_files = [r for r in scan_results if r.get("risk_score", 0) >= 25]
    suspicious_reg = [e for e in registry_entries if e.get("suspicious")]
    return {
        "hunt_type": "Startup Folder Persistence (T1547.001)",
        "timestamp": datetime.now().isoformat(),
        "startup_paths": get_startup_paths(),
        "statistics": {
            "total_startup_files": len(scan_results),
            "suspicious_files": len(suspicious_files),
            "registry_run_entries": len(registry_entries),
            "suspicious_registry_entries": len(suspicious_reg),
        },
        "file_analysis": scan_results[:30],
        "registry_analysis": registry_entries[:20],
        "mitre_technique": {
            "id": "T1547.001",
            "name": "Boot or Logon Autostart Execution: Registry Run Keys / Startup Folder",
            "tactic": "Persistence, Privilege Escalation",
        },
        "recommendation": "Investigate CRITICAL and HIGH files. Verify hashes against known-good baselines."
            if suspicious_files else "No suspicious startup entries detected.",
    }


def main():
    parser = argparse.ArgumentParser(description="Startup Folder Persistence Hunting Agent (T1547.001)")
    sub = parser.add_subparsers(dest="command")
    sub.add_parser("scan", help="Scan startup folders for suspicious files")
    sub.add_parser("registry", help="Check Registry Run keys")
    p_mon = sub.add_parser("monitor", help="Monitor startup folders in real-time")
    p_mon.add_argument("--duration", type=int, default=60, help="Monitor duration in seconds")
    sub.add_parser("full", help="Full persistence threat hunt")
    args = parser.parse_args()

    if args.command == "scan":
        result = scan_startup_folders()
    elif args.command == "registry":
        result = check_registry_run_keys()
    elif args.command == "monitor":
        result = monitor_startup(args.duration)
    elif args.command == "full" or args.command is None:
        result = full_hunt()
    else:
        parser.print_help()
        return
    print(json.dumps(result, indent=2, default=str))


if __name__ == "__main__":
    main()
