Files
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

240 lines
8.2 KiB
Python

#!/usr/bin/env python3
"""Cobalt Strike beacon configuration extraction and analysis agent.
Extracts C2 configuration from beacon payloads including server addresses,
communication settings, malleable C2 profile details, and watermark values.
"""
import struct
import os
import sys
import hashlib
from collections import OrderedDict
# Cobalt Strike beacon configuration field IDs (Type-Length-Value format)
BEACON_CONFIG_FIELDS = {
1: ("BeaconType", "short"),
2: ("Port", "short"),
3: ("SleepTime", "int"),
4: ("MaxGetSize", "int"),
5: ("Jitter", "short"),
7: ("PublicKey", "bytes"),
8: ("C2Server", "str"),
9: ("UserAgent", "str"),
10: ("PostURI", "str"),
11: ("Malleable_C2_Instructions", "bytes"),
12: ("HttpGet_Metadata", "bytes"),
13: ("HttpPost_Metadata", "bytes"),
14: ("SpawnToX86", "str"),
15: ("SpawnToX64", "str"),
19: ("CryptoScheme", "short"),
26: ("GetVerb", "str"),
27: ("PostVerb", "str"),
28: ("HttpPostChunk", "int"),
29: ("Spawnto_x86", "str"),
30: ("Spawnto_x64", "str"),
31: ("CryptoScheme2", "str"),
37: ("Watermark", "int"),
38: ("StageCleanup", "short"),
39: ("CFGCaution", "short"),
43: ("DNS_Idle", "int"),
44: ("DNS_Sleep", "int"),
50: ("HostHeader", "str"),
54: ("PipeName", "str"),
}
BEACON_TYPES = {0: "HTTP", 1: "Hybrid HTTP/DNS", 2: "SMB", 4: "TCP", 8: "HTTPS", 16: "DNS over HTTPS"}
XOR_KEY_V3 = 0x69
XOR_KEY_V4 = 0x2E
def compute_hash(filepath):
"""Compute SHA-256 hash of file."""
sha256 = hashlib.sha256()
with open(filepath, "rb") as f:
for chunk in iter(lambda: f.read(65536), b""):
sha256.update(chunk)
return sha256.hexdigest()
def find_config_offset(data):
"""Find the beacon configuration blob in PE data or shellcode."""
# Look for XOR-encoded config patterns
for xor_key in [XOR_KEY_V3, XOR_KEY_V4]:
# Config starts with 0x0001 (BeaconType field ID) XOR-encoded
encoded_marker = bytes([0x00 ^ xor_key, 0x01 ^ xor_key, 0x00 ^ xor_key, 0x01 ^ xor_key])
offset = data.find(encoded_marker)
if offset != -1:
return offset, xor_key
# Try unencoded
for offset in range(len(data) - 100):
if data[offset:offset+4] == b"\x00\x01\x00\x01":
return offset, None
return -1, None
def xor_decode(data, key):
"""XOR decode data with single byte key."""
if key is None:
return data
return bytes(b ^ key for b in data)
def parse_config_field(data, offset):
"""Parse a single TLV config field."""
if offset + 6 > len(data):
return None, None, None, offset
field_id = struct.unpack_from(">H", data, offset)[0]
field_type = struct.unpack_from(">H", data, offset + 2)[0]
if field_type == 1: # short
value = struct.unpack_from(">H", data, offset + 4)[0]
return field_id, "short", value, offset + 6
elif field_type == 2: # int
value = struct.unpack_from(">I", data, offset + 4)[0]
return field_id, "int", value, offset + 8
elif field_type == 3: # str/bytes
length = struct.unpack_from(">H", data, offset + 4)[0]
if offset + 6 + length > len(data):
return None, None, None, offset
value = data[offset + 6:offset + 6 + length]
return field_id, "str", value, offset + 6 + length
return None, None, None, offset + 2
def extract_beacon_config(filepath):
"""Extract and parse Cobalt Strike beacon configuration."""
with open(filepath, "rb") as f:
data = f.read()
config_offset, xor_key = find_config_offset(data)
if config_offset == -1:
return {"error": "No beacon configuration found", "file": filepath}
config_data = xor_decode(data[config_offset:config_offset + 4096], xor_key)
config = OrderedDict()
config["_meta"] = {
"config_offset": f"0x{config_offset:08X}",
"xor_key": f"0x{xor_key:02X}" if xor_key else "none",
"version_guess": "4.x" if xor_key == XOR_KEY_V4 else "3.x" if xor_key == XOR_KEY_V3 else "unknown",
}
offset = 0
max_fields = 100
parsed = 0
while offset < len(config_data) - 4 and parsed < max_fields:
field_id, field_type, value, new_offset = parse_config_field(config_data, offset)
if field_id is None or new_offset == offset:
break
offset = new_offset
parsed += 1
field_info = BEACON_CONFIG_FIELDS.get(field_id)
if field_info:
field_name, expected_type = field_info
if isinstance(value, bytes):
try:
str_value = value.rstrip(b"\x00").decode("utf-8", errors="replace")
config[field_name] = str_value
except Exception:
config[field_name] = value.hex()[:100]
elif field_id == 1:
config[field_name] = BEACON_TYPES.get(value, f"Unknown({value})")
else:
config[field_name] = value
return config
def extract_c2_indicators(config):
"""Extract C2 indicators from parsed config for threat intelligence."""
indicators = {"c2_servers": [], "user_agents": [], "uris": [],
"pipes": [], "watermark": None, "dns": []}
c2 = config.get("C2Server", "")
if c2:
for server in c2.split(","):
server = server.strip().rstrip("/")
if server:
indicators["c2_servers"].append(server)
ua = config.get("UserAgent", "")
if ua:
indicators["user_agents"].append(ua)
for key in ["PostURI"]:
uri = config.get(key, "")
if uri:
indicators["uris"].append(uri)
pipe = config.get("PipeName", "")
if pipe:
indicators["pipes"].append(pipe)
wm = config.get("Watermark")
if wm:
indicators["watermark"] = wm
return indicators
def assess_operator_opsec(config):
"""Assess operator OPSEC based on beacon configuration."""
findings = []
sleep = config.get("SleepTime", 0)
jitter = config.get("Jitter", 0)
if sleep < 30000:
findings.append({"level": "INFO", "detail": f"Low sleep time: {sleep}ms - high beacon frequency"})
if jitter == 0:
findings.append({"level": "WARN", "detail": "No jitter configured - predictable beacon interval"})
ua = config.get("UserAgent", "")
if "Mozilla" not in ua and ua:
findings.append({"level": "WARN", "detail": f"Non-standard User-Agent: {ua[:60]}"})
spawn86 = config.get("SpawnToX86", config.get("Spawnto_x86", ""))
if "rundll32" in spawn86.lower():
findings.append({"level": "INFO", "detail": "Default spawn-to process (rundll32) - easy to detect"})
cleanup = config.get("StageCleanup", 0)
if cleanup == 0:
findings.append({"level": "INFO", "detail": "Stage cleanup disabled - beacon stub remains in memory"})
return findings
if __name__ == "__main__":
print("=" * 60)
print("Cobalt Strike Beacon Configuration Extractor")
print("C2 extraction, watermark analysis, OPSEC assessment")
print("=" * 60)
target = sys.argv[1] if len(sys.argv) > 1 else None
if not target or not os.path.exists(target):
print("\n[DEMO] Usage: python agent.py <beacon_sample.exe>")
print(" Extracts: C2 servers, sleep/jitter, watermark, malleable profile")
sys.exit(0)
print(f"\n[*] Analyzing: {target}")
print(f"[*] SHA-256: {compute_hash(target)}")
print(f"[*] Size: {os.path.getsize(target)} bytes")
config = extract_beacon_config(target)
if "error" in config:
print(f"\n[!] {config['error']}")
sys.exit(1)
print("\n--- Beacon Configuration ---")
for key, value in config.items():
if key == "_meta":
for mk, mv in value.items():
print(f" {mk}: {mv}")
else:
print(f" {key}: {value}")
indicators = extract_c2_indicators(config)
print("\n--- C2 Indicators ---")
for c2 in indicators["c2_servers"]:
print(f" [C2] {c2}")
if indicators["watermark"]:
print(f" [Watermark] {indicators['watermark']}")
for pipe in indicators["pipes"]:
print(f" [Pipe] {pipe}")
opsec = assess_operator_opsec(config)
print("\n--- Operator OPSEC Assessment ---")
for f in opsec:
print(f" [{f['level']}] {f['detail']}")