#!/usr/bin/env python3 """CAPE Sandbox automated malware analysis agent. Submits malware samples to a CAPE Sandbox instance via its REST API, polls for analysis completion, and retrieves behavioral reports including process trees, network activity, dropped files, and extracted configs. """ import argparse import json import os import sys import time from datetime import datetime, timezone try: import requests except ImportError: print("[!] 'requests' library required: pip install requests", file=sys.stderr) sys.exit(1) def get_cape_config(): """Return CAPE server URL and optional API token.""" server = os.environ.get("CAPE_URL", "http://localhost:8090") token = os.environ.get("CAPE_API_TOKEN", "") return server.rstrip("/"), token def submit_file(server, token, file_path, timeout_minutes=5, machine=None): """Submit a file to CAPE for analysis.""" url = f"{server}/apiv2/tasks/create/file/" headers = {"Authorization": f"Token {token}"} if token else {} if not os.path.isfile(file_path): print(f"[!] File not found: {file_path}", file=sys.stderr) sys.exit(1) data = {"timeout": timeout_minutes * 60} if machine: data["machine"] = machine print(f"[*] Submitting {os.path.basename(file_path)} to CAPE...") with open(file_path, "rb") as f: resp = requests.post( url, files={"file": (os.path.basename(file_path), f)}, data=data, headers=headers, timeout=120, ) resp.raise_for_status() result = resp.json() if result.get("error"): print(f"[!] Submission error: {result.get('error_value', result['error'])}", file=sys.stderr) sys.exit(1) task_id = result.get("data", {}).get("task_ids", [None])[0] if not task_id: task_id = result.get("task_id") or result.get("data", {}).get("task_id") print(f"[+] Submitted successfully, task ID: {task_id}") return task_id def submit_url(server, token, target_url, timeout_minutes=5): """Submit a URL to CAPE for analysis.""" url = f"{server}/apiv2/tasks/create/url/" headers = {"Authorization": f"Token {token}"} if token else {} data = {"url": target_url, "timeout": timeout_minutes * 60} print(f"[*] Submitting URL: {target_url}") resp = requests.post(url, data=data, headers=headers, timeout=60) resp.raise_for_status() result = resp.json() task_id = result.get("data", {}).get("task_ids", [None])[0] if not task_id: task_id = result.get("task_id") print(f"[+] Submitted, task ID: {task_id}") return task_id def poll_task_status(server, token, task_id, max_wait=600, interval=15): """Poll CAPE until the analysis task completes or times out.""" url = f"{server}/apiv2/tasks/status/{task_id}/" headers = {"Authorization": f"Token {token}"} if token else {} print(f"[*] Waiting for analysis to complete (max {max_wait}s)...") elapsed = 0 while elapsed < max_wait: try: resp = requests.get(url, headers=headers, timeout=30) resp.raise_for_status() data = resp.json() status = data.get("data", data.get("status", "unknown")) if isinstance(status, dict): status = status.get("status", "unknown") if status in ("reported", "completed"): print(f"[+] Analysis complete (status: {status})") return True if status in ("failed_analysis", "failed_processing"): print(f"[!] Analysis failed: {status}", file=sys.stderr) return False print(f" Status: {status} ({elapsed}s elapsed)") except requests.RequestException as e: print(f" Connection error: {e}") time.sleep(interval) elapsed += interval print("[!] Timed out waiting for analysis", file=sys.stderr) return False def get_report(server, token, task_id): """Retrieve the full analysis report for a completed task.""" url = f"{server}/apiv2/tasks/get/report/{task_id}/" headers = {"Authorization": f"Token {token}"} if token else {} resp = requests.get(url, headers=headers, timeout=120) resp.raise_for_status() return resp.json() def extract_findings(report): """Extract structured findings from CAPE report.""" findings = [] # Signatures (behavioral detections) for sig in report.get("signatures", []): findings.append({ "category": "signature", "name": sig.get("name", "Unknown"), "severity": sig.get("severity", 0), "description": sig.get("description", ""), "families": sig.get("families", []), "ttp": sig.get("ttp", {}), }) # Network IOCs network = report.get("network", {}) for dns_entry in network.get("dns", []): findings.append({ "category": "network_dns", "request": dns_entry.get("request", ""), "type": dns_entry.get("type", ""), "answers": [a.get("data", "") for a in dns_entry.get("answers", [])], }) for http_entry in network.get("http", []): findings.append({ "category": "network_http", "method": http_entry.get("method", ""), "uri": http_entry.get("uri", ""), "host": http_entry.get("host", ""), "port": http_entry.get("port", 80), }) # Dropped files for dropped in report.get("dropped", []): findings.append({ "category": "dropped_file", "name": dropped.get("name", ""), "path": dropped.get("filepath", ""), "type": dropped.get("type", ""), "size": dropped.get("size", 0), "sha256": dropped.get("sha256", ""), }) # CAPE extracted payloads and configs cape_data = report.get("CAPE", {}) if isinstance(cape_data, dict): for cape_item in cape_data.get("payloads", []): findings.append({ "category": "cape_payload", "name": cape_item.get("name", ""), "module": cape_item.get("module_path", ""), "sha256": cape_item.get("sha256", ""), "cape_type": cape_item.get("cape_type", ""), }) for config in cape_data.get("configs", []): findings.append({ "category": "cape_config", "family": config.get("family", "unknown"), "config_data": config, }) return findings def format_summary(report, findings): """Print human-readable analysis summary.""" info = report.get("info", {}) target = report.get("target", {}) score = report.get("malscore", info.get("score", 0)) print(f"\n{'='*60}") print(f" CAPE Sandbox Analysis Report") print(f"{'='*60}") print(f" Task ID : {info.get('id', 'N/A')}") print(f" Duration : {info.get('duration', 0)}s") print(f" Malscore : {score}/10") file_info = target.get("file", {}) if file_info: print(f" File : {file_info.get('name', 'N/A')}") print(f" SHA256 : {file_info.get('sha256', 'N/A')}") print(f" Type : {file_info.get('type', 'N/A')}") sig_findings = [f for f in findings if f["category"] == "signature"] net_dns = [f for f in findings if f["category"] == "network_dns"] net_http = [f for f in findings if f["category"] == "network_http"] dropped = [f for f in findings if f["category"] == "dropped_file"] configs = [f for f in findings if f["category"] == "cape_config"] print(f"\n Signatures : {len(sig_findings)}") print(f" DNS Queries : {len(net_dns)}") print(f" HTTP Reqs : {len(net_http)}") print(f" Dropped : {len(dropped)}") print(f" CAPE Configs: {len(configs)}") if sig_findings: print(f"\n Top Signatures:") for s in sorted(sig_findings, key=lambda x: x.get("severity", 0), reverse=True)[:10]: sev = s.get("severity", 0) label = "HIGH" if sev >= 3 else "MEDIUM" if sev >= 2 else "LOW" print(f" [{label:6s}] {s['name']}: {s['description'][:80]}") if configs: print(f"\n Extracted Configs:") for c in configs: print(f" Family: {c.get('family', 'unknown')}") return score def main(): parser = argparse.ArgumentParser( description="CAPE Sandbox malware analysis agent" ) group = parser.add_mutually_exclusive_group(required=True) group.add_argument("--file", help="Path to malware sample to submit") group.add_argument("--url", help="URL to submit for analysis") group.add_argument("--task-id", type=int, help="Retrieve report for existing task") parser.add_argument("--output", "-o", help="Output JSON report path") parser.add_argument("--server", help="CAPE server URL (or set CAPE_URL env var)") parser.add_argument("--token", help="API token (or set CAPE_API_TOKEN env var)") parser.add_argument("--machine", help="Specific VM to use for analysis") parser.add_argument("--timeout", type=int, default=5, help="Analysis timeout in minutes") parser.add_argument("--wait", type=int, default=600, help="Max seconds to wait for completion") parser.add_argument("--verbose", "-v", action="store_true") args = parser.parse_args() if args.server: os.environ["CAPE_URL"] = args.server if args.token: os.environ["CAPE_API_TOKEN"] = args.token server, token = get_cape_config() if args.task_id: task_id = args.task_id elif args.file: task_id = submit_file(server, token, args.file, args.timeout, args.machine) else: task_id = submit_url(server, token, args.url, args.timeout) if not args.task_id: if not poll_task_status(server, token, task_id, args.wait): sys.exit(1) report = get_report(server, token, task_id) findings = extract_findings(report) score = format_summary(report, findings) output_report = { "timestamp": datetime.now(timezone.utc).isoformat(), "tool": "CAPE Sandbox", "task_id": task_id, "malscore": score, "findings_count": len(findings), "findings": findings, "risk_level": ( "CRITICAL" if score >= 8 else "HIGH" if score >= 5 else "MEDIUM" if score >= 3 else "LOW" ), } if args.output: with open(args.output, "w") as f: json.dump(output_report, f, indent=2) print(f"\n[+] Report saved to {args.output}") elif args.verbose: print(json.dumps(output_report, indent=2)) if __name__ == "__main__": main()