All Security Research
Security ResearchClickFixWordPressOverlayWarGuardFrame

A ClickFix loader rewritten to get past a rival's page cleaner

Two ClickFix kits fought over the same screen on hacked WordPress sites. One wrote the fight into its code, in Russian, down to the moment the browser broke.

Kirk | ADAMnetworks••Updated
Two fake CAPTCHA checkbox panels overlapping in a browser window, with arrows between them and a clipboard holding a command prompt below

Key takeaways

  • A ClickFix kit we track as GuardFrame installs as a rogue WordPress plugin and was rewritten to get past a rival kit’s page cleaner. We captured it on 33 hacked sites between 24 September and 14 October 2026.
  • The rival, which we track as OverlayWar, ships a cleaner that deletes any full-screen overlay without a trusted name. The cleaner ran on 526 sites between 7 September and 14 October 2026, beside a lure loaded through Polygon smart contracts.
  • GuardFrame borrows two names the cleaner trusts, hides its panel one level below where the cleaner looks, and suppresses rival screens with styles only. Its first version, which deleted rivals, set off 3,372 page changes in a single page load.
  • A code comment quotes an operator’s complaint about the overlay failing on first visits, and debug logging left on in every copy narrates the fight to the browser console.
  • The overlay frames a Windows and macOS ClickFix lure from 27-april-tag[.]com, branded with each hacked site’s own logo. It removes itself on any failure, so site owners see a working site while visitors get a lure they cannot close.

Background

A ClickFix lure is a fake check, usually dressed as a CAPTCHA. It tells the visitor to open PowerShell or Terminal, paste a command, and press Enter. The command then fetches and runs the next stage. Only one lure can hold the screen, and that is the one whose command gets pasted.

Hacked pages often carry more than one kit. Earlier research on derp.ca covering 1,509 compromised WordPress sites found one page carrying four separate ClickFix script blocks. The kits in this post compete for the same visitors on the same pages:

Name What it is Seen
GuardFrame A rogue WordPress plugin that injects wp-security.js, a “Security Guard” script, and an iframe holding a ClickFix lure 33 sites, 24 September to 14 October 2026
OverlayWar A cleaner script, settings object CFG_CONFIG, that protects a ClickFix lure loaded through Polygon smart contracts 526 sites, 7 September to 14 October 2026
TimberCheck The Windows and macOS lure that GuardFrame frames, served by 27-april-tag[.]com Inside GuardFrame’s iframe

GuardFrame’s author studied the cleaner’s rules, found a way to pass as one of its own, and then wrote a guard to clear rival lures off the page. The code comments say the first version of that guard set off “a war of observers”. Figure 1 shows how GuardFrame passes each of the cleaner’s checks.

Two columns of four boxes. Left: the OverlayWar cleaner’s trusted-name check, MutationObserver, 150 ms sweep, and click handler. Right: GuardFrame’s borrowed names, late class change, nested panel, and click pass-through, each answering the check beside it.

Figure 1: OverlayWar’s cleaner checks and the GuardFrame technique that passes each one.

The author was writing for a customer. One comment quotes an operator’s complaint, translated from Russian: “first visit - empty, reload - it’s there”. The fix was to start the overlay as soon as the page body exists, instead of waiting for the page’s load event. Rival scripts on the same pages were holding that event back by 10 to 30 seconds on a first visit.

Everything below comes from page bodies and scripts collected by our scanners from hacked WordPress sites, matched against our YARA rules. Figure 2 shows the comments in which GuardFrame’s author describes the problem.

Russian comments naming CFG_CONFIG and describing the cancelled iframe requests and the class-change workaround

Figure 2: Comments in the GuardFrame loader, collected 29 September 2026. Comments in every figure are translated from Russian beneath it.

PRE-ARM: the positioning class (.wp-security-root - fixed, z, size) is NOT applied yet.

On rigquip, someone else's "cleaner" (CFG_CONFIG) checks EVERY added DOM node immediately (MutationObserver, microtask), and body.children every 150 ms (a sweep): "fixed + z >= 9990 + >= 90% of the screen" = "foreign overlay" -> display:none !important + removeChild.

