mirror of
https://github.com/mukul975/Anthropic-Cybersecurity-Skills.git
synced 2026-07-14 11:51:16 +03:00
27c6414ca5
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
175 lines
6.5 KiB
Python
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()
|