#!/usr/bin/env python3
"""Agent for performing memory forensics with Volatility 3.

Automates memory dump analysis including process enumeration,
network connection extraction, malware detection, and credential
extraction using Volatility 3 framework via subprocess.
"""

import subprocess
import json
import sys
import re
from pathlib import Path


class MemoryForensicsAgent:
    """Automates Volatility 3 memory forensics analysis."""

    def __init__(self, memory_dump, output_dir):
        self.memory_dump = memory_dump
        self.output_dir = Path(output_dir)
        self.output_dir.mkdir(parents=True, exist_ok=True)

    def _run_vol(self, plugin, extra_args=None):
        """Execute a Volatility 3 plugin and return output."""
        cmd = ["vol", "-f", self.memory_dump, plugin]
        if extra_args:
            cmd.extend(extra_args)
        result = subprocess.run(cmd, capture_output=True, text=True, timeout=300)
        return {"output": result.stdout, "stderr": result.stderr, "rc": result.returncode}

    def get_os_info(self):
        """Identify the operating system from the memory dump."""
        result = self._run_vol("windows.info")
        if result["rc"] != 0:
            result = self._run_vol("linux.info")
        return result

    def list_processes(self):
        """List all running processes."""
        return self._run_vol("windows.pslist")

    def get_process_tree(self):
        """Show process tree with parent-child relationships."""
        return self._run_vol("windows.pstree")

    def scan_hidden_processes(self):
        """Scan for hidden/unlinked processes via pool scanning."""
        return self._run_vol("windows.psscan")

    def detect_injected_code(self):
        """Detect process injection via malfind plugin."""
        return self._run_vol("windows.malfind")

    def get_network_connections(self):
        """Extract active network connections."""
        return self._run_vol("windows.netscan")

    def get_command_lines(self):
        """Extract command lines for all processes."""
        return self._run_vol("windows.cmdline")

    def dump_process_memory(self, pid):
        """Dump memory of a specific process."""
        dump_dir = self.output_dir / "process_dumps"
        dump_dir.mkdir(exist_ok=True)
        return self._run_vol("windows.memmap", [
            "--pid", str(pid), "--dump", "-o", str(dump_dir)
        ])

    def extract_hashes(self):
        """Extract cached password hashes."""
        return self._run_vol("windows.hashdump")

    def scan_with_yara(self, yara_file):
        """Scan memory with YARA rules."""
        return self._run_vol("yarascan", ["--yara-file", yara_file])

    def get_registry_keys(self, key_path):
        """Extract specific registry keys from memory."""
        return self._run_vol("windows.registry.printkey", ["--key", key_path])

    def list_services(self):
        """List Windows services from memory."""
        return self._run_vol("windows.svcscan")

    def list_loaded_modules(self):
        """List loaded kernel modules."""
        return self._run_vol("windows.modules")

    def scan_hidden_modules(self):
        """Scan for hidden kernel modules."""
        return self._run_vol("windows.modscan")

    def get_dll_list(self, pid=None):
        """List DLLs loaded by processes."""
        args = ["--pid", str(pid)] if pid else []
        return self._run_vol("windows.dlllist", args if args else None)

    def detect_anomalies(self):
        """Compare pslist vs psscan to find hidden processes."""
        pslist = self._run_vol("windows.pslist")
        psscan = self._run_vol("windows.psscan")

        pslist_pids = set(re.findall(r"^\s*(\d+)\s", pslist["output"], re.MULTILINE))
        psscan_pids = set(re.findall(r"^\s*(\d+)\s", psscan["output"], re.MULTILINE))

        hidden = psscan_pids - pslist_pids
        return {
            "pslist_count": len(pslist_pids),
            "psscan_count": len(psscan_pids),
            "hidden_pids": sorted(hidden),
            "hidden_count": len(hidden),
        }

    def generate_report(self, yara_file=None):
        """Run comprehensive memory analysis and generate report."""
        report = {
            "memory_dump": self.memory_dump,
            "os_info": self.get_os_info()["output"][:500],
        }

        report["process_list"] = self.list_processes()["output"]
        report["process_tree"] = self.get_process_tree()["output"]
        report["malfind"] = self.detect_injected_code()["output"]
        report["network"] = self.get_network_connections()["output"]
        report["cmdline"] = self.get_command_lines()["output"]
        report["hashes"] = self.extract_hashes()["output"]
        report["services"] = self.list_services()["output"][:2000]
        report["hidden_processes"] = self.detect_anomalies()

        if yara_file:
            report["yara_hits"] = self.scan_with_yara(yara_file)["output"]

        report_path = self.output_dir / "memory_forensics_report.json"
        with open(report_path, "w") as f:
            json.dump(report, f, indent=2)

        print(f"Memory Forensics Report: {report_path}")
        print(f"Hidden processes: {report['hidden_processes']['hidden_count']}")
        if report["malfind"]["output"].strip():
            print("Malfind detections found - check report for details")
        return report


def main():
    if len(sys.argv) < 3:
        print("Usage: agent.py <memory_dump> <output_dir> [yara_rules]")
        sys.exit(1)

    memory_dump = sys.argv[1]
    output_dir = sys.argv[2]
    yara_file = sys.argv[3] if len(sys.argv) > 3 else None

    agent = MemoryForensicsAgent(memory_dump, output_dir)
    agent.generate_report(yara_file)


if __name__ == "__main__":
    main()
