#!/usr/bin/env python3 """IOC Collection Agent - Extracts, enriches, and exports indicators of compromise.""" import json import re import logging import argparse from datetime import datetime import requests from stix2 import Indicator, Bundle logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s") logger = logging.getLogger(__name__) IPV4_PATTERN = re.compile(r"\b(?:\d{1,3}\.){3}\d{1,3}\b") DOMAIN_PATTERN = re.compile(r"\b(?:[a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}\b") SHA256_PATTERN = re.compile(r"\b[a-fA-F0-9]{64}\b") MD5_PATTERN = re.compile(r"\b[a-fA-F0-9]{32}\b") URL_PATTERN = re.compile(r"https?://[^\s\"'<>]+") def extract_iocs_from_text(text): """Extract IOCs (IPs, domains, hashes, URLs) from unstructured text.""" iocs = { "ipv4": list(set(IPV4_PATTERN.findall(text))), "domain": list(set(DOMAIN_PATTERN.findall(text))), "sha256": list(set(SHA256_PATTERN.findall(text))), "md5": list(set(MD5_PATTERN.findall(text))), "url": list(set(URL_PATTERN.findall(text))), } total = sum(len(v) for v in iocs.values()) logger.info("Extracted %d IOCs from text input", total) return iocs def extract_iocs_from_file(file_path): """Extract IOCs from a file (text report, log file, etc.).""" with open(file_path, "r", errors="ignore") as f: content = f.read() return extract_iocs_from_text(content) def enrich_ip_virustotal(ip_address, api_key): """Enrich an IP address using VirusTotal API.""" url = f"https://www.virustotal.com/api/v3/ip_addresses/{ip_address}" headers = {"x-apikey": api_key} resp = requests.get(url, headers=headers, timeout=30) if resp.status_code == 200: data = resp.json()["data"]["attributes"] result = { "ip": ip_address, "malicious": data.get("last_analysis_stats", {}).get("malicious", 0), "country": data.get("country", "unknown"), "asn": data.get("asn", 0), "as_owner": data.get("as_owner", "unknown"), } logger.info("VT enrichment for %s: %d malicious detections", ip_address, result["malicious"]) return result return {"ip": ip_address, "error": resp.status_code} def enrich_hash_malwarebazaar(file_hash): """Enrich a file hash using MalwareBazaar API.""" url = "https://mb-api.abuse.ch/api/v1/" resp = requests.post(url, data={"query": "get_info", "hash": file_hash}, timeout=30) result = resp.json() if result.get("query_status") == "ok": sample = result["data"][0] return { "hash": file_hash, "family": sample.get("signature", "unknown"), "file_type": sample.get("file_type", "unknown"), "tags": sample.get("tags", []), "first_seen": sample.get("first_seen"), } return {"hash": file_hash, "status": "not_found"} def enrich_domain_abuseipdb(domain, api_key): """Check domain reputation via AbuseIPDB.""" url = "https://api.abuseipdb.com/api/v2/check" headers = {"Key": api_key, "Accept": "application/json"} resp = requests.get(url, headers=headers, params={"ipAddress": domain}, timeout=30) if resp.status_code == 200: data = resp.json()["data"] return { "domain": domain, "abuse_confidence": data.get("abuseConfidenceScore", 0), "total_reports": data.get("totalReports", 0), "country": data.get("countryCode", "unknown"), } return {"domain": domain, "error": resp.status_code} def score_ioc(ioc_type, enrichment_data): """Assign confidence score (0-100) based on enrichment results.""" if ioc_type == "ipv4": malicious_count = enrichment_data.get("malicious", 0) if malicious_count >= 10: return 95 elif malicious_count >= 5: return 80 elif malicious_count >= 1: return 60 return 30 elif ioc_type in ("sha256", "md5"): if enrichment_data.get("family") and enrichment_data["family"] != "unknown": return 95 return 50 return 50 def export_stix_bundle(iocs_with_scores, output_path): """Export IOCs as a STIX 2.1 bundle.""" pattern_map = { "ipv4": lambda v: f"[ipv4-addr:value = '{v}']", "domain": lambda v: f"[domain-name:value = '{v}']", "url": lambda v: f"[url:value = '{v}']", "sha256": lambda v: f"[file:hashes.'SHA-256' = '{v}']", "md5": lambda v: f"[file:hashes.MD5 = '{v}']", } stix_indicators = [] for ioc in iocs_with_scores: ioc_type = ioc["type"] pattern_fn = pattern_map.get(ioc_type) if pattern_fn: indicator = Indicator( name=f"{ioc_type}: {ioc['value']}", pattern=pattern_fn(ioc["value"]), pattern_type="stix", valid_from=datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%SZ"), confidence=ioc.get("confidence", 50), labels=[ioc_type], ) stix_indicators.append(indicator) bundle = Bundle(objects=stix_indicators) with open(output_path, "w") as f: f.write(bundle.serialize(pretty=True)) logger.info("STIX bundle exported: %d indicators to %s", len(stix_indicators), output_path) def generate_ioc_report(iocs_with_scores): """Print a formatted IOC report.""" lines = [ "INDICATOR OF COMPROMISE REPORT", "=" * 40, f"Date: {datetime.utcnow().strftime('%Y-%m-%d %H:%M UTC')}", f"Total IOCs: {len(iocs_with_scores)}", "", ] for ioc_type in ["ipv4", "domain", "sha256", "md5", "url"]: typed = [i for i in iocs_with_scores if i["type"] == ioc_type] if typed: lines.append(f"\n{ioc_type.upper()} ({len(typed)}):") for ioc in typed[:10]: lines.append(f" {ioc['value'][:60]:60s} Confidence: {ioc.get('confidence', 'N/A')}") print("\n".join(lines)) def main(): parser = argparse.ArgumentParser(description="IOC Collection Agent") parser.add_argument("--input-file", help="File to extract IOCs from") parser.add_argument("--input-text", help="Text string to extract IOCs from") parser.add_argument("--vt-key", help="VirusTotal API key for enrichment") parser.add_argument("--output", default="ioc_bundle.json", help="STIX bundle output") args = parser.parse_args() raw_iocs = {} if args.input_file: raw_iocs = extract_iocs_from_file(args.input_file) elif args.input_text: raw_iocs = extract_iocs_from_text(args.input_text) iocs_with_scores = [] for ioc_type, values in raw_iocs.items(): for value in values: entry = {"type": ioc_type, "value": value, "confidence": 50} if ioc_type == "ipv4" and args.vt_key: enrichment = enrich_ip_virustotal(value, args.vt_key) entry["confidence"] = score_ioc("ipv4", enrichment) entry["enrichment"] = enrichment elif ioc_type in ("sha256", "md5"): enrichment = enrich_hash_malwarebazaar(value) entry["confidence"] = score_ioc(ioc_type, enrichment) entry["enrichment"] = enrichment iocs_with_scores.append(entry) generate_ioc_report(iocs_with_scores) export_stix_bundle(iocs_with_scores, args.output) if __name__ == "__main__": main()