Hunting Cobalt Strike Beacons in Network Traffic

6 January 2026 | 5 min read | justruss.tech

Cobalt Strike appears in a significant proportion of major breach reports. The tool itself is legitimate commercial adversary simulation software, but cracked and leaked versions have been in widespread use since around 2020 and feature in the majority of large-scale ransomware intrusions and nation-state operations. Understanding how it works in detail makes hunting for it considerably more practical.

How a Cobalt Strike beacon works

A beacon is a shellcode payload that runs in memory. On execution it decodes its configuration from an embedded XOR-encoded block, resolves C2 server details from that config, 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 for different operational requirements.

// Extracting the beacon configuration from a file or memory dump
pip install cobaltstrikeparser

// From a beacon binary
python3 -m cobaltstrikeparser beacon.bin

// Example output:
// BeaconType:        HTTPS
// Port:              443
// SleepTime:         60000   (milliseconds)
// MaxGetSize:        1048576
// Jitter:            0       (0% jitter = perfectly regular intervals)
// C2Server:          updates.microsoft-cdn.com,/updates/check
// UserAgent:         Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36
// HttpGetHeader:     Accept: */*
// Watermark:         305419896
// bProcInject:       1
// INJECTION_PROCESS: explorer.exe

// The Watermark is a numeric identifier tied to a specific license
// Known watermarks for cracked/leaked copies are documented in threat intel feeds

Default profile network indicators in Zeek

// Default HTTP beacon traffic - zero jitter makes it unmistakable
// 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, near-identical byte counts
// Real user traffic to the same server would show completely irregular timing

// From http.log (default profile):
// method=GET  uri=/updates/check  user_agent="Mozilla/5.0..."
// method=POST uri=/submit.php     user_agent="Mozilla/5.0..."

// The GET retrieves tasks, the POST submits results
// Default URI patterns are well-documented and easy to Sigma rule against

JA3 fingerprinting

// Default Cobalt Strike HTTPS beacon JA3 hash
// From ssl.log:
// ja3: a0e9f5d64349fb13191bc781f81f42e1
// This is produced by the Java TLS implementation used in the stager

// Check all TLS connections for this hash
cat /opt/zeek/logs/current/ssl.log | \
    python3 -c "
import sys, json
for line in sys.stdin:
    try:
        rec = json.loads(line)
        if rec.get('ja3') == 'a0e9f5d64349fb13191bc781f81f42e1':
            print('COBALT STRIKE DEFAULT JA3 DETECTED')
            print(f'  src={rec[\"id.orig_h\"]} dst={rec[\"id.resp_h\"]}')
            print(f'  sni={rec.get(\"server_name\",\"none\")}')
    except:
        pass
"

// Sigma rule for Elastic/Sysmon correlation
title: Cobalt Strike Default JA3 Hash
logsource:
    category: network
    product: zeek
detection:
    selection:
        event.dataset: zeek.ssl
        ssl.ja3: 'a0e9f5d64349fb13191bc781f81f42e1'
    condition: selection
level: high

Malleable C2 profile evasion

Experienced operators use custom Malleable C2 profiles that change every aspect of beacon traffic. A profile impersonating Amazon S3 requests:

// Example Malleable C2 profile (publicly available, used for understanding)
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";
        }
    }
}

// Against this profile, traffic looks like legitimate AWS SDK requests
// Content-based detection fails entirely
// You need behavioural detection instead

Behavioural detection that survives any profile

// Calculate connection timing regularity - works regardless of profile
python3 << EOF
import json, statistics
from collections import defaultdict
import os

log_path = "/opt/zeek/logs/current/conn.log"
if not os.path.exists(log_path):
    print("conn.log not found - adjust path")
    exit()

connections = defaultdict(list)
with open(log_path) as f:
    for line in f:
        try:
            rec = json.loads(line)
            if rec.get("proto") == "tcp" and str(rec.get("id.resp_p")) == "443":
                key = (rec["id.orig_h"], rec["id.resp_h"])
                connections[key].append(float(rec["ts"]))
        except:
            pass

print("Checking for beaconing to port 443...")
for (src, dst), timestamps in sorted(connections.items()):
    if len(timestamps) < 8:
        continue
    timestamps.sort()
    intervals = [timestamps[i+1]-timestamps[i] for i in range(len(timestamps)-1)]
    if len(intervals) < 5:
        continue
    mean_i = statistics.mean(intervals)
    stdev_i = statistics.stdev(intervals)
    if mean_i == 0:
        continue
    cv = stdev_i / mean_i
    # Very regular intervals, beacon-range timing
    if cv < 0.15 and 20 < mean_i  {dst}:443")
        print(f"  mean_interval={mean_i:.1f}s  stdev={stdev_i:.1f}s  cv={cv:.3f}  samples={len(intervals)}")
EOF

Named pipe beacon detection (SMB C2)

// SMB beacons use named pipes for C2, leaving no external network trace
// Detection requires host-based telemetry

// Sysmon Events 17 and 18 cover pipe creation and connection
// Default Cobalt Strike named pipe patterns:
// \pipe\MSSE-[guid]-0000-0000-0000-000000000000
// \pipe\postex_[hex]
// \pipe\msagent_[hex]

// Sigma rule for default named pipe patterns
title: Cobalt Strike Named Pipe Default Pattern
logsource:
    product: windows
    category: pipe_created
detection:
    selection:
        PipeName|startswith:
            - '\MSSE-'
            - '\postex_'
            - '\msagent_'
            - '\status_'
    condition: selection
level: high

// Also look for unusual processes connecting to named pipes
// svchost.exe connecting to a pipe in \Device\NamedPipe\postex_abc123
// is not legitimate behaviour

Hunting process injection from beacons

Cobalt Strike beacons commonly inject into other processes for post-exploitation. The spawn_to process (default: rundll32.exe) is spawned and injected into for most post-exploitation operations.

// Sysmon Event ID 8: CreateRemoteThread from beacon into spawn_to process
// Default spawn_to: C:\Windows\SysWOW64\rundll32.exe (no arguments)

// Sigma rule for Cobalt Strike spawn_to pattern
title: Cobalt Strike Spawn-To Process Default
logsource:
    product: windows
    category: create_remote_thread
detection:
    selection:
        TargetImage|endswith: '\rundll32.exe'
    filter_legit:
        SourceImage|startswith:
            - 'C:\Windows\System32\'
            - 'C:\Windows\SysWOW64\'
    condition: selection and not filter_legit
falsepositives:
    - Legitimate software using rundll32 for DLL loading
level: medium