Files
Anthropic-Cybersecurity-Skills/skills/eradicating-malware-from-infected-systems/scripts/process.py
T

443 lines
18 KiB
Python

#!/usr/bin/env python3
"""
Malware Eradication Automation Script
Scans systems for persistence mechanisms, removes identified malware
artifacts, and validates eradication success.
Requirements:
pip install psutil yara-python
"""
import argparse
import csv
import hashlib
import json
import logging
import os
import platform
import subprocess
import sys
from datetime import datetime, timezone
from pathlib import Path
try:
import psutil
except ImportError:
print("Install psutil: pip install psutil")
sys.exit(1)
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s [%(levelname)s] %(message)s",
handlers=[
logging.StreamHandler(),
logging.FileHandler(f"eradication_{datetime.now().strftime('%Y%m%d_%H%M%S')}.log"),
],
)
logger = logging.getLogger("malware_eradication")
class PersistenceScanner:
"""Scan for known persistence mechanisms on Windows and Linux."""
def __init__(self):
self.findings = []
self.system = platform.system()
def scan_all(self) -> list:
"""Run all persistence checks."""
if self.system == "Windows":
self._scan_registry_run_keys()
self._scan_scheduled_tasks()
self._scan_services()
self._scan_startup_folders()
self._scan_wmi_subscriptions()
else:
self._scan_crontabs()
self._scan_systemd_services()
self._scan_rc_local()
self._scan_shell_profiles()
self._scan_authorized_keys()
self._scan_ld_preload()
return self.findings
def _scan_registry_run_keys(self):
"""Scan Windows registry Run keys."""
run_keys = [
r"HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Run",
r"HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\Run",
r"HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\RunOnce",
r"HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\RunOnce",
r"HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\Explorer\Run",
]
for key in run_keys:
try:
result = subprocess.run(
["reg", "query", key], capture_output=True, text=True, timeout=10,
)
if result.returncode == 0 and result.stdout.strip():
for line in result.stdout.strip().split("\n"):
line = line.strip()
if line and "REG_SZ" in line or "REG_EXPAND_SZ" in line:
self.findings.append({
"type": "registry_run_key",
"location": key,
"value": line,
"risk": "medium",
})
except Exception as e:
logger.warning(f"Failed to scan {key}: {e}")
def _scan_scheduled_tasks(self):
"""Scan Windows scheduled tasks."""
try:
result = subprocess.run(
["schtasks", "/query", "/fo", "CSV", "/v"],
capture_output=True, text=True, timeout=30,
)
if result.returncode == 0:
lines = result.stdout.strip().split("\n")
if len(lines) > 1:
reader = csv.DictReader(lines)
for row in reader:
task_name = row.get("TaskName", "")
task_run = row.get("Task To Run", "")
if task_name and task_run and "N/A" not in task_run:
# Flag tasks with suspicious indicators
suspicious = any(kw in task_run.lower() for kw in [
"powershell", "cmd /c", "mshta", "wscript",
"cscript", "certutil", "bitsadmin", "temp",
"appdata", "public",
])
self.findings.append({
"type": "scheduled_task",
"location": task_name,
"value": task_run,
"risk": "high" if suspicious else "low",
})
except Exception as e:
logger.warning(f"Failed to scan scheduled tasks: {e}")
def _scan_services(self):
"""Scan Windows services for suspicious entries."""
try:
for service in psutil.win_service_iter():
try:
info = service.as_dict()
binpath = info.get("binpath", "")
if binpath and not any(
binpath.lower().startswith(p) for p in [
"c:\\windows\\", "c:\\program files\\",
"c:\\program files (x86)\\",
]
):
self.findings.append({
"type": "service",
"location": info.get("name", ""),
"value": binpath,
"risk": "medium",
"status": info.get("status", ""),
})
except Exception:
continue
except Exception as e:
logger.warning(f"Failed to scan services: {e}")
def _scan_startup_folders(self):
"""Scan Windows startup folders."""
startup_paths = [
os.path.expandvars(r"%APPDATA%\Microsoft\Windows\Start Menu\Programs\Startup"),
r"C:\ProgramData\Microsoft\Windows\Start Menu\Programs\Startup",
]
for path in startup_paths:
if os.path.exists(path):
for item in os.listdir(path):
full_path = os.path.join(path, item)
if os.path.isfile(full_path) and item != "desktop.ini":
self.findings.append({
"type": "startup_folder",
"location": path,
"value": full_path,
"risk": "medium",
})
def _scan_wmi_subscriptions(self):
"""Scan for WMI event subscriptions (Windows)."""
try:
result = subprocess.run(
["powershell", "-Command",
"Get-WMIObject -Namespace root\\Subscription -Class __EventFilter | Select-Object Name, Query | ConvertTo-Json"],
capture_output=True, text=True, timeout=15,
)
if result.stdout.strip():
filters = json.loads(result.stdout)
if isinstance(filters, dict):
filters = [filters]
for f in filters:
self.findings.append({
"type": "wmi_subscription",
"location": "root\\Subscription\\__EventFilter",
"value": f.get("Name", "") + ": " + f.get("Query", ""),
"risk": "high",
})
except Exception as e:
logger.warning(f"Failed to scan WMI subscriptions: {e}")
def _scan_crontabs(self):
"""Scan Linux crontab entries."""
cron_paths = ["/etc/crontab"]
cron_dirs = ["/etc/cron.d", "/etc/cron.daily", "/etc/cron.hourly",
"/etc/cron.weekly", "/etc/cron.monthly"]
# User crontabs
spool_dir = "/var/spool/cron/crontabs"
if os.path.exists(spool_dir):
for f in os.listdir(spool_dir):
cron_paths.append(os.path.join(spool_dir, f))
for d in cron_dirs:
if os.path.exists(d):
for f in os.listdir(d):
cron_paths.append(os.path.join(d, f))
for path in cron_paths:
if os.path.isfile(path):
try:
with open(path) as f:
for line in f:
line = line.strip()
if line and not line.startswith("#"):
self.findings.append({
"type": "crontab",
"location": path,
"value": line,
"risk": "medium",
})
except PermissionError:
pass
def _scan_systemd_services(self):
"""Scan for custom systemd services."""
service_dirs = ["/etc/systemd/system", "/usr/lib/systemd/system"]
for sdir in service_dirs:
if os.path.exists(sdir):
for f in os.listdir(sdir):
if f.endswith(".service"):
fpath = os.path.join(sdir, f)
try:
with open(fpath) as sf:
content = sf.read()
if "ExecStart" in content:
self.findings.append({
"type": "systemd_service",
"location": fpath,
"value": f,
"risk": "low",
})
except PermissionError:
pass
def _scan_rc_local(self):
"""Scan /etc/rc.local."""
if os.path.exists("/etc/rc.local"):
try:
with open("/etc/rc.local") as f:
for line in f:
line = line.strip()
if line and not line.startswith("#") and line != "exit 0":
self.findings.append({
"type": "rc_local",
"location": "/etc/rc.local",
"value": line,
"risk": "high",
})
except PermissionError:
pass
def _scan_shell_profiles(self):
"""Scan shell profile files for suspicious entries."""
profile_files = [
os.path.expanduser("~/.bashrc"),
os.path.expanduser("~/.profile"),
os.path.expanduser("~/.bash_profile"),
"/etc/profile",
]
suspicious_commands = ["curl", "wget", "nc ", "ncat", "python -c", "perl -e",
"base64", "eval", "/dev/tcp"]
for pf in profile_files:
if os.path.exists(pf):
try:
with open(pf) as f:
for line in f:
line = line.strip()
if any(cmd in line.lower() for cmd in suspicious_commands):
self.findings.append({
"type": "shell_profile",
"location": pf,
"value": line,
"risk": "high",
})
except PermissionError:
pass
def _scan_authorized_keys(self):
"""Scan for unauthorized SSH keys."""
home_dirs = ["/root"] + [
os.path.join("/home", d) for d in os.listdir("/home")
if os.path.isdir(os.path.join("/home", d))
] if os.path.exists("/home") else ["/root"]
for hd in home_dirs:
ak_path = os.path.join(hd, ".ssh", "authorized_keys")
if os.path.exists(ak_path):
try:
with open(ak_path) as f:
for line in f:
line = line.strip()
if line and not line.startswith("#"):
self.findings.append({
"type": "authorized_keys",
"location": ak_path,
"value": line[:100] + "..." if len(line) > 100 else line,
"risk": "medium",
})
except PermissionError:
pass
def _scan_ld_preload(self):
"""Scan for LD_PRELOAD hijacking."""
if os.path.exists("/etc/ld.so.preload"):
try:
with open("/etc/ld.so.preload") as f:
content = f.read().strip()
if content:
self.findings.append({
"type": "ld_preload",
"location": "/etc/ld.so.preload",
"value": content,
"risk": "critical",
})
except PermissionError:
pass
class MalwareRemover:
"""Remove identified malware artifacts."""
def __init__(self, dry_run: bool = True):
self.dry_run = dry_run
self.removal_log = []
def remove_file(self, filepath: str) -> bool:
if self.dry_run:
logger.info(f"[DRY RUN] Would remove file: {filepath}")
self.removal_log.append({"action": "remove_file", "target": filepath, "status": "dry_run"})
return True
try:
if os.path.exists(filepath):
file_hash = hashlib.sha256(open(filepath, "rb").read()).hexdigest()
os.remove(filepath)
self.removal_log.append({
"action": "remove_file", "target": filepath,
"status": "removed", "sha256": file_hash,
})
logger.info(f"Removed file: {filepath} (SHA256: {file_hash})")
return True
except Exception as e:
self.removal_log.append({"action": "remove_file", "target": filepath, "status": f"failed: {e}"})
logger.error(f"Failed to remove {filepath}: {e}")
return False
def remove_scheduled_task(self, task_name: str) -> bool:
if self.dry_run:
logger.info(f"[DRY RUN] Would remove scheduled task: {task_name}")
self.removal_log.append({"action": "remove_task", "target": task_name, "status": "dry_run"})
return True
try:
result = subprocess.run(
["schtasks", "/delete", "/tn", task_name, "/f"],
capture_output=True, text=True, timeout=10,
)
status = "removed" if result.returncode == 0 else f"failed: {result.stderr}"
self.removal_log.append({"action": "remove_task", "target": task_name, "status": status})
return result.returncode == 0
except Exception as e:
self.removal_log.append({"action": "remove_task", "target": task_name, "status": f"failed: {e}"})
return False
def remove_registry_value(self, key: str, value_name: str) -> bool:
if self.dry_run:
logger.info(f"[DRY RUN] Would remove registry: {key}\\{value_name}")
self.removal_log.append({"action": "remove_reg", "target": f"{key}\\{value_name}", "status": "dry_run"})
return True
try:
result = subprocess.run(
["reg", "delete", key, "/v", value_name, "/f"],
capture_output=True, text=True, timeout=10,
)
status = "removed" if result.returncode == 0 else f"failed: {result.stderr}"
self.removal_log.append({"action": "remove_reg", "target": f"{key}\\{value_name}", "status": status})
return result.returncode == 0
except Exception as e:
self.removal_log.append({"action": "remove_reg", "target": f"{key}\\{value_name}", "status": f"failed: {e}"})
return False
def generate_report(findings: list, removal_log: list, output_path: str):
"""Generate eradication report."""
report = {
"timestamp": datetime.now(timezone.utc).isoformat(),
"hostname": platform.node(),
"platform": platform.platform(),
"persistence_findings": {
"total": len(findings),
"by_risk": {},
"by_type": {},
"details": findings,
},
"eradication_actions": removal_log,
}
for f in findings:
risk = f.get("risk", "unknown")
ftype = f.get("type", "unknown")
report["persistence_findings"]["by_risk"][risk] = report["persistence_findings"]["by_risk"].get(risk, 0) + 1
report["persistence_findings"]["by_type"][ftype] = report["persistence_findings"]["by_type"].get(ftype, 0) + 1
with open(output_path, "w") as fp:
json.dump(report, fp, indent=2)
logger.info(f"Report saved to: {output_path}")
return report
def main():
parser = argparse.ArgumentParser(description="Malware Eradication Tool")
parser.add_argument("--scan", action="store_true", help="Scan for persistence mechanisms")
parser.add_argument("--remove-files", nargs="*", help="Files to remove")
parser.add_argument("--remove-tasks", nargs="*", help="Scheduled tasks to remove")
parser.add_argument("--dry-run", action="store_true", default=True, help="Simulate removal (default)")
parser.add_argument("--execute", action="store_true", help="Actually perform removal (CAUTION)")
parser.add_argument("--output", default="./eradication_report.json", help="Report output path")
args = parser.parse_args()
dry_run = not args.execute
findings = []
if args.scan:
scanner = PersistenceScanner()
findings = scanner.scan_all()
logger.info(f"Found {len(findings)} persistence items")
for f in findings:
risk_label = f"[{f['risk'].upper()}]"
logger.info(f" {risk_label} {f['type']}: {f['location']} -> {f['value'][:80]}")
remover = MalwareRemover(dry_run=dry_run)
if args.remove_files:
for fp in args.remove_files:
remover.remove_file(fp)
if args.remove_tasks:
for task in args.remove_tasks:
remover.remove_scheduled_task(task)
generate_report(findings, remover.removal_log, args.output)
if __name__ == "__main__":
main()