mirror of
https://github.com/mukul975/Anthropic-Cybersecurity-Skills.git
synced 2026-06-11 21:54:56 +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
188 lines
7.0 KiB
Python
188 lines
7.0 KiB
Python
#!/usr/bin/env python3
|
|
# For authorized testing only
|
|
"""AFL++ fuzzing campaign management and crash triage agent."""
|
|
|
|
import json
|
|
import argparse
|
|
import os
|
|
import subprocess
|
|
from datetime import datetime
|
|
|
|
|
|
def instrument_target(source_path, output_path, compiler="afl-clang-fast",
|
|
sanitizer=None):
|
|
"""Compile target with AFL++ instrumentation."""
|
|
cmd = [compiler, "-o", output_path, source_path]
|
|
if sanitizer == "asan":
|
|
cmd.insert(1, "-fsanitize=address")
|
|
elif sanitizer == "ubsan":
|
|
cmd.insert(1, "-fsanitize=undefined")
|
|
result = subprocess.run(cmd, capture_output=True, text=True, timeout=120)
|
|
return {
|
|
"compiler": compiler,
|
|
"source": source_path,
|
|
"output": output_path,
|
|
"sanitizer": sanitizer,
|
|
"success": result.returncode == 0,
|
|
"stderr": result.stderr[:500] if result.stderr else "",
|
|
}
|
|
|
|
|
|
def minimize_corpus(afl_cmin_path, target_binary, input_dir, output_dir):
|
|
"""Minimize seed corpus using afl-cmin."""
|
|
cmd = [afl_cmin_path or "afl-cmin", "-i", input_dir, "-o", output_dir,
|
|
"--", target_binary]
|
|
result = subprocess.run(cmd, capture_output=True, text=True, timeout=600)
|
|
before = len(os.listdir(input_dir)) if os.path.isdir(input_dir) else 0
|
|
after = len(os.listdir(output_dir)) if os.path.isdir(output_dir) else 0
|
|
return {
|
|
"before": before,
|
|
"after": after,
|
|
"reduction_pct": round((1 - after / max(before, 1)) * 100, 1),
|
|
"success": result.returncode == 0,
|
|
}
|
|
|
|
|
|
def parse_fuzzer_stats(output_dir):
|
|
"""Parse afl-fuzz fuzzer_stats file for campaign metrics."""
|
|
stats_path = os.path.join(output_dir, "default", "fuzzer_stats")
|
|
if not os.path.exists(stats_path):
|
|
stats_path = os.path.join(output_dir, "fuzzer_stats")
|
|
if not os.path.exists(stats_path):
|
|
return {"error": f"fuzzer_stats not found in {output_dir}"}
|
|
|
|
stats = {}
|
|
with open(stats_path, "r") as f:
|
|
for line in f:
|
|
if ":" in line:
|
|
key, val = line.split(":", 1)
|
|
stats[key.strip()] = val.strip()
|
|
|
|
return {
|
|
"start_time": stats.get("start_time", ""),
|
|
"last_update": stats.get("last_update", ""),
|
|
"execs_done": int(stats.get("execs_done", 0)),
|
|
"execs_per_sec": float(stats.get("execs_per_sec", 0)),
|
|
"paths_total": int(stats.get("corpus_count", stats.get("paths_total", 0))),
|
|
"paths_found": int(stats.get("paths_found", 0)),
|
|
"unique_crashes": int(stats.get("saved_crashes", stats.get("unique_crashes", 0))),
|
|
"unique_hangs": int(stats.get("saved_hangs", stats.get("unique_hangs", 0))),
|
|
"stability": stats.get("stability", ""),
|
|
"bitmap_cvg": stats.get("bitmap_cvg", ""),
|
|
"command_line": stats.get("command_line", ""),
|
|
}
|
|
|
|
|
|
def triage_crashes(output_dir):
|
|
"""Enumerate and classify crash files from AFL++ output."""
|
|
crash_dirs = [
|
|
os.path.join(output_dir, "default", "crashes"),
|
|
os.path.join(output_dir, "crashes"),
|
|
]
|
|
crash_dir = None
|
|
for d in crash_dirs:
|
|
if os.path.isdir(d):
|
|
crash_dir = d
|
|
break
|
|
if not crash_dir:
|
|
return {"crashes": [], "total": 0}
|
|
|
|
crashes = []
|
|
for filename in sorted(os.listdir(crash_dir)):
|
|
if filename.startswith("README") or filename == ".state":
|
|
continue
|
|
filepath = os.path.join(crash_dir, filename)
|
|
size = os.path.getsize(filepath)
|
|
sig_parts = filename.split(",")
|
|
signal = ""
|
|
for part in sig_parts:
|
|
if part.startswith("sig:"):
|
|
signal = part.split(":")[1]
|
|
crashes.append({
|
|
"filename": filename,
|
|
"size_bytes": size,
|
|
"signal": signal,
|
|
"path": filepath,
|
|
})
|
|
return {"crashes": crashes, "total": len(crashes)}
|
|
|
|
|
|
def minimize_crash(afl_tmin_path, target_binary, crash_file, output_file):
|
|
"""Minimize a crash test case with afl-tmin."""
|
|
cmd = [afl_tmin_path or "afl-tmin", "-i", crash_file, "-o", output_file,
|
|
"--", target_binary]
|
|
result = subprocess.run(cmd, capture_output=True, text=True, timeout=120)
|
|
orig_size = os.path.getsize(crash_file) if os.path.exists(crash_file) else 0
|
|
min_size = os.path.getsize(output_file) if os.path.exists(output_file) else 0
|
|
return {
|
|
"original_size": orig_size,
|
|
"minimized_size": min_size,
|
|
"reduction_pct": round((1 - min_size / max(orig_size, 1)) * 100, 1),
|
|
"success": result.returncode == 0,
|
|
}
|
|
|
|
|
|
def run_whatsup(output_dir):
|
|
"""Run afl-whatsup to get multi-instance campaign summary."""
|
|
cmd = ["afl-whatsup", "-s", output_dir]
|
|
result = subprocess.run(cmd, capture_output=True, text=True, timeout=30)
|
|
return {"output": result.stdout[:2000] if result.stdout else result.stderr[:500]}
|
|
|
|
|
|
def run_audit(args):
|
|
"""Execute AFL++ fuzzing campaign audit."""
|
|
print(f"\n{'='*60}")
|
|
print(f" AFL++ FUZZING CAMPAIGN AUDIT")
|
|
print(f" Generated: {datetime.utcnow().isoformat()} UTC")
|
|
print(f"{'='*60}\n")
|
|
|
|
report = {}
|
|
|
|
if args.output_dir:
|
|
stats = parse_fuzzer_stats(args.output_dir)
|
|
report["fuzzer_stats"] = stats
|
|
print(f"--- FUZZER STATS ---")
|
|
print(f" Executions: {stats.get('execs_done', 0):,}")
|
|
print(f" Exec/sec: {stats.get('execs_per_sec', 0)}")
|
|
print(f" Paths: {stats.get('paths_total', 0)}")
|
|
print(f" Crashes: {stats.get('unique_crashes', 0)}")
|
|
print(f" Hangs: {stats.get('unique_hangs', 0)}")
|
|
print(f" Stability: {stats.get('stability', '')}")
|
|
print(f" Coverage: {stats.get('bitmap_cvg', '')}")
|
|
|
|
crash_data = triage_crashes(args.output_dir)
|
|
report["crash_triage"] = crash_data
|
|
print(f"\n--- CRASH TRIAGE ({crash_data['total']} crashes) ---")
|
|
for c in crash_data["crashes"][:20]:
|
|
print(f" {c['filename']} ({c['size_bytes']}B) signal={c['signal']}")
|
|
|
|
if args.instrument_src and args.instrument_out:
|
|
inst = instrument_target(args.instrument_src, args.instrument_out,
|
|
sanitizer=args.sanitizer)
|
|
report["instrumentation"] = inst
|
|
print(f"\n--- INSTRUMENTATION ---")
|
|
print(f" {'SUCCESS' if inst['success'] else 'FAILED'}: {inst['source']}")
|
|
|
|
return report
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(description="AFL++ Fuzzing Campaign Agent")
|
|
parser.add_argument("--output-dir", help="AFL++ output directory to analyze")
|
|
parser.add_argument("--instrument-src", help="Source file to instrument")
|
|
parser.add_argument("--instrument-out", help="Output path for instrumented binary")
|
|
parser.add_argument("--sanitizer", choices=["asan", "ubsan"],
|
|
help="Address or undefined behavior sanitizer")
|
|
parser.add_argument("--output", help="Save report to JSON file")
|
|
args = parser.parse_args()
|
|
|
|
report = run_audit(args)
|
|
if args.output:
|
|
with open(args.output, "w") as f:
|
|
json.dump(report, f, indent=2, default=str)
|
|
print(f"\n[+] Report saved to {args.output}")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|