Cobalt Strike Traffic
Cobalt Strike is the most widely used commercial offensive framework and the C2 tool you will encounter most often in real-world intrusions. Understanding exactly what Cobalt Strike traffic looks like — in default configuration and with Malleable C2 profiles — is essential for detection engineering. Cobalt Strike's C2 traffic has multiple layers of identifiable characteristics even when an operator has configured a custom Malleable C2 profile.
An intrusion analyst receives a PCAP from a compromised host. The traffic shows regular HTTPS connections to a VPS IP every 60 seconds. You need to confirm this is Cobalt Strike, not a legitimate application. You analyze: the JA3 hash (matches default CS profile), the beacon timing (CV = 0.008, essentially zero jitter), the HTTP response body size pattern (exactly 4 bytes for empty check-in responses), and the URI pattern (/submit.php with 150-byte fixed POST bodies). Each individually is ambiguous; all together confirm Cobalt Strike with high confidence.
Cobalt Strike Default Indicators
Cobalt Strike Network Indicators — Default Profile
═══════════════════════════════════════════════════════════════════
Transport: HTTP or HTTPS (most common), DNS, SMB pipe (lateral movement)
HTTP/S Default Profile (no Malleable C2):
├── GET check-in: GET /[random 4-char].js or /updates
│ No Cookie header (default)
│ User-Agent: Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; .NET CLR 1.1.4322)
│ Note: IE 7.0 on Windows XP in 2024 = red flag
├── POST for task data: POST /[random]/submit.php
│ Metadata encoded in URI or POST body
│ Default body: ~150 bytes, very consistent size
└── Server response: 4 bytes " " (spaces) for empty task
JA3 Hashes (Java-based TLS):
├── Default: 51c64c77e60f3980eea90869b68c58a8 (3.12-3.14)
├── Variant: 07d14d16d21d21d07c07d14d07d21d69e3ff64f35a08ef2a6d5bb20d6e2fc (JARM)
└── Java TLS fingerprint is more unique than browser TLS
SMB C2 (named pipe for lateral movement):
├── Named pipe: \\.\pipe\MSSE-[hex]-server (default)
├── Written to C:\Windows\Temp\ or %TEMP%
└── Parent process: spawned via PsExec-style or WMI
Timing: default sleep = 60 seconds, jitter = 0%
→ CV ≈ 0.001 (practically zero)
Staged vs Stageless:
├── Staged: initial beacon downloads shellcode (~200-400KB GET response)
└── Stageless: full shellcode in initial payload (no staging request)
Cobalt Strike Detection Script
#!/bin/bash
PCAP="$1"
echo "=== Known Cobalt Strike JA3 Hashes ==="
CS_JA3="51c64c77e60f3980eea90869b68c58a8"
tshark -r "$PCAP" -n -Y "tls.handshake.type==1" -T fields \
-E separator="\t" \
-e ip.src -e ip.dst -e tcp.dstport \
| while read src dst port; do
# Would need JA3 computation here — use Zeek ssl.log ja3 field instead
echo "Connection: $src → $dst:$port"
done
echo ""
echo "=== Cobalt Strike default User-Agent (IE7/XP) ==="
tshark -r "$PCAP" -n \
-Y "http.user_agent contains \"MSIE 7.0\"" \
-T fields \
-E separator="\t" \
-e frame.time_epoch -e ip.src -e ip.dst \
-e http.user_agent -e http.request.uri
echo ""
echo "=== Stager download indicators (200-400KB HTTP GET response) ==="
tshark -r "$PCAP" -n \
-Y "http.response.code==200 and http.content_length >= 200000 and http.content_length <= 500000" \
-T fields \
-E separator="\t" \
-e frame.time_epoch -e ip.dst -e ip.src \
-e http.content_length -e http.request.uri
echo ""
echo "=== Cobalt Strike SMB pipe names ==="
tshark -r "$PCAP" -n \
-Y "smb2.filename matches \"MSSE-[0-9a-f]+-server\"" \
-T fields \
-E separator="\t" \
-e frame.time_epoch -e ip.src -e ip.dst -e smb2.filename
echo ""
echo "=== Consistent small POST bodies (C2 metadata) ==="
tshark -r "$PCAP" -n \
-Y "http.request.method==POST" \
-T fields \
-E separator="\t" \
-e frame.time_epoch -e ip.src -e ip.dst \
-e http.request.uri -e http.content_length \
| awk -F'\t' 'NF>=5 && $5>100 && $5<300 {print}' | head -30
Malleable C2 Profiles — What Changes, What Stays
| Indicator | Malleable C2 Can Change | Cannot Change / Residual |
|---|---|---|
| User-Agent | Yes — any string | Consistent across all sessions |
| URI paths | Yes — any path pattern | Pattern consistency across sessions |
| HTTP headers | Yes — add/remove headers | Java TLS fingerprint more resistant to change |
| JA3 hash | Partially — cipher order adjustable | Java TLS base fingerprint differs from NSS/BoringSSL |
| Beacon timing | Yes — sleep + jitter | Timer drift over days produces detectable pattern |
| Response size | Yes — padding configurable | Empty check-in response has minimum size |
| Pipe names | Yes — custom pipe names | Pipe over SMB IPC$ pattern remains |
| JARM hash | No (without redirector) | Java TLS stack's JARM is distinctive |
| Staging URL | Yes — custom stager | Large shellcode download (200-400KB) is visible |
Any single Cobalt Strike indicator — a suspicious JA3, a regular beacon interval, a POST to /submit.php — could be a false positive from legitimate software. Detection confidence comes from corroboration: how many independent indicators point to the same conclusion? A connection with CV = 0.003 (extremely regular), a Java TLS JA3 hash, a User-Agent for an ancient browser version, and URIs matching the CS default stager pattern — four independent indicators pointing to the same source IP and destination IP — is a confirmed Cobalt Strike beacon. Build your detection logic to accumulate evidence: score each indicator separately and alert when the cumulative score exceeds a threshold, rather than alerting on any single indicator.
Q & A
Q: The operator is using a Malleable C2 profile that mimics Microsoft Windows Update traffic. How do I detect it?
Mimicking Windows Update is a sophisticated but detectable approach. Vectors: (1) Destination IP/domain: Windows Update uses a specific set of Microsoft IPs and domains (windowsupdate.com, update.microsoft.com). A Cobalt Strike implant can mimic the URI patterns and User-Agent of Windows Update but must use a different IP/domain (the attacker doesn't control Microsoft's servers). Comparing the destination to known Microsoft IP ranges will expose the fake. (2) Certificate CN: Windows Update servers present certificates with Microsoft-issued certificates. An attacker's server will present a different certificate — either self-signed, a different CA, or a lookalike domain cert. (3) Traffic pattern**: real Windows Update downloads happen after user-initiated checks or scheduled triggers and involve large downloads (hundreds of MB). A beacon checking in every 60 seconds with 150-byte POST bodies is not Windows Update, even if the URI looks right. (4) Frequency: Windows Update checks happen roughly daily, not every 60 seconds. The beacon interval is the most reliable discriminator once you have a clean baseline of real Windows Update timing on your network.