<!-- Copyright (c) 2026 defconxt. All rights reserved. -->
<!-- Licensed under AGPL-3.0 — see LICENSE file for details. -->
---
name: analyzing-cobalt-strike-beacon
description: >-
  Extract and analyze Cobalt Strike beacon configurations from PE files, shellcode,
  and memory dumps to identify C2 infrastructure, malleable profiles, and operator tradecraft.
domain: cybersecurity
subdomain: malware-analysis
tags:
  - cobalt-strike
  - beacon
  - c2-analysis
  - config-extraction
  - threat-hunting
  - red-team-tools
  - incident-response
version: "1.0"
author: defconxt
license: AGPL-3.0
metadata:
  mitre-attack: ["T1071.001", "T1573.002"]
---

# Analyzing Cobalt Strike Beacon Configuration

## Overview

Cobalt Strike is a commercial adversary simulation tool widely abused by threat actors for post-exploitation operations. Beacon payloads embed configuration data revealing C2 server addresses, communication protocols, sleep intervals, jitter values, malleable C2 profile settings, watermark identifiers, and encryption keys. The beacon configuration is XOR-encoded (0x69 for v3, 0x2e for v4) and stored in TLV format within the `.data` section. Extracting this data is critical for IR teams mapping attacker infrastructure and attributing campaigns.

## Prerequisites

- Python 3.10+ with `dissect.cobaltstrike`, `pefile`, `yara-python`
- SentinelOne CobaltStrikeParser
- Hex editor (010 Editor, HxD) for manual inspection
- Understanding of PE format and XOR encoding
- Memory dump tools (Volatility3, WinDbg)
- Network analysis tools (Wireshark) for C2 traffic correlation
- Isolated analysis environment (VM, no production network bridge)

## Key Concepts

### Beacon Configuration Structure

Cobalt Strike beacons store configuration as a TLV (Type-Length-Value) blob within the PE `.data` section. Stageless beacons XOR the entire beacon with a 4-byte key; the config blob uses a single-byte XOR key. Each TLV entry has a 2-byte type (e.g., `0x0001` BeaconType, `0x0008` C2Server), 2-byte length type, 2-byte length, and variable-length data.

### Malleable C2 Profiles

The configuration encodes the malleable C2 profile dictating HTTP request/response transformations — URI paths, headers, metadata encoding (Base64, NetBIOS), and data transforms. Analyzing these reveals how the beacon disguises traffic to blend with legitimate web traffic.

### Watermark and License Identification

Each Cobalt Strike license embeds a unique watermark (4-byte integer). Extracting the watermark links beacons to the same operator or cracked license. Known watermark databases map values to specific threat actors or leaked keys.

### Beacon Types

| Type | Value | Description |
|------|-------|-------------|
| HTTP | 0 | Standard HTTP beacon |
| HTTPS | 8 | HTTPS beacon |
| DNS | 1 | DNS beacon using A/AAAA/TXT records |
| SMB | 2 | Named pipe beacon for lateral movement |
| TCP | 4 | TCP bind/reverse beacon |

## Workflow

### Step 1: Identify and Triage Beacon Sample

```bash
# File identification
file suspect.exe
sha256sum suspect.exe

# Check for Cobalt Strike indicators
strings -a suspect.exe | grep -iE 'beacon|cobaltstrike|msf|meterpreter|pipe\\'

# YARA scan for beacon signatures
yara -r cobalt_strike_rules.yar suspect.exe

# PE section analysis — look for high-entropy .data section
python3 -c "
import pefile
pe = pefile.PE('suspect.exe')
for s in pe.sections:
    print(f'{s.Name.decode().rstrip(chr(0)):8s} entropy={s.get_entropy():.2f} size={s.SizeOfRawData}')
"
```

### Step 2: Extract Configuration with dissect.cobaltstrike

```python
#!/usr/bin/env python3
"""Extract beacon config using dissect.cobaltstrike."""
from dissect.cobaltstrike.beacon import BeaconConfig
import json, sys

configs = list(BeaconConfig.from_path(sys.argv[1]))
if not configs:
    print("[-] No beacon configuration found", file=sys.stderr)
    sys.exit(1)

for i, config in enumerate(configs):
    settings = config.as_dict()
    critical = {
        "c2_server": settings.get("SETTING_DOMAINS", ""),
        "beacon_type": settings.get("SETTING_BEACONTYPE", ""),
        "port": settings.get("SETTING_PORT", ""),
        "sleep_time": settings.get("SETTING_SLEEPTIME", ""),
        "jitter": settings.get("SETTING_JITTER", ""),
        "user_agent": settings.get("SETTING_USERAGENT", ""),
        "spawn_x86": settings.get("SETTING_SPAWNTO_X86", ""),
        "spawn_x64": settings.get("SETTING_SPAWNTO_X64", ""),
        "pipe_name": settings.get("SETTING_PIPENAME", ""),
        "watermark": settings.get("SETTING_WATERMARK", ""),
        "public_key": settings.get("SETTING_PUBKEY", ""),
    }
    print(json.dumps(critical, indent=2, default=str))
```

