Files
Anthropic-Cybersecurity-Skills/skills/analyzing-network-traffic-of-malware/scripts/agent.py
T
mukul975 c47eed6a64 Production hardening: security fixes, code quality, 724 skills complete
- Fix 25 shell=True subprocess calls with list-based commands
- Fix 49 verify=False in defensive skills (env-var override)
- Add timeout to 231 HTTP/subprocess/socket calls
- Fix 6 SQL injection patterns with whitelist validation
- Replace 8 __import__() with standard imports
- Remove 701 unused imports across 442 files
- Add authorized-testing disclaimers to all offensive skills
- Complete 11 incomplete skill directories
- Expand 10 stub SKILL.md files with full content
- Fix 2 YAML parse errors in frontmatter
- Fix 5 pre-existing syntax errors
- Convert 22 hardcoded paths/ports to environment variables
- Back up 21 redundant skill pairs to .bak
- Fix 2 global declaration errors
- 724/724 skills with full folder anatomy (SKILL.md + agent.py + api-reference.md + LICENSE)
- 0 compile errors across all 724 agent.py files
2026-03-19 13:26:49 +01:00

244 lines
8.6 KiB
Python

#!/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>")