#!/usr/bin/env python3 """Rootkit indicator detection script. Detects potential rootkit presence by comparing process/module enumeration methods, checking for suspicious drivers, and identifying hook indicators. Works with both live systems and memory dump analysis output. """ from __future__ import annotations import argparse import json import os import platform import subprocess import sys from datetime import datetime from pathlib import Path def detect_hidden_processes_linux() -> list: """Compare /proc enumeration vs ps output to find hidden processes.""" findings = [] try: # Get PIDs from /proc proc_pids = set() if os.path.exists("/proc"): for entry in os.listdir("/proc"): if entry.isdigit(): proc_pids.add(int(entry)) # Get PIDs from ps result = subprocess.run( ["ps", "-eo", "pid", "--no-headers"], capture_output=True, text=True, timeout=10 ) ps_pids = set() for line in result.stdout.strip().split("\n"): line = line.strip() if line.isdigit(): ps_pids.add(int(line)) # Discrepancies indicate potential rootkit hidden_from_ps = proc_pids - ps_pids hidden_from_proc = ps_pids - proc_pids if hidden_from_ps: findings.append({ "type": "hidden_process", "severity": "critical", "description": f"Processes in /proc but not in ps: {hidden_from_ps}", "pids": list(hidden_from_ps) }) if hidden_from_proc: findings.append({ "type": "hidden_process", "severity": "critical", "description": f"Processes in ps but not in /proc: {hidden_from_proc}", "pids": list(hidden_from_proc) }) except Exception as e: findings.append({ "type": "error", "severity": "info", "description": f"Process comparison failed: {e}" }) return findings def check_kernel_modules_linux() -> list: """Check for suspicious kernel modules on Linux.""" findings = [] try: result = subprocess.run( ["lsmod"], capture_output=True, text=True, timeout=10 ) suspicious_indicators = [ "hide", "stealth", "rootkit", "hook", "sniff", "keylog", "backdoor", "inject" ] for line in result.stdout.strip().split("\n")[1:]: # skip header parts = line.split() if not parts: continue module_name = parts[0].lower() for indicator in suspicious_indicators: if indicator in module_name: findings.append({ "type": "suspicious_module", "severity": "high", "description": f"Suspicious kernel module: {parts[0]}", "module": parts[0], "indicator": indicator }) # Check for modules not in /lib/modules for line in result.stdout.strip().split("\n")[1:]: parts = line.split() if not parts: continue module_name = parts[0] modinfo = subprocess.run( ["modinfo", module_name], capture_output=True, text=True, timeout=5 ) if modinfo.returncode != 0: findings.append({ "type": "unknown_module", "severity": "medium", "description": f"Module with no modinfo: {module_name}", "module": module_name }) except FileNotFoundError: findings.append({ "type": "error", "severity": "info", "description": "lsmod not available" }) except Exception as e: findings.append({ "type": "error", "severity": "info", "description": f"Module check failed: {e}" }) return findings def check_syscall_table_linux() -> list: """Check for syscall table modifications (requires root).""" findings = [] syscall_table = "/proc/kallsyms" if os.path.exists(syscall_table): try: with open(syscall_table, "r") as f: content = f.read() # Check if sys_call_table is readable (non-zero) if "sys_call_table" in content: findings.append({ "type": "syscall_table", "severity": "info", "description": "sys_call_table found in kallsyms - verify integrity manually" }) except PermissionError: findings.append({ "type": "info", "severity": "info", "description": "Cannot read /proc/kallsyms - need root for syscall table analysis" }) return findings def check_linux_rootkit_tools() -> list: """Run standard Linux rootkit detection tools if available.""" findings = [] tools = ["chkrootkit", "rkhunter"] for tool in tools: result = subprocess.run( ["which", tool], capture_output=True, text=True ) if result.returncode == 0: findings.append({ "type": "tool_available", "severity": "info", "description": f"{tool} is available - run: {tool} --check" }) return findings def check_windows_drivers() -> list: """Check for suspicious Windows drivers (basic heuristics).""" findings = [] try: result = subprocess.run( ["driverquery", "/v", "/fo", "csv"], capture_output=True, text=True, timeout=30 ) suspicious_indicators = [ "hide", "stealth", "rootkit", "hook", "sniff", "keylog", "inject", "shadow" ] for line in result.stdout.strip().split("\n")[1:]: line_lower = line.lower() for indicator in suspicious_indicators: if indicator in line_lower: findings.append({ "type": "suspicious_driver", "severity": "high", "description": f"Suspicious driver name: {line.split(',')[0]}", "indicator": indicator }) except FileNotFoundError: pass except Exception as e: findings.append({ "type": "error", "severity": "info", "description": f"Driver check failed: {e}" }) return findings def analyze_volatility_output(vol_json_path) -> list: """Analyze Volatility 3 JSON output for rootkit indicators.""" findings = [] try: with open(vol_json_path, "r") as f: data = json.load(f) if "pslist" in data and "psscan" in data: pslist_pids = {p.get("PID") for p in data["pslist"] if p.get("PID")} psscan_pids = {p.get("PID") for p in data["psscan"] if p.get("PID")} hidden = psscan_pids - pslist_pids if hidden: findings.append({ "type": "dkom_hidden_process", "severity": "critical", "description": f"DKOM-hidden processes detected (in psscan, not pslist): {hidden}", "pids": list(hidden) }) if "ssdt" in data: for entry in data["ssdt"]: module = entry.get("Module", "") if module and module not in ["ntoskrnl.exe", "win32k.sys", "ntkrnlmp.exe", "ntkrnlpa.exe"]: findings.append({ "type": "ssdt_hook", "severity": "critical", "description": f"SSDT hook detected: {entry.get('Function', 'unknown')} -> {module}", "function": entry.get("Function"), "module": module }) if "modules" in data: for module in data["modules"]: name = module.get("Name", "").lower() suspicious = ["hide", "stealth", "rootkit", "hook", "inject"] for s in suspicious: if s in name: findings.append({ "type": "suspicious_module", "severity": "high", "description": f"Suspicious kernel module: {module.get('Name')}", "module": module.get("Name"), "base": module.get("Base") }) except (json.JSONDecodeError, KeyError) as e: findings.append({ "type": "error", "severity": "info", "description": f"Failed to parse Volatility output: {e}" }) return findings def main() -> None: parser = argparse.ArgumentParser( description="Detect rootkit indicators on live systems or from Volatility output" ) parser.add_argument( "--mode", choices=["live", "volatility"], default="live", help="Analysis mode: 'live' for live system, 'volatility' for memory dump analysis" ) parser.add_argument( "--input", "--vol-output", type=str, help="Path to Volatility 3 JSON output file (for volatility mode)" ) parser.add_argument( "--output", "-o", type=str, help="Output file path (JSON)" ) parser.add_argument( "--format", choices=["json", "text"], default="text", help="Output format" ) args = parser.parse_args() findings = [] if args.mode == "live": system = platform.system() if system == "Linux": findings.extend(detect_hidden_processes_linux()) findings.extend(check_kernel_modules_linux()) findings.extend(check_syscall_table_linux()) findings.extend(check_linux_rootkit_tools()) elif system == "Windows": findings.extend(check_windows_drivers()) else: print(f"[!] Unsupported platform: {system}", file=sys.stderr) sys.exit(1) elif args.mode == "volatility": if not args.vol_output: print("[!] --vol-output required for volatility mode", file=sys.stderr) sys.exit(1) findings.extend(analyze_volatility_output(args.vol_output)) report = { "tool": "rootkit_detector", "timestamp": datetime.now().isoformat(), "mode": args.mode, "platform": platform.system(), "findings": findings, "summary": { "total": len(findings), "critical": sum(1 for f in findings if f.get("severity") == "critical"), "high": sum(1 for f in findings if f.get("severity") == "high"), "medium": sum(1 for f in findings if f.get("severity") == "medium"), "info": sum(1 for f in findings if f.get("severity") == "info") } } if args.format == "json": output = json.dumps(report, indent=2) else: output = f"=== Rootkit Detection Report ===\n" output += f"Timestamp: {report['timestamp']}\n" output += f"Mode: {report['mode']} | Platform: {report['platform']}\n" output += f"Findings: {report['summary']['total']} " output += f"(Critical: {report['summary']['critical']}, " output += f"High: {report['summary']['high']}, " output += f"Medium: {report['summary']['medium']})\n\n" for f in findings: severity = f.get("severity", "info").upper() output += f"[{severity}] {f['type']}: {f['description']}\n" if args.output: Path(args.output).write_text(output) print(f"[+] Report saved to {args.output}") else: print(output) if __name__ == "__main__": main()