Files
Anthropic-Cybersecurity-Skills/skills/performing-ransomware-incident-response/scripts/agent.py
T
mukul975 c21af3347e Complete folder anatomy for all 649 cybersecurity skills + update LICENSE to Mahipal
- Add scripts/agent.py and references/api-reference.md to all remaining skills
- Update all 648 LICENSE files: copyright now reads 'Mahipal'
- Add implementing-security-monitoring-with-datadog (new skill with full anatomy)
- All 649 skills now have: SKILL.md, LICENSE, scripts/agent.py, references/api-reference.md
2026-03-11 00:22:12 +01:00

159 lines
6.2 KiB
Python

#!/usr/bin/env python3
"""Ransomware Incident Response agent — automates initial triage, IOC
collection, and containment actions during a ransomware event."""
import argparse
import hashlib
import json
import os
import subprocess
import sys
from datetime import datetime
from pathlib import Path
RANSOMWARE_EXTENSIONS = {
".encrypted", ".locked", ".crypt", ".cry", ".crypto", ".enc",
".locky", ".cerber", ".zepto", ".wncry", ".wnry", ".wcry",
".onion", ".aaa", ".abc", ".xyz", ".zzz", ".micro", ".r5a",
}
RANSOM_NOTE_PATTERNS = [
"readme.txt", "how_to_decrypt.txt", "decrypt_instructions.html",
"restore_files.txt", "help_decrypt.html", "recovery.txt",
"_readme.txt", "how_to_recover.txt",
]
def scan_encrypted_files(target_dir: str, max_files: int = 5000) -> list[dict]:
"""Scan directory for files with ransomware-associated extensions."""
findings = []
count = 0
for root, dirs, files in os.walk(target_dir):
for fname in files:
if count >= max_files:
return findings
fpath = os.path.join(root, fname)
ext = os.path.splitext(fname)[1].lower()
if ext in RANSOMWARE_EXTENSIONS:
try:
stat = os.stat(fpath)
findings.append({
"path": fpath,
"extension": ext,
"size_bytes": stat.st_size,
"modified": datetime.fromtimestamp(stat.st_mtime).isoformat(),
})
except OSError:
pass
count += 1
return findings
def find_ransom_notes(target_dir: str) -> list[dict]:
"""Search for known ransom note filenames."""
notes = []
for root, dirs, files in os.walk(target_dir):
for fname in files:
if fname.lower() in RANSOM_NOTE_PATTERNS:
fpath = os.path.join(root, fname)
try:
with open(fpath, "r", encoding="utf-8", errors="ignore") as fh:
content = fh.read(2048)
notes.append({"path": fpath, "preview": content[:500]})
except OSError:
notes.append({"path": fpath, "preview": "[unreadable]"})
return notes
def collect_file_hashes(file_paths: list[str], max_hash: int = 100) -> list[dict]:
"""Compute SHA-256 hashes for IOC submission."""
hashes = []
for fpath in file_paths[:max_hash]:
try:
sha = hashlib.sha256()
with open(fpath, "rb") as fh:
for chunk in iter(lambda: fh.read(8192), b""):
sha.update(chunk)
hashes.append({"path": fpath, "sha256": sha.hexdigest()})
except OSError:
pass
return hashes
def check_shadow_copies(platform: str = sys.platform) -> dict:
"""Check if Volume Shadow Copies are intact (Windows) or snapshots exist."""
if platform == "win32":
try:
result = subprocess.run(
["vssadmin", "list", "shadows"],
capture_output=True, text=True, timeout=30
)
shadow_count = result.stdout.count("Shadow Copy ID")
return {"platform": "windows", "shadow_copies": shadow_count,
"intact": shadow_count > 0, "raw": result.stdout[:1000]}
except (subprocess.SubprocessError, FileNotFoundError):
return {"platform": "windows", "shadow_copies": 0, "intact": False, "error": "vssadmin unavailable"}
return {"platform": platform, "shadow_copies": -1, "intact": False, "note": "Manual check required"}
def generate_containment_actions(encrypted_count: int, notes_found: int) -> list[dict]:
"""Produce recommended containment actions based on findings."""
actions = [
{"priority": 1, "action": "Isolate affected hosts from network immediately",
"detail": "Disable network adapters or move to quarantine VLAN"},
{"priority": 2, "action": "Preserve forensic evidence before remediation",
"detail": "Create disk images of affected systems"},
{"priority": 3, "action": "Reset credentials for all privileged accounts",
"detail": "Include krbtgt, service accounts, and domain admins"},
]
if encrypted_count > 100:
actions.append({"priority": 4, "action": "Activate business continuity plan",
"detail": f"{encrypted_count} encrypted files detected — significant data impact"})
if notes_found > 0:
actions.append({"priority": 5, "action": "Collect and analyze ransom notes for variant identification",
"detail": "Submit to ID Ransomware (id-ransomware.malwarehunterteam.com)"})
return actions
def generate_report(target_dir: str, max_files: int) -> dict:
"""Run all checks and build consolidated incident report."""
encrypted = scan_encrypted_files(target_dir, max_files)
notes = find_ransom_notes(target_dir)
file_hashes = collect_file_hashes([f["path"] for f in encrypted[:50]])
shadow_status = check_shadow_copies()
actions = generate_containment_actions(len(encrypted), len(notes))
return {
"report": "ransomware_incident_response",
"generated_at": datetime.utcnow().isoformat() + "Z",
"target_directory": target_dir,
"encrypted_files_found": len(encrypted),
"ransom_notes_found": len(notes),
"shadow_copy_status": shadow_status,
"containment_actions": actions,
"encrypted_files_sample": encrypted[:20],
"ransom_notes": notes[:10],
"file_hashes": file_hashes,
}
def main():
parser = argparse.ArgumentParser(description="Ransomware Incident Response Agent")
parser.add_argument("--target", required=True, help="Directory to scan for ransomware artifacts")
parser.add_argument("--max-files", type=int, default=5000, help="Max files to scan (default: 5000)")
parser.add_argument("--output", help="Output JSON file path")
args = parser.parse_args()
report = generate_report(args.target, args.max_files)
output = json.dumps(report, indent=2)
if args.output:
Path(args.output).write_text(output, encoding="utf-8")
print(f"Report written to {args.output}")
else:
print(output)
if __name__ == "__main__":
main()