ClickFix - EtherHiding - Jpeg Stego - AiTM proxy
Six layers deep: ClickFix to browser-in-the-middle
A compromised UK trade-association site, a config fetched from the Base blockchain, and a fake CAPTCHA get one line into the victim’s Run box. Six decoding layers later - including a payload hidden in the brightness of a JPEG - the chain lands a local TLS proxy that puts the attacker inside the victim’s Chrome session.


Most ClickFix write-ups stop at the clipboard. That is a shame, because the interesting engineering is downstream. This chain is worth walking end to end for three reasons: the operator moved their staging config onto a blockchain so it cannot be taken down, they hid the executable payload in a JPEG using a trick specifically designed to survive lossy recompression, and the final implant is not a commodity stealer at all - it is a persistent man-in-the-middle proxy for the victim’s own browser.

Compromised site → blockchain config → clipboard
The analysed object is http[:]//www[.]saema[.]org/ - a legitimate UK trade association running WordPress with a heavy plugin load (Elementor, Master Slider, FooBox, Complianz, Essential Addons). Nothing in the page itself is malicious in a way that survives inspection, which is the point.
Injected JavaScript on the compromised site reaches out to mainnet.base.org, the public RPC endpoint for Coinbase’s Base L2. This is EtherHiding: the next-stage configuration is stored in a smart contract’s read-only state and pulled with a standard eth_call.
Stage-1 - Clickfix Command
mshta.exe is a signed Microsoft binary that will happily execute remote HTA content, giving the operator script execution without writing a file to disk. From there the chain moves to PowerShell — launched hidden, with profile loading suppressed, execution policy bypassed, and the command Base64-encoded. A scheduled task is registered in the same window, which is what re-launches PowerShell in the observed process tree.
Process Tree

One of the uploaded artefacts is the residue of that csc.exe invocation: a 261-byte C# file left in %TEMP% by PowerShell’s Add-Type. It declares two P/Invokes - GetConsoleWindow from kernel32 and ShowWindow from user32. The malware compiles a tiny helper class purely to hide its own console window from the user who just pasted the command.
Stage-2 Arithmetic obfuscation, and how to collapse it
The stage-2 script pulled from gpuh.gravityzone[.]army is 4.3 MB of this:
$NO1IO70TjUdgO = 650 $uLWVV5YhxzdUNXx = ((($bS0dF9ABPHBASPtkPzd4RvQu+8)-($ea2pCWelb11xG) +($ea2pCWelb11xG)-8)+(((($issRF7NJk+10)-($VOZn58aEGLDu4Zp9yZ) +($VOZn58aEGLDu4Zp9yZ)-10)+(10)))+((39))+($SoMx5uv535)-($SoMx5uv535)+47-47)
Roughly 7,100 of the first lines are pure noise: integer assignments wrapped in self-cancelling arithmetic. Note the pattern +($X)-($X) and +47-47 —-these terms always sum to zero regardless of what $X holds. They exist to defeat naive string-matching and to make the file expensive to read.
The payload is assembled from those integers with character casts:

