""" Velociraptor IR Collection Automation Script Manages artifact collection, hunt creation, and result analysis via Velociraptor API. """ import json import os import csv import hashlib from datetime import datetime from pathlib import Path from collections import defaultdict class VelociraptorCollector: """Manages Velociraptor artifact collection for incident response.""" TRIAGE_ARTIFACTS_WINDOWS = [ "Windows.EventLogs.EvtxHunter", "Windows.Forensics.Prefetch", "Windows.Registry.AppCompatCache", "Windows.Forensics.Amcache", "Windows.Forensics.UserAssist", "Windows.System.TaskScheduler", "Windows.System.Pslist", "Windows.Network.Netstat", "Windows.Network.DNSCache", "Windows.Forensics.PowerShellHistory", "Windows.System.Services", "Windows.System.StartupItems", "Windows.Persistence.PermanentWMIEvents", ] TRIAGE_ARTIFACTS_LINUX = [ "Linux.Sys.AuthLogs", "Linux.Forensics.BashHistory", "Linux.Sys.Crontab", "Linux.Sys.Pslist", "Linux.Network.Netstat", "Linux.Ssh.AuthorizedKeys", "Linux.Services", ] def __init__(self, server_url=None, api_key=None, output_dir="velociraptor_results"): self.server_url = server_url or "https://localhost:8001" self.api_key = api_key self.output_dir = Path(output_dir) self.output_dir.mkdir(parents=True, exist_ok=True) self.collection_manifest = [] def generate_vql_triage_pack(self, target_os="windows", output_file=None): """Generate VQL artifact collection pack for triage.""" artifacts = ( self.TRIAGE_ARTIFACTS_WINDOWS if target_os == "windows" else self.TRIAGE_ARTIFACTS_LINUX ) vql_queries = [] for artifact in artifacts: vql_queries.append({ "artifact": artifact, "vql": f"SELECT * FROM Artifact.{artifact}()", "description": f"Collect {artifact.split('.')[-1]} artifacts", }) pack = { "name": f"IR Triage Pack - {target_os.title()}", "version": "1.0", "generated": datetime.utcnow().isoformat(), "target_os": target_os, "artifacts": vql_queries, } if output_file is None: output_file = self.output_dir / f"triage_pack_{target_os}.json" with open(output_file, "w") as f: json.dump(pack, f, indent=2) print(f"[+] Generated {target_os} triage pack: {output_file}") print(f" Artifacts: {len(vql_queries)}") return pack def generate_hunt_config(self, hunt_name, description, artifacts, parameters=None, include_labels=None, exclude_labels=None): """Generate hunt configuration for enterprise-wide collection.""" hunt_config = { "hunt_name": hunt_name, "description": description, "created": datetime.utcnow().isoformat(), "artifacts": artifacts, "parameters": parameters or {}, "targeting": { "include_labels": include_labels or [], "exclude_labels": exclude_labels or [], }, "resource_limits": { "cpu_limit": 50, "iops_limit": 100, "timeout_seconds": 600, "max_rows": 100000, "max_upload_bytes": 104857600, }, } config_file = self.output_dir / f"hunt_{hunt_name.replace(' ', '_')}.json" with open(config_file, "w") as f: json.dump(hunt_config, f, indent=2) print(f"[+] Generated hunt config: {config_file}") return hunt_config def generate_ioc_hunt(self, iocs, hunt_name="IOC Hunt"): """Generate VQL-based IOC hunt queries.""" vql_queries = [] if "hashes" in iocs: hash_list = "|".join(iocs["hashes"]) vql_queries.append({ "name": "Hash Hunt", "vql": f"""SELECT * FROM Artifact.Generic.Detection.HashHunter( Hashes='{hash_list}' )""", }) if "ips" in iocs: ip_regex = "|".join(ip.replace(".", "\\.") for ip in iocs["ips"]) vql_queries.append({ "name": "Network Connection Hunt", "vql": f"""SELECT * FROM Artifact.Windows.Network.Netstat() WHERE RemoteAddr =~ '{ip_regex}'""", }) if "domains" in iocs: domain_regex = "|".join(d.replace(".", "\\.") for d in iocs["domains"]) vql_queries.append({ "name": "DNS Cache Hunt", "vql": f"""SELECT * FROM Artifact.Windows.Network.DNSCache() WHERE Name =~ '{domain_regex}'""", }) if "filenames" in iocs: file_regex = "|".join(iocs["filenames"]) vql_queries.append({ "name": "File Hunt", "vql": f"""SELECT * FROM Artifact.Windows.NTFS.MFT( FileRegex='{file_regex}' )""", }) if "yara_rules" in iocs: for rule_name, rule_content in iocs["yara_rules"].items(): vql_queries.append({ "name": f"YARA Hunt - {rule_name}", "vql": f"""SELECT * FROM Artifact.Windows.Detection.Yara.Process( YaraRule='{rule_content}' )""", }) hunt_file = self.output_dir / f"ioc_hunt_{hunt_name.replace(' ', '_')}.json" with open(hunt_file, "w") as f: json.dump({"name": hunt_name, "queries": vql_queries}, f, indent=2) print(f"[+] Generated IOC hunt with {len(vql_queries)} queries: {hunt_file}") return vql_queries def generate_collection_checklist(self, case_id, target_hosts): """Generate collection checklist for IR case.""" checklist = { "case_id": case_id, "generated": datetime.utcnow().isoformat(), "targets": [], } for host in target_hosts: target = { "hostname": host, "collections": [ {"artifact": "Volatile Data", "status": "pending", "items": [ "Running processes", "Network connections", "DNS cache", "Logged-in users", "Open files", ]}, {"artifact": "Event Logs", "status": "pending", "items": [ "Security.evtx", "System.evtx", "Application.evtx", "PowerShell/Operational.evtx", "Sysmon/Operational.evtx", ]}, {"artifact": "Execution Evidence", "status": "pending", "items": [ "Prefetch files", "Amcache.hve", "Shimcache", "UserAssist", "BAM/DAM", ]}, {"artifact": "Persistence Mechanisms", "status": "pending", "items": [ "Scheduled tasks", "Services", "Registry Run keys", "WMI subscriptions", "Startup items", ]}, {"artifact": "File System", "status": "pending", "items": [ "MFT entries", "USN Journal", "Recycle Bin", "Recent files", "Downloads folder", ]}, {"artifact": "User Activity", "status": "pending", "items": [ "Browser history", "PowerShell history", "RDP cache", "Recent documents", "Jump lists", ]}, ], } checklist["targets"].append(target) checklist_file = self.output_dir / f"collection_checklist_{case_id}.json" with open(checklist_file, "w") as f: json.dump(checklist, f, indent=2) print(f"[+] Collection checklist for {len(target_hosts)} hosts: {checklist_file}") return checklist def analyze_collection_results(self, results_dir): """Analyze collected Velociraptor results for suspicious indicators.""" results_path = Path(results_dir) findings = [] for json_file in results_path.glob("**/*.json"): try: with open(json_file) as f: data = json.load(f) if isinstance(data, list): for item in data: finding = self._check_for_indicators(item, str(json_file)) if finding: findings.append(finding) except (json.JSONDecodeError, KeyError): continue findings_file = self.output_dir / "analysis_findings.json" with open(findings_file, "w") as f: json.dump(findings, f, indent=2, default=str) print(f"[+] Analysis complete: {len(findings)} findings -> {findings_file}") return findings def _check_for_indicators(self, item, source_file): """Check a single result item for suspicious indicators.""" suspicious_processes = [ "mimikatz", "rubeus", "lazagne", "sharphound", "bloodhound", "cobaltstrike", "beacon", "psexec", "wmiexec", "smbexec", ] suspicious_commands = [ "invoke-mimikatz", "invoke-expression", "downloadstring", "net user /add", "net localgroup administrators", "reg save hklm\\sam", "reg save hklm\\system", "ntdsutil", "vssadmin create shadow", ] name = str(item.get("Name", "") or item.get("CommandLine", "")).lower() for proc in suspicious_processes: if proc in name: return { "severity": "CRITICAL", "indicator": f"Suspicious process: {proc}", "detail": item, "source": source_file, } for cmd in suspicious_commands: if cmd in name: return { "severity": "HIGH", "indicator": f"Suspicious command: {cmd}", "detail": item, "source": source_file, } return None def generate_report(self): """Generate collection summary report.""" report = { "title": "Velociraptor IR Collection Report", "generated": datetime.utcnow().isoformat(), "collections": self.collection_manifest, "output_directory": str(self.output_dir), } report_file = self.output_dir / "collection_report.json" with open(report_file, "w") as f: json.dump(report, f, indent=2) print(f"[+] Collection report: {report_file}") return report def main(): import argparse parser = argparse.ArgumentParser(description="Velociraptor IR Collection Manager") parser.add_argument("--action", choices=[ "triage-pack", "hunt-config", "ioc-hunt", "checklist", "analyze", ], required=True) parser.add_argument("--os", default="windows", choices=["windows", "linux"]) parser.add_argument("--case-id", default="IR-2025-001") parser.add_argument("--hosts", nargs="+", help="Target hostnames") parser.add_argument("--iocs", help="JSON file with IOCs") parser.add_argument("--results-dir", help="Directory with collection results") parser.add_argument("-o", "--output", default="velociraptor_results") args = parser.parse_args() collector = VelociraptorCollector(output_dir=args.output) if args.action == "triage-pack": collector.generate_vql_triage_pack(target_os=args.os) elif args.action == "hunt-config": collector.generate_hunt_config("IR Hunt", "Incident response hunt", collector.TRIAGE_ARTIFACTS_WINDOWS) elif args.action == "ioc-hunt": if args.iocs: with open(args.iocs) as f: iocs = json.load(f) collector.generate_ioc_hunt(iocs) elif args.action == "checklist": hosts = args.hosts or ["WKS001", "SRV001", "DC01"] collector.generate_collection_checklist(args.case_id, hosts) elif args.action == "analyze": if args.results_dir: collector.analyze_collection_results(args.results_dir) if __name__ == "__main__": main()