Skip to content
Persistencebeginner

Launchd Persistence

Malware drops a LaunchAgent or LaunchDaemon property list so macOS launchd starts its payload at login or boot, the dominant persistence mechanism on macOS.

Launchd is the macOS service manager and the parent of every process on the system. It starts and supervises jobs described by property-list (.plist) files: LaunchAgents run in a user's context at login, while LaunchDaemons run as root at boot before any user logs in. Because launchd is the sanctioned way to run background software on macOS, nearly all macOS malware — adware droppers, stealers, and backdoors alike — persists through it.

A persistence plist names the job, points ProgramArguments at the payload, and sets a trigger such as RunAtLoad (start immediately) or KeepAlive (restart on exit). The combination of RunAtLoad and KeepAlive gives both boot/login persistence and a watchdog. The location the plist is written to determines its privilege and scope.

How it works

Launchd reads plists from fixed directories, in increasing order of privilege:

text
~/Library/LaunchAgents/             per-user agent (no admin)
/Library/LaunchAgents/              all-user agent (admin)
/Library/LaunchDaemons/             root daemon, runs at boot (admin)
/System/Library/Launch*/            Apple-only (SIP-protected)

A minimal malicious agent plist contains:

text
Label:            com.apple.softwareupdate.helper
ProgramArguments: ["/Users/Shared/.update/agent"]
RunAtLoad:        true
KeepAlive:        true

The Label is chosen to impersonate Apple or a trusted vendor (com.apple.*, com.adobe.*), and the payload is stashed in a hidden directory under /Users/Shared or the user's Library. Operators load the job with launchctl load (or bootstrap on current macOS) so it runs without a reboot, and the plist re-loads automatically at the next login or boot.

Detection & analysis

Static analysis:

  • Enumerate the four non-Apple Launch* directories above on a live or imaged Mac and read each plist. Inspect Label, ProgramArguments, RunAtLoad, and KeepAlive; a binary in /Users/Shared, a hidden dotted path, or /tmp, especially behind an Apple-looking label, is high-signal.
  • Convert binary plists to readable XML with plutil -p <file> before triage. The plist's mtime and the payload's signing status (codesign -dv, often unsigned or ad-hoc) date and characterise the intrusion.
  • In a captured sample, look for writes to the LaunchAgents/LaunchDaemons paths, the .plist extension, the keys ProgramArguments/RunAtLoad, or execve of launchctl.

Dynamic analysis:

  • Run the sample under Endpoint Security / fs_usage and watch for a .plist being written into any Launch* directory, followed by a launchctl load/bootstrap. The writing process and the new label are the key artefacts.
  • At runtime, launchd spawning an unexpected child from /Users/Shared or a hidden path, or a new label appearing in launchctl list, confirms the persistence.

Detection rule hint:

Alert on creation or modification of any .plist under the writable Launch* directories by a process that is not Apple's installer/pkgutil. Flag plists whose ProgramArguments[0] resolves to /tmp, /Users/Shared, or a hidden directory, those whose Label impersonates com.apple.* while the binary is unsigned, and any pairing of RunAtLoad with KeepAlive outside a known-good baseline.

Votes

Comments(0)