#!/usr/bin/env python3
"""Process injection detection agent using Volatility and Sysmon analysis."""

import json
import os
import subprocess
import sys
from datetime import datetime

try:
    import Evtx.Evtx as evtx
    HAS_EVTX = True
except ImportError:
    HAS_EVTX = False


INJECTION_TECHNIQUES = {
    "classic_dll_injection": {
        "apis": ["OpenProcess", "VirtualAllocEx", "WriteProcessMemory", "CreateRemoteThread"],
        "sysmon_events": [8, 10],
        "description": "Classic DLL injection via remote thread creation",
    },
    "process_hollowing": {
        "apis": ["CreateProcess(SUSPENDED)", "NtUnmapViewOfSection", "VirtualAllocEx",
                 "WriteProcessMemory", "SetThreadContext", "ResumeThread"],
        "sysmon_events": [1, 10],
        "description": "Process hollowing - replace legitimate process image",
    },
    "apc_injection": {
        "apis": ["OpenThread", "VirtualAllocEx", "WriteProcessMemory", "QueueUserAPC"],
        "sysmon_events": [8, 10],
        "description": "APC queue injection via QueueUserAPC",
    },
    "reflective_dll": {
        "apis": ["VirtualAlloc", "memcpy", "CreateThread"],
        "sysmon_events": [7],
        "description": "Reflective DLL loading without LoadLibrary",
    },
    "process_doppelganging": {
        "apis": ["CreateTransaction", "CreateFileTransacted", "NtCreateSection",
                 "NtCreateProcessEx", "RollbackTransaction"],
        "sysmon_events": [1],
        "description": "Process doppelganging via NTFS transactions",
    },
}


def run_volatility_malfind(memory_dump, pid=None):
    """Run Volatility malfind plugin to detect injected code."""
    if not os.path.exists(memory_dump):
        return {"error": f"Memory dump not found: {memory_dump}"}

    cmd = ["vol3", "-f", memory_dump, "windows.malfind"]
    if pid:
        cmd.extend(["--pid", str(pid)])
    try:
        result = subprocess.run(cmd, capture_output=True, text=True, timeout=300)
        findings = []
        current = {}
        for line in result.stdout.splitlines():
            if line.strip() and not line.startswith("Volatility"):
                parts = line.split()
                if len(parts) >= 6 and parts[0].isdigit():
                    if current:
                        findings.append(current)
                    current = {
                        "pid": parts[0],
                        "process": parts[1] if len(parts) > 1 else "",
                        "address": parts[2] if len(parts) > 2 else "",
                        "protection": parts[4] if len(parts) > 4 else "",
                    }
        if current:
            findings.append(current)
        return {"findings": findings, "count": len(findings)}
    except FileNotFoundError:
        return {"error": "Volatility 3 (vol3) not installed"}
    except subprocess.TimeoutExpired:
        return {"error": "Analysis timed out after 300s"}


def run_volatility_hollowfind(memory_dump):
    """Detect process hollowing via VAD/PEB image path mismatch."""
    if not os.path.exists(memory_dump):
        return {"error": f"Memory dump not found: {memory_dump}"}

    cmd = ["vol3", "-f", memory_dump, "windows.pslist"]
    try:
        result = subprocess.run(cmd, capture_output=True, text=True, timeout=300)
        processes = []
        for line in result.stdout.splitlines():
            parts = line.split()
            if len(parts) >= 4 and parts[0].isdigit():
                processes.append({
                    "pid": parts[0],
                    "ppid": parts[1] if len(parts) > 1 else "",
                    "name": parts[2] if len(parts) > 2 else "",
                    "threads": parts[3] if len(parts) > 3 else "",
                })
        return {"processes": processes, "count": len(processes)}
    except FileNotFoundError:
        return {"error": "Volatility 3 not installed"}
    except subprocess.TimeoutExpired:
        return {"error": "Analysis timed out"}


def scan_sysmon_injection_events(evtx_path):
    """Scan Sysmon logs for injection-related events."""
    if not HAS_EVTX:
        return {"error": "python-evtx not installed (pip install python-evtx)"}
    if not os.path.exists(evtx_path):
        return {"error": f"EVTX file not found: {evtx_path}"}

    injection_events = []
    with evtx.Evtx(evtx_path) as log:
        for record in log.records():
            try:
                xml = record.xml()
                if "<EventID>8</EventID>" in xml:
                    injection_events.append({
                        "event_id": 8,
                        "type": "CreateRemoteThread",
                        "timestamp": record.timestamp().isoformat(),
                        "snippet": xml[:500],
                    })
                elif "<EventID>10</EventID>" in xml:
                    if "PROCESS_VM_WRITE" in xml or "PROCESS_CREATE_THREAD" in xml:
                        injection_events.append({
                            "event_id": 10,
                            "type": "ProcessAccess (injection prep)",
                            "timestamp": record.timestamp().isoformat(),
                            "snippet": xml[:500],
                        })
            except Exception:
                continue

    return {"injection_events": len(injection_events), "events": injection_events[:50]}


