#!/usr/bin/env python3
"""Malware network traffic analysis agent for C2 protocol decoding and signature generation."""

import os
import sys
import math
from collections import defaultdict, Counter

try:
    import dpkt
    HAS_DPKT = True
except ImportError:
    HAS_DPKT = False

try:
    from scapy.all import rdpcap, IP, TCP, DNS, DNSQR
    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 <malware_traffic.pcap>")
