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

249 lines
9.3 KiB
Python

#!/usr/bin/env python3
"""Agent for performing malware persistence investigation.
Enumerates Windows registry Run keys, services, scheduled tasks,
WMI subscriptions, and Linux cron/systemd persistence mechanisms.
"""
import json
import sys
import xml.etree.ElementTree as ET
from pathlib import Path
SUSPICIOUS_INDICATORS = [
"powershell", "cmd /c", "wscript", "cscript", "mshta",
"regsvr32", "rundll32", "certutil", "bitsadmin",
"downloadstring", "invoke-", "iex", "hidden",
"bypass", "base64", "-enc", "-e ", ".ps1", ".vbs", ".hta",
"programdata", "appdata\\local\\temp", "/tmp/",
]
class PersistenceInvestigator:
"""Investigates malware persistence mechanisms on forensic images."""
def __init__(self, evidence_root, output_dir):
self.evidence_root = Path(evidence_root)
self.output_dir = Path(output_dir)
self.output_dir.mkdir(parents=True, exist_ok=True)
self.findings = []
def _is_suspicious(self, value):
"""Check if a value matches suspicious indicators."""
value_lower = str(value).lower()
return any(ind in value_lower for ind in SUSPICIOUS_INDICATORS)
def check_registry_run_keys(self):
"""Check registry Run keys using python-registry."""
try:
from Registry import Registry
except ImportError:
return [{"error": "Install python-registry: pip install python-registry"}]
entries = []
sw_path = self.evidence_root / "Windows/System32/config/SOFTWARE"
if not sw_path.exists():
return entries
reg = Registry.Registry(str(sw_path))
run_paths = [
"Microsoft\\Windows\\CurrentVersion\\Run",
"Microsoft\\Windows\\CurrentVersion\\RunOnce",
"Wow6432Node\\Microsoft\\Windows\\CurrentVersion\\Run",
]
for key_path in run_paths:
try:
key = reg.open(key_path)
for value in key.values():
entry = {
"location": f"HKLM\\SOFTWARE\\{key_path}",
"name": value.name(),
"value": str(value.value()),
"timestamp": str(key.timestamp()),
"suspicious": self._is_suspicious(str(value.value())),
}
entries.append(entry)
if entry["suspicious"]:
self.findings.append(entry)
except Exception:
continue
return entries
def check_services(self):
"""Check Windows services for suspicious auto-start entries."""
try:
from Registry import Registry
except ImportError:
return []
entries = []
system_path = self.evidence_root / "Windows/System32/config/SYSTEM"
if not system_path.exists():
return entries
reg = Registry.Registry(str(system_path))
try:
select = reg.open("Select")
current = select.value("Current").value()
cs = f"ControlSet{current:03d}"
services = reg.open(f"{cs}\\Services")
for svc in services.subkeys():
try:
start = svc.value("Start").value()
if start not in (0, 1, 2):
continue
image_path = ""
try:
image_path = svc.value("ImagePath").value()
except Exception:
continue
entry = {
"service_name": svc.name(),
"image_path": image_path,
"start_type": start,
"timestamp": str(svc.timestamp()),
"suspicious": self._is_suspicious(image_path),
}
entries.append(entry)
if entry["suspicious"]:
self.findings.append(entry)
except Exception:
continue
except Exception:
pass
return entries
def check_scheduled_tasks(self):
"""Parse Windows scheduled task XML files."""
tasks_dir = self.evidence_root / "Windows/System32/Tasks"
if not tasks_dir.exists():
return []
entries = []
ns = {"t": "http://schemas.microsoft.com/windows/2004/02/mit/task"}
for task_file in tasks_dir.rglob("*"):
if not task_file.is_file():
continue
try:
tree = ET.parse(str(task_file))
root = tree.getroot()
for action in root.findall(".//t:Exec", ns):
cmd = action.find("t:Command", ns)
args = action.find("t:Arguments", ns)
cmd_text = cmd.text if cmd is not None else ""
args_text = args.text if args is not None else ""
combined = f"{cmd_text} {args_text}"
entry = {
"task_name": task_file.name,
"command": cmd_text,
"arguments": args_text,
"suspicious": self._is_suspicious(combined),
}
entries.append(entry)
if entry["suspicious"]:
self.findings.append(entry)
except Exception:
continue
return entries
def check_startup_folder(self):
"""Check Windows startup folder contents."""
entries = []
startup_paths = [
"ProgramData/Microsoft/Windows/Start Menu/Programs/Startup",
]
for user_dir in (self.evidence_root / "Users").iterdir() if (self.evidence_root / "Users").exists() else []:
if user_dir.is_dir():
startup_paths.append(
f"Users/{user_dir.name}/AppData/Roaming/Microsoft/Windows/Start Menu/Programs/Startup"
)
for spath in startup_paths:
target = self.evidence_root / spath
if target.exists() and target.is_dir():
for item in target.iterdir():
if item.is_file():
entries.append({
"location": str(spath),
"filename": item.name,
"size": item.stat().st_size,
"suspicious": self._is_suspicious(item.name),
})
return entries
def check_linux_persistence(self):
"""Check Linux persistence mechanisms."""
entries = []
checks = {
"crontab": "etc/crontab",
"rc_local": "etc/rc.local",
"ld_preload": "etc/ld.so.preload",
}
for name, path in checks.items():
target = self.evidence_root / path
if target.exists() and target.is_file():
content = target.read_text(errors="ignore")
entries.append({
"mechanism": name,
"file": path,
"content_preview": content[:500],
"suspicious": self._is_suspicious(content),
})
cron_dirs = ["etc/cron.d", "etc/cron.daily", "var/spool/cron/crontabs"]
for cron_dir in cron_dirs:
target = self.evidence_root / cron_dir
if target.exists() and target.is_dir():
for f in target.iterdir():
if f.is_file():
content = f.read_text(errors="ignore")
entries.append({
"mechanism": "cron_job",
"file": str(f.relative_to(self.evidence_root)),
"content_preview": content[:300],
})
auth_keys_pattern = "home/*/. ssh/authorized_keys".replace(". ", ".")
for ak in self.evidence_root.glob("home/*/.ssh/authorized_keys"):
entries.append({
"mechanism": "ssh_authorized_keys",
"file": str(ak.relative_to(self.evidence_root)),
"content_preview": ak.read_text(errors="ignore")[:300],
})
return entries
def generate_report(self):
"""Run all persistence checks and generate report."""
report = {
"case_output": str(self.output_dir),
"evidence_root": str(self.evidence_root),
"registry_run_keys": self.check_registry_run_keys(),
"services": self.check_services()[:30],
"scheduled_tasks": self.check_scheduled_tasks(),
"startup_folder": self.check_startup_folder(),
"linux_persistence": self.check_linux_persistence(),
"suspicious_findings": self.findings,
"total_suspicious": len(self.findings),
}
report_path = self.output_dir / "persistence_report.json"
with open(report_path, "w") as f:
json.dump(report, f, indent=2)
print(json.dumps(report, indent=2))
return report
def main():
if len(sys.argv) < 3:
print("Usage: agent.py <evidence_root> <output_dir>")
sys.exit(1)
investigator = PersistenceInvestigator(sys.argv[1], sys.argv[2])
investigator.generate_report()
if __name__ == "__main__":
main()