Hunting Cobalt Strike: Beacons, Named Pipes, and Malleable Profiles
A hands-on playbook for hunting Cobalt Strike: how Beacon works, network and named pipe signatures, malleable C2 profiles, memory indicators, ready-to-use YARA and Sigma rules, and post-detection triage.
Why Cobalt Strike still matters
Cobalt Strike was built as a legitimate red team platform. It became the most abused post-exploitation framework on the planet because it is modular, well documented, and its cracked versions circulate freely. Ransomware affiliates, access brokers, and state-aligned crews all lean on it.
The good news for defenders: Beacon leaves a lot of tracks. Even with heavy customization, operators rarely change everything, and the defaults are a gift. This playbook walks through where to look, what to write, and what to do once something lights up.
How Beacon works
Beacon is the implant. It runs in memory, checks in with a team server over HTTP, HTTPS, or DNS, and executes tasking between sleep cycles. The important properties for hunting:
- Sleep and jitter. Beacon sleeps between check-ins (default 60 seconds) and applies jitter to randomize the interval. Low jitter produces suspiciously regular beaconing.
- Staging. A small stager can pull the full payload, or the payload can be fully staged (self-contained). Staged delivery exposes a predictable request pattern.
- Malleable C2. A profile file lets the operator reshape how Beacon talks: URIs, headers, user agents, and how data is encoded inside requests and responses.
- Post-exploitation jobs. Many capabilities (keylogging, screenshots,
mimikatz, token theft) spawn temporary processes and named pipes.
Hunt across three planes: network, process and pipe, and memory. A hit on one plane should push you to confirm on the others.
Network signatures
Default self-signed certificate
For years the default Cobalt Strike HTTPS listener shipped with a self-signed certificate whose fields are notorious. The SHA-256 thumbprint 87f2085c32b6a2cc709b365f55873e207a9caa10bffecf2fd16d3cf9d94d390c and a serial of 8BB00EE show up in countless writeups. Any TLS certificate carrying those values is a near-certain finding.
Beyond the exact default, look for certificate anomalies: a self-signed cert on a listener that claims to be a CDN, mismatched CN and SNI, or a freshly issued Let's Encrypt cert fronting an IP with no legitimate service.
URI and beaconing patterns
Out of the box, Beacon check-ins use short, static URIs and a recognizable Cookie header carrying the encrypted metadata blob. Even after operators load a malleable profile, the traffic often keeps a rhythm.
| Signal | What to look for |
|---|---|
| Regular interval | Connections every N seconds with tight variance |
| Small requests, larger responses | Check-in is tiny; tasking response is bigger |
| Long-lived destination | Same host contacted for hours with no user browsing |
| Odd user agent | Hardcoded or slightly-off UA strings |
Beaconing detection is a job for your SIEM or NDR, not a single rule. Aggregate connections per source and destination, compute the interval variance, and flag low-variance repeaters. Enrich the destination IP or domain to separate real CDNs from imposters. A quick reputation and passive DNS check on mlab.sh will tell you whether that "cloudfront" host is anything of the sort.
Named pipe patterns
Beacon uses named pipes heavily: for SMB Beacon peer-to-peer links, and for post-exploitation jobs that need inter-process communication. The default pipe names are a reliable indicator.
Classic defaults include:
\\.\pipe\msagent_##
\\.\pipe\MSSE-####-server
\\.\pipe\status_##
\\.\pipe\postex_####
\\.\pipe\postex_ssh_####
The postex_ and postex_ssh_ pipes are created by post-exploitation modules and are strong signals because legitimate software rarely mints pipes with those names. On Windows, pipe creation and connection surface in Sysmon Event IDs 17 (Pipe Created) and 18 (Pipe Connected).
title: Cobalt Strike Default Named Pipe
id: 9b3c2f10-7a44-4c88-9d2e-1f6b0a2c4e77
status: experimental
logsource:
product: windows
category: pipe_created
detection:
selection:
PipeName|startswith:
- '\msagent_'
- '\MSSE-'
- '\postex_'
- '\postex_ssh_'
- '\status_'
condition: selection
falsepositives:
- Legitimate software rarely uses these names, but validate in your environment
level: high
tags:
- attack.command_and_control
- attack.t1071
Operators can and do rename pipes through their profile. Treat the list above as a starting baseline, then hunt for the structural pattern: short-lived pipes created by a process that has no business creating them (a rundll32.exe or a browser child spawning a postex-style pipe).
Malleable C2 profiles
A malleable C2 profile customizes almost every observable in Beacon traffic. Operators use it to impersonate legitimate services (Amazon, jQuery CDN, Microsoft update traffic). That sounds like it defeats detection, and it raises the bar, but it introduces its own tells.
Profiles that impersonate a known service often get the details slightly wrong: a jQuery profile that requests /jquery-3.3.1.min.js but returns a payload with the wrong content length, or an "Amazon" profile whose Host header does not match the destination IP's real ownership. Public profiles from GitHub repositories are reused constantly, so a profile that matches a known public template is itself a signal.
The takeaway: malleable profiles change the surface, not the substance. Beacon still sleeps, still checks in, still runs jobs that spawn pipes. Layer your detection so one evaded plane does not blind you.
Memory indicators and YARA
Because Beacon lives in memory, scanning process memory with YARA is one of the most effective approaches. The in-memory configuration block and known code stubs survive even when the payload on disk is packed or absent.
rule CobaltStrike_Beacon_Memory {
meta:
description = "Cobalt Strike Beacon indicators in process memory"
author = "Mlab Team"
severity = "critical"
reference = "https://hunt.mlab.sh"
strings:
// Default named pipe fragments
$pipe1 = "\\\\.\\pipe\\msagent_" ascii wide
$pipe2 = "\\\\.\\pipe\\postex_" ascii wide
$pipe3 = "MSSE-" ascii wide
// Beacon spawn and job strings
$s1 = "%s as %s\\%s: %d" ascii
$s2 = "beacon.dll" ascii nocase
$s3 = "ReflectiveLoader" ascii
// Config block XOR key markers seen across versions
$cfg = { 2e 2f 2e 2f 2e 2c 2e 2f }
condition:
3 of them
}
Run it against live processes or a memory image. With yara-python you can walk running processes and match by PID, which is exactly the kind of sweep you fire when a network alert points at one host. If you author and validate rules in the browser first, hunt.mlab.sh will flag syntax and logic problems before you push them to an EDR fleet.
A word on tuning: three-of-them keeps this rule honest. Any single default string can appear in a benign artifact or a security tool's own signatures, so require corroboration.
Process behavior with Sigma
Two behaviors catch a large share of Beacon activity: spawn-to injection and rundll32 executing without a DLL argument.
By default, Beacon spawns a sacrificial process (historically rundll32.exe) and injects post-exploitation code into it. That produces a rundll32.exe process with no command-line arguments, which is abnormal.
title: Rundll32 Without Arguments (Possible Beacon Spawn)
id: 2d7e5a91-4c3b-4a1e-8b90-6f2c1d3e9a44
status: experimental
logsource:
category: process_creation
product: windows
detection:
selection:
Image|endswith: '\rundll32.exe'
filter:
CommandLine|contains: '.dll'
condition: selection and not filter
falsepositives:
- Rare legitimate wrappers; baseline before alerting
level: high
tags:
- attack.defense_evasion
- attack.t1055
Pair that with parent-child anomalies: rundll32.exe whose parent is a browser, an Office application, or another injected process. The spawnto value is configurable, so also hunt for the pattern generically, a short-lived child process with an unbacked memory region hosting executable code, which EDR memory telemetry can surface.
A layered hunting workflow
Bring the planes together into a repeatable loop:
- Network first. Hunt beaconing with interval-variance analysis and flag low-jitter repeaters. Enrich destinations to strip out real CDNs.
- Certificate sweep. Match TLS certs against the known default thumbprint and serial, plus self-signed anomalies.
- Pipe and process hunt. Query Sysmon 17/18 for default and structurally-odd pipes, and process creation for argument-less
rundll32. - Memory confirmation. Scan suspect hosts with the YARA rule above.
- Correlate. A single plane can be evaded; two or three together is confirmation.
Post-detection triage
Once a host lights up, move fast but preserve evidence.
- Capture memory before killing anything. Beacon is in RAM. Pull a memory image so you keep the config block, injected regions, and network state.
- Extract the config. Tools that parse the Beacon configuration recover the C2 servers, sleep and jitter, spawn-to values, watermark, and the malleable profile. The watermark is an operator or license identifier and helps cluster activity across incidents.
- Pivot on infrastructure. Take the recovered C2 domains and IPs and enrich them. Passive DNS and reputation lookups on mlab.sh can expand a single server into the wider campaign footprint.
- Scope laterally. SMB Beacon links mean peers. Check for the pipe artifacts on adjacent hosts and follow named pipe connections.
- Contain deliberately. Isolating one host tips the operator. Understand the spread before you pull the plug, unless active damage forces your hand.
Closing
Cobalt Strike is designed to blend in, but blending in is not the same as disappearing. Beacon still beacons, defaults still leak, and post-exploitation still touches pipes and memory. Hunt on multiple planes, require corroboration, and confirm in RAM. Catch one operator's slip on any layer and the rest of the picture falls into place.
Beacon hides in memory, not in mystery. Scan for it and it shows up.