The root, when added with its positioning class, was killed before the iframe finished navigating (in Network: canceled at 3 ms / 74 ms).

Therefore: a neutral class (display:none), a neutral wrapper (not a direct child of body - the sweep does not see it), and the class is applied in a macrotask AFTER their microtask. Their observer watches only childList; it is blind to class changes.

Initial access: a rogue plugin built to survive caching

GuardFrame installs as a WordPress plugin with a randomized name in the pattern wp-<word>-<word>-<4 characters>, such as wp-security-optimizer-bncm or wp-cache-shield-jv4t. We have seen 25 names. The plugin adds four parts to every page:

  • A stylesheet, assets/css/wp-security.css.
  • An inline “Security Guard” script in the page head.
  • An inline settings object in the footer, with a randomized global name.
  • The loader, assets/js/wp-security.js, tagged ver=1.2.2- plus a four-character suffix.

The loader is 26,828 bytes and the guard 14,889 bytes. Both are identical on every site apart from the settings object’s name.

The page HTML holds only the static settings. The loader builds the iframe and generates a 16-character one-time token in the browser with crypto.getRandomValues. A comment explains why: full-page caches such as LiteSpeed would otherwise freeze one token into the cached page for every visitor. The iframe also carries the data-no-lazy, data-skip-lazy, and data-lazyloaded attributes, which lazy-loading plugins treat as instructions to leave an element alone.

The iframe points to 27-april-tag[.]com and passes the token, the site’s host name, a timestamp, and the site’s own WordPress icon as the logo for the lure.

Technical analysis

OverlayWar’s cleaner deletes anything shaped like a full-screen overlay

The cleaner’s first check is whether an element carries a trusted name. It joins the element’s ID and class names into one string and matches that string against a short list. In most copies the list holds a random label from the cleaner’s own loading spinner, plus any name containing cfm. A separate skip pattern covers media-modal, elementor, and adminbar. Anything that matches is left alone regardless of its size. We have seen three spinner labels: uS1j3xMa60p9uwhK, l98luimoo7724mcv, and ckrj3vw143eg5hip.

Elements without a trusted name are measured instead. The cleaner acts on an element that is fixed to the browser window, carries a z-index of 9990 or more, and covers at least 90 percent of the window’s width and height. The z-index decides which of two overlapping elements is drawn on top. The cleaner removes the element’s mouse input, hides it with display:none !important, and deletes it from the page. The panel’s text is never examined.

These checks run from three places:

  • A MutationObserver, the browser feature that notifies a script when the page changes, inspects each element as it is added.
  • A timer re-scans the top level of the page body every 150 milliseconds until five seconds after load.
  • A click handler inspects whatever the visitor clicks, along with every element enclosing it.

The observer and the click handler keep running for as long as the page is open. GuardFrame originally inserted its panel with the full-screen styling already applied, and the cleaner removed it before the embedded page finished loading. Figure 3 shows the cleaner’s rules.

Cleanup configuration, trusted-name test, and full-screen panel size test

Figure 3: OverlayWar cleanup code collected 2 October 2026, with whitespace changed for readability. An archived page in the Moncha repository preserves an exact copy.

The cleaner protects OverlayWar’s own lure. Its loader asks a smart contract on the Polygon blockchain for a list of delivery servers, decodes the reply, and fetches /js/all.min.js?m=1 from one of them. The addresses come from the blockchain, and the script comes from an ordinary web server. The same spinner label appears in the cleaner’s settings and in this loader. The earliest page in our collection carrying both is engetrina.com.br on 7 September 2026 at 14:40 UTC, with the cleaner, the loader, the spinner label uS1j3xMa60p9uwhK, and the first contract on one page.

Field Effect described a Polygon-backed loader fetching /js/all.min.js on 24 August 2026, and Unit 42’s 2 July JokerStat research and technical figures show the same getServers lookup.

