#!/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()
