Malware traffic

Cobalt Strike beacon traffic: a TLS check-in every 20.4 seconds

Updated

File
cobalt_strike.pcapng
Format
pcapng, ether
Size
975.66 KB
Packets
1754
Duration
2 min 33 s
Captured (UTC)
2023-12-18 13:56:07
SHA-256
e8a26f4aede779fcb11135a7b7678a961eae00045bb06e6e82d172e3f533c26f

Most of the 1,754 packets in this Cobalt Strike beacon PCAP are a freshly booted Windows 10 machine doing ordinary Windows things: fetching certificate trust lists, talking to the Microsoft Store, signing in to login.live.com. One destination has nothing to do with Microsoft. At 7.9 seconds into the 153-second run the host resolves mail.googlesmail.xyz through Google's public resolver and opens a TLS connection to 104.21.88.185 on TCP port 2096, a Cloudflare edge address. Then it keeps coming back, every 20.4 seconds, until the sandbox stops recording.

The file is the network capture of a Recorded Future Triage run from 18 December 2023, report 231218-q8n44acdg8, where a 13 KB Windows executable was classified as Cobalt Strike. Netresec used the same run for a write-up on hunting Cobalt Strike in packet captures. This report reads it in the order an incident responder would: the destination that does not belong, the rhythm of its check-ins, and what the TLS keys stored inside this pcapng reveal once Wireshark applies them.

SSL/TLS view of the Cobalt Strike beacon PCAP in A-Packets. The Client Hello tab shows mail.googlesmail.xyz in the session to 104.21.88.185:2096.
The Client Hello tab shows mail.googlesmail.xyz in the session to 104.21.88.185:2096.

Where the Cobalt Strike beacon sits among Windows background traffic

Twenty-five TLS sessions appear in the capture. A-Packets groups them by server, server name and leaf certificate, which collapses them into eleven rows. Nine rows are Microsoft: arc.msn.com, displaycatalog.mp.microsoft.com, slscr.update.microsoft.com and their siblings, each with a certificate issued by a Microsoft Azure or Microsoft Update CA, or by DigiCert for login.live.com. The remaining two rows are the same beacon. Five of its nine sessions carry a full handshake with a certificate for googlesmail.xyz issued by Google Trust Services (GTS CA 1P5), valid from 8 December 2023 to 7 March 2024. The other four resumed an earlier TLS session, so the server sent no certificate and they land in a row of their own. The certificate was issued ten days before the run, for a name built to read like Google Mail at a glance.

Server nameServerVersion and cipherCertificate
arc.msn.com x220.31.169.57:443TLS 1.2TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384ALPN h2CN=arc.msn.com, O=Microsoft Corporation, L=Redmond, ST=WA, C=US expiredissued by CN=Microsoft Azure TLS Issuing CA 05, O=Microsoft Corporation, C=USvalid 2023-05-01 to 2024-04-25
displaycatalog.mp.microsoft.com x220.82.154.241:443TLS 1.2TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384ALPN h2CN=frontdoor-consumer.bigcatalog.commerce.microsoft.com, O=Microsoft Corporation, L=Redmond, ST=WA, C=US expiredissued by CN=Microsoft Azure TLS Issuing CA 01, O=Microsoft Corporation, C=USvalid 2023-12-16 to 2024-06-27
fe3cr.delivery.mp.microsoft.com13.85.23.206:443TLS 1.2TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384ALPN h2CN=fe3cr.delivery.mp.microsoft.com, O=Microsoft, OU=DSP, L=Redmond, ST=WA, C=US expiredissued by CN=Microsoft ECC Update Secure Server CA 2.1, O=Microsoft Corporation, L=Redmond, ST=Washington, C=USvalid 2023-04-04 to 2024-04-04
licensing.mp.microsoft.com x220.123.104.105:443TLS 1.2TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256ALPN h2CN=licensing.md.mp.microsoft.com, O=Microsoft Corporation, L=Redmond, ST=WA, C=US expiredissued by CN=Microsoft Azure RSA TLS Issuing CA 04, O=Microsoft Corporation, C=USvalid 2023-12-01 to 2024-11-25
login.live.com x320.190.181.2:443TLS 1.2TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384ALPN h2, http/1.1CN=login.live.com, O=Microsoft Corporation, L=Redmond, ST=Washington, C=US expiredissued by CN=DigiCert SHA2 Secure Server CA, O=DigiCert Inc, C=USvalid 2023-09-30 to 2024-09-30
mail.googlesmail.xyz x5104.21.88.185:2096TLS 1.2TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256CN=googlesmail.xyz expiredissued by CN=GTS CA 1P5, O=Google Trust Services LLC, C=USvalid 2023-12-08 to 2024-03-07
mail.googlesmail.xyz x4104.21.88.185:2096TLS 1.2TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
nexusrules.officeapps.live.com52.111.227.13:443TLS 1.2TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384ALPN h2CN=nexusrules.officeapps.live.com, O=Microsoft Corporation, L=Redmond, ST=WA, C=US expiredissued by CN=Microsoft Azure RSA TLS Issuing CA 03, O=Microsoft Corporation, C=USvalid 2023-12-03 to 2024-11-27
self.events.data.microsoft.com20.50.73.13:443TLS 1.2TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384CN=*.events.data.microsoft.com, O=Microsoft Corporation, L=Redmond, ST=WA, C=US expiredissued by CN=Microsoft Azure RSA TLS Issuing CA 04, O=Microsoft Corporation, C=USvalid 2023-10-02 to 2024-09-26
slscr.update.microsoft.com x352.165.165.26:443TLS 1.2TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384ALPN h2CN=slscr.update.microsoft.com, O=Microsoft, OU=DSP, L=Redmond, ST=WA, C=US expiredissued by CN=Microsoft ECC Update Secure Server CA 2.1, O=Microsoft Corporation, L=Redmond, ST=Washington, C=USvalid 2023-04-04 to 2024-04-04
storesdk.dsx.mp.microsoft.com96.16.110.41:443TLS 1.2TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384CN=sfdataservice.microsoft.com, O=Microsoft Corporation, L=Redmond, ST=WA, C=US expiredissued by CN=Microsoft Azure ECC TLS Issuing CA 02, O=Microsoft Corporation, C=USvalid 2023-03-28 to 2024-03-22