Once downloaded, the script waits for a click, then sends browser, device, and page details to /first_allow_host, an endpoint ANY.RUN’s 7 September ruleset changelog flags as TerminalFix-related. The server replies with an ID, a command, and a decision on whether to show the lure. An approved Windows visitor gets a full-screen panel with the ID cfm, and the page title changes to “Just a moment…”. Clicking the checkbox copies the command, and the instructions tell the visitor to open PowerShell or Terminal, paste, and press Enter. Every five seconds after that, the script asks /check_allow_host whether to keep the lure up, and a negative answer hides the panel and reloads the page. Gelato’s 28 September analysis documents this controller and the decoder for its delivery addresses.

The cleaner is tied to three Polygon contracts, created on 3 September, 14 September, and 21 September. Each serves its own group of sites over overlapping dates:

Contract Sites with the cleaner First seen
0x91639826046fd209ef96c76cf9089e6dde2d408a 320 2026-09-07
0xb90658783fda2e726c5756f94a461291c0cef691 131 2026-09-16
0xa1cb583820efe60c840498aada3f741ab6869cbe 29 2026-09-22

The removal rules are identical across the three groups. Two retained verification scripts of 69,051 bytes each differ only in two copies of the spinner label.

Twelve copies of the cleaner delete their own CAPTCHA

The cleaner’s exemptions vary between copies. Our scanners recorded it in 560 visits to 526 sites between 7 September and 14 October 2026. In 548 of those visits it trusts cfm. The other 12, on eight sites, keep only the spinner label and the skip pattern.

In those 12, the cleaner’s own lure meets every removal condition once it appears: the cfm panel is fixed, fills the window, and sits at a z-index of 2147483647. The earliest cleaner we hold, from 7 September, is this variant, and copies that trust cfm appear from 8 September. Both variants were still live on 12 October.

Stage 1: GuardFrame borrows two names and hides its panel one level down

GuardFrame’s author dealt with the name check first. The panel’s class list includes og-cfm, which contains cfm, and media-modal, which is on the skip pattern. The panel is exempt in copies of the cleaner that trust cfm and in the 12 that do not.

The size check took more work, because each of the cleaner’s three checks sees a different moment of the panel’s arrival. The loader builds the panel, wp-security-root, inside a plain container, wp-security-shell, that has no positioning and no z-index. At that point the panel carries a neutral class and none of the full-screen styling. When the container is inserted, the 150-millisecond sweep and the observer both see only the plain shell, because neither looks inside an added element.

The full-screen classes are then applied on a zero-delay timer, after the observer has finished. A class change neither adds nor removes an element, so the observer stays quiet. The author’s note reads: “Their observer watches only childList; it is blind to class changes.”

The click handler needed one more step. Until the embedded page confirms it has loaded, the loader sets pointer-events:none on its iframe, so a click falls through to the site underneath and the cleaner, walking up from the clicked element, finds only the site’s own elements. After confirmation, the borrowed names cover the click handler as well.

The loader does not copy the spinner label because, as its comment says, that label is regenerated and “would give away the overlap.” The three labels in our captures bear this out. Figure 4 shows the shell, the delayed class change, and the borrowed names.

Loader comments and code showing the plain wrapper, deferred positioning, and borrowed class names

Figure 4: GuardFrame loader code collected 29 September 2026.

og-cfm: a safeguard against other people's "overlay killers" (CFG_CONFIG and similar): they treat any element whose id/class contains "cfm" as "their own." They will leave our root with this class alone.

Neutral wrapper: the other script's sweep iterates over body.children - the wrapper (position:static, no z) is not an overlay as far as it is concerned, and the sweep does not see the root inside it at all.

Macrotask: by this point, MutationObserver (their CFG_CONFIG) has already processed the "addition" of the neutral nodes (microtask) and found nothing foreign. Changing the class does not create a childList mutation.

We clear pointer-events on the iframe only in arm() - until the signal is confirmed, clicks must pass through to the site (see the comment above).

media-modal: a class from the other "cleaner's" skipRe (CFG_CONFIG.skip = "media-modal|elementor|adminbar", static).

isTrusted() checks id+class for a match against skipRe - our root becomes "one of theirs": the sweep, MutationObserver and click-capture handler no longer touch it.

