#!/usr/bin/env python3 """Agent for performing purple team exercises. Coordinates red team technique execution with blue team detection validation, tracks ATT&CK-mapped test results, and generates detection coverage reports. """ import json import sys from datetime import datetime from pathlib import Path class PurpleTeamAgent: """Manages purple team exercise execution and tracking.""" def __init__(self, exercise_id, output_dir): self.exercise_id = exercise_id self.output_dir = Path(output_dir) self.output_dir.mkdir(parents=True, exist_ok=True) self.test_plan = [] self.results = [] def add_technique(self, attack_id, name, tool, expected_detection): """Add a technique to the test plan.""" self.test_plan.append({ "attack_id": attack_id, "name": name, "tool": tool, "expected_detection": expected_detection, "status": "pending", }) def load_test_plan(self, plan_path): """Load test plan from a JSON file.""" with open(plan_path, "r") as f: data = json.load(f) self.test_plan = data.get("techniques", []) def build_default_test_plan(self): """Build a default FIN7-style purple team test plan.""" techniques = [ ("T1059.001", "PowerShell Execution", "Atomic Red Team", "PowerShell alert"), ("T1053.005", "Scheduled Task", "Atomic Red Team", "Task creation alert"), ("T1547.001", "Registry Run Keys", "Atomic Red Team", "Registry modification alert"), ("T1003.001", "LSASS Memory Access", "Mimikatz", "Credential dumping alert"), ("T1550.002", "Pass-the-Hash", "Mimikatz", "NTLM anomaly detection"), ("T1021.002", "PsExec", "PsExec.exe", "PsExec service creation alert"), ("T1047", "WMI Execution", "wmic", "WMI remote execution alert"), ("T1021.001", "RDP Lateral Movement", "xfreerdp", "RDP lateral movement alert"), ("T1071.001", "Web C2 Channel", "C2 framework", "C2 beacon detection"), ("T1041", "Exfiltration over C2", "rclone", "Data exfiltration alert"), ("T1490", "Inhibit Recovery", "vssadmin", "Shadow copy deletion alert"), ("T1070.001", "Clear Event Logs", "wevtutil", "Log clearing detection"), ] for attack_id, name, tool, detection in techniques: self.add_technique(attack_id, name, tool, detection) def record_execution(self, attack_id, execution_time=None): """Record that a red team technique has been executed.""" if execution_time is None: execution_time = datetime.utcnow().isoformat() for technique in self.test_plan: if technique["attack_id"] == attack_id: technique["execution_time"] = execution_time technique["status"] = "executed" break def record_detection(self, attack_id, detected, alert_name=None, detection_time=None, notes=""): """Record blue team detection result for a technique.""" if detection_time is None and detected: detection_time = datetime.utcnow().isoformat() for technique in self.test_plan: if technique["attack_id"] == attack_id: exec_time = technique.get("execution_time", "") latency = None if detected and exec_time and detection_time: try: t1 = datetime.fromisoformat(exec_time) t2 = datetime.fromisoformat(detection_time) latency = (t2 - t1).total_seconds() except ValueError: pass result = { "attack_id": attack_id, "name": technique["name"], "detected": detected, "alert_name": alert_name, "execution_time": exec_time, "detection_time": detection_time, "latency_seconds": latency, "notes": notes, "status": "PASS" if detected else "FAIL", } self.results.append(result) technique["status"] = "detected" if detected else "gap" return result return None def get_coverage_metrics(self): """Calculate detection coverage metrics.""" if not self.results: return {} total = len(self.results) detected = sum(1 for r in self.results if r["detected"]) gaps = total - detected latencies = [r["latency_seconds"] for r in self.results if r["latency_seconds"] is not None] avg_latency = sum(latencies) / len(latencies) if latencies else 0 return { "total_techniques": total, "detected": detected, "gaps": gaps, "coverage_pct": round(detected / total * 100, 1) if total else 0, "avg_latency_seconds": round(avg_latency, 1), "min_latency": round(min(latencies), 1) if latencies else None, "max_latency": round(max(latencies), 1) if latencies else None, } def get_gap_analysis(self): """Identify detection gaps requiring remediation.""" return [ { "attack_id": r["attack_id"], "name": r["name"], "notes": r["notes"], "remediation": f"Create detection rule for {r['name']}", } for r in self.results if not r["detected"] ] def generate_report(self): """Generate comprehensive purple team exercise report.""" metrics = self.get_coverage_metrics() gaps = self.get_gap_analysis() report = { "exercise_id": self.exercise_id, "report_date": datetime.utcnow().isoformat(), "coverage_metrics": metrics, "detailed_results": self.results, "detection_gaps": gaps, "test_plan": self.test_plan, } report_path = self.output_dir / f"{self.exercise_id}_report.json" with open(report_path, "w") as f: json.dump(report, f, indent=2) print(f"PURPLE TEAM EXERCISE REPORT - {self.exercise_id}") print("=" * 50) print(f"Techniques Tested: {metrics.get('total_techniques', 0)}") print(f"Detected: {metrics.get('detected', 0)} ({metrics.get('coverage_pct', 0)}%)") print(f"Gaps: {metrics.get('gaps', 0)}") print(f"Avg Detection Latency: {metrics.get('avg_latency_seconds', 0)}s") print(f"\nDetailed Results:") for r in self.results: status = "PASS" if r["detected"] else "FAIL" latency = f"{r['latency_seconds']}s" if r["latency_seconds"] else "N/A" print(f" [{status}] {r['attack_id']} {r['name']} (Latency: {latency})") if gaps: print(f"\nDetection Gaps:") for g in gaps: print(f" - {g['attack_id']} {g['name']}: {g['notes']}") return report def main(): if len(sys.argv) < 2: print("Usage: agent.py [output_dir] [plan_file]") sys.exit(1) exercise_id = sys.argv[1] output_dir = sys.argv[2] if len(sys.argv) > 2 else "./purple_team_output" agent = PurpleTeamAgent(exercise_id, output_dir) if len(sys.argv) > 3: agent.load_test_plan(sys.argv[3]) else: agent.build_default_test_plan() print(json.dumps({"test_plan": agent.test_plan}, indent=2)) print(f"\nTest plan created with {len(agent.test_plan)} techniques") print(f"Output directory: {output_dir}") if __name__ == "__main__": main()