Open the SSL/TLS view β†’

The port is the second tell. Every Microsoft session goes to 443; the beacon goes to 2096, one of the alternative HTTPS ports Cloudflare documents as proxied alongside 443 (2053, 2083, 2087, 2096 and 8443). An operator who fronts a team server with Cloudflare can pick any of them, and egress rules or TLS inspection policies written only for 443 never see the traffic. The Open Ports view lists the server as 2096 nbx-dir, the IANA service name registered for that number, a useful reminder that a port label says nothing about what actually answers on it.

Volume points the same way. In the Connections view the busiest pair in the whole capture is not Windows Update but the beacon's server: 296 KB came down from 104.21.88.185 against 9 KB sent to it. Almost all of that arrived in the first connection, opened at 7.9 seconds and held for 21 seconds, before any regular check-in began. The 173 KB download from Microsoft's delivery network at 88.221.135.217 (dl.delivery.mp.microsoft.com) comes second.

FromToBytes
πŸ‡ΊπŸ‡Έ 104.21.88.18510.127.0.72296.41 KB
88.221.135.21710.127.0.72172.71 KB
πŸ‡ΊπŸ‡Έ 20.82.154.24110.127.0.7280.76 KB
πŸ‡ΊπŸ‡Έ 52.165.165.2610.127.0.7260.85 KB
πŸ‡ΈπŸ‡ͺ 20.190.181.210.127.0.7247.4 KB
πŸ‡ΊπŸ‡Έ 20.31.169.5710.127.0.7218.21 KB

29 more pairs in the interactive report.

Open the Connections view β†’

Nine TLS sessions, one every 20.4 seconds

Encryption hides what a beacon says, not when it speaks. Lining up the nine connections to port 2096 by their first SYN gives an almost metronomic series. Wireshark can reproduce it with the display filter ip.dst == 104.21.88.185 && tcp.flags.syn == 1 && tcp.flags.ack == 0, which matches ten packets: nine connection attempts and one SYN the host sent again a second later when the eighth attempt went unanswered.

Cobalt Strike timeline: four successive check-ins to port 2096 at 8.60, 29.03, 49.48 and 69.92 seconds.
Cobalt Strike timeline: four successive check-ins to port 2096 at 8.60, 29.03, 49.48 and 69.92 seconds.
ConnectionFirst SYNGapRole
17.93 sInitial download, 266 KB of payload over 21 s
28.60 s0.67 sFirst check-in
329.03 s20.43 sCheck-in
449.48 s20.45 sCheck-in
569.92 s20.44 sCheck-in
690.37 s20.45 sCheck-in
7110.78 s20.41 sCheck-in
8131.28 s20.50 sCheck-in, SYN retransmitted at 132.29 s
9153.01 s21.73 sCheck-in, the last before the run ends

The gap is 20.4 seconds rather than 20 because the sleep timer starts when a check-in ends, not when it begins. Measured from the arrival of each server response to the next SYN, the interval is 20.01 seconds every time, which matches the 20,000 ms sleep and zero jitter in the configuration Triage extracted from the beacon. The ninth gap is longer only because the eighth check-in finished late, after its retransmitted SYN.

Why two beacon sockets are always open

