mirror of
https://github.com/mukul975/Anthropic-Cybersecurity-Skills.git
synced 2026-07-16 12:45:17 +03:00
230 lines
9.2 KiB
Python
230 lines
9.2 KiB
Python
#!/usr/bin/env python3
|
|
"""Registry Run Key Persistence Hunter - detects T1547.001 persistence via Sysmon Event 13 analysis and registry run key auditing"""
|
|
|
|
import argparse
|
|
import json
|
|
import re
|
|
import sys
|
|
from collections import Counter, defaultdict
|
|
from datetime import datetime
|
|
from pathlib import Path
|
|
|
|
RUN_KEY_PATHS = [
|
|
r"HKLM\Software\Microsoft\Windows\CurrentVersion\Run",
|
|
r"HKLM\Software\Microsoft\Windows\CurrentVersion\RunOnce",
|
|
r"HKCU\Software\Microsoft\Windows\CurrentVersion\Run",
|
|
r"HKCU\Software\Microsoft\Windows\CurrentVersion\RunOnce",
|
|
r"HKLM\Software\Microsoft\Windows\CurrentVersion\RunServices",
|
|
r"HKLM\Software\Microsoft\Windows\CurrentVersion\RunServicesOnce",
|
|
r"HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders",
|
|
r"HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders",
|
|
r"HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\User Shell Folders",
|
|
r"HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\User Shell Folders",
|
|
]
|
|
|
|
SUSPICIOUS_PATHS = [
|
|
r"\\temp\\", r"\\tmp\\", r"\\appdata\\local\\temp",
|
|
r"\\downloads\\", r"\\programdata\\", r"\\public\\",
|
|
r"\\users\\public", r"\\recycler\\", r"\\perflogs\\",
|
|
]
|
|
|
|
LOLBINS = [
|
|
"mshta.exe", "rundll32.exe", "regsvr32.exe", "wscript.exe", "cscript.exe",
|
|
"certutil.exe", "bitsadmin.exe", "msiexec.exe", "forfiles.exe",
|
|
"pcalua.exe", "bash.exe", "scriptrunner.exe",
|
|
]
|
|
|
|
SUSPICIOUS_MODIFIERS = [
|
|
"cmd.exe", "powershell.exe", "pwsh.exe", "python.exe", "python3.exe",
|
|
"wmic.exe", "reg.exe", "mshta.exe", "cscript.exe", "wscript.exe",
|
|
]
|
|
|
|
KNOWN_GOOD_VALUES = [
|
|
"SecurityHealth", "WindowsDefender", "iTunesHelper", "VMware",
|
|
"RealTimeProtection", "OneDrive", "Teams", "Spotify",
|
|
]
|
|
|
|
|
|
def load_data(path):
|
|
return json.loads(Path(path).read_text(encoding="utf-8"))
|
|
|
|
|
|
def is_run_key(target_object):
|
|
"""Check if registry path matches Run/RunOnce keys."""
|
|
target_lower = target_object.lower().replace("/", "\\")
|
|
for rk in RUN_KEY_PATHS:
|
|
if rk.lower() in target_lower:
|
|
return True
|
|
return False
|
|
|
|
|
|
def analyze_sysmon_events(events):
|
|
"""Analyze Sysmon Event ID 13 (RegistryEvent Value Set) for persistence."""
|
|
findings = []
|
|
for evt in events:
|
|
event_id = evt.get("EventID", evt.get("event_id", evt.get("EventCode", 0)))
|
|
if str(event_id) != "13":
|
|
continue
|
|
event_data = evt.get("EventData", evt)
|
|
target_obj = event_data.get("TargetObject", event_data.get("target_object", ""))
|
|
details = event_data.get("Details", event_data.get("details", event_data.get("value", "")))
|
|
image = event_data.get("Image", event_data.get("image", event_data.get("process", "")))
|
|
user = event_data.get("User", event_data.get("user", ""))
|
|
timestamp = evt.get("TimeCreated", evt.get("timestamp", evt.get("UtcTime", "")))
|
|
if not is_run_key(target_obj):
|
|
continue
|
|
severity = "medium"
|
|
indicators = []
|
|
details_lower = (details or "").lower()
|
|
for susp_path in SUSPICIOUS_PATHS:
|
|
if susp_path.lower() in details_lower:
|
|
severity = "high"
|
|
indicators.append(f"suspicious_path:{susp_path.strip(chr(92))}")
|
|
break
|
|
for lolbin in LOLBINS:
|
|
if lolbin.lower() in details_lower:
|
|
severity = "high"
|
|
indicators.append(f"lolbin:{lolbin}")
|
|
break
|
|
if "powershell" in details_lower and ("-enc" in details_lower or "-e " in details_lower or "encodedcommand" in details_lower):
|
|
severity = "critical"
|
|
indicators.append("encoded_powershell")
|
|
if re.search(r"(FromBase64|IEX|Invoke-Expression|DownloadString|Net\.WebClient)", details or "", re.IGNORECASE):
|
|
severity = "critical"
|
|
indicators.append("malicious_powershell_pattern")
|
|
image_name = (image or "").split("\\")[-1].lower()
|
|
for susp_mod in SUSPICIOUS_MODIFIERS:
|
|
if susp_mod.lower() == image_name:
|
|
severity = max(severity, "high", key=lambda x: {"low": 0, "medium": 1, "high": 2, "critical": 3}[x])
|
|
indicators.append(f"suspicious_modifier:{susp_mod}")
|
|
break
|
|
value_name = target_obj.split("\\")[-1] if "\\" in target_obj else target_obj
|
|
is_known_good = any(kg.lower() in value_name.lower() for kg in KNOWN_GOOD_VALUES)
|
|
if is_known_good:
|
|
severity = "low"
|
|
indicators.append("known_good_match")
|
|
findings.append({
|
|
"type": "registry_run_key_persistence",
|
|
"severity": severity,
|
|
"resource": target_obj,
|
|
"detail": f"Run key set by {image_name or 'unknown'}: {(details or '')[:120]}",
|
|
"mitre_technique": "T1547.001",
|
|
"mitre_tactic": "Persistence",
|
|
"modifying_process": image,
|
|
"registry_value": details,
|
|
"user": user,
|
|
"timestamp": timestamp,
|
|
"indicators": indicators,
|
|
})
|
|
return findings
|
|
|
|
|
|
def analyze_registry_snapshot(entries):
|
|
"""Analyze a static registry snapshot for suspicious Run key values."""
|
|
findings = []
|
|
for entry in entries:
|
|
key_path = entry.get("key", entry.get("path", ""))
|
|
value_name = entry.get("name", entry.get("value_name", ""))
|
|
value_data = entry.get("data", entry.get("value_data", entry.get("value", "")))
|
|
if not is_run_key(key_path) and not any(rk.lower().split("\\")[-1] in key_path.lower() for rk in RUN_KEY_PATHS[:4]):
|
|
continue
|
|
severity = "medium"
|
|
indicators = []
|
|
data_lower = (value_data or "").lower()
|
|
for susp_path in SUSPICIOUS_PATHS:
|
|
if susp_path.lower() in data_lower:
|
|
severity = "high"
|
|
indicators.append(f"suspicious_path")
|
|
break
|
|
for lolbin in LOLBINS:
|
|
if lolbin.lower() in data_lower:
|
|
severity = "high"
|
|
indicators.append(f"lolbin:{lolbin}")
|
|
break
|
|
if not Path(value_data.strip('"').split(" ")[0]).suffix.lower() in (".exe", ".dll", ".bat", ".cmd", ".ps1", ".vbs", ".js", ".hta", ".scr", ".com", ""):
|
|
pass
|
|
is_known = any(kg.lower() in (value_name or "").lower() for kg in KNOWN_GOOD_VALUES)
|
|
if is_known:
|
|
severity = "low"
|
|
findings.append({
|
|
"type": "run_key_entry",
|
|
"severity": severity,
|
|
"resource": f"{key_path}\\{value_name}",
|
|
"detail": f"Value: {(value_data or '')[:120]}",
|
|
"mitre_technique": "T1547.001",
|
|
"indicators": indicators,
|
|
})
|
|
return findings
|
|
|
|
|
|
def generate_sigma_rule(finding):
|
|
"""Generate a Sigma detection rule from a finding."""
|
|
details = finding.get("registry_value", finding.get("detail", ""))
|
|
return {
|
|
"title": f"Suspicious Run Key Persistence - {finding['resource'].split(chr(92))[-1]}",
|
|
"status": "experimental",
|
|
"logsource": {"product": "windows", "category": "registry_set"},
|
|
"detection": {
|
|
"selection": {
|
|
"EventType": "SetValue",
|
|
"TargetObject|contains": "CurrentVersion\\Run",
|
|
"Details|contains": details[:60] if details else "",
|
|
},
|
|
"condition": "selection",
|
|
},
|
|
"level": finding["severity"],
|
|
"tags": ["attack.persistence", "attack.t1547.001"],
|
|
}
|
|
|
|
|
|
def analyze(data):
|
|
findings = []
|
|
if isinstance(data, list):
|
|
has_event_id = any("EventID" in e or "event_id" in e or "EventCode" in e for e in data)
|
|
if has_event_id:
|
|
findings.extend(analyze_sysmon_events(data))
|
|
else:
|
|
findings.extend(analyze_registry_snapshot(data))
|
|
elif isinstance(data, dict):
|
|
sysmon = data.get("sysmon_events", data.get("events", []))
|
|
registry = data.get("registry_entries", data.get("snapshot", []))
|
|
findings.extend(analyze_sysmon_events(sysmon))
|
|
findings.extend(analyze_registry_snapshot(registry))
|
|
return findings
|
|
|
|
|
|
def generate_report(input_path):
|
|
data = load_data(input_path)
|
|
findings = analyze(data)
|
|
sev = Counter(f["severity"] for f in findings)
|
|
critical_high = [f for f in findings if f["severity"] in ("critical", "high")]
|
|
sigma_rules = [generate_sigma_rule(f) for f in critical_high[:5]]
|
|
return {
|
|
"report": "registry_run_key_persistence_hunt",
|
|
"generated_at": datetime.utcnow().isoformat() + "Z",
|
|
"mitre_technique": "T1547.001",
|
|
"total_findings": len(findings),
|
|
"severity_summary": dict(sev),
|
|
"high_priority_count": len(critical_high),
|
|
"sigma_rules": sigma_rules,
|
|
"findings": findings,
|
|
}
|
|
|
|
|
|
def main():
|
|
ap = argparse.ArgumentParser(description="Registry Run Key Persistence Hunter")
|
|
ap.add_argument("--input", required=True, help="Input JSON with Sysmon events or registry snapshot")
|
|
ap.add_argument("--output", help="Output JSON report path")
|
|
args = ap.parse_args()
|
|
report = generate_report(args.input)
|
|
out = json.dumps(report, indent=2)
|
|
if args.output:
|
|
Path(args.output).write_text(out, encoding="utf-8")
|
|
print(f"Report written to {args.output}")
|
|
else:
|
|
print(out)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|