For the complete documentation index, see llms.txt. This page is also available as Markdown.

Another day, another ClickFix

ClickFix on macOS: A Fake Captcha That Ends in a Go-Based AMOS Stealer

Platform: macOS (Intel and Apple Silicon) Vector: fake reCAPTCHA / ClickFix Payload: Go AMOS fork (Poseidon/Odyssey lineage)


Background

ClickFix is social engineering, not an exploit. The attacker convinces the user to run a command themselves, so every control that assumes "the user would not paste a shell command into Terminal" is the control being bypassed.

This writeup walks the full chain end to end, from the injected JavaScript on a compromised website through to the final stealer, and gives you the indicators and hunts to find it in your own environment. The sample set is a Go-compiled stealer from the AMOS family. It injects into Electron wallet apps to phish recovery phrases directly.


The Attack Chain

The campaign runs in three stages, plus a separate telemetry channel that beacons at every step.

  1. Stage 1, the lure. Obfuscated JavaScript injected into a compromised site (WordPress, based on the admin-detection logic) renders a fake reCAPTCHA overlay. It only fires on real Macs. Clicking the checkbox writes a base64-wrapped shell command to the clipboard and shows localised instructions in 14 languages telling the user to open Terminal and paste it.

  2. Stage 2, the loader. A bash script that fingerprints the CPU architecture, pulls the matching binary, strips quarantine, ad-hoc signs it to defeat Gatekeeper, runs it, wipes the last line of shell history, and reports each step home.

  3. Stage 3, the stealer. A Go Mach-O (separate Intel and ARM builds) that dumps the keychain, raids browsers and around seventy wallet products, grabs developer and cloud credentials, and injects JavaScript into Electron wallet apps to steal seed phrases.


Stage 1: The Fake Captcha

The injected script is obfuscated with the standard obfuscator.io string-array rotation. Once decoded, the logic is straightforward.

It gates hard on platform before doing anything:

It also bails out for automation and for logged-in site admins, which is how it stays quiet on compromised hosts:

  • navigator.webdriver, zero navigator.languages, or a headless user agent (HeadlessChrome, PhantomJS, Puppeteer, Selenium) all stop execution.

  • A wp-login.php?action=logout link in the DOM means a logged-in WordPress admin is viewing the page, so it sets a suppression cookie and exits.

  • A ?force=1 parameter overrides the gating, which is how the operators test it.

When the user clicks the checkbox, the script builds a command and writes it to the clipboard. Reconstructed, the inner command is:

That command is then base64-encoded and wrapped so the clipboard string looks like a harmless "reCAPTCHA Verification ID." When pasted into Terminal it decodes and pipes to bash. The SRC_URL variable carries the source site forward to the next stage for affiliate tracking.


Stage 2: The Loader

The downloaded second stage is a bash script, not a binary. It pads itself with dead functions and two base64 blobs that decode to bash no-ops, both designed to waste an analyst's time. The real behaviour:

It picks the payload by architecture:

It defeats Gatekeeper on the downloaded file before running it:

It stages a copy under a name that blends in:

This is a masquerade and staging location, not persistence in itself. No LaunchAgent plist is written here. Persistence is the stealer's job.

It then cleans up after the user:

Every step beacons to a telemetry host that is separate from the download host:

Beacon events seen: loader_start, download_ok, exec_start, exec_ok, exec_fail, download_fail, each carrying a random request id and the source URL.


Stage 3: The Stealer

The final payload is a Go-compiled Mach-O, around 46 MB once you account for the Go runtime. This is not the lightweight C++ Atomic build. It matches the Poseidon/Odyssey branch of the AMOS family: Go compilation, PAM-based password validation, and an Electron injection module.

The imports map the capability cleanly. It links libpam.2.dylib to validate a captured password through PAM, the full SecKeychain set to dump the login keychain, and NSPasteboard to read the clipboard.

What it collects:

Keychain and credentials. Direct access to login.keychain-db via SecKeychainOpen, SecKeychainUnlock, SecKeychainFindGenericPassword, and SecItemCopyMatching, with PAM used to confirm the stolen user password is valid.

Browsers. Chrome, Brave, Edge, Firefox, and Opera GX. Cookies, history, Local Storage, key4.db, login data, autofill, and saved cards.

Wallets. Around seventy browser-extension and desktop wallets, including MetaMask, Phantom, Keplr, Exodus, Atomic, Electrum, Ledger Live, Trezor, OneKey, Coinomi, and many more, plus exchange references for Binance, Kraken, KuCoin, Bitget, Gate.io, Gemini, BitMEX, Phemex, CoinEx, and others.

Developer and cloud credentials. SSH keys, .gnupg, AWS, gcloud, .npmrc, .netrc, Vercel, Netlify, Railway, Heroku, Linode, Hetzner, Docker, .pgpass, rclone, Pulumi, Doppler, Conjur, Stripe, and crypto dev tooling like Foundry, Hardhat, Truffle, and Starkli.

File grabber. Scans user folders for files matching keywords such as seed, privkey, pub_key, keyring, 12words, 24words, api_key, .env, and phrase.

The part worth your attention: Electron wallet seed-phrase injection

