Files
T
mukul975 c21af3347e Complete folder anatomy for all 649 cybersecurity skills + update LICENSE to Mahipal
- Add scripts/agent.py and references/api-reference.md to all remaining skills
- Update all 648 LICENSE files: copyright now reads 'Mahipal'
- Add implementing-security-monitoring-with-datadog (new skill with full anatomy)
- All 649 skills now have: SKILL.md, LICENSE, scripts/agent.py, references/api-reference.md
2026-03-11 00:22:12 +01:00

176 lines
6.1 KiB
Python

#!/usr/bin/env python3
"""Agent for hunting C2 beaconing across multiple data sources."""
import argparse
import json
import math
import os
import re
import subprocess
import sys
from collections import defaultdict
from datetime import datetime, timezone
C2_INDICATORS = {
"known_ports": {443, 8443, 8080, 4444, 5555, 8888, 9090, 1337},
"suspicious_user_agents": [
"mozilla/4.0", "python-requests", "curl/", "wget/",
"java/", "go-http-client",
],
"dns_c2_patterns": [
r'^[a-z0-9]{30,}\.', # Long random subdomain (DNS tunneling)
r'^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$', # Direct IP
],
}
def analyze_dns_queries(dns_log_path):
"""Analyze DNS query logs for C2 indicators."""
findings = []
domain_counts = defaultdict(int)
try:
with open(dns_log_path, "r") as f:
for line in f:
if line.startswith("#"):
continue
fields = line.strip().split("\t")
if len(fields) < 10:
continue
query = fields[9] if len(fields) > 9 else ""
domain_counts[query] += 1
for pattern in C2_INDICATORS["dns_c2_patterns"]:
if re.match(pattern, query):
findings.append({
"type": "suspicious_dns",
"query": query,
"pattern": pattern,
})
except FileNotFoundError:
pass
high_freq = sorted(domain_counts.items(), key=lambda x: x[1], reverse=True)[:20]
for domain, count in high_freq:
if count > 100 and len(domain) > 20:
findings.append({
"type": "high_frequency_dns",
"domain": domain,
"query_count": count,
})
return findings
def analyze_http_logs(http_log_path):
"""Analyze HTTP logs for C2-like traffic patterns."""
findings = []
try:
with open(http_log_path, "r") as f:
for line in f:
if line.startswith("#"):
continue
fields = line.strip().split("\t")
if len(fields) < 13:
continue
host = fields[8] if len(fields) > 8 else ""
uri = fields[9] if len(fields) > 9 else ""
user_agent = fields[12] if len(fields) > 12 else ""
for ua in C2_INDICATORS["suspicious_user_agents"]:
if ua in user_agent.lower():
findings.append({
"type": "suspicious_user_agent",
"host": host,
"uri": uri[:100],
"user_agent": user_agent[:100],
})
break
if re.match(r'^/[a-zA-Z0-9]{4,8}$', uri):
findings.append({
"type": "c2_uri_pattern",
"host": host,
"uri": uri,
"note": "Short random URI typical of C2 frameworks",
})
except FileNotFoundError:
pass
return findings
def analyze_connection_patterns(conn_log_path):
"""Detect persistent long-duration connections typical of C2."""
findings = []
try:
with open(conn_log_path, "r") as f:
for line in f:
if line.startswith("#"):
continue
fields = line.strip().split("\t")
if len(fields) < 10:
continue
src = fields[2]
dst = fields[4]
dst_port = fields[5]
duration = fields[8] if len(fields) > 8 else "0"
orig_bytes = fields[9] if len(fields) > 9 else "0"
resp_bytes = fields[10] if len(fields) > 10 else "0"
try:
dur = float(duration) if duration != "-" else 0
ob = int(orig_bytes) if orig_bytes != "-" else 0
rb = int(resp_bytes) if resp_bytes != "-" else 0
except ValueError:
continue
if dur > 3600 and ob > 0 and rb > 0:
ratio = ob / rb if rb > 0 else 999
if 0.8 < ratio < 1.2:
findings.append({
"type": "persistent_symmetric",
"src": src, "dst": dst, "port": dst_port,
"duration_hours": round(dur / 3600, 1),
"data_ratio": round(ratio, 2),
})
except FileNotFoundError:
pass
return findings
def main():
parser = argparse.ArgumentParser(
description="Hunt for C2 beaconing across network data sources"
)
parser.add_argument("--conn-log", help="Zeek conn.log")
parser.add_argument("--dns-log", help="Zeek dns.log")
parser.add_argument("--http-log", help="Zeek http.log")
parser.add_argument("--output", "-o", help="Output JSON report")
args = parser.parse_args()
print("[*] C2 Beaconing Hunting Agent")
report = {"timestamp": datetime.now(timezone.utc).isoformat(), "findings": {}}
if args.dns_log:
dns = analyze_dns_queries(args.dns_log)
report["findings"]["dns"] = dns
print(f"[*] DNS findings: {len(dns)}")
if args.http_log:
http = analyze_http_logs(args.http_log)
report["findings"]["http"] = http
print(f"[*] HTTP findings: {len(http)}")
if args.conn_log:
conn = analyze_connection_patterns(args.conn_log)
report["findings"]["connections"] = conn
print(f"[*] Connection findings: {len(conn)}")
total = sum(len(v) for v in report["findings"].values())
report["risk_level"] = "CRITICAL" if total >= 10 else "HIGH" if total >= 5 else "MEDIUM" if total > 0 else "LOW"
if args.output:
with open(args.output, "w") as f:
json.dump(report, f, indent=2)
print(f"[*] Report saved to {args.output}")
else:
print(json.dumps(report, indent=2))
if __name__ == "__main__":
main()