Files
Anthropic-Cybersecurity-Skills/skills/hunting-for-startup-folder-persistence/scripts/agent.py
T
mukul975 915ea611e5 Add 10 new cybersecurity skills with full folder anatomy
Skills added:
- implementing-privileged-access-workstation (IAM, PAW hardening)
- detecting-suspicious-oauth-application-consent (cloud security, Graph API)
- performing-hardware-security-module-integration (cryptography, PKCS#11)
- analyzing-android-malware-with-apktool (malware analysis, androguard)
- hunting-for-unusual-service-installations (threat hunting, T1543.003)
- detecting-shadow-it-cloud-usage (cloud security, proxy/DNS log analysis)
- performing-active-directory-forest-trust-attack (red team, impacket)
- implementing-deception-based-detection-with-canarytoken (deception, Canary API)
- analyzing-office365-audit-logs-for-compromise (cloud security, BEC detection)
- hunting-for-startup-folder-persistence (threat hunting, T1547.001)

Each skill includes SKILL.md, LICENSE, scripts/agent.py, references/api-reference.md
2026-03-11 00:47:03 +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, timedelta
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()