#!/usr/bin/env python3 """Malware network traffic analysis agent for C2 protocol decoding and signature generation.""" import os import sys import json import math import subprocess from collections import defaultdict, Counter try: import dpkt HAS_DPKT = True except ImportError: HAS_DPKT = False try: from scapy.all import rdpcap, IP, TCP, UDP, DNS, DNSQR, Raw HAS_SCAPY = True except ImportError: HAS_SCAPY = False def shannon_entropy(data): """Calculate Shannon entropy of byte data.""" if not data: return 0.0 counter = Counter(data) length = len(data) return -sum((c / length) * math.log2(c / length) for c in counter.values()) def extract_tcp_streams(pcap_path): """Extract TCP stream payloads grouped by conversation.""" if not HAS_DPKT: return {} streams = defaultdict(list) with open(pcap_path, "rb") as f: pcap = dpkt.pcap.Reader(f) for ts, buf in pcap: try: eth = dpkt.ethernet.Ethernet(buf) if not isinstance(eth.data, dpkt.ip.IP): continue ip = eth.data if not isinstance(ip.data, dpkt.tcp.TCP): continue tcp = ip.data if len(tcp.data) > 0: src = f"{dpkt.utils.inet_to_str(ip.src)}:{tcp.sport}" dst = f"{dpkt.utils.inet_to_str(ip.dst)}:{tcp.dport}" key = tuple(sorted([src, dst])) streams[key].append({ "ts": ts, "src": src, "dst": dst, "data": tcp.data, "data_len": len(tcp.data), }) except Exception: continue return streams def analyze_payload_structure(payloads): """Analyze payload structure to identify protocol framing.""" if not payloads: return {} analysis = { "total_payloads": len(payloads), "sizes": [len(p) for p in payloads], "avg_size": sum(len(p) for p in payloads) / len(payloads), "entropy_values": [], } for p in payloads[:20]: ent = shannon_entropy(p) analysis["entropy_values"].append(round(ent, 4)) avg_ent = sum(analysis["entropy_values"]) / len(analysis["entropy_values"]) analysis["avg_entropy"] = round(avg_ent, 4) analysis["likely_encrypted"] = avg_ent > 7.5 # Check for common header patterns first_bytes = [p[:4] for p in payloads if len(p) >= 4] if first_bytes: byte_counter = Counter([b.hex() for b in first_bytes]) most_common = byte_counter.most_common(3) analysis["common_headers"] = [ {"hex": h, "count": c} for h, c in most_common ] return analysis def detect_dns_tunneling(pcap_path, entropy_threshold=3.5): """Detect DNS tunneling in malware traffic.""" if not HAS_SCAPY: return [] packets = rdpcap(pcap_path) suspicious = [] for pkt in packets: if DNS in pkt and pkt[DNS].qr == 0 and DNSQR in pkt: qname = pkt[DNSQR].qname.decode("utf-8", errors="replace").rstrip(".") parts = qname.split(".") if len(parts) > 2: subdomain = ".".join(parts[:-2]) ent = shannon_entropy(subdomain.encode()) if ent > entropy_threshold or len(subdomain) > 50: suspicious.append({ "query": qname, "subdomain_length": len(subdomain), "entropy": round(ent, 4), "src": pkt[IP].src if IP in pkt else "?", "qtype": pkt[DNSQR].qtype, }) return suspicious def detect_dga_domains(pcap_path, min_length=12, entropy_threshold=3.5): """Detect DGA (Domain Generation Algorithm) domains.""" if not HAS_SCAPY: return [] packets = rdpcap(pcap_path) dga_suspects = [] for pkt in packets: if DNS in pkt and pkt[DNS].qr == 0 and DNSQR in pkt: qname = pkt[DNSQR].qname.decode("utf-8", errors="replace").rstrip(".") parts = qname.split(".") if len(parts) >= 2: sld = parts[-2] if len(sld) >= min_length: ent = shannon_entropy(sld.encode()) if ent > entropy_threshold: dga_suspects.append({ "domain": qname, "sld": sld, "length": len(sld), "entropy": round(ent, 4), }) return dga_suspects def extract_http_c2(pcap_path): """Extract HTTP-based C2 communication patterns.""" if not HAS_DPKT: return [] requests = [] with open(pcap_path, "rb") as f: pcap = dpkt.pcap.Reader(f) for ts, buf in pcap: try: eth = dpkt.ethernet.Ethernet(buf) if not isinstance(eth.data, dpkt.ip.IP): continue ip = eth.data if not isinstance(ip.data, dpkt.tcp.TCP): continue tcp = ip.data if len(tcp.data) > 0: try: http = dpkt.http.Request(tcp.data) requests.append({ "timestamp": ts, "src": dpkt.utils.inet_to_str(ip.src), "dst": dpkt.utils.inet_to_str(ip.dst), "method": http.method, "uri": http.uri, "host": http.headers.get("host", ""), "user_agent": http.headers.get("user-agent", ""), "content_type": http.headers.get("content-type", ""), "body_size": len(http.body) if http.body else 0, "body_entropy": round(shannon_entropy(http.body), 4) if http.body else 0, }) except (dpkt.dpkt.NeedData, dpkt.dpkt.UnpackError): pass except Exception: continue return requests def generate_suricata_signatures(http_requests, dns_tunneling): """Generate Suricata IDS signatures from observed malware network patterns.""" rules = [] sid = 9100000 seen_uris = set() for req in http_requests: if req["uri"] not in seen_uris: seen_uris.add(req["uri"]) rules.append( f'alert http $HOME_NET any -> $EXTERNAL_NET any (' f'msg:"MALWARE Suspected C2 HTTP {req["method"]} {req["uri"][:30]}"; ' f'flow:established,to_server; ' f'http.method; content:"{req["method"]}"; ' f'http.uri; content:"{req["uri"]}"; ' f'sid:{sid}; rev:1;)' ) sid += 1 if dns_tunneling: domains = set() for t in dns_tunneling: parts = t["query"].split(".") if len(parts) >= 2: domains.add(".".join(parts[-2:])) for domain in list(domains)[:5]: rules.append( f'alert dns $HOME_NET any -> any any (' f'msg:"MALWARE DNS Tunneling to {domain}"; ' f'dns.query; content:"{domain}"; nocase; ' f'sid:{sid}; rev:1;)' ) sid += 1 return rules if __name__ == "__main__": print("=" * 60) print("Malware Network Traffic Analysis Agent") print("C2 protocol decoding, DNS tunneling, DGA detection") print("=" * 60) pcap = sys.argv[1] if len(sys.argv) > 1 else None if pcap and os.path.exists(pcap): print(f"\n[*] Analyzing: {pcap}") print("\n--- HTTP C2 Communication ---") http_reqs = extract_http_c2(pcap) for r in http_reqs[:10]: print(f" {r['method']} {r['host']}{r['uri']} " f"(body={r['body_size']}B, entropy={r['body_entropy']})") print("\n--- DNS Tunneling Detection ---") tunneling = detect_dns_tunneling(pcap) for t in tunneling[:10]: print(f" [!] {t['query']} (len={t['subdomain_length']}, ent={t['entropy']})") print("\n--- DGA Domain Detection ---") dga = detect_dga_domains(pcap) for d in dga[:10]: print(f" [!] {d['domain']} (sld_len={d['length']}, ent={d['entropy']})") print("\n--- Generated Suricata Rules ---") rules = generate_suricata_signatures(http_reqs, tunneling) for r in rules[:5]: print(f" {r}") else: print(f"\n[DEMO] Usage: python agent.py ")