Extracting Cobalt Strike Beacon Configuration

Static and dynamic techniques for identifying Cobalt Strike beacons, extracting C2 configs, and generating detection signatures - covering the configuration block format, parser internals, malleable C2 fingerprinting, and YARA strategy.

Why CS Beacons Are Worth Hunting

Cobalt Strike has been the gold-standard commercial C2 framework for nearly a decade. Threat actors love it because the licensed product is feature-complete and widely documented; cracked builds (every major version since 3.x has leaked within weeks of release) circulate broadly in the criminal underground. The result: a huge fraction of real-world ransomware, espionage, and APT intrusions ride on a CS beacon at some stage of the kill chain.

For a defender or threat-intel analyst, that ubiquity is the gift. CS beacons share a small, well-understood configuration format. Extract it from a sample and you instantly get the C2 server, the operator’s “watermark” (license/team identifier), the malleable profile fingerprint, and the lateral-movement parameters. That config feeds your IOC pipeline, your threat-actor attribution, and your detection rules - all from a single binary.

This post covers the practical end-to-end: identifying a beacon, extracting its config, mapping fields to intelligence value, and producing detections that survive the next malleable profile customisation.

Identifying CS Beacons

Cobalt Strike beacons have recognizable patterns: XOR-encoded configuration blocks, specific PE section characteristics, and known shellcode stagers.

File-Format Indicators

Beacons ship in three common flavours:

  1. Stage 0 shellcode - a very small (~200-800 byte) blob that decrypts and loads stage 1.
  2. Stage 1 reflective DLL (beacon.dll) - the actual beacon, packaged with a reflective loader.
  3. Stage 1 raw shellcode (beacon.bin) - the same DLL pre-converted via sRDI for shellcode-only loading.

PE-based stage 1 beacons have characteristic traits:

  • A .text section beginning with the reflective loader stub (most signatures still match Stephen Fewer’s original ReflectiveLoader shape).
  • A configuration block embedded in .data (or sometimes .text) - encoded with a single-byte XOR key.
  • Strings such as %s as %s\\%s: %d (used in mimikatz integration), ReflectiveLoader, beacon, named pipe templates \\.\pipe\msagent_*, and PowerShell host integration markers.
  • Imports limited to a small canonical set when packed; verbose imports including wininet/winhttp/crypt32 when unpacked.

XOR Keys Used Across Versions

Different CS versions encode the configuration with different fixed XOR keys:

Version Config XOR key Notes
3.x 0x69 First widely-leaked builds
4.0-4.2 0x2E Updated key
4.3+ 0x2E or per-build random Newer profiles randomise

When you don’t know the key, brute-force all 256 possibilities and look for the resulting XOR-decoded blob containing the byte sequence 0x00 0x01 0x00 0x01 0x00 0x02 0x00 0x02 (the field-type/length preamble of the first two configuration entries). That’s the fingerprint of a valid decoded config regardless of XOR key.

The Beacon Configuration Format

The configuration is a sequence of TLV (type-length-value) records terminated by a zero record. Each record:

+--------+--------+--------+----+
| index  |  type  | length | ...  |
+--------+--------+--------+----+
   2B       2B       2B     N B
  • index: a uint16 identifying which configuration field this record holds (e.g. 1 = beacon type, 7 = public key, 8 = sleep time).
  • type: 1 = uint16, 2 = uint32, 3 = binary blob.
  • length: number of bytes that follow.
  • value: the data itself.

Approximately 80 well-known indices are documented. The most operationally interesting are listed in the Key Config Fields table later.

Static Extraction with 1768.py

The Cobalt Strike config is XOR-encoded at a known offset. Tools like Didier Stevens’ 1768.py or SentinelOne’s CobaltStrikeParser decode it:

python3 1768.py beacon.bin

This reveals: C2 server, beacon interval, jitter percentage, watermark, user-agent, spawn-to process, named pipes, and license ID.

Walking Through a Real Extraction

A typical 1768.py run on an unpacked beacon prints something like:

File: beacon.exe
SHA256: 3c5a1c...
PE: True
Config found via NORMAL search at offset 0x0000a3f0  XOR key: 0x2e

