I want to start with an uncomfortable admission about how a lot of us detect macOS malware: we are betting it will be loud. Our behavioral rules watch for curl and zsh spawning under a user session, osascript reaching for security, a process tree that announces itself. That bet has paid off for years because commodity macOS stealers were noisy. PamStealer is a commodity stealer, and it took the bet apart on purpose.
It starts where every Mac user is most conditioned to comply. Someone installs what they think is Maccy, a clipboard manager, and gets a prompt: "Maccy wants to make changes." They type their password. Most infostealers grab whatever you typed and run. This one runs your password through PAM first -- the Pluggable Authentication Modules stack macOS uses to actually authenticate you -- confirms it is real, and only then decides you are worth robbing. That single check is why Jamf Threat Labs named it PamStealer, and it is a small, precise example of the thing that makes the whole sample worth your time: it does the detectable work through the operating system's own machinery, so the process chains you are watching for never show up.
Two labs independently tore this campaign down -- Jamf, who named it, and ManageEngine, who track it as "Fake Maccy Stealer." I will lean on Jamf for the deepest internals and flag where the two diverge, because a couple of the divergences matter.
Stage one: a dropper that skips the shell
Delivery is the familiar macOS pattern with a twist. A compiled AppleScript, Maccy.scpt, ships on a disk image from a lookalike domain (maccyapp[.]com) and opens in Script Editor. Inside is an obfuscated JXA (JavaScript for Automation) payload that does the download.
The twist is that it does not shell out for the fetch. A normal AppleScript downloader reaches for curl or zsh, and every one of those is a process spawn your rules watch for. This one stages the payload through NSURLSession and the Objective-C bridge -- native APIs, no subprocess for the network stage.
It is not processless, though, and this is the part the first wave of coverage got wrong. Before launch it ad-hoc signs the staged bundle:
codesign -fs - --deep /path/to/staged.app
That codesign process, spawned by Script Editor against a bundle in Application Support, is a detection opportunity, and both teardowns flag it. It also drops a .Maccy marker file and launches hidden, no window, no Dock. So the honest read is narrower than "no artifacts": it skips the download chain you key on and leaves other artifacts in its place.
It is also picky about targets. The dropper derives a key from a host fingerprint -- CPU architecture, locale, keyboard layout, time zone -- and the encrypted config only unlocks on a matching machine. ManageEngine notes those checks double as a CIS geo-fence: it aborts on hosts that look Russian or post-Soviet. Names and config values rotate sample to sample while behavior holds, which reads as an automated builder, not hand-editing.
Stage two: Rust, a framework loaded at runtime, and a very loud clipboard
The payload is a stripped, arm64-native Mach-O in Rust -- uncommon in a stealer scene Jamf notes is dominated by Swift, Go, and Objective-C. Most strings decode at runtime, so static triage gets you the linked libraries and the ObjC selectors and not much else.
The evasion move worth internalizing: instead of linking Security.framework, it loads it at runtime. Its keychain-access capability never lands in the static import table. If your triage leans on the load commands, you will not see it there. It bundles its own SQLite and reads browser credential, cookie, and wallet-extension databases as files directly, and it encrypts exfil with ChaCha20-Poly1305, so the traffic resists inspection even though the .config it drops holds the C2 URL in cleartext.
Then it gets loud. Running as a fake Finder, Jamf's sample read the clipboard by spawning pbpaste on a loop -- every 10 to 30 seconds, for the whole run. A process posing as Finder, out of Application Support, shelling out to pbpaste every few seconds is about the loudest signature in the chain. Here is the first divergence to hold: ManageEngine's variant did the same theft in-process through NSPasteboard with no subprocess at all, so that noise is Jamf's sample, not a law of the campaign.
The password, and why PAM beats dscl for the attacker
Back to the namesake. Jamf watched it show an NSAlert with a secure text field, styled like a real authorization request -- "Maccy wants to make changes," account name pre-filled -- and validate what you type through the PAM API (pam_start / pam_authenticate / pam_end).
Why that matters for detection is the whole point of the piece. Other commodity stealers confirm a captured password by shelling out; MacSync uses dscl. That dscl call is a process you can catch. A PAM check runs in-process and spawns nothing. One less chain in the tree.
Get it wrong and it re-prompts. Get it right and it throws a decoy -- "'Maccy' is damaged and can't be opened. You should move it to the Trash," a near-copy of the real Gatekeeper warning -- so you trash the lure and assume a bad download while it is already persistent. Persistence uses login items, modern and legacy (SMAppService and LSSharedFileList), and it deliberately avoids the LaunchAgents and LaunchDaemons where detection coverage is densest. It masquerades as Finder with the genuine icon and delays its Full Disk Access request, made under that disguise, by up to 40 minutes so the prompt does not line up with launch.
Line the choices up and the philosophy is consistent: JXA over the Objective-C bridge instead of curl, a runtime-loaded Security.framework instead of a linked one, a PAM check instead of dscl, login items instead of the LaunchAgents everyone watches, a permission prompt decorrelated from launch.
It doesn't erase its footprint. It moves the footprint to where the old detections weren't looking.
This is the pattern now, not the exception
Here is my actual position, and I will mark it as opinion rather than a vendor finding: this is where the low end of macOS malware is going, and that should bother you more than a single clever sample would.
macOS detection leaned for years on the tells of noisy malware because commodity stealers were noisy. PamStealer is commodity -- ManageEngine ties it to a malware-as-a-service pattern, per-host binary rotation and all -- and it is quiet in exactly the places those rules watch. The uncomfortable part is not that one crew got clever. It is that quiet-native tradecraft is showing up in automated-builder, spray-to-a-lookalike-domain malware, which is precisely where techniques stop being exotic and become the baseline. The detection stack we built assumed attackers would announce themselves. That assumption is now a liability, and it is a liability at the cheap end of the market, which is the part that scales.
What this does NOT solve
I try to end these with the honest part, because it is the part that separates analysis from a press release.
Going native does not make this thing invisible. It makes it quieter against one class of detection. Everything else is still on the table. Named caches under ~/Library/Caches/com.apple.finder.core/, a .config with the C2 in cleartext, a codesign spawn from Script Editor, login-item registration, and in Jamf's sample a pbpaste loop are all sitting right there. ManageEngine's sample still tripped a VirusTotal engine at first submission. The C2 still has to talk.
It also does not get past behavioral EDR that watches the right things instead of the loud things -- framework loads, process lineage, a Finder doing something Finder does not do. And it does not get around the root cause, which is human: the entire chain needs someone to download from the wrong domain, run a script they should not, and approve a prompt they should have questioned. "Train the users" has never closed that gap and never will, because installing a clipboard manager off a lookalike domain is a normal act in an environment that makes the fake and the real indistinguishable until it is too late.
And honestly, the analysis itself does not fully close. Jamf and ManageEngine independently tore down the same campaign and diverge on what PAM is even for: Jamf documents the validate-your-password-before-stealing workflow the name is built on, while ManageEngine describes the binary linking libpam and infers it could intercept sudo and system-auth responses. Both saw PAM; they emphasize different uses. The Ethereum JSON-RPC endpoints Jamf found in the decrypted server config, and the one it caught the malware connecting to, have an unknown purpose -- resilient dead-drop or wallet recon, Jamf did not capture the calls and did not guess. If someone tells you they know exactly what this thing does end to end, they are out ahead of the public evidence.
So where does that leave your detection?
If you want to hunt it, the artifacts that survive are the boring, reliable ones:
- A login item or Background Task Management entry pointing at a Finder or Software Update bundle in a user directory (
sudo sfltool dumpbtm)
- A process named Finder running from anywhere other than
/System/Library/CoreServices/Finder.app
- Script Editor spawning
codesign against a bundle in ~/Library/Application Support/
- A "Finder" spawning
pbpaste on a loop or reading a Chromium credential database
- An ad-hoc
- code signature where you would expect a Developer ID
But the artifacts are not really the argument. Here is what I actually want to know from anyone running macOS detection at scale: are your rules still keyed on shell spawns, or have you moved them to framework loads and process lineage? And be honest with me -- has behavioral detection ever caught one of these native-tradecraft stealers in your environment before the IOCs dropped, or is on-disk matching still doing the real work? I have a suspicion about the answer, and I would genuinely like to be wrong.
Originally published at blog.vertexops.org.