mirror of
https://github.com/mukul975/Anthropic-Cybersecurity-Skills.git
synced 2026-06-14 23:14:55 +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
235 lines
8.3 KiB
Python
235 lines
8.3 KiB
Python
#!/usr/bin/env python3
|
|
"""CobaltStrike Malleable C2 Profile Analyzer - parses profiles to extract C2 indicators, detection signatures, and evasion techniques"""
|
|
# For authorized security research and defensive analysis only
|
|
|
|
import argparse
|
|
import json
|
|
import re
|
|
from collections import Counter
|
|
from datetime import datetime
|
|
from pathlib import Path
|
|
|
|
try:
|
|
from dissect.cobaltstrike.c2profile import C2Profile
|
|
HAS_DISSECT = True
|
|
except ImportError:
|
|
HAS_DISSECT = False
|
|
|
|
RUN_KEY_SUSPICIOUS = ["powershell", "cmd.exe", "mshta", "rundll32", "regsvr32", "wscript", "cscript"]
|
|
|
|
KNOWN_SPOOF_TARGETS = {
|
|
"amazon": "Amazon CDN impersonation",
|
|
"google": "Google services impersonation",
|
|
"microsoft": "Microsoft services impersonation",
|
|
"slack": "Slack API impersonation",
|
|
"cloudfront": "CloudFront CDN impersonation",
|
|
"jquery": "jQuery CDN impersonation",
|
|
"outlook": "Outlook Web impersonation",
|
|
"onedrive": "OneDrive impersonation",
|
|
}
|
|
|
|
|
|
def load_data(path):
|
|
return json.loads(Path(path).read_text(encoding="utf-8"))
|
|
|
|
|
|
def parse_profile_with_dissect(profile_path):
|
|
"""Parse a .profile file using dissect.cobaltstrike C2Profile."""
|
|
if not HAS_DISSECT:
|
|
return None
|
|
profile = C2Profile.from_path(profile_path)
|
|
return profile.as_dict()
|
|
|
|
|
|
def parse_profile_regex(content):
|
|
"""Regex-based parser for malleable C2 profile when dissect is unavailable."""
|
|
config = {}
|
|
set_pattern = re.compile(r'set\s+(\w+)\s+"([^"]*)"', re.MULTILINE)
|
|
for match in set_pattern.finditer(content):
|
|
config[match.group(1)] = match.group(2)
|
|
block_pattern = re.compile(r'(http-get|http-post|http-stager|https-certificate|dns-beacon|process-inject|post-ex)\s*\{', re.MULTILINE)
|
|
for match in block_pattern.finditer(content):
|
|
config.setdefault("blocks", []).append(match.group(1))
|
|
uri_pattern = re.compile(r'set\s+uri\s+"([^"]*)"', re.MULTILINE)
|
|
for match in uri_pattern.finditer(content):
|
|
config.setdefault("uris", []).append(match.group(1))
|
|
header_pattern = re.compile(r'header\s+"([^"]+)"\s+"([^"]*)"', re.MULTILINE)
|
|
for match in header_pattern.finditer(content):
|
|
config.setdefault("headers", []).append({"name": match.group(1), "value": match.group(2)})
|
|
spawn_pattern = re.compile(r'set\s+spawnto_x(?:86|64)\s+"([^"]*)"', re.MULTILINE)
|
|
for match in spawn_pattern.finditer(content):
|
|
config.setdefault("spawn_to", []).append(match.group(1))
|
|
return config
|
|
|
|
|
|
def analyze_profile(config):
|
|
"""Analyze parsed profile configuration for detection opportunities."""
|
|
findings = []
|
|
ua = config.get("useragent", config.get("user_agent", ""))
|
|
if ua:
|
|
findings.append({
|
|
"type": "user_agent_identified",
|
|
"severity": "info",
|
|
"resource": "http-config",
|
|
"detail": f"User-Agent: {ua[:100]}",
|
|
"indicator": ua,
|
|
})
|
|
for target, desc in KNOWN_SPOOF_TARGETS.items():
|
|
if target.lower() in ua.lower():
|
|
findings.append({
|
|
"type": "service_impersonation",
|
|
"severity": "medium",
|
|
"resource": "user-agent",
|
|
"detail": f"{desc} detected in User-Agent string",
|
|
})
|
|
sleeptime = config.get("sleeptime", config.get("sleep_time", ""))
|
|
jitter = config.get("jitter", "")
|
|
if sleeptime:
|
|
try:
|
|
sleep_ms = int(sleeptime)
|
|
if sleep_ms < 1000:
|
|
findings.append({
|
|
"type": "aggressive_beaconing",
|
|
"severity": "high",
|
|
"resource": "beacon-config",
|
|
"detail": f"Very low sleep time: {sleep_ms}ms - aggressive C2 callback rate",
|
|
})
|
|
except ValueError:
|
|
pass
|
|
uris = config.get("uris", [])
|
|
for uri in uris:
|
|
findings.append({
|
|
"type": "c2_uri",
|
|
"severity": "high",
|
|
"resource": "http-config",
|
|
"detail": f"C2 URI path: {uri}",
|
|
"indicator": uri,
|
|
})
|
|
headers = config.get("headers", [])
|
|
for h in headers:
|
|
name = h.get("name", "") if isinstance(h, dict) else str(h)
|
|
value = h.get("value", "") if isinstance(h, dict) else ""
|
|
if name.lower() in ("host", "cookie", "authorization"):
|
|
findings.append({
|
|
"type": "c2_header",
|
|
"severity": "medium",
|
|
"resource": "http-config",
|
|
"detail": f"Custom header: {name}: {value[:60]}",
|
|
})
|
|
spawn_to = config.get("spawn_to", config.get("spawnto_x86", []))
|
|
if isinstance(spawn_to, str):
|
|
spawn_to = [spawn_to]
|
|
for proc in spawn_to:
|
|
findings.append({
|
|
"type": "spawn_to_process",
|
|
"severity": "high",
|
|
"resource": "process-inject",
|
|
"detail": f"Beacon spawns to: {proc}",
|
|
"indicator": proc,
|
|
})
|
|
pipename = config.get("pipename", config.get("pipename_stager", ""))
|
|
if pipename:
|
|
findings.append({
|
|
"type": "named_pipe",
|
|
"severity": "high",
|
|
"resource": "process-inject",
|
|
"detail": f"Named pipe: {pipename}",
|
|
"indicator": pipename,
|
|
})
|
|
dns_idle = config.get("dns_idle", "")
|
|
if dns_idle:
|
|
findings.append({
|
|
"type": "dns_beacon_config",
|
|
"severity": "medium",
|
|
"resource": "dns-beacon",
|
|
"detail": f"DNS idle IP: {dns_idle}",
|
|
})
|
|
watermark = config.get("watermark", "")
|
|
if watermark:
|
|
findings.append({
|
|
"type": "watermark",
|
|
"severity": "info",
|
|
"resource": "beacon-config",
|
|
"detail": f"Beacon watermark: {watermark}",
|
|
})
|
|
return findings
|
|
|
|
|
|
def generate_suricata_rules(findings, sid_start=9000001):
|
|
"""Generate Suricata rules from extracted indicators."""
|
|
rules = []
|
|
sid = sid_start
|
|
for f in findings:
|
|
if f["type"] == "c2_uri" and f.get("indicator"):
|
|
uri = f["indicator"].replace('"', '\\"')
|
|
rules.append(
|
|
f'alert http $HOME_NET any -> $EXTERNAL_NET any '
|
|
f'(msg:"MALWARE CobaltStrike Malleable C2 URI {uri}"; '
|
|
f'flow:established,to_server; '
|
|
f'http.uri; content:"{uri}"; '
|
|
f'sid:{sid}; rev:1;)'
|
|
)
|
|
sid += 1
|
|
elif f["type"] == "named_pipe" and f.get("indicator"):
|
|
pipe = f["indicator"]
|
|
rules.append(
|
|
f'# Named pipe detection requires endpoint monitoring: {pipe}'
|
|
)
|
|
return rules
|
|
|
|
|
|
def analyze(data):
|
|
if isinstance(data, str):
|
|
config = parse_profile_regex(data)
|
|
elif isinstance(data, dict):
|
|
config = data
|
|
else:
|
|
config = data[0] if isinstance(data, list) and data else {}
|
|
return analyze_profile(config)
|
|
|
|
|
|
def generate_report(input_path):
|
|
path = Path(input_path)
|
|
if path.suffix in (".profile", ".txt"):
|
|
content = path.read_text(encoding="utf-8")
|
|
config = parse_profile_regex(content)
|
|
findings = analyze_profile(config)
|
|
else:
|
|
data = load_data(input_path)
|
|
if isinstance(data, list):
|
|
findings = []
|
|
for profile in data:
|
|
findings.extend(analyze_profile(profile))
|
|
else:
|
|
findings = analyze_profile(data)
|
|
sev = Counter(f["severity"] for f in findings)
|
|
iocs = [f.get("indicator", "") for f in findings if f.get("indicator")]
|
|
rules = generate_suricata_rules(findings)
|
|
return {
|
|
"report": "cobaltstrike_malleable_c2_analysis",
|
|
"generated_at": datetime.utcnow().isoformat() + "Z",
|
|
"total_findings": len(findings),
|
|
"severity_summary": dict(sev),
|
|
"extracted_iocs": iocs,
|
|
"suricata_rules": rules,
|
|
"findings": findings,
|
|
}
|
|
|
|
|
|
def main():
|
|
ap = argparse.ArgumentParser(description="CobaltStrike Malleable C2 Profile Analyzer")
|
|
ap.add_argument("--input", required=True, help="Input .profile file or JSON with parsed config")
|
|
ap.add_argument("--output", help="Output JSON report path")
|
|
args = ap.parse_args()
|
|
report = generate_report(args.input)
|
|
out = json.dumps(report, indent=2)
|
|
if args.output:
|
|
Path(args.output).write_text(out, encoding="utf-8")
|
|
print(f"Report written to {args.output}")
|
|
else:
|
|
print(out)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|