mirror of
https://github.com/mukul975/Anthropic-Cybersecurity-Skills.git
synced 2026-06-15 23:44:56 +03:00
c47eed6a64
- 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
244 lines
8.6 KiB
Python
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>")
|