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.
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.
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.
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, zeronavigator.languages, or a headless user agent (HeadlessChrome,PhantomJS,Puppeteer,Selenium) all stop execution.A
wp-login.php?action=logoutlink in the DOM means a logged-in WordPress admin is viewing the page, so it sets a suppression cookie and exits.A
?force=1parameter 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 = falsein the preload sorequire()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
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