def detect_rwx_memory_regions():
    """Detect processes with suspicious RWX memory regions (Windows)."""
    ps_cmd = (
        "Get-Process | ForEach-Object { "
        "$p = $_; "
        "try { "
        "$modules = $p.Modules | Select-Object -First 1; "
        "[PSCustomObject]@{Name=$p.ProcessName; PID=$p.Id; WorkingSet=$p.WorkingSet64} "
        "} catch {} } | ConvertTo-Json"
    )
    try:
        result = subprocess.run(
            ["powershell", "-Command", ps_cmd],
            capture_output=True, text=True, timeout=15
        )
        if result.returncode == 0 and result.stdout.strip():
            return json.loads(result.stdout)
        return {"error": result.stderr.strip()}
    except Exception as e:
        return {"error": str(e)}


def check_unusual_parent_child():
    """Detect unusual parent-child process relationships."""
    suspicious_combos = [
        ("svchost.exe", "cmd.exe"),
        ("svchost.exe", "powershell.exe"),
        ("explorer.exe", "cmd.exe"),
        ("winword.exe", "powershell.exe"),
        ("winword.exe", "cmd.exe"),
        ("excel.exe", "powershell.exe"),
        ("outlook.exe", "powershell.exe"),
    ]
    ps_cmd = (
        "Get-WmiObject Win32_Process | Select-Object ProcessId, Name, ParentProcessId | "
        "ConvertTo-Json"
    )
    try:
        result = subprocess.run(
            ["powershell", "-Command", ps_cmd],
            capture_output=True, text=True, timeout=15
        )
        if result.returncode != 0:
            return {"error": result.stderr.strip()}
        processes = json.loads(result.stdout)
        if not isinstance(processes, list):
            processes = [processes]

        pid_map = {p["ProcessId"]: p["Name"] for p in processes}
        suspicious = []
        for proc in processes:
            parent_name = pid_map.get(proc.get("ParentProcessId"), "").lower()
            child_name = (proc.get("Name") or "").lower()
            for p, c in suspicious_combos:
                if parent_name == p.lower() and child_name == c.lower():
                    suspicious.append({
                        "parent": parent_name,
                        "child": child_name,
                        "child_pid": proc["ProcessId"],
                        "risk": "HIGH",
                    })
        return {"suspicious_relationships": suspicious, "count": len(suspicious)}
    except Exception as e:
        return {"error": str(e)}


def generate_report(memory_dump=None, sysmon_evtx=None):
    """Generate process injection detection report."""
    report = {
        "timestamp": datetime.utcnow().isoformat() + "Z",
        "techniques_catalog": {k: v["description"] for k, v in INJECTION_TECHNIQUES.items()},
    }
    if memory_dump:
        report["malfind"] = run_volatility_malfind(memory_dump)
    if sysmon_evtx:
        report["sysmon_injection"] = scan_sysmon_injection_events(sysmon_evtx)
    report["parent_child_anomalies"] = check_unusual_parent_child()
    return report


if __name__ == "__main__":
    action = sys.argv[1] if len(sys.argv) > 1 else "help"
    if action == "malfind" and len(sys.argv) > 2:
        pid = int(sys.argv[3]) if len(sys.argv) > 3 else None
        print(json.dumps(run_volatility_malfind(sys.argv[2], pid), indent=2, default=str))
    elif action == "sysmon" and len(sys.argv) > 2:
        print(json.dumps(scan_sysmon_injection_events(sys.argv[2]), indent=2, default=str))
    elif action == "parent-child":
        print(json.dumps(check_unusual_parent_child(), indent=2))
    elif action == "report":
        mem = sys.argv[2] if len(sys.argv) > 2 else None
        sysmon = sys.argv[3] if len(sys.argv) > 3 else None
        print(json.dumps(generate_report(mem, sysmon), indent=2, default=str))
    else:
        print("Usage: agent.py [malfind <memory.dmp> [pid]|sysmon <Sysmon.evtx>|parent-child|report [mem] [sysmon]]")