0x0001 BeaconType                           0  (HTTP)
0x0002 Port                                443
0x0003 SleepTime                          60000  (60 s)
0x0004 MaxGetSize                         1048576
0x0005 Jitter                                10  (%)
0x0007 PublicKey                          (RSA-2048 binary blob)
0x0008 Server,GetURI                      ['cdn.cloudfront.net,/api/v2/profile/photo']
0x0009 UserAgent                          Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 ...
0x000A HttpPostUri                        /api/v2/upload/avatar
0x002B HostHeader                         (none)
0x0033 dns_idle                           8.8.8.8
0x0037 Watermark                          0x5f5e10ff   ← operator/license ID
0x004F SpawnTo_x86                        %windir%\\syswow64\\rundll32.exe
0x0050 SpawnTo_x64                        %windir%\\sysnative\\rundll32.exe
0x004E Pipe Name                          \\.\pipe\msagent_6e

Every field maps to a piece of intel; the next section maps them.

Other Public Parsers

Tool Strengths
Didier Stevens’ 1768.py Most up-to-date with newer CS versions; YARA-driven scanner mode
SentinelOne’s CobaltStrikeParser Clean Python, easy to embed in pipelines
nccgroup / nccgroup’s CSPF-style scripts Useful when 1768.py misses heavily mutated configs
Volatility plugin cobaltstrikeconfig Memory-dump extraction (running beacons in process memory)

Key Config Fields

Field Intelligence Value
C2 Server Infrastructure IOC
Watermark Operator/license tracking
SpawnTo Process injection target
Named Pipe Lateral movement indicator
User-Agent Network detection signature
Sleep Time + Jitter Behavioral pattern

Expanded with operational context:

Index Field Why analysts care
0x0001 BeaconType (HTTP/HTTPS/DNS/SMB/TCP) Tells you which network detection rule set to deploy
0x0008 Server / GetURI Primary IOC; pivot in passive DNS, VirusTotal Graph
0x000A PostURI Secondary IOC; profile fingerprint
0x000B HttpGet metadata First half of the malleable C2 profile transform
0x000C HttpPost metadata Second half - both together identify the profile uniquely
0x0009 User-Agent Direct network detection string
0x002B HostHeader (domain fronting target) Reveals fronted CDN setup
0x0033 DNS idle target Used during DNS beacon
0x0037 Watermark Per-license number - pivot to other samples by same operator group
0x004F/4E SpawnTo paths Process-injection target for fork-and-run
0x004E Named pipe template SMB beacon lateral-movement hint
0x0035 killdate Auto-suicide date - sets a campaign window
0x0007 RSA public key Cryptographically identifies the team server

The watermark in particular is gold. A watermark is a 32-bit integer baked into every beacon a given Cobalt Strike team server generates. Over the years, the threat-intel community has built large mappings of watermark → known-actor (e.g. 0x5f5e10ff/5f5e10ff was the most common cracked-build watermark for years; several APT groups have unique watermarks tied to specific operators).

The public key is the strongest cryptographic identifier. Because it is the team server’s RSA public key, every beacon staged from the same server has the same key. SHA-256-ing the key gives a perfect cross-sample server identifier. Trackers like tek.tlol.in and Recorded Future’s TLS-cert + key-pivots use this routinely.

Malleable C2 Profiles

Operators customize beacon traffic via malleable profiles. Analyze HTTP headers, URI patterns, and data transforms to fingerprint the specific profile being used.

What a Malleable Profile Controls

A malleable C2 profile is a .profile file (a Cobalt Strike DSL) that customises:

  • HTTP method (GET/POST), URI patterns, query parameters
  • Request and response headers (Host, Authorization, X-Custom-*, …)
  • Encoding/transform pipeline (base64, base64url, prepend, append, mask, netbios)
  • Where the beacon ID and beacon data live in the request (cookie, parameter, body)
  • TLS certificate (self-signed by default, but can specify SNI / cert details)
  • Spawn-to process names per architecture
  • Process injection technique
  • HTTP user-agent

Fingerprinting Profiles Without the Source

You usually don’t have the .profile source - you have a packet capture or a network sensor signature. Practical fingerprinting:

  • The “amazon” profile has been the most-used template for years (Host: www.amazon.com, distinctive cookie patterns).
  • The “jquery” profile uses requests for /jquery-3.3.1.min.js with a beacon ID hidden in the Cookie header.
  • The “havex” profile mimics the Havex APT’s HTTP traffic.
  • Custom enterprise profiles mimic legitimate SaaS endpoints (api.salesforce.com, graph.microsoft.com).

