Extracting Cobalt Strike beacon configs at scale
Cobalt Strike beacon config extraction means recovering an implant's embedded settings block, its command and control hosts, sleep and jitter timing, user agent, request paths and build watermark, by decoding those bytes statically instead of running the sample. The framework is legitimate commercial red team software that criminal operators abuse widely, so a recovered configuration answers what a verdict cannot: which infrastructure this build talks to, and how it behaves on the wire. Our static engine decodes the block on the analysis host and publishes only derived indicators.
Key facts
| Item | Detail |
|---|---|
| Where it runs | Host side, in the static analyzer chain of every file analysis |
| Input | The sample's bytes, parsed and decoded, never executed |
| Method | Signature sweep for the encoded first record, XOR decode, record walk |
| Recovered today | Implant type, port, sleep, jitter, watermark, user agent, POST path, DNS idle value, host header, C2 list |
| Verdict effect | Drives the malicious path and confirms the family |
| What leaves the host | Derived indicators only |
Why a recovered configuration beats a malicious verdict
A verdict is one label. A configuration is a set of facts you can pivot on for months: the hosts the implant calls, the interval it calls them on, the path it posts to, and the watermark tying this build to others from the same operator.
The scoring layer treats a decoded config as close to definitive: it is one of the few static signals that alone forces the malicious path instead of adding points to a pile. Attribution follows the same rule. Most family claims need two independent sources before the platform calls attribution confirmed, and a decoded configuration is trusted alone, because decoding a family's own record format says more than a rule name matching a byte pattern.
How a beacon configuration is laid out as type length value records
The block is a flat sequence of records. Each carries a six byte header, big endian throughout: a two byte setting index, a two byte value type, a two byte length, then the value. Three value types cover everything: a two byte short, a four byte integer, a raw byte run. The index is the setting number, so the block is a dictionary rather than a struct, and a build that omits a setting omits its record.
Obfuscation is one byte of XOR over the whole stream, enough to defeat a naive strings pass and not much else, and that is the property the extractor turns against it. The walk stops at index zero or at a length past a sane ceiling and caps how many records it accepts, so a hostile block costs bounded work.
How the parser finds the block without executing the sample
The first record of a valid block is fixed: setting one, short type, length two. Six known plaintext bytes under a candidate key give six known encoded bytes, so finding the block is a substring search, not a scan of every offset against every key. The engine sweeps the head of the file for that signature, decodes a window from the hit, walks the records, and refuses the result unless a C2 value or a port came out. Measured on small carved children the step costs about two tenths of a second, and it is skipped for plainly textual files.
Not running the sample is a platform rule rather than an implementation detail. Tools that parse attacker chosen bytes on the host run in a bubblewrap namespace with no network and a read only filesystem; anything that would interpret or execute a sample belongs in a detonation VM. The third party family parsers are the documented exception, since they receive the whole sample: they run as forked children, bounded in wall clock and address space, killed if they overrun, and loaded only from a corpus an operator pinned to a commit. See our security model.
What the recovered configuration fields tell an analyst
| Field | What it tells you |
|---|---|
| Implant type | HTTP, HTTPS, DNS, SMB or raw TCP, which decides where you can see it |
| Sleep and jitter | The callback rhythm, and how much randomness hides it from interval based analytics |
| Watermark | A per build value that groups an operator's samples better than a hash does |
| User agent and host header | Exact request headers to hunt in proxy logs, impersonation included |
| POST path | The URI the implant sends collected data to, the strongest network anchor |
| DNS idle value | The address returned when the DNS channel has no work, which separates live traffic from noise |
| C2 list | Hosts and addresses, promoted to indicators |
Named settings are a subset of what the record stream carries. A build also holds a spawn target, the process the implant starts to host post exploitation jobs; a named pipe pattern for linked implants; and staging paths an HTTP stager fetches its payload from. The first two are host telemetry you can write a rule against; the third is network telemetry. Our decoder keeps the indexes it can name and drops the rest rather than guessing, and the installed parser corpus names more of them. Terms here are defined in the glossary.
How extracted configs become hunting material and detection rules
Every C2 value from a decoded configuration becomes a typed indicator row tagged with its provenance. That tag matters: a host decoded out of an embedded config is recorded as malicious evidence, while the same value found loose in the sample's strings is informational. One is what the malware was built to contact, the other merely happened to be present.
From those rows the platform generates network rules that alert on the DNS query and on the request path. Two guards sit in front of that generator, both earned. Values are scrubbed against a curated allowlist immediately before the rule text is written, and rules are suppressed when the scan's own verdict came out clean. We measured the stored corpus before adding the second guard: almost all generated rows came from clean scans, most naming our own headless browser's ordinary traffic as command and control. Rules can be exported, kept as a personal ruleset, or pulled through the API, and a new rule runs backwards over previous scans. Public reports carry the same fields, so you can read real output on recent samples.
Where config extraction fails, honestly
Packed loaders are the common miss. If the block only exists after the loader unpacks itself, the static sweep has nothing to find, and static analysis is close to blind on that sample. Detonation covers it: process memory is dumped and scanned with the same rule corpus against the unpacked image, an ordinary process baseline subtracted, and every payload dropped to disk gets its own static pass, config extraction included.
In-memory only stagers that never touch disk are harder. What you get is the dump plus the observed endpoints, which the runtime heuristic reports as an unattributed configuration. Treat that as a lead, not an identification. Unknown variants fail more plainly: a changed key, a rearranged layout, or a family whose parser postdates the pinned corpus all read as absent rather than as an error.
One failure mode is worth naming because it bit us. An extractor once picked up the sandbox's own collector address and reported it as a C2 host. The report now gates the words command and control behind a curated source, the right fix where a confident wrong answer is worse than none.
Frequently asked questions
What is a beacon configuration block?
A beacon configuration block is the settings table an implant carries inside itself: callback hosts, port, sleep and jitter values, request paths, headers and a build watermark. It is stored as a sequence of type length value records under light obfuscation, so a decoder can read it out of a file without that file ever running.
Does extracting a config require running the malware?
No. Static extraction reads the sample's bytes, finds the encoded record header, reverses the single byte obfuscation and walks the records. The Malwagon static layer parses samples on the host under a restricted namespace and never executes them there; execution happens only inside an isolated detonation VM, and only when a scan asks for dynamic analysis.
Why is a decoded configuration treated as near definitive?
Decoding a family's own record format and getting coherent values out of it is stronger evidence than a pattern match. Random bytes do not yield a valid record header, a plausible sleep interval and a routable host at one offset. The scoring layer therefore lets a decoded configuration drive a malicious verdict and confirm attribution alone.
What happens when the sample is packed?
Static extraction usually finds nothing, because the configuration only exists after the loader unpacks itself in memory. Detonation covers that case: the platform dumps process memory, runs the rule corpus against the unpacked image with a benign baseline subtracted, and puts every dropped payload through a fresh static pass, so a second stage on disk still gets decoded.