#!/usr/bin/env python3 """Agent for performing network forensics with Wireshark/pyshark. Analyzes PCAP files to extract conversations, DNS queries, HTTP objects, detect beaconing patterns, and identify C2 communications. """ import pyshark import json import sys from collections import defaultdict from pathlib import Path class NetworkForensicsAgent: """Analyzes PCAP files for forensic investigations.""" def __init__(self, pcap_path, output_dir): self.pcap_path = pcap_path self.output_dir = Path(output_dir) self.output_dir.mkdir(parents=True, exist_ok=True) def get_capture_info(self): """Get basic capture file statistics.""" cap = pyshark.FileCapture(self.pcap_path, only_summaries=True) packet_count = 0 first_time = None last_time = None for pkt in cap: packet_count += 1 if first_time is None: first_time = pkt.time last_time = pkt.time cap.close() return { "file": self.pcap_path, "packets": packet_count, "first_packet": str(first_time), "last_packet": str(last_time), } def extract_dns_queries(self, limit=5000): """Extract DNS queries from the capture.""" cap = pyshark.FileCapture(self.pcap_path, display_filter="dns.qr==0") queries = [] count = 0 for pkt in cap: if count >= limit: break try: queries.append({ "timestamp": str(pkt.sniff_time), "src_ip": pkt.ip.src, "query": pkt.dns.qry_name, "type": pkt.dns.qry_type, }) count += 1 except AttributeError: continue cap.close() return queries def detect_dns_tunneling(self, min_length=30): """Detect potential DNS tunneling by subdomain length.""" queries = self.extract_dns_queries() suspicious = [] for q in queries: domain = q.get("query", "") subdomain = domain.split(".")[0] if "." in domain else domain if len(subdomain) >= min_length: suspicious.append({ "query": domain, "subdomain_length": len(subdomain), "src_ip": q["src_ip"], "timestamp": q["timestamp"], }) return suspicious def extract_http_requests(self, limit=5000): """Extract HTTP requests with method, host, URI, and user-agent.""" cap = pyshark.FileCapture(self.pcap_path, display_filter="http.request") requests_list = [] count = 0 for pkt in cap: if count >= limit: break try: req = { "timestamp": str(pkt.sniff_time), "src_ip": pkt.ip.src, "dst_ip": pkt.ip.dst, "method": pkt.http.request_method, "host": getattr(pkt.http, "host", ""), "uri": getattr(pkt.http, "request_uri", ""), "user_agent": getattr(pkt.http, "user_agent", ""), } requests_list.append(req) count += 1 except AttributeError: continue cap.close() return requests_list def extract_tls_sni(self, limit=5000): """Extract TLS Server Name Indication values.""" cap = pyshark.FileCapture( self.pcap_path, display_filter="tls.handshake.extensions_server_name" ) sni_list = [] count = 0 for pkt in cap: if count >= limit: break try: sni_list.append({ "timestamp": str(pkt.sniff_time), "src_ip": pkt.ip.src, "dst_ip": pkt.ip.dst, "sni": pkt.tls.handshake_extensions_server_name, }) count += 1 except AttributeError: continue cap.close() return sni_list def get_top_talkers(self, limit=20): """Identify top source and destination IPs by packet count.""" cap = pyshark.FileCapture(self.pcap_path, only_summaries=True) ip_counts = defaultdict(int) for pkt in cap: try: ip_counts[pkt.source] += 1 ip_counts[pkt.destination] += 1 except AttributeError: continue cap.close() sorted_ips = sorted(ip_counts.items(), key=lambda x: x[1], reverse=True) return [{"ip": ip, "packets": count} for ip, count in sorted_ips[:limit]] def detect_beaconing(self, target_ip, tolerance=5): """Detect beaconing patterns to a specific IP.""" cap = pyshark.FileCapture( self.pcap_path, display_filter=f"ip.dst=={target_ip} and tcp.flags.syn==1" ) timestamps = [] for pkt in cap: try: timestamps.append(float(pkt.sniff_timestamp)) except (AttributeError, ValueError): continue cap.close() if len(timestamps) < 3: return {"beaconing": False, "connections": len(timestamps)} intervals = [timestamps[i+1] - timestamps[i] for i in range(len(timestamps)-1)] avg_interval = sum(intervals) / len(intervals) consistent = sum(1 for i in intervals if abs(i - avg_interval) < tolerance) return { "target_ip": target_ip, "connections": len(timestamps), "avg_interval_sec": round(avg_interval, 1), "consistent_intervals": consistent, "total_intervals": len(intervals), "beaconing": consistent / len(intervals) > 0.7 if intervals else False, } def find_suspicious_ports(self): """Find connections to commonly malicious ports.""" suspicious_ports = {"4444", "8080", "1337", "6667", "9001", "31337"} cap = pyshark.FileCapture(self.pcap_path, display_filter="tcp") findings = defaultdict(lambda: {"count": 0, "sources": set()}) for pkt in cap: try: dport = pkt.tcp.dstport if dport in suspicious_ports: findings[dport]["count"] += 1 findings[dport]["sources"].add(pkt.ip.src) except AttributeError: continue cap.close() return { port: {"count": data["count"], "sources": list(data["sources"])} for port, data in findings.items() } def generate_report(self, target_ip=None): """Generate comprehensive network forensics report.""" report = { "capture_info": self.get_capture_info(), "top_talkers": self.get_top_talkers(), "dns_query_count": len(self.extract_dns_queries()), "dns_tunneling_suspects": self.detect_dns_tunneling(), "http_request_count": len(self.extract_http_requests()), "tls_sni_count": len(self.extract_tls_sni()), "suspicious_ports": self.find_suspicious_ports(), } if target_ip: report["beaconing_analysis"] = self.detect_beaconing(target_ip) report_path = self.output_dir / "network_forensics_report.json" with open(report_path, "w") as f: json.dump(report, f, indent=2, default=list) print(json.dumps(report, indent=2, default=list)) return report def main(): if len(sys.argv) < 3: print("Usage: agent.py [target_ip]") sys.exit(1) pcap_path = sys.argv[1] output_dir = sys.argv[2] target_ip = sys.argv[3] if len(sys.argv) > 3 else None agent = NetworkForensicsAgent(pcap_path, output_dir) agent.generate_report(target_ip) if __name__ == "__main__": main()