The decoded HttpGet/HttpPost transforms in the configuration tell you exactly which profile is in use - no PCAP needed. Compare against the public profile repository at Cobalt-Strike/Malleable-C2-Profiles for matches.

Network Detection Signatures

The malleable profile is supposed to make traffic look benign, but a few invariants persist:

  1. JA3/JA3S TLS fingerprints - the team server’s Java-based TLS stack (or its Python launcher) has distinctive ClientHello fingerprints.
  2. Default certificate fields - unless replaced, the team server self-signs with a CN of Major Cobalt Strike or similar boilerplate.
  3. HTTP “no-staging” stagers (X64 stager) - a fixed sequence of about 800 bytes; even when wrapped in TLS, the size and timing are characteristic.
  4. Beacon timing - the sleep_time + jitter combination produces a measurable inter-arrival distribution that sits between truly random and perfectly periodic.

Public detections that work: Suricata’s et open ruleset, the Florian Roth signature-base repository, Elastic’s prebuilt rules (Network: Cobalt Strike Default Beacon), and the various JA3 hashlists (J. Salmela’s curated list).

YARA Rules

Write signatures matching beacon config markers, XOR key patterns, and shellcode stagers. Focus on invariant bytes that survive profile customization.

Anchor Strategy

A robust YARA rule for CS beacons doesn’t try to match a single byte sequence - beacons are too easily mutated. Instead it anchors on multiple invariants:

rule CobaltStrike_Beacon_Generic
{
    meta:
        author      = "robinx0"
        description = "Detects Cobalt Strike beacon - XOR'd config + reflective loader"
        date        = "2026-03-05"
        tlp         = "white"

    strings:
        // Reflective loader fingerprint (Stephen Fewer's stub, used by CS)
        $rl_str = "ReflectiveLoader"
        // Unique CS strings even after obfuscation
        $cs_pipe   = /\\\\\.\\pipe\\msagent_[a-f0-9]{2}/ ascii wide
        $cs_powershell = "PowerShell.exe -nop -w hidden -encodedcommand"
        $cs_user_agent = "Mozilla/5.0 (compatible; MSIE"
        // XOR-encoded config preamble (any XOR key)
        // First 8 bytes of decoded config: 00 01 00 01 00 02 00 02
        // These XORed with single key K give: K^00 K^01 K^00 K^01 K^00 K^02 K^00 K^02
        // i.e. byte0==byte2==byte4==byte6, byte1 differs from byte5 by 0x03
        $cfg_pattern = {
            ?? ?? ?? ?? ?? ?? ?? ??
        }

    condition:
        // Either string-based confirmation OR config block heuristic
        (uint16(0) == 0x5A4D and 1 of ($cs_*) and $rl_str)
        or ( for any i in (0..filesize - 8) :
            ( uint8(i) == uint8(i+2) and
              uint8(i)   == uint8(i+4) and
              uint8(i)   == uint8(i+6) and
              uint8(i+1) == uint8(i+3) and
              (uint8(i+1) ^ uint8(i+5)) == 0x03 and
              uint8(i+1) == uint8(i+3) ) )
}

The condition’s second branch encodes “anywhere in the file there are 8 bytes that, when XORed with a constant, yield the canonical CS config preamble” - invariant against the XOR key.

Detection Tradecraft

Keep two YARA tiers:

  • High-confidence rules matching the unpacked beacon - strings, reflective loader, config block. False-positive rate near zero, recall ~ 60% (heavy obfuscation evades).
  • Heuristic rules matching common stagers, packers, and download cradles even when the final beacon is encrypted. Higher false positives but catches the loader chain.

Memory-Resident Beacons

Beacons rarely live on disk in their final form. Production hunting workflow:

  1. Pull a process memory dump (procdump -ma, ETW LiveDump, MemProcFS).
  2. Strings + entropy scan for signs (PowerShell, ReflectiveLoader, large XOR-uniform regions).
  3. Volatility’s cobaltstrikeconfig plugin or 1768.py --memory mode.
  4. Pivot on extracted C2/watermark/public-key.

For the EDR side, BeaconEye actively scans live processes for beacon configs and is genuinely useful as a “it shouldn’t be there” alert source.

Every CS beacon contains a wealth of threat intelligence. Extracting the config is the first step in attribution and infrastructure mapping - and once you have a watermark + public key, you have a thread you can pull all the way back to the team server.

← Home More Malware analysis →