We do not add the random trusted token (uS1j3xMa60p9uwhK) - it is regenerated and would give away the overlap.

Stage 2: The guard hides rival panels by their shape

Getting past the cleaner left other fake CAPTCHAs on the same sites free to draw over GuardFrame’s panel. The guard script in the page head handles them. Its comments name the targets as foreign loading spinners, cfm-* CAPTCHAs, and other fake Cloudflare-style screens that cover or delete its panel.

The guard matches on shape. Any visible, non-empty panel fixed over at least 90 percent of the window in both directions qualifies if its z-index is 900 or more, a threshold far below the cleaner’s 9990. The guard’s own panel is exempt through its ID and a data-og-self marker that also covers everything inside it.

One comment gives an example: interseq[.]at with the marker data-cfmd-instr, which PhishEye’s 15 September investigation documented along with interseqf[.]com serving fake-verification JavaScript. That component draws its screen inside a shadow DOM, a block of page elements sealed inside one host element, and hiding the host hides everything within it. interseq[.]at was already on a page beside the OverlayWar cleaner on 8 September. A third injection with its own Polygon contract, 0xb29927b7d854b232d5e58cfc201866376e68c676, appears on 14 of the cleaner’s sites, so at least three kits compete on these pages. Figure 5 shows the guard’s targeting rules.

Guard comments describing foreign fake CAPTCHAs and the interseq example

Figure 5: Comments in the GuardFrame guard, collected 30 September 2026, naming the competing screens it suppresses.

Whether the element is a foreign full-screen fixed overlay.

Criteria: position:fixed, z-index >= 900, visible area >= 90% of the viewport on both axes, not empty (has content), and not ours.

Foreign anti-bot overlays (their spinner and the foreign CAPTCHA with cfm-* classes) meet these criteria and are suppressed: these are precisely what we hide. Our own overlay is protected separately - by its id and the data-og-self attribute (isSelf), rather than by the criteria.

Gentle suppression of foreign full-screen layers: styles only, without removeChild. Called only by the timer (boot, 3-second interval, 30-second final pass) - NOT in the MutationObserver callback.

Synchronous mutations in the callback trigger a cascade with the other observer (a war, 3372 mutations on a live site).

A foreign host with a shadow DOM (for example interseq.at data-cfmd-instr) meets the isForeignOverlay criteria: fixed + z MAX_INT + >= 90% of the viewport + non-empty; display:none on it also hides its shadow-DOM contents.

Stage 3: Deleting rivals set off 3,372 page changes

Deleting an element is itself a page change, and other scripts’ observers react to it. The guard’s header says an earlier version, which deleted rival panels, woke the competing observers and produced 3,372 page changes in a single page load on a live site. The comments call this a “war of observers.”

The same loop ran from the other side when the guard put its own panel back inside its observer callback, which “instantly woke the other observer.” The two scripts then cut each other down within a single tick, and the page never got a chance to redraw. The author sums up the result as “the browser broke.”

From version 1.2.3, the guard’s header says, killForeign() only sets styles. Every deployed copy is still tagged 1.2.2 in the plugin’s file version, so the guard’s own version number runs ahead of the plugin’s. The function applies display:none to hide the panel and pointer-events:none to block clicks, both with !important, and never calls removeChild(). The visitor sees the rival screen vanish, and the rival’s observer, which watches only for added and removed elements, has nothing to react to.

The guard’s own work moved onto timers at the same time: once at start, every three seconds after that, and once more at 30 seconds. A comment says the interval used to be one second. Re-adding its own panel now waits 300 milliseconds on a timer, with a 400-millisecond cooldown. Figure 6 shows the suppression function.

The guard’s style-only suppression function and its author’s note on the war of observers

Figure 6: The GuardFrame guard’s suppression function, collected 30 September 2026.

Gentle suppression: styles only, NO removeChild. removeChild wakes up the other MutationObservers (childList) - a war of observers (3372 mutations on a live site). Changing styles wakes only observers watching the style attribute, and the other script's sweep generally does not maintain those. For a foreign host with a shadow DOM, display:none on the light-DOM host hides the contents too - that is enough.