This is what lifts the sample above a commodity stealer. It carries a JavaScript payload that it injects into Electron-based wallet desktop apps. The injected code:

  • Forces webPreferences.sandbox = false in the preload so require() works inside the app.

  • Disables the wallet's own auto-updater through its Redux store, for example window.__TREZOR_STORE__.dispatch({ type: '@update/enable', payload: { enabled: false } }), so the tampered app is not repaired by an update.

  • Renders a fake "critical security update" or "data integrity error" dialog, in multiple languages, telling the user to re-enter their recovery phrase to "verify wallet integrity."

  • Validates the entered phrase against the bip39 checksum, then exfiltrates it.

  • Hooks a clipboard paste handler to auto-fill seed fields.

Named targets are Trezor Suite, Ledger Live, Exodus, and Atomic. The exfiltrated artefacts are written with obvious names: TrezorSeed.txt, LedgerSeed.txt, AtomicPassword.txt, ExodusPassword.txt, ExodusSeedEncrypted.txt, AtomicWalletData.txt.

Read that sequence again. The malware does not guess your seed. It asks for it, inside the real wallet window, behind a dialog that says the phrase is "verified locally and never sent to our servers." Legitimate wallet software never asks you to type your recovery phrase back to it for a security check.

Persistence and evasion

Persistence is a LaunchAgent plist, re-signed with codesign --deep --sign -. Anti-analysis checks hw.model for virtualisation and the username for analyst, malware, and sandbox. The downloaded binary itself is unsigned: the ad-hoc signature is applied on the victim host by the stage 2 loader, so there is no code-signing identity to pivot on from the sample.

Where it exfiltrates

The stealer talks over a WebSocket to wss://<host>/api/t. The host is supplied at runtime from a config struct (C2Ip, C2Ept), so it is not a plaintext string in the binary. The /api/t path is shared with the stage 2 telemetry host 147.45.43.209:8133, which ties the two together. Confirming the live exfil host needs dynamic detonation or config extraction.


Indicators of Compromise

Type
Indicator
Notes

Distribution host

138.124.91[.]33

Serves stages 1, 2, and 3 over plain HTTP

Stage 1 URL

http://138.124.91[.]33/xxxxxxxx?force=1

Clipboard command target

Stage 3 URL (ARM)

http://138.124.91[.]33/xxxxxxxx?force=1

Apple Silicon build

Stage 3 URL (Intel)

http://138.124.91[.]33/77422b219b1040?force=1

Intel build

Telemetry / C2

http://147.45.43[.]209:8133/api/t

Loader beacons; shares path with stealer exfil

Stealer exfil

wss://<host>/api/t

Host is runtime config, not plaintext

Geolocation lookup

ip-api, ipinfo, ipwhois

Outbound from a non-browser process is suspicious

Dropped file (temp)

/tmp/.<6 random chars>

Hidden, executed then deleted

Staged binary

~/Library/Caches/com.apple.system/com.apple.update

Masquerade location

Marker file

/var/db/.4743c531

Infection or sandbox sentinel

Persistence

LaunchAgent plist, short or random label, RunAtLoad

Re-signed with codesign --deep --sign -

Gatekeeper bypass

xattr -d com.apple.quarantine + codesign --force --sign -

On a freshly written file

Anti-forensics

sed -i '' '$d' on .zsh_history and .bash_history

Removes the pasted command

Seed exfil files

TrezorSeed.txt, LedgerSeed.txt, AtomicPassword.txt, ExodusPassword.txt, ExodusSeedEncrypted.txt, AtomicWalletData.txt

From the Electron inject

Campaign markers

af92109bfa41a94c, 2f6e6abf141d825d

Embedded build tags (execute as no-ops)

SHA-256 (Intel)

d6e5884827cc92b023add3c28383f02b1fa2f4e0392062765c3089914477d13c

stage3_x86.bin

MD5 (Intel)

190f79f7f4ebd642a705d59f59f0b6b6

stage3_x86.bin

SHA-256 (ARM)

record locally from stage3_arm64.bin

Hash the slice you retrieve


Detection and Hunting

Endpoint process chains. The strongest signals sit in the parent-child relationships, not any single process:

Network. Block and alert on egress to 138.124.91[.]33 and 147.45.43[.]209:8133. Both are raw HTTP to a bare IP, so look in flow and connection logs, not DNS. Flag WebSocket connections to a /api/t path. Treat ip-api, ipinfo, and ipwhois lookups from non-browser processes as a weak signal worth correlating.

Electron tampering. Hunt for modified wallet application bundles: a preload or asar carrying sandbox: false, integrity check failures in Trezor Suite, Ledger Live, Exodus, or Atomic, and any wallet showing an unexpected disableUpdate flag.

Filesystem. New LaunchAgent plists with short or random labels and RunAtLoad, the marker file /var/db/.4743c531, and any binary staged under ~/Library/Caches/com.apple.system/.


What to Tell Your Users

The technical detections above will catch what reaches the endpoint. The cheaper control is the one that stops stage 1 cold, and it is a single sentence your users can remember.

A real captcha never asks you to open Terminal, paste anything, or press Command and Return to prove you are human. If a website gives you those instructions, close the tab. If you have already pasted and run a command from a "verification" page, treat the machine as compromised, rotate every credential and seed phrase from a clean device, and raise it with your security team.

The wallet angle deserves a second sentence. No legitimate wallet, hardware or software, will ever ask you to type your recovery phrase back into the app for a "security update" or an "integrity check." That prompt is theft, even when it appears inside the genuine application window.


Last updated