The host never closes a connection when its check-in ends. It keeps the last two open and, a few milliseconds before each new SYN, sends a FIN on the oldest one: the first connection is closed at 29.027 seconds, two milliseconds before the third opens, and the pattern repeats to the end. That is why most connections last about 41 seconds, two cycles, and why two sockets to port 2096 are alive at any moment. The detail matters for detection. A rule that counts concurrent connections to one destination sees an unremarkable pair of idle sessions; a rule that measures the spacing of new connections sees a heartbeat. Hunting tools built for this, such as the beacon analysis in RITA over Zeek conn.log data, score exactly that regularity. With no jitter, this short capture already gives six intervals within a tenth of a second of each other; jitter would spread them and need a longer window for the same score.

The A-Packets timeline is not the right instrument here. It stacks payload bytes per two-second column, and the regular check-ins move only 1 to 8 KB each, which disappears next to the 266 KB first download and the Microsoft download at 41 seconds. Periodicity shows up in connection start times, so that is where to measure it.

What the TLS keys inside the pcapng reveal

This pcapng carries a Decryption Secrets Block: the sandbox recorded the TLS session secrets next to the packets. Opened in Wireshark, the file decrypts itself, and the beacon's HTTP becomes readable. A-Packets does not apply embedded secrets, so its report stops at the handshake: server name, certificate, version and the cipher, which here is TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 under TLS 1.2. What follows comes from Wireshark on the same file, and it explains the shape seen above.

tshark -r cobalt_strike.pcapng -Y 'http.request && ip.dst == 104.21.88.185' \
  -T fields -e frame.number -e frame.time_relative -e http.request.method -e http.request.uri

The first request, frame 44 at 8.05 seconds, is GET /home/indexs, and the 266 KB that follow on that connection are the largest transfer in the capture. The eight later requests, from frame 581 to frame 1751, are all GET /api/upload/image with the user agent of Chrome 106 on Windows 10 and a cookie of the form SESSIONID=AEJUL24M234L13I0C814KZKLIOZLLAWE;data=…. The SESSIONID value never changes: it is a constant from the Malleable C2 profile, chosen to look like a web session. The data part is the beacon's encrypted metadata, which is how the team server recognises the host on every check-in. Every poll gets the same answer: status 200, 263 bytes, application/*; charset=utf-8, served through Cloudflare.

A reply that never changes size means the team server had nothing queued. The configuration that Triage extracted also names a POST method and a third URI, /web/upload/image, for task output, and neither appears anywhere in the traffic: no output went back during these 153 seconds. What the capture documents is the moment a host became controllable, a 13 KB first stage followed by a first download large enough to carry the full beacon, then a heartbeat waiting for an operator. The decrypted HTTP still holds the payload in the profile's own encoding, so the bytes of that first response are best confirmed from the sandbox memory dumps.

The embedded secrets also change how the file must be handled. A pcapng with a Decryption Secrets Block is as sensitive as the traffic it can decrypt: keep the original and its SHA-256 under restricted access, and share a copy saved without the block when only the metadata is needed. For this published sandbox run the keys are deliberate; in an incident capture they deserve a line in the handover.

What A-Packets shows without the keys

On a capture without secrets, which is the normal case for traffic taken off a SPAN port or a firewall, the evidence is the set already covered: a server name that imitates Google Mail, a fresh certificate from a public CA, an unusual HTTPS port, a byte profile dominated by one external host, and a connection rhythm with a fixed period. The HTTP view of this report shows only the cleartext Microsoft requests to ctldl.windowsupdate.com, go.microsoft.com and ocsp.digicert.com; the beacon never touched plain HTTP. If your own capture comes with an SSLKEYLOGFILE or embedded secrets, open it in Wireshark for the payloads and use the report for the map of everything else.

Hunting and containing this beacon

Block and hunt by name rather than address. 104.21.88.185 and 172.67.152.42, the two answers the DNS lookup returned, are shared Cloudflare anycast addresses that front thousands of unrelated sites, so an IP block causes collateral damage and is trivial for the operator to sidestep. The server name mail.googlesmail.xyz in TLS Client Hello messages, DNS queries for googlesmail.xyz and its subdomains, and outbound TLS to port 2096 from workstations are the indicators that carry over. Where proxy or EDR telemetry exposes HTTP after decryption, the constant SESSIONID=AEJUL24M234L13I0C814KZKLIOZLLAWE cookie and requests to /api/upload/image on that host identify this profile directly.

In MITRE ATT&CK terms the check-ins are T1071.001, Application Layer Protocol over web protocols, inside ordinary TLS 1.2. The use of 2096 instead of 443 is T1571, Non-Standard Port, and the large first download fits T1105, Ingress Tool Transfer. Treat a host that beacons this way as one an operator can reach at any moment. Isolate it, take a memory image before rebooting, since a staged beacon runs from memory and is gone after a restart, and search the rest of the fleet for the same server name over the days before the first sighting.

The same reading works on traffic from your own network. Upload a PCAP for a public analysis, or keep captures from real incidents out of the public feed with one of the private plans. For the wider picture of what encrypted traffic still gives away, see how to analyze encrypted traffic in PCAP files; the SHub Stealer report is a macOS case where the payloads stay sealed and the server names carry the investigation.

More sample reports