Files
T
mukul975 27c6414ca5 Add folder anatomy (scripts/agent.py + references/api-reference.md) for 648 cybersecurity skills
Complete skill folder anatomy across all cybersecurity skills:
- scripts/agent.py: 80-150 line Python agents using real libraries (impacket,
  boto3, azure-mgmt-*, kubernetes, pefile, yara, scapy, shodan, stix2, etc.)
- references/api-reference.md: real API documentation with method signatures
- LICENSE: MIT license for all skill folders
2026-03-10 21:02:12 +01:00

175 lines
6.5 KiB
Python

#!/usr/bin/env python3
"""Agent for analyzing Cobalt Strike malleable C2 profiles and JARM fingerprinting."""
import os
import json
import subprocess
import argparse
from pathlib import Path
from datetime import datetime
from malleablec2 import Profile
def extract_profile_indicators(profile_path):
"""Extract detection indicators from a malleable C2 profile."""
with open(profile_path) as f:
content = f.read()
profile = Profile.from_string(content)
indicators = {
"file": str(profile_path),
"source_lines": len(content.splitlines()),
"reconstructed": str(profile),
}
keywords = ["sleeptime", "jitter", "useragent", "pipename", "host_stage",
"dns_idle", "dns_sleep", "spawnto_x86", "spawnto_x64"]
options = {}
for kw in keywords:
for line in content.splitlines():
stripped = line.strip().rstrip(";").strip()
if kw in stripped.lower() and "set " in stripped.lower():
parts = stripped.split('"')
if len(parts) >= 2:
options[kw] = parts[1]
indicators["global_options"] = options
uris = []
for line in content.splitlines():
if "set uri" in line.strip().lower():
parts = line.strip().split('"')
if len(parts) >= 2:
uris.append(parts[1])
indicators["uris"] = uris
headers = []
for line in content.splitlines():
stripped = line.strip()
if "header " in stripped.lower() and '"' in stripped:
parts = stripped.split('"')
if len(parts) >= 4:
headers.append({"name": parts[1], "value": parts[3]})
indicators["custom_headers"] = headers
return indicators
def scan_directory_profiles(directory):
"""Scan a directory for malleable C2 profiles and extract indicators."""
results = []
for path in Path(directory).rglob("*.profile"):
try:
indicators = extract_profile_indicators(str(path))
results.append(indicators)
except Exception as e:
results.append({"file": str(path), "error": str(e)})
return results
KNOWN_CS_JARM = {
"07d14d16d21d21d07c42d41d00041d24a458a375eef0c576d23a7bab9a9fb1":
"Cobalt Strike (default)",
"07d14d16d21d21d00042d41d00041de5fb3038104f457d92ba02e9311512c2":
"Cobalt Strike (Java 11)",
}
def compute_jarm_fingerprint(host, port=443):
"""Compute JARM fingerprint by invoking the salesforce/jarm scanner."""
jarm_script = os.getenv("JARM_SCRIPT", "jarm.py")
try:
result = subprocess.run(
["python3", jarm_script, host, "-p", str(port)],
capture_output=True, text=True, timeout=30,
)
for line in result.stdout.splitlines():
if len(line.strip()) >= 62:
return line.strip().split()[-1]
return result.stdout.strip()
except Exception as e:
return f"Error: {e}"
def check_jarm_against_known(fingerprint):
"""Check a JARM fingerprint against known Cobalt Strike signatures."""
for jarm_hash, description in KNOWN_CS_JARM.items():
if fingerprint.strip() == jarm_hash:
return {"match": True, "description": description, "fingerprint": fingerprint}
return {"match": False, "fingerprint": fingerprint}
def batch_jarm_scan(targets, port=443):
"""Scan multiple targets for JARM fingerprints and check against known CS hashes."""
results = []
for target in targets:
fp = compute_jarm_fingerprint(target, port)
match = check_jarm_against_known(fp)
match["target"] = target
results.append(match)
return results
def generate_snort_rules(indicators_list):
"""Generate Snort/Suricata rules from extracted profile indicators."""
rules = []
sid = 1000001
for ind in indicators_list:
for uri in ind.get("uris", []):
rules.append(
f'alert http $HOME_NET any -> $EXTERNAL_NET any '
f'(msg:"CS Beacon URI {uri}"; '
f'content:"{uri}"; http_uri; sid:{sid}; rev:1;)'
)
sid += 1
ua = ind.get("global_options", {}).get("useragent", "")
if ua:
rules.append(
f'alert http $HOME_NET any -> $EXTERNAL_NET any '
f'(msg:"CS Beacon User-Agent"; '
f'content:"{ua}"; http_header; sid:{sid}; rev:1;)'
)
sid += 1
return rules
def main():
parser = argparse.ArgumentParser(description="Cobalt Strike Malleable Profile Analyzer")
parser.add_argument("--profile", help="Path to a single malleable C2 profile")
parser.add_argument("--directory", help="Directory of malleable profiles")
parser.add_argument("--jarm-targets", nargs="*", help="Hosts to JARM fingerprint")
parser.add_argument("--output", default="cs_analysis_report.json")
parser.add_argument("--action", choices=[
"parse", "scan_dir", "jarm", "generate_rules", "full_analysis"
], default="full_analysis")
args = parser.parse_args()
report = {"generated_at": datetime.utcnow().isoformat(), "findings": {}}
if args.action in ("parse", "full_analysis") and args.profile:
indicators = extract_profile_indicators(args.profile)
report["findings"]["profile_indicators"] = indicators
print(f"[+] Parsed: {args.profile} ({len(indicators.get('uris', []))} URIs)")
if args.action in ("scan_dir", "full_analysis") and args.directory:
results = scan_directory_profiles(args.directory)
report["findings"]["directory_scan"] = results
print(f"[+] Scanned {len(results)} profiles in {args.directory}")
if args.action in ("jarm", "full_analysis") and args.jarm_targets:
jarm_results = batch_jarm_scan(args.jarm_targets)
report["findings"]["jarm_scan"] = jarm_results
matches = [r for r in jarm_results if r.get("match")]
print(f"[+] JARM: {len(jarm_results)} scanned, {len(matches)} CS matches")
if args.action in ("generate_rules", "full_analysis"):
profiles = report["findings"].get("directory_scan", [])
if not profiles and args.profile:
profiles = [report["findings"].get("profile_indicators", {})]
rules = generate_snort_rules(profiles)
report["findings"]["snort_rules"] = rules
print(f"[+] Generated {len(rules)} Snort rules")
with open(args.output, "w") as f:
json.dump(report, f, indent=2, default=str)
print(f"[+] Report saved to {args.output}")
if __name__ == "__main__":
main()