All 77 distinct page bodies we hold with the guard, from 34 visits, use the style-only design. The deletion-based version survives only in the comments.

Stage 4: The guard props its own panel back up on every pass

Each pass forces the guard’s panel back to full screen with display:flex, a fixed position, and a z-index of 2147483647. While the panel is waiting or active, the pass also restores its visibility and opacity, and turns mouse input back on when the panel is meant to block the page. The comment explains the display reset: “the other killer sets display:none !important, and neither visibility nor opacity can override it.”

The guard also moves its shell to the end of the page body on every pass. When two panels share a z-index, the one later in the page is drawn on top, and the comment names interseq’s host, which loads late, as the competitor. Figure 7 shows the restoration code.

The guard marking its own panel, moving its shell last, and restoring display and position

Figure 7: The GuardFrame guard’s restoration code, collected 30 September 2026.

Always reapply display:flex: the other killer sets display:none !important, and neither visibility nor opacity can override it.

Stage 5: Its recovery code watches the wrong level

If another script deletes the panel, the guard is supposed to put it back. Its observer watches only the top level of the page body and checks each removed element for the ID wp-security-root. Live pages record the panel one level down, at body > div.wp-security-shell > div#wp-security-root. Figure 8 shows a page record from 30 September with the panel inside the shell, carrying the borrowed names, the data-og-self marker, and the forced styles.

Saved page tags showing wp-security-root nested inside wp-security-shell, with borrowed names and forced full-screen styles

Figure 8: Page structure recorded 30 September 2026, with the opening tags wrapped for readability.

If the panel itself is deleted, the observer, watching one level too high, never hears about it. If the shell is deleted instead, the observer sees a removal with the wrong ID. If the observer did fire, it would put the panel back directly under the page body, outside the shell that hides it from the cleaner’s sweep.

The file’s own comments disagree on the layout. The observer setup says the panel is “a direct child of body,” while the shell-moving code places it inside the shell and relies on that mismatch, because moving the shell registers as a removal with a different ID and so “the cascade will not start.” The wrapper that gets the panel past the cleaner also stops the guard from reacting to its own moves, and it leaves the recovery code watching a spot the panel never occupies.

Stage 6: The framed lure serves Windows and macOS commands

The page inside GuardFrame’s iframe is a second ClickFix lure, TimberCheck, titled “Just a moment…” with one command for Windows and one for macOS. 27-april-tag[.]com returns an empty 403 response to a direct request and serves the lure only inside the overlay, with a fresh token and the hacked site’s name and logo. The page was rebuilt in early October, from 152 KB on 30 September to 63 KB from 5 October, and kept both commands.

Clicking its checkbox copies a command, and a copy handler substitutes that command whenever the visitor copies anything else on the page. The Windows command fetches and runs remote PowerShell. The macOS command, once its nested encodings are stripped, installs a LaunchAgent configured to start on load and keep running, then reads a Polygon contract for an address and pulls more AppleScript from it. That contract, 0xA3a603F8a454a9c905b4c579Bb72628F7C15C2A0, is the one Have I Been Squatted documented on 6 July, and UnderDefense and Bolster have since traced the same macOS chain arriving through other delivery routes.

The lure and the loader talk through postMessage messages keyed __overlayGate, each carrying the token and a status, and accepted only from https://27-april-tag[.]com. The statuses ready and alive switch the overlay on. empty counts as a failure and removes it, and close removes it. This branch has its own command to keep on screen and runs separately from OverlayWar’s Polygon-loaded cfm controller.

Stage 7: Built to fail quietly and hold the visitor

The overlay starts inactive, and any failure removes it “rather than blocking the site”. Numbered fallbacks in the code cover three cases: the embedded page fails to answer within 10 seconds, the iframe fails to load within eight, or the whole process passes 15. A visitor who fails twice within an hour is marked with a cookie and left alone for 60 minutes. The cookie names default to og_fail and og_suppress and are randomized per site. A broken page would draw the site owner’s attention. A lure that quietly skips a visitor draws none.

