mirror of
https://github.com/mukul975/Anthropic-Cybersecurity-Skills.git
synced 2026-06-16 16:03:17 +03:00
c47eed6a64
- 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
176 lines
7.0 KiB
Python
176 lines
7.0 KiB
Python
#!/usr/bin/env python3
|
|
"""Volatile evidence collection agent for compromised hosts.
|
|
|
|
Collects volatile forensic artifacts following RFC 3227 order of volatility:
|
|
registers, memory, network state, running processes, disk, and logs.
|
|
Uses platform-native tools for live response evidence gathering.
|
|
"""
|
|
|
|
import sys
|
|
import json
|
|
import os
|
|
import datetime
|
|
import subprocess
|
|
import shlex
|
|
import platform
|
|
import hashlib
|
|
|
|
|
|
VOLATILITY_ORDER = [
|
|
{"priority": 1, "source": "memory", "description": "Physical memory (RAM) dump",
|
|
"tool_linux": "avml", "tool_windows": "winpmem_mini_x64.exe"},
|
|
{"priority": 2, "source": "network_connections", "description": "Active network connections",
|
|
"tool_linux": "ss -tunap", "tool_windows": "netstat -anob"},
|
|
{"priority": 3, "source": "running_processes", "description": "Running process list with details",
|
|
"tool_linux": "ps auxwwf", "tool_windows": "tasklist /V /FO CSV"},
|
|
{"priority": 4, "source": "open_files", "description": "Open file handles",
|
|
"tool_linux": "lsof -nP", "tool_windows": "handle64.exe -a"},
|
|
{"priority": 5, "source": "network_config", "description": "Network interface configuration",
|
|
"tool_linux": "ip addr show", "tool_windows": "ipconfig /all"},
|
|
{"priority": 6, "source": "routing_table", "description": "Network routing table",
|
|
"tool_linux": "ip route show", "tool_windows": "route print"},
|
|
{"priority": 7, "source": "arp_cache", "description": "ARP cache entries",
|
|
"tool_linux": "ip neigh show", "tool_windows": "arp -a"},
|
|
{"priority": 8, "source": "dns_cache", "description": "DNS resolver cache",
|
|
"tool_linux": "cat /etc/resolv.conf", "tool_windows": "ipconfig /displaydns"},
|
|
{"priority": 9, "source": "logged_users", "description": "Currently logged-in users",
|
|
"tool_linux": "w", "tool_windows": "query user"},
|
|
{"priority": 10, "source": "scheduled_tasks", "description": "Scheduled tasks and cron jobs",
|
|
"tool_linux": ["crontab -l", "ls /etc/cron.d/"], "tool_windows": "schtasks /query /FO CSV /V"},
|
|
]
|
|
|
|
|
|
def _run_single_cmd(cmd_str, timeout=60):
|
|
"""Run a single command string without shell, return stdout and stderr."""
|
|
return subprocess.run(
|
|
shlex.split(cmd_str), capture_output=True, text=True, timeout=timeout
|
|
)
|
|
|
|
|
|
def collect_artifact(source_config, output_dir):
|
|
"""Collect a single volatile artifact."""
|
|
is_windows = platform.system() == "Windows"
|
|
cmd = source_config["tool_windows"] if is_windows else source_config["tool_linux"]
|
|
source_name = source_config["source"]
|
|
output_file = os.path.join(output_dir, source_name + ".txt")
|
|
|
|
result = {
|
|
"source": source_name,
|
|
"priority": source_config["priority"],
|
|
"command": cmd if isinstance(cmd, str) else "; ".join(cmd),
|
|
"timestamp": datetime.datetime.utcnow().isoformat() + "Z",
|
|
"status": "pending",
|
|
}
|
|
|
|
try:
|
|
# Run multiple commands sequentially if cmd is a list, combining output
|
|
if isinstance(cmd, list):
|
|
combined_stdout = ""
|
|
combined_stderr = ""
|
|
last_rc = 0
|
|
for sub_cmd in cmd:
|
|
sub_proc = _run_single_cmd(sub_cmd, timeout=60)
|
|
combined_stdout += sub_proc.stdout
|
|
combined_stderr += sub_proc.stderr
|
|
last_rc = sub_proc.returncode
|
|
proc = type("CombinedResult", (), {
|
|
"stdout": combined_stdout,
|
|
"stderr": combined_stderr,
|
|
"returncode": last_rc,
|
|
})()
|
|
else:
|
|
proc = _run_single_cmd(cmd, timeout=60)
|
|
result["status"] = "collected"
|
|
result["output_lines"] = len(proc.stdout.splitlines())
|
|
result["output_file"] = output_file
|
|
result["exit_code"] = proc.returncode
|
|
|
|
with open(output_file, "w", encoding="utf-8") as f:
|
|
f.write("# Collected: {}\n".format(result["timestamp"]))
|
|
f.write("# Command: {}\n".format(cmd))
|
|
f.write("# Exit code: {}\n\n".format(proc.returncode))
|
|
f.write(proc.stdout)
|
|
if proc.stderr:
|
|
f.write("\n# STDERR:\n" + proc.stderr)
|
|
|
|
sha256 = hashlib.sha256(proc.stdout.encode()).hexdigest()
|
|
result["sha256"] = sha256
|
|
except subprocess.TimeoutExpired:
|
|
result["status"] = "timeout"
|
|
except Exception as e:
|
|
result["status"] = "error"
|
|
result["error"] = str(e)
|
|
|
|
return result
|
|
|
|
|
|
def run_collection(output_dir, sources=None):
|
|
"""Run full volatile evidence collection."""
|
|
os.makedirs(output_dir, exist_ok=True)
|
|
if sources is None:
|
|
sources = VOLATILITY_ORDER
|
|
|
|
manifest = {
|
|
"collection_start": datetime.datetime.utcnow().isoformat() + "Z",
|
|
"hostname": platform.node(),
|
|
"platform": platform.system(),
|
|
"output_dir": output_dir,
|
|
"artifacts": [],
|
|
}
|
|
|
|
for source_config in sorted(sources, key=lambda x: x["priority"]):
|
|
if source_config["source"] == "memory":
|
|
manifest["artifacts"].append({
|
|
"source": "memory",
|
|
"priority": 1,
|
|
"status": "skipped",
|
|
"note": "Memory dump requires elevated privileges and dedicated tool",
|
|
})
|
|
continue
|
|
|
|
result = collect_artifact(source_config, output_dir)
|
|
manifest["artifacts"].append(result)
|
|
|
|
manifest["collection_end"] = datetime.datetime.utcnow().isoformat() + "Z"
|
|
manifest["total_collected"] = sum(1 for a in manifest["artifacts"] if a["status"] == "collected")
|
|
|
|
manifest_path = os.path.join(output_dir, "collection_manifest.json")
|
|
with open(manifest_path, "w", encoding="utf-8") as f:
|
|
json.dump(manifest, f, indent=2)
|
|
manifest["manifest_file"] = manifest_path
|
|
|
|
return manifest
|
|
|
|
|
|
if __name__ == "__main__":
|
|
print("=" * 60)
|
|
print("Volatile Evidence Collection Agent")
|
|
print("RFC 3227 order of volatility, live response artifacts")
|
|
print("=" * 60)
|
|
print(" Platform: {}".format(platform.system()))
|
|
print(" Hostname: {}".format(platform.node()))
|
|
|
|
output_dir = sys.argv[1] if len(sys.argv) > 1 else os.path.join(
|
|
os.path.expanduser("~"), "volatile_evidence_" + datetime.datetime.utcnow().strftime("%Y%m%d_%H%M%S")
|
|
)
|
|
|
|
print("\n--- RFC 3227 Volatility Order ---")
|
|
for v in VOLATILITY_ORDER:
|
|
tool = v["tool_windows"] if platform.system() == "Windows" else v["tool_linux"]
|
|
print(" P{}: {:25s} [{}]".format(v["priority"], v["source"], tool))
|
|
|
|
print("\n[*] Collecting volatile evidence to: {}".format(output_dir))
|
|
manifest = run_collection(output_dir)
|
|
|
|
print("\n--- Collection Results ---")
|
|
for a in manifest["artifacts"]:
|
|
status_marker = "+" if a["status"] == "collected" else "-"
|
|
print(" [{}] P{}: {} -> {}".format(
|
|
status_marker, a["priority"], a["source"], a["status"]))
|
|
|
|
print("\nTotal collected: {}/{}".format(
|
|
manifest["total_collected"], len(manifest["artifacts"])))
|
|
print("Manifest: {}".format(manifest.get("manifest_file", "")))
|
|
|
|
print("\n" + json.dumps({"artifacts_collected": manifest["total_collected"]}, indent=2))
|