#!/usr/bin/env python3 """Agent for performing endpoint forensics investigation on Windows systems.""" import json import argparse import subprocess import os import hashlib from datetime import datetime def collect_system_info(): """Collect basic system information for forensic context.""" info = {} commands = { "hostname": ["hostname"], "os_version": ["wmic", "os", "get", "Caption,Version,BuildNumber", "/format:list"], "network_config": ["ipconfig", "/all"], "logged_users": ["query", "user"], "uptime": ["wmic", "os", "get", "LastBootUpTime", "/format:list"], } for key, cmd in commands.items(): try: result = subprocess.run(cmd, capture_output=True, text=True, timeout=10) info[key] = result.stdout.strip()[:1000] except Exception as e: info[key] = f"Error: {e}" return {"timestamp": datetime.utcnow().isoformat(), "system_info": info} def collect_running_processes(): """Collect running processes with parent PIDs and command lines.""" try: result = subprocess.run( ["wmic", "process", "get", "Name,ProcessId,ParentProcessId,CommandLine,ExecutablePath", "/format:csv"], capture_output=True, text=True, timeout=30 ) except Exception as e: return {"error": str(e)} import csv from io import StringIO processes = [] reader = csv.DictReader(StringIO(result.stdout)) for row in reader: if row.get("Name"): processes.append({ "name": row.get("Name", ""), "pid": row.get("ProcessId", ""), "ppid": row.get("ParentProcessId", ""), "path": row.get("ExecutablePath", ""), "cmdline": row.get("CommandLine", "")[:500], }) return {"total": len(processes), "processes": processes} def collect_network_connections(): """Collect active network connections.""" try: result = subprocess.run( ["netstat", "-ano"], capture_output=True, text=True, timeout=15 ) except Exception as e: return {"error": str(e)} connections = [] for line in result.stdout.split("\n")[4:]: parts = line.split() if len(parts) >= 5: connections.append({ "protocol": parts[0], "local_addr": parts[1], "remote_addr": parts[2], "state": parts[3] if len(parts) > 4 else "", "pid": parts[-1], }) established = [c for c in connections if c.get("state") == "ESTABLISHED"] listening = [c for c in connections if c.get("state") == "LISTENING"] return { "total": len(connections), "established": len(established), "listening": len(listening), "connections": connections, } def collect_autoruns(): """Collect common persistence locations.""" autoruns = {} reg_keys = [ r"HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Run", r"HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\Run", ] for key in reg_keys: try: result = subprocess.run(["reg", "query", key], capture_output=True, text=True, timeout=10) autoruns[key] = result.stdout.strip()[:1000] except Exception: continue try: result = subprocess.run(["schtasks", "/query", "/fo", "CSV"], capture_output=True, text=True, timeout=30) autoruns["scheduled_tasks_count"] = result.stdout.count("\n") - 1 except Exception: pass return autoruns def hash_file(filepath): """Calculate MD5, SHA1, SHA256 hashes of a file for evidence integrity.""" hashes = {} algos = {"md5": hashlib.md5(), "sha1": hashlib.sha1(), "sha256": hashlib.sha256()} try: with open(filepath, "rb") as f: while True: chunk = f.read(8192) if not chunk: break for algo in algos.values(): algo.update(chunk) for name, algo in algos.items(): hashes[name] = algo.hexdigest() hashes["file"] = str(filepath) hashes["size"] = os.path.getsize(filepath) except Exception as e: hashes["error"] = str(e) return hashes def full_triage(): """Run full endpoint forensic triage collection.""" return { "timestamp": datetime.utcnow().isoformat(), "system_info": collect_system_info(), "processes": collect_running_processes(), "network": collect_network_connections(), "autoruns": collect_autoruns(), } def main(): parser = argparse.ArgumentParser(description="Endpoint Forensics Investigation Agent") sub = parser.add_subparsers(dest="command") sub.add_parser("triage", help="Full forensic triage collection") sub.add_parser("processes", help="Collect running processes") sub.add_parser("network", help="Collect network connections") sub.add_parser("autoruns", help="Collect autorun/persistence entries") h = sub.add_parser("hash", help="Hash a file for evidence") h.add_argument("--file", required=True) args = parser.parse_args() if args.command == "triage": result = full_triage() elif args.command == "processes": result = collect_running_processes() elif args.command == "network": result = collect_network_connections() elif args.command == "autoruns": result = collect_autoruns() elif args.command == "hash": result = hash_file(args.file) else: parser.print_help() return print(json.dumps(result, indent=2, default=str)) if __name__ == "__main__": main()