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:
- Stage 0 shellcode - a very small (~200-800 byte) blob that decrypts and loads stage 1.
- Stage 1 reflective DLL (
beacon.dll) - the actual beacon, packaged with a reflective loader. - 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
.textsection beginning with the reflective loader stub (most signatures still match Stephen Fewer’s originalReflectiveLoadershape). - 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 inmimikatzintegration),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/crypt32when 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.jswith 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:
- JA3/JA3S TLS fingerprints - the team server’s Java-based TLS stack (or its Python launcher) has distinctive ClientHello fingerprints.
- Default certificate fields - unless replaced, the team server self-signs with a CN of
Major Cobalt Strikeor similar boilerplate. - HTTP “no-staging” stagers (X64 stager) - a fixed sequence of about 800 bytes; even when wrapped in TLS, the size and timing are characteristic.
- Beacon timing - the
sleep_time + jittercombination 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:
- Pull a process memory dump (
procdump -ma, ETW LiveDump, MemProcFS). - Strings + entropy scan for signs (
PowerShell,ReflectiveLoader, large XOR-uniform regions). - Volatility’s
cobaltstrikeconfigplugin or1768.py --memorymode. - 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.