Files
Anthropic-Cybersecurity-Skills/skills/hunting-for-startup-folder-persistence/scripts/agent.py
T
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

291 lines
10 KiB
Python

#!/usr/bin/env python3
"""Agent for hunting T1547.001 startup folder persistence on Windows."""
import json
import os
import hashlib
import argparse
import time
from datetime import datetime
from pathlib import Path
try:
from watchdog.observers import Observer
from watchdog.events import FileSystemEventHandler
except ImportError:
Observer = None
FileSystemEventHandler = object
SUSPICIOUS_EXTENSIONS = {
".exe": 30, ".bat": 35, ".cmd": 35, ".vbs": 40, ".vbe": 40,
".js": 40, ".jse": 40, ".wsf": 40, ".wsh": 35, ".ps1": 45,
".pif": 45, ".scr": 40, ".hta": 45, ".lnk": 15, ".url": 20,
}
LEGITIMATE_ENTRIES = [
"desktop.ini", "Send to OneNote.lnk", "OneNote 2016.lnk",
"Microsoft Teams.lnk", "Outlook.lnk", "OneDrive.lnk",
"Cisco AnyConnect Secure Mobility Client.lnk",
"Skype for Business.lnk", "Zoom.lnk",
]
def get_startup_paths():
"""Return user-specific and all-users startup folder paths."""
paths = []
user_startup = os.path.join(
os.environ.get("APPDATA", ""),
"Microsoft", "Windows", "Start Menu", "Programs", "Startup"
)
if os.path.isdir(user_startup):
paths.append({"path": user_startup, "scope": "current_user"})
all_users_startup = os.path.join(
os.environ.get("PROGRAMDATA", r"C:\ProgramData"),
"Microsoft", "Windows", "Start Menu", "Programs", "Startup"
)
if os.path.isdir(all_users_startup):
paths.append({"path": all_users_startup, "scope": "all_users"})
return paths
def compute_file_hash(filepath):
"""Compute SHA-256 hash of a file."""
sha256 = hashlib.sha256()
try:
with open(filepath, "rb") as f:
for chunk in iter(lambda: f.read(8192), b""):
sha256.update(chunk)
return sha256.hexdigest()
except (PermissionError, OSError):
return "access_denied"
def analyze_file(filepath, scope="unknown"):
"""Analyze a single file in a startup directory."""
path = Path(filepath)
name = path.name
ext = path.suffix.lower()
try:
stat = path.stat()
created = datetime.fromtimestamp(stat.st_ctime)
modified = datetime.fromtimestamp(stat.st_mtime)
size = stat.st_size
except (PermissionError, OSError):
return {"file": str(filepath), "error": "access_denied"}
is_legitimate = name in LEGITIMATE_ENTRIES
age_days = (datetime.now() - created).days
risk = 0
indicators = []
risk += SUSPICIOUS_EXTENSIONS.get(ext, 0)
if ext in SUSPICIOUS_EXTENSIONS and ext != ".lnk":
indicators.append(f"suspicious_extension_{ext}")
if age_days < 7:
risk += 25
indicators.append("recently_created")
if age_days < 1:
risk += 15
indicators.append("created_within_24h")
if size == 0:
risk += 10
indicators.append("zero_byte_file")
if size > 10 * 1024 * 1024:
risk += 10
indicators.append("large_file_over_10mb")
if not is_legitimate:
risk += 10
indicators.append("not_in_baseline")
if scope == "all_users" and ext in SUSPICIOUS_EXTENSIONS:
risk += 10
indicators.append("all_users_startup")
risk = min(risk, 100)
return {
"file": str(filepath),
"filename": name,
"extension": ext,
"scope": scope,
"size_bytes": size,
"created": created.isoformat(),
"modified": modified.isoformat(),
"age_days": age_days,
"sha256": compute_file_hash(filepath),
"is_legitimate_baseline": is_legitimate,
"suspicious_indicators": indicators,
"risk_score": risk,
"risk_level": "CRITICAL" if risk >= 70 else "HIGH" if risk >= 50 else "MEDIUM" if risk >= 25 else "LOW",
}
def scan_startup_folders():
"""Scan all startup directories and analyze contents."""
startup_paths = get_startup_paths()
results = []
for sp in startup_paths:
folder = sp["path"]
scope = sp["scope"]
try:
for entry in os.listdir(folder):
full_path = os.path.join(folder, entry)
if os.path.isfile(full_path):
analysis = analyze_file(full_path, scope)
results.append(analysis)
except PermissionError:
results.append({"path": folder, "error": "access_denied"})
results.sort(key=lambda x: x.get("risk_score", 0), reverse=True)
return results
def check_registry_run_keys():
"""Check Registry Run keys for autostart entries."""
import subprocess
run_keys = [
r"HKCU\Software\Microsoft\Windows\CurrentVersion\Run",
r"HKCU\Software\Microsoft\Windows\CurrentVersion\RunOnce",
r"HKLM\Software\Microsoft\Windows\CurrentVersion\Run",
r"HKLM\Software\Microsoft\Windows\CurrentVersion\RunOnce",
]
entries = []
for key in run_keys:
try:
result = subprocess.run(
["reg", "query", key],
capture_output=True, text=True, timeout=10
)
if result.returncode == 0:
for line in result.stdout.strip().split("\n"):
line = line.strip()
if line and not line.startswith("HK") and "REG_" in line:
parts = line.split("REG_SZ", 1) if "REG_SZ" in line else line.split("REG_EXPAND_SZ", 1)
name = parts[0].strip() if parts else line
value = parts[1].strip() if len(parts) > 1 else ""
entries.append({
"registry_key": key,
"name": name,
"value": value,
"suspicious": any(p in value.lower() for p in
["powershell", "cmd.exe", "\\temp\\", "\\appdata\\",
"mshta", "-enc", "downloadstring"]),
})
except Exception:
pass
return entries
class StartupMonitorHandler(FileSystemEventHandler):
"""Watchdog handler for monitoring startup folder changes."""
def __init__(self):
self.events = []
def on_created(self, event):
if not event.is_directory:
analysis = analyze_file(event.src_path)
alert = {
"event": "FILE_CREATED",
"timestamp": datetime.now().isoformat(),
"file": event.src_path,
"risk_score": analysis.get("risk_score", 0),
"risk_level": analysis.get("risk_level", "UNKNOWN"),
"indicators": analysis.get("suspicious_indicators", []),
}
self.events.append(alert)
print(json.dumps(alert, indent=2))
def on_modified(self, event):
if not event.is_directory:
alert = {
"event": "FILE_MODIFIED",
"timestamp": datetime.now().isoformat(),
"file": event.src_path,
}
self.events.append(alert)
print(json.dumps(alert, indent=2))
def on_deleted(self, event):
if not event.is_directory:
alert = {
"event": "FILE_DELETED",
"timestamp": datetime.now().isoformat(),
"file": event.src_path,
}
self.events.append(alert)
print(json.dumps(alert, indent=2))
def monitor_startup(duration_seconds=60):
"""Monitor startup folders in real-time using watchdog."""
if not Observer:
return {"error": "watchdog not installed: pip install watchdog"}
handler = StartupMonitorHandler()
observer = Observer()
startup_paths = get_startup_paths()
for sp in startup_paths:
observer.schedule(handler, sp["path"], recursive=False)
observer.start()
try:
time.sleep(duration_seconds)
except KeyboardInterrupt:
pass
observer.stop()
observer.join()
return {"monitored_seconds": duration_seconds, "events_detected": handler.events}
def full_hunt():
"""Run comprehensive startup persistence threat hunt."""
scan_results = scan_startup_folders()
registry_entries = check_registry_run_keys()
suspicious_files = [r for r in scan_results if r.get("risk_score", 0) >= 25]
suspicious_reg = [e for e in registry_entries if e.get("suspicious")]
return {
"hunt_type": "Startup Folder Persistence (T1547.001)",
"timestamp": datetime.now().isoformat(),
"startup_paths": get_startup_paths(),
"statistics": {
"total_startup_files": len(scan_results),
"suspicious_files": len(suspicious_files),
"registry_run_entries": len(registry_entries),
"suspicious_registry_entries": len(suspicious_reg),
},
"file_analysis": scan_results[:30],
"registry_analysis": registry_entries[:20],
"mitre_technique": {
"id": "T1547.001",
"name": "Boot or Logon Autostart Execution: Registry Run Keys / Startup Folder",
"tactic": "Persistence, Privilege Escalation",
},
"recommendation": "Investigate CRITICAL and HIGH files. Verify hashes against known-good baselines."
if suspicious_files else "No suspicious startup entries detected.",
}
def main():
parser = argparse.ArgumentParser(description="Startup Folder Persistence Hunting Agent (T1547.001)")
sub = parser.add_subparsers(dest="command")
sub.add_parser("scan", help="Scan startup folders for suspicious files")
sub.add_parser("registry", help="Check Registry Run keys")
p_mon = sub.add_parser("monitor", help="Monitor startup folders in real-time")
p_mon.add_argument("--duration", type=int, default=60, help="Monitor duration in seconds")
sub.add_parser("full", help="Full persistence threat hunt")
args = parser.parse_args()
if args.command == "scan":
result = scan_startup_folders()
elif args.command == "registry":
result = check_registry_run_keys()
elif args.command == "monitor":
result = monitor_startup(args.duration)
elif args.command == "full" or args.command is None:
result = full_hunt()
else:
parser.print_help()
return
print(json.dumps(result, indent=2, default=str))
if __name__ == "__main__":
main()