Once the lure is showing, the visitor cannot dismiss it. The settings disable closing with Escape and clicking outside the panel, and the page’s scrolling is locked. A handler for the browser’s back-forward cache restarts the overlay with a new token and clears the failure cookies. The comment says the CAPTCHA must be impossible to get rid of “by refreshing or by history navigation”.

Figure 9 shows the overlay’s life cycle.

A flow from start inactive, to loading the lure, to waiting for the lure’s handshake, to armed. Failure paths from loading and waiting lead to a box listing the 8, 10, and 15 second limits and the 60-minute skip. A back or forward navigation from the armed state restarts the overlay.

Figure 9: The GuardFrame overlay’s life cycle. Every failure removes the overlay, and a confirmed lure cannot be dismissed.

The operator keeps an exit. A panicKey setting lets Ctrl+Shift+Q remove the overlay, and window.overlayGateDismiss() does the same.

Stage 8: Debug logging narrates the fight to the console

Every copy we collected ships with "debug":true. A visitor who opens the browser console on an infected page watches the fight in Russian, in lines prefixed [Security] and [Guard]. They report the content confirmed with the overlay active and a foreign overlay displaced with styles. Figure 10 shows a run of the collected scripts.

Browser console lines prefixed Security and Guard: the loader blocks the site, frames the lure, receives its ready handshake and confirms the overlay, then the guard displaces a rival cfm-overlay and the lure keeps sending alive messages until the guard stops watching at 30 seconds

Figure 10: Console output from the GuardFrame loader and guard collected from soleresult[.]com, run in an isolated test page with the network blocked, a local stand-in for the lure’s handshake, and a stand-in rival overlay. Repeated alive messages are grouped.

[Security] markers: og_fail=0, suppression=no
[Security] blocking the site while content loads
[Security] iframe started: https://27-april-tag.com/?og_token=...
[Security] waiting for a signal from the page, limit 10000 ms
[Guard] protection active
[Security] iframe loaded, waiting for a signal
[Security] message received: https://27-april-tag.com {"__overlayGate":1,"token":"...","status":"ready"}
[Security] content confirmed, overlay active
[Guard] foreign overlay displaced (styles): cfm-overlay
[Guard] observation finished (30 s)

Other traces of the author show through. The iframe’s title and the dialog’s accessible label are the Russian phrase for “Additional content”, which screen readers announce on English-language sites. The guard’s header says its functions are written as plain declarations so they can be checked in Node without a browser, in a test script named test_guard.js.

Infrastructure

The hacked sites carrying GuardFrame include technology, news, casino, and Turkish construction company sites. A public scan recorded the loader on soleresult.com on 23 September 2026, and our earliest captures came the next day, from soleresult.com, technologymiles.com, tradingriser.com, and technofee.com. Across 24 September to 14 October, the loader appeared in 26 visits to 25 sites and the guard in 34 visits to 33 sites.

27-april-tag[.]com served three builds of the framed lure, on 30 September, 5 to 8 October on five sites, and 13 October.

OverlayWar’s cfm controllers answer at /first_allow_host. humanwebpage[.]online and webpagesafe[.]online were still active on 15 October 2026, along with interseq[.]at.

Detection opportunities

The plugin and page markers below are stable across every GuardFrame copy we hold:

  • A plugin directory matching wp-<word>-<word>-<4 characters> that serves assets/js/wp-security.js?ver=1.2.2-<suffix> (26,828 bytes) and assets/css/wp-security.css.
  • Inline elements <style id="wp-security-inline-css"> and <script id="wp-security-js-before"> holding a settings object with a contentUrl field.
  • The page structure div.wp-security-shell > div#wp-security-root with the classes og-cfm and media-modal and the attribute data-og-self.
  • Browser console lines prefixed [Security] and [Guard].
  • Window messages keyed __overlayGate, and cookies named og_fail and og_suppress.

OverlayWar’s cleaner is identifiable by a CFG_CONFIG settings object with the skip pattern media-modal|elementor|adminbar beside a Polygon getServers loader that fetches /js/all.min.js?m=1.

For site owners, a plugin directory in the GuardFrame naming pattern that you did not install means the site is compromised.

