Files
mukul975 c47eed6a64 Production hardening: security fixes, code quality, 724 skills complete
- Fix 25 shell=True subprocess calls with list-based commands
- Fix 49 verify=False in defensive skills (env-var override)
- Add timeout to 231 HTTP/subprocess/socket calls
- Fix 6 SQL injection patterns with whitelist validation
- Replace 8 __import__() with standard imports
- Remove 701 unused imports across 442 files
- Add authorized-testing disclaimers to all offensive skills
- Complete 11 incomplete skill directories
- Expand 10 stub SKILL.md files with full content
- Fix 2 YAML parse errors in frontmatter
- Fix 5 pre-existing syntax errors
- Convert 22 hardcoded paths/ports to environment variables
- Back up 21 redundant skill pairs to .bak
- Fix 2 global declaration errors
- 724/724 skills with full folder anatomy (SKILL.md + agent.py + api-reference.md + LICENSE)
- 0 compile errors across all 724 agent.py files
2026-03-19 13:26:49 +01:00

208 lines
7.2 KiB
Python

#!/usr/bin/env python3
"""Agent for Linux memory forensics using LiME acquisition and Volatility 3."""
import json
import subprocess
import argparse
from datetime import datetime
from pathlib import Path
def acquire_memory_lime(output_path, lime_format="lime"):
"""Acquire memory using LiME kernel module."""
kernel_version = subprocess.run(
["uname", "-r"], capture_output=True, text=True, timeout=120
).stdout.strip()
lime_module = f"lime-{kernel_version}.ko"
if not Path(lime_module).exists():
lime_module = "lime.ko"
cmd = ["insmod", lime_module, f"path={output_path}", f"format={lime_format}"]
result = subprocess.run(cmd, capture_output=True, text=True, timeout=120)
return {
"status": "success" if result.returncode == 0 else "failed",
"output_path": output_path,
"format": lime_format,
"kernel": kernel_version,
"stderr": result.stderr,
}
def run_vol3_plugin(image_path, plugin_name, extra_args=None):
"""Run a Volatility 3 plugin and capture output."""
cmd = ["vol3", "-f", image_path, plugin_name]
if extra_args:
cmd.extend(extra_args)
try:
result = subprocess.run(
cmd, capture_output=True, text=True, timeout=300,
)
lines = result.stdout.strip().splitlines()
return {"plugin": plugin_name, "output": lines, "error": result.stderr.strip()}
except subprocess.TimeoutExpired:
return {"plugin": plugin_name, "output": [], "error": "Timeout"}
def parse_pslist_output(lines):
"""Parse Volatility linux.pslist output into structured data."""
processes = []
for line in lines:
parts = line.split()
if len(parts) >= 4 and parts[0].isdigit():
processes.append({
"pid": int(parts[0]),
"ppid": int(parts[1]) if parts[1].isdigit() else 0,
"name": parts[-1],
})
return processes
def list_processes(image_path):
"""List all processes from memory image."""
result = run_vol3_plugin(image_path, "linux.pslist")
return parse_pslist_output(result.get("output", []))
def extract_bash_history(image_path):
"""Extract bash command history from memory."""
result = run_vol3_plugin(image_path, "linux.bash")
commands = []
for line in result.get("output", []):
parts = line.split(None, 3)
if len(parts) >= 4 and parts[0].isdigit():
commands.append({
"pid": int(parts[0]),
"name": parts[1],
"timestamp": parts[2] if len(parts) > 2 else "",
"command": parts[3] if len(parts) > 3 else "",
})
return commands
def list_network_connections(image_path):
"""List network connections from memory."""
result = run_vol3_plugin(image_path, "linux.sockstat")
connections = []
for line in result.get("output", []):
if "TCP" in line or "UDP" in line:
connections.append(line.strip())
return connections
def list_kernel_modules(image_path):
"""List loaded kernel modules to detect rootkits."""
result = run_vol3_plugin(image_path, "linux.lsmod")
modules = []
for line in result.get("output", []):
parts = line.split()
if parts and not parts[0].startswith("Offset"):
modules.append({"name": parts[-1] if parts else line.strip()})
return modules
def detect_hidden_processes(image_path):
"""Compare pslist vs psscan to find hidden processes."""
pslist = run_vol3_plugin(image_path, "linux.pslist")
psscan = run_vol3_plugin(image_path, "linux.psscan")
pslist_pids = set()
for line in pslist.get("output", []):
parts = line.split()
if parts and parts[0].isdigit():
pslist_pids.add(int(parts[0]))
hidden = []
for line in psscan.get("output", []):
parts = line.split()
if parts and parts[0].isdigit():
pid = int(parts[0])
if pid not in pslist_pids and pid > 0:
hidden.append({"pid": pid, "line": line.strip()})
return hidden
def detect_suspicious_commands(bash_history):
"""Flag suspicious commands in bash history."""
suspicious_patterns = [
"curl.*|.*sh", "wget.*&&.*chmod", "base64.*-d",
"nc.*-e", "python.*-c.*import.*socket",
"nohup", "rm.*-rf.*/var/log", "history.*-c",
"iptables.*-F", "chmod.*777", "chattr.*-i",
]
import re
findings = []
for entry in bash_history:
cmd = entry.get("command", "")
for pattern in suspicious_patterns:
if re.search(pattern, cmd, re.IGNORECASE):
findings.append({
"pid": entry["pid"],
"command": cmd,
"pattern": pattern,
"severity": "HIGH",
})
break
return findings
def check_malfind(image_path):
"""Run malfind to detect injected code."""
result = run_vol3_plugin(image_path, "linux.malfind")
return result.get("output", [])
def main():
parser = argparse.ArgumentParser(description="LiME + Volatility 3 Forensics Agent")
parser.add_argument("--image", help="Path to memory image")
parser.add_argument("--acquire", help="Output path for LiME acquisition")
parser.add_argument("--output", default="memory_forensics_report.json")
parser.add_argument("--action", choices=[
"acquire", "pslist", "bash", "network", "modules",
"hidden", "malfind", "full_analysis"
], default="full_analysis")
args = parser.parse_args()
report = {"generated_at": datetime.utcnow().isoformat(), "findings": {}}
if args.action == "acquire" and args.acquire:
result = acquire_memory_lime(args.acquire)
report["findings"]["acquisition"] = result
print(f"[+] Memory acquisition: {result['status']}")
return
if not args.image:
print("[-] --image required for analysis actions")
return
if args.action in ("pslist", "full_analysis"):
procs = list_processes(args.image)
report["findings"]["processes"] = procs
print(f"[+] Processes: {len(procs)}")
if args.action in ("bash", "full_analysis"):
history = extract_bash_history(args.image)
report["findings"]["bash_history"] = history
suspicious = detect_suspicious_commands(history)
report["findings"]["suspicious_commands"] = suspicious
print(f"[+] Bash commands: {len(history)}, Suspicious: {len(suspicious)}")
if args.action in ("network", "full_analysis"):
conns = list_network_connections(args.image)
report["findings"]["connections"] = conns
print(f"[+] Network connections: {len(conns)}")
if args.action in ("modules", "full_analysis"):
modules = list_kernel_modules(args.image)
report["findings"]["kernel_modules"] = modules
print(f"[+] Kernel modules: {len(modules)}")
if args.action in ("hidden", "full_analysis"):
hidden = detect_hidden_processes(args.image)
report["findings"]["hidden_processes"] = hidden
print(f"[+] Hidden processes: {len(hidden)}")
with open(args.output, "w") as f:
json.dump(report, f, indent=2, default=str)
print(f"[+] Report saved to {args.output}")
if __name__ == "__main__":
main()