### Step 3: Manual XOR Decryption

```python
import struct

def decrypt_beacon_config(data: bytes, xor_key: int) -> dict:
    """Decrypt and parse beacon config TLV entries."""
    decrypted = bytes([b ^ xor_key for b in data])
    entries = {}
    offset = 0
    field_map = {
        0x0001: "BeaconType", 0x0002: "Port", 0x0003: "SleepTime",
        0x0005: "Jitter", 0x0008: "C2Server", 0x0009: "UserAgent",
        0x000d: "SpawnTo_x86", 0x000e: "SpawnTo_x64",
        0x001a: "Watermark", 0x0024: "PipeName",
    }
    while offset + 6 <= len(decrypted):
        etype = struct.unpack(">H", decrypted[offset:offset+2])[0]
        if etype == 0:
            break
        elen_type = struct.unpack(">H", decrypted[offset+2:offset+4])[0]
        elen = struct.unpack(">H", decrypted[offset+4:offset+6])[0]
        val = decrypted[offset+6:offset+6+elen]
        name = field_map.get(etype, f"0x{etype:04x}")
        if elen_type == 3:
            entries[name] = val.rstrip(b'\x00').decode('utf-8', errors='replace')
        elif elen_type == 2:
            entries[name] = struct.unpack(">I", val[:4])[0]
        elif elen_type == 1:
            entries[name] = struct.unpack(">H", val[:2])[0]
        offset += 6 + elen
    return entries

# Search for config in raw file
with open("suspect.exe", "rb") as f:
    data = f.read()
for key in [0x2e, 0x69]:
    magic = bytes([0x00 ^ key, 0x01 ^ key, 0x00 ^ key, 0x02 ^ key])
    pos = data.find(magic)
    if pos != -1:
        config = decrypt_beacon_config(data[pos:pos+4096], key)
        print(f"Config found at 0x{pos:x} (XOR key 0x{key:02x})")
        for k, v in config.items():
            print(f"  {k}: {v}")
        break
```

### Step 4: Generate Detection Signatures

```yaml
# Sigma rule for beacon spawn-to process
title: Cobalt Strike Beacon Spawn-To Process
id: a1b2c3d4-e5f6-7890-abcd-ef1234567890
status: experimental
description: Detects default Cobalt Strike spawn-to processes
logsource:
  category: process_creation
  product: windows
detection:
  selection:
    ParentImage|endswith:
      - '\rundll32.exe'
      - '\dllhost.exe'
    Image|endswith:
      - '\rundll32.exe'
      - '\gpupdate.exe'
      - '\mstsc.exe'
  condition: selection
falsepositives:
  - Legitimate rundll32 usage with specific DLL arguments
level: high
tags:
  - attack.t1055
  - attack.defense_evasion
```

### Step 5: Network Signature Generation

```python
def generate_network_sigs(config: dict) -> list[str]:
    """Generate Suricata signatures from beacon config."""
    sigs = []
    ua = config.get("UserAgent", "")
    c2 = config.get("C2Server", "")
    if ua:
        sigs.append(
            f'alert http $HOME_NET any -> $EXTERNAL_NET any '
            f'(msg:"CS Beacon User-Agent"; content:"{ua}"; '
            f'http_user_agent; sid:9000001; rev:1;)'
        )
    if c2:
        for domain in c2.split(","):
            domain = domain.strip().split("/")[0]
            if domain:
                sigs.append(
                    f'alert dns $HOME_NET any -> any 53 '
                    f'(msg:"CS Beacon C2 Domain"; dns.query; '
                    f'content:"{domain}"; sid:9000002; rev:1;)'
                )
    return sigs
```

## Verification

- [ ] Beacon config successfully extracted from PE/memory dump
- [ ] C2 domains/IPs identified with port and protocol
- [ ] Malleable C2 profile parameters decoded
- [ ] Watermark extracted for attribution correlation
- [ ] Sleep/jitter values match observed beacon intervals
- [ ] YARA rules detect beacon in packed and unpacked samples
- [ ] Network signatures generated and validated
- [ ] IOCs documented and shared with threat intel team

## References

- [SentinelOne CobaltStrikeParser](https://github.com/Sentinel-One/CobaltStrikeParser)
- [dissect.cobaltstrike Library](https://github.com/fox-it/dissect.cobaltstrike)
- [MITRE ATT&CK - Cobalt Strike S0154](https://attack.mitre.org/software/S0154/)
- [Cobalt Strike Beacon Config Extraction](https://blog.securehat.co.uk/cobaltstrike/extracting-config-from-cobaltstrike-stager-shellcode)
- [Google Cloud Threat Intel - CS Analysis](https://cloud.google.com/blog/topics/threat-intelligence/defining-cobalt-strike-components)