Indicators of compromise (IOCs)

Network indicators

Indicator Type Context
27-april-tag[.]com Domain GuardFrame content host serving the TimberCheck lure, last seen 13 October 2026
humanwebpage[.]online Domain OverlayWar cfm controller (/first_allow_host), last seen 15 October 2026
webpagesafe[.]online Domain OverlayWar cfm controller, last seen 15 October 2026
simplecheck[.]online Domain OverlayWar cfm controller, last seen 29 September 2026
webpagehelp[.]online Domain OverlayWar cfm controller, last seen 29 September 2026
hi-tuffglass[.]com Domain OverlayWar cfm controller, last seen 3 October 2026
interseq[.]at Domain Rival fake CAPTCHA named in the GuardFrame guard, last seen 15 October 2026
interseqf[.]com Domain Rival fake CAPTCHA, last seen 24 September 2026

Polygon contracts

Indicator Type Context
0x91639826046fd209ef96c76cf9089e6dde2d408a Contract OverlayWar loader, 320 sites, last seen 14 October 2026
0xb90658783fda2e726c5756f94a461291c0cef691 Contract OverlayWar loader, 131 sites, last seen 13 October 2026
0xa1cb583820efe60c840498aada3f741ab6869cbe Contract OverlayWar loader, 29 sites, last seen 9 October 2026
0xA3a603F8a454a9c905b4c579Bb72628F7C15C2A0 Contract macOS stage address lookup in the TimberCheck command

Host artifacts

Indicator Type Context
wp-content/plugins/<name>/assets/js/wp-security.js?ver=1.2.2-<suffix> File path GuardFrame loader
wp-security-optimizer-bncm, wp-cache-shield-jv4t, wp-kit-security-n4a4, wp-guard-core-gdnm, wp-kit-helper-zspg Plugin names Examples from 25 GuardFrame plugin names seen
wp-security-root, wp-security-shell, data-og-self Page markup GuardFrame overlay
__overlayGate postMessage key GuardFrame and TimberCheck handshake

MITRE ATT&CK mapping

Tactic Technique ID Technique Observed behavior
Resource Development T1584.004 Compromise Infrastructure: Server Rogue plugins installed on hacked WordPress sites
Initial Access T1189 Drive-by Compromise Overlay and lure injected into pages visitors browse to
Execution T1204.004 User Execution: Malicious Copy and Paste Visitor pastes the copied command into PowerShell or Terminal
Execution T1059.001 Command and Scripting Interpreter: PowerShell Windows command fetches and runs remote PowerShell
Execution T1059.002 Command and Scripting Interpreter: AppleScript macOS stage pulls and runs AppleScript
Persistence T1543.001 Create or Modify System Process: Launch Agent macOS command installs a LaunchAgent that starts on load
Command and Control T1102.001 Web Service: Dead Drop Resolver Polygon contracts return delivery server addresses

Every stage still needs a network connection

Whichever panel ends up on top, the scripts still have to load from delivery servers, OverlayWar’s branch still has to reach its controller for a command, GuardFrame’s iframe still has to reach 27-april-tag[.]com, and the pasted command still has to download its payload.

ADAMnetworks’ adam:ONE applies Don’t Talk To Strangers (DTTS), which denies IP connections by default unless the destination was first requested through DNS and allowed by policy. Loader, controller, lure, and payload connections to disallowed destinations fail under that rule, whatever the page shows.

The ClickNix browser extension checks page HTML locally against YARA rules for ClickFix content, and its rules match all 26 GuardFrame loader captures and the framed lure page. ClickNix inspects the page in the browser, and DTTS covers the connections that follow it. Our PasteSwitch investigation followed one lure from the browser to the command. This post covers the step before that, where kits compete over whose instructions the visitor reads.

Kirk Bio Picture

About the author

Kirk | ADAMnetworks

Security Researcher

Kirk is a security researcher known for reverse engineering of ransomware, malware and web threats.

Author profile

See it on your own network

Zero Trust Connectivity reduces attack surface 7000:1 by denying egress by default. Talk to us about what that looks like in your environment.