It resolves 1,626 variables and yields 450 distinct strings. Most are decoys — deliberate four-character garbage like 'k'qH' and 'zY\2[Gvi('. A handful are not:
SPlO0IY8JKHJIUqcYZ = 'AMSI_RESULT_NOT_DETECTED' Ku6cmFucZLsDXLiD4 = 'System.Threading.ManualResetEvent' R52bv8v5JC6 = 'new' YUX2G7c8V5Eel0F2f6mfIOs = 'WaitOne' gBx18jmTO1RSFQmcvVWtjF = 'Dispose'
The execution tail
Past the noise, the last few lines do the actual work: a 49,324-element byte array holding Base64 text, then a fully reflective invocation where every method name is a variable. Resolved, it reduces to:

One detail worth flagging: XOR is not written as -bxor. It is expressed as (a -bor b) -band -bnot (a -band b), which is algebraically identical but evades signatures keyed on the obvious operator. That identity is itself a decent hunting string.
Decoding gives 36,991 bytes of the next layer.
Stage-3 - A stream cipher that rewrites its own key
It opens with Set-StrictMode -Version 2 — a small piece of tradecraft that causes the script to fail loudly if an analyst’s tampering leaves a variable undefined. It carries a 32-byte key and 7,065 ciphertext bytes, and applies two passes.
Pass one is a byte-wise subtraction: (b − 40 + 256) mod 256.
Pass two is where it gets interesting. It is a repeating-key XOR, except the key mutates as it goes — after each byte, the key slot used is incremented by the pre-XOR plaintext byte:
The consequence is that the keystream never repeats and the ciphertext shows no periodicity, so classic repeating-XOR key-recovery (Kasiski, index of coincidence, frequency analysis per key position) fails outright. It is not cryptographically strong — the key is sitting right there in the script — but it defeats automated deobfuscators that only know how to solve fixed-key XOR.
Forty bytes of junk are stripped from the front and ten from the end - alignment padding to break naive header-matching - and the remaining 7,015 bytes are passed to [scriptblock]::create(). Two Thread.Sleep calls of 54 ms and 78 ms bracket the transition; odd, non-round intervals like these are typical of sandbox-timing heuristics.
Stage-4 - Hiding the payload in the brightness of an image
Layer Four is the most technically considered part of the chain. It does two things: it fetches over HTTP without touching any .NET networking class, and it recovers its payload from the pixel data of a JPEG.

Bypassing the managed network stack
Rather than Invoke-WebRequest or System.Net.WebClient, the loader builds delegate types at runtime via AppDomain.DefineDynamicAssembly, resolves Microsoft.Win32.UnsafeNativeMethods out of the already-loaded System.dll, and uses its GetModuleHandle/GetProcAddress pair to bind raw WinINet exports:

This matters operationally: EDR products that hook managed HTTP entry points or rely on System.Net ETW events see nothing. The traffic still leaves the box, so network telemetry remains the reliable observation point.
The carrier
The fetched object is a genuine, structurally valid JPEG hosted on i.ibb.co (ImgBB, a free public image host) at 5608×5608 pixels. It opens in any viewer. There is no appended data after the EOI marker and no anomalous APP segment - the payload is in the picture itself.
The extraction routine divides the image into a 701×701 grid of 8×8 pixel blocks. For each block it averages all three channels across all 64 pixels and rounds to a single byte. That one byte is the hidden data; the block’s visual content is irrelevant.

The extracted byte stream is framed with a four-byte big-endian length, then XORed with a key that is not stored directly — it is derived at runtime by XORing two hardcoded 32-byte arrays together. The result is printable ASCII, which suggests the operator generated it as a passphrase and split it to keep the string out of the binary. Gunzipping the result yields 1.77 MB of PowerShell.
Stage-5 - The stealer: classic injection, nothing clever
After all that engineering, the injector is unremarkable. The same reflective-delegate pattern resolves three kernel32 exports, decrypts a blob with a 16-byte repeating XOR key, and runs it:

The decrypted shellcode is 353,181 bytes with a Shannon entropy of 7.999 — still encrypted internally. It opens with a relative call jumping 0x55A18 bytes forward, past the data, into a loader stub whose prologue is the register-save-and-frame sequence characteristic of Donut- and sRDI-style converters. In other words: a packed .NET or PE module wrapped for in-memory execution.
Behaviourally the sandbox is unambiguous about what it does. Under T1552.001 alone it recorded 80 detections: browser credential theft, password-manager access, crypto-wallet files, 2FA artefacts, FTP and VPN configuration, messenger and email data. Combined with T1555.003 and T1217, this is a broad-spectrum commodity infostealer. Collection is exfiltrated to ultraspeed[.]pro/collect.
Stage-6 - GhostPipe: a browser-in-the-middle that lives in your Chrome profile
The second payload is a different class of tool entirely. It is not obfuscated at all - it ships with a comment header, a -Reset uninstall switch, and a rollback manifest. This is maintained tooling, written by someone who expected to support it.
GhostPipe does not steal a credential database. It installs itself between Chrome and the network, which means it sees post-authentication session state: cookies, bearer tokens, and anything typed into a page it is proxying - regardless of MFA.
**How the hook is installed
**The implant lives in %LOCALAPPDATA%\Google\Update — a real Google updater path, chosen so its files do not look out of place. Installation has five moving parts:
1 - A self-signed certificate for accounts.google.com and *.google.com, RSA-2048, one-year validity, friendly name Chrome Update Service.
2 - Root trust, installed via registry. The script writes the certificate blob straight to HKLM\SOFTWARE\Microsoft\SystemCertificates\Root\Certificates. Its own comments explain why: X509Store.Add() raises a user consent dialog, and a direct registry write does not. This requires administrator rights.
3 - A launcher shim. bgsvc.vbs reads a flag string from prefs.dat, appends it to the real chrome.exe path, and forwards any arguments. The critical flag is --host-resolver-rules="MAP accounts.google.com 127.0.0.1:8443", supported by --disable-quic (QUIC would bypass the proxy) and --disable-features=CertificateTransparencyEnforcement,ServiceWorker,… (CT would reject a certificate with no SCT).
4 - Launch hijack. Chrome shortcuts are rewritten to point at wscript.exe running the shim, and HKCU\…\App Paths\chrome.exe is redirected the same way — so Win+R → chrome is also captured. Originals are snapshotted to shortcuts-backup.json for clean rollback.
5 - Cache invalidation. Chrome’s TransportSecurity and Network Persistent State files and the Service Worker, Cache and Code Cache directories are deleted. This clears pinned HSTS state and any service worker that would otherwise serve cached content and bypass the proxy.
The proxy logic Once running, GhostPipe classifies each request rather than blindly forwarding it. It reads Sec-Fetch-Dest and Accept to decide the upstream target: navigation requests (Sec-Fetch-Dest: document, or an Accept containing text/html) go to the attacker’s phishing path, while static assets — anything under /_next/, /static/, /assets/, or with an extension like .js, .css, .woff2 — are proxied to the genuine origin.
The effect is a phishing page that loads Google’s real stylesheets, fonts and scripts from Google’s real servers, at Google’s real domain, behind a valid padlock. There is no lookalike domain for a user to notice and none for a defender to blocklist.
Supporting machinery includes a per-user mutex derived from a SHA-256 of the user SID, health checks against the listening port, respawn logic, and a full uninstall path driven by an .own manifest that records every artefact created so it can be reversed cleanly. The code is defensive about not clobbering pre-existing state.
Highest-value detection signals
explorer.exe → mshta.exe
Process telemetry
Near-zero legitimate frequency. Parent explorer.exe means the Run dialog. This is the single best ClickFix detection.
RunMRU contains mshta / powershell / curl
HKCU registry
…\Explorer\RunMRU preserves what was typed or pasted.
New root CA in HKLM SystemCertificates
Registry audit
Direct writes to \Root\Certificates outside of MSI or GPO. Alert on any addition.
chrome.exe with --host-resolver-rules
Command line
Effectively never legitimate on a user endpoint. Same for --disable-features naming CertificateTransparencyEnforcement.
chrome.exe parented by wscript.exe
Process telemetry
Browsers are launched by explorer.exe. A script host parent is the launch hijack.
powershell.exe listening on a TCP port
Network telemetry
PowerShell binding a local socket, especially 8443, is anomalous on a workstation.
Run key value referencing wscript + LOCALAPPDATA
Autoruns
GoogleUpdateSync is not a real Google Update entry.
DNS to mainnet.base.org or other base rpc blockchains from a browser
DNS logs
Benign on developer machines; strong when correlated with a subsequent mshta launch.
csc.exe parented by powershell.exe
Process telemetry
Inline Add-Type. Noisy alone, valuable in a chain.