Cobalt Strike is a commercial adversary simulation platform. A licensed copy costs around $3,500 per year per operator. Cracked versions have been in widespread circulation since approximately 2020 and appear in the majority of large-scale
ransomware and APT intrusions. Hunting for it effectively requires understanding both the default configuration and how operators modify it.
How a Cobalt Strike beacon works
A beacon is a shellcode payload that runs in memory. Once executed, it decodes its configuration from an embedded XOR-encoded block, connects to the team server (the C2 infrastructure), and enters a sleep-check-task loop. The default sleep is 60
seconds with 0% jitter. The C2 protocol is HTTP or HTTPS by default, though DNS and SMB named pipe transports are also supported.
The configuration block can be extracted from a beacon in memory or on disk. The structure is documented and tools like CobaltStrikeParser can decode it:
pip install cobaltstrikeparser python3 -m cobaltstrikeparser beacon.bin # Example output: BeaconType - HTTPS Port - 443 SleepTime - 60000 (ms) MaxGetSize - 1048576 Jitter - 0 C2Server - updates.microsoft-cdn.com HttpGet_Uri - /updates/check UserAgent - Mozilla/5.0 (Windows NT 10.0; Win64; x64)... Watermark - 305419896
The Watermark field is a numeric identifier tied to a specific licensed copy (or cracked build). Known watermarks for common cracked versions are documented in threat intelligence feeds.
Network indicators — default profile
Default HTTP beacon traffic has recognisable characteristics visible in Zeek conn.log and http.log:
# Beaconing pattern: regular intervals, consistent byte counts # From conn.log: ts id.orig_h id.resp_h duration orig_bytes resp_bytes 1696200000 192.168.1.101 203.0.113.50 0.341 285 2048 1696200060 192.168.1.101 203.0.113.50 0.287 285 2048 1696200120 192.168.1.101 203.0.113.50 0.301 285 2048 1696200180 192.168.1.101 203.0.113.50 0.298 285 2048
Exactly 60-second intervals, almost identical byte counts — this is what 0% jitter looks like. Real user-generated traffic to the same host would show irregular timing.
JA3 fingerprinting
The default Cobalt Strike HTTPS beacon uses a specific TLS configuration that produces a consistent JA3 hash. From ssl.log:
ts id.orig_h id.resp_h ja3 ja3s server_name 1696200000 192.168.1.101 203.0.113.50 a0e9f5d64349fb13191bc781f81f42e1 ec74a5c51106f0419184d0dd08fb05bc updates.microsoft-cdn.com
The JA3 hash a0e9f5d64349fb13191bc781f81f42e1 is the most widely documented Cobalt Strike default. It is generated by the Java TLS implementation used in the beacon stager. The JA3S hash ec74a5c51106f0419184d0dd08fb05bc
identifies the server-side response fingerprint.
Malleable C2 profile evasion
Experienced operators deploy custom Malleable C2 profiles that change every aspect of beacon traffic. A profile impersonating Amazon S3:
http-get {
set uri "/s3/bucket/update.bin";
client {
header "Host" "s3.amazonaws.com";
header "User-Agent" "aws-sdk-go/1.44.0 (go1.18; linux; amd64)";
metadata {
base64url;
prepend "AWSAccessKeyId=";
parameter "X-Amz-Security-Token";
}
}
server {
header "Content-Type" "application/octet-stream";
header "x-amz-id-2" "random";
output {
base64;
print;
}
}
}
Against a profile like this, content-based detection fails. The traffic looks indistinguishable from legitimate AWS SDK requests at the application layer.
Behavioural detection that survives profile changes
Regardless of what the traffic looks like, beacon behaviour has consistent properties:
# Zeek: calculate coefficient of variation (CV) of connection intervals
# Low CV = highly regular timing = beacon candidate
# Run against conn.log with Python:
import json, statistics
from collections import defaultdict
connections = defaultdict(list)
with open("conn.log") as f:
for line in f:
rec = json.loads(line)
key = (rec["id.orig_h"], rec["id.resp_h"], rec["id.resp_p"])
connections[key].append(float(rec["ts"]))
for key, timestamps in connections.items():
if len(timestamps) < 10:
continue
timestamps.sort()
intervals = [timestamps[i+1]-timestamps[i] for i in range(len(timestamps)-1)]
if len(intervals) 0 else 999
if cv < 0.1 and 30 < mean < 300: # very regular, 30s-5min intervals
print(f"{key}: mean_interval={mean:.1f}s stdev={stdev:.1f}s cv={cv:.3f} samples={len(intervals)}")
A CV under 0.1 (10% standard deviation relative to mean) across 10+ samples in the 30-300 second interval range is a high-confidence beaconing indicator regardless of what the HTTP traffic looks like.
Hunting named pipe beacons
SMB beacon uses named pipes for C2 and leaves no network trace to external servers. Detection requires host-based telemetry. Sysmon Event ID 17/18 (PipeCreated/PipeConnected):
EventID: 17 PipeName: \MSSE-5d04b01a-0000-0000-0000-000000000000 Image: C:\Windows\System32\svchost.exe
Default Cobalt Strike named pipe format: \MSSE-[guid]-0000-... or \postex_[hex]. These are documented in public threat intel and easy to Sigma-rule against.