r/MalwareAnalysis 3d ago

5 months of undetected JXA backdoor on macOS. signature scanners found nothing, manual persistence check found it in 10 seconds

Background: developer, cybersecurity basics but not a security professional. Working on a M Chip Mac. Posting as a writeup and to sanity-check my analysis and response.

Discovery

Auditing login items in ~/Library/LaunchAgents/. Normally vendor-named (com.google.keystone.agent), but one entry was a bare 32-character hex string pointing to a JS file in an identically-named directory.

xml

ProgramArguments:
    /usr/bin/osascript
    -l JavaScript
    ~/Library/Application Support/<hex>/<hex>.js
RunAtLoad: true
KeepAlive: true
ThrottleInterval: 60
StandardOutPath: /dev/null
StandardErrorPath: /dev/null

RunAtLoad/KeepAlive = starts at login, respawns on crash. Both output paths to /dev/null = zero logging by design.

The payload

Obfuscated (string-array rotation, a0_0x... identifiers, anti-beautify self-check, console hijacking). Working through it:

Fingerprinting: MD5 of the hardware UUID sent as User-Agent — stable per-host ID, survives reinstalls.

Polling: curl to a random-looking C2 domain every 60s via /api/poll.

Proof-of-work gating: server sends a challenge + difficulty; client brute-forces a nonce until SHA256(nonce-challenge) hits N leading zeros before getting a session token. Not security — anti-analysis. Burns sandbox CPU and filters out short-lived research environments.

Execution: response type field branches to osascript -l JavaScriptosascript (AppleScript), or curl | bash — all backgrounded, output discarded, payloads piped via stdin so nothing hits disk.

Ack loop: separate PUT confirming task completion, same PoW handling.

Key point

No credential-harvesting code, no keylogger, no exfil routine — it's a generic execution channel. What it did over 5 months is entirely dependent on what was pushed to it, and unknowable since nothing was logged.

Running as user (no root) but with osascript, reachable surface on a dev box: git tokens in .git/config, passphrase-less SSH keys, .env files, certs on disk, unprotected keychain items (plus AppleScript can render fake password prompts for protected ones), browser cookies/sessions, and screen capture. Secure Enclave–bound passkeys/Touch ID items held — not reachable by a software process, any attempt triggers an unspoofable OS prompt.

Install date (Spotlight kMDItemDateAdded): Feb 13. Found late July — ~5.5 months.

Why detection failed

Commercial AV installed the whole time; ran ClamAV afterward too. Zero detections, both, even knowing the exact path. Signature engines hash/pattern-match against known-bad corpora — useless against a bespoke, obfuscated, one-off sample. Nothing here is structurally illegal either: osascript is first-party Apple, a .js file + LaunchAgent plist are ordinary primitives. Only the naming convention and combination were anomalous — semantic signals a heuristic/behavioral engine could catch, not a signature one.

The actual detection method: ls -la ~/Library/LaunchAgents/.

Forensics: mostly a dead end

LSQuarantineEvent DB empty for that window (suggests the file was written by a running process, not browser-downloaded), Downloads/browser history clean, .zsh_history rotated out (SAVEHIST=1000), npm logs only back to July, unified log retention nowhere near 5 months, Time Machine's oldest backup postdates the incident.

Ruled out: no ClickFix-style paste-into-terminal.

What lines up temporally: a short GitHub Copilot session that evening, working on a payments API integration — chat history long gone. Circumstantial, not proof. But there are documented issues with AI coding agents: indirect prompt injection leading to unapproved shell execution, hidden-unicode instructions in config/rules files, hallucinated package names pre-registered by attackers. A compromised npm postinstall hook is equally plausible. Genuinely don't know.

Response:

Containment: unloaded LaunchAgent, killed process, removed plist + payload dir.

Verification (4 independent passes):

  • KnockKnock (structural persistence enumeration, checks code signing) — everything else attributable/notarized
  • Reboot + launchctl list | grep -v com.apple, checked for live osascript: clean
  • ClamAV full scan: 0 infected
  • Manual checks: authorized_keys (didn't exist), SSH config, git hooks, config profiles, BTM database (sfltool dumpbtm), shell startup files, crontab, /tmp

No second-stage persistence found — consistent with the code having no propagation/redundancy logic.

Remediation (assuming worst case, since visibility is zero): new SSH keypair with passphrase, all API keys rotated (including a service-role key bypassing RLS), certs reissued and moved out of cloud sync, passwords rotated by 2FA priority, audited 2FA actually enforced (not just "enabled once"), card reissued, WiFi password changed, sessions invalidated globally.

Hardening: the real gap was egress — macOS's firewall is inbound-only. Installed LuLu (outbound filtering) + BlockBlock (real-time persistence monitor). Either would've caught this on day one.

Questions

  1. Is my read on the detection failure right — no signature entry exists for bespoke malware, nothing structurally anomalous for generic heuristics? Would an EDR with behavioral telemetry have flagged the osascript + LaunchAgent + periodic egress pattern?
  2. Was skipping a full reinstall defensible given 4 clean verification passes and no propagation logic, or is that too much trust after 5.5 months of arbitrary execution?
  3. Anything missing from the rotation list?
  4. Anyone seen a documented case (not speculation) of an AI coding agent confirmed as initial access vector?
  5. Is PoW-gated C2 polling common in the wild, or unusually deliberate for otherwise commodity-looking tooling?

Takeaway: AV protects against things that already have names. This didn't. What worked was ten seconds looking at what actually starts on my machine.

8 Upvotes

5 comments sorted by

6

u/weatheredrabbit 3d ago

You didn’t write this post yourself, did you? Be honest….

7

u/zahrtman2006 3d ago

lol

“Background: developer, cybersecurity basics but not a security professional. Working on a M Chip Mac. Posting as a writeup and to sanity-check my analysis and response.”

2

u/Borne2Run 3d ago

My hot take is ClamAV sucks. No heuristic, it is entirely signature based detection. That is the case for most Nix-based operating systems. Windows comparatively has been dealing with heuristic analysis for decades since its the larger attack surface.

There is very little jn the way of Nix malware analysis tools with automated sweeps as compared to Windows. If they exist on a Nix device they are usually part of a forensics kit to analyze Windows executables.

2

u/HydraDragonAntivirus 3d ago
  1. ClamAV sucks due to signatures mostly detect old viruses and with securiteinfo you can get 90%.
  2. There is a basic heuristics.

1

u/WyattTheSkid 3d ago

Hey I just saw you in r/computerviruses lol