Defeating Malicious Launch Persistence

A New Mitigation Strategy for the most used macOS Persistence Technique

Why Launch Persistence Research?

This research started with me looking for suggestions for my organization on mitigating launch persistence techniques for macOS. As a red team operator, sharing knowledge and recommendations with our security organizations is crucial to making our customers safer.

Persistence

Persistence is a vital tactic in the adversarial kill chain. It is typically the next step after initial access and payload/malware execution. "Persistence is the means by which malware ensures that it will be automatically (re)executed by the operating system on system startup or user (re)login" (Wardle, 2020, p. 2). Most malware attempts to gain persistence. Otherwise, a system reboot would kill access to the infected system.

Launch Persistence and launchd

Launch persistence includes launch agents and launch daemons. “Launch items are the Apple recommended way to persist non-application binaries (e.g., software updates, background processes, etc)” (Wardle, 2020, p. 5).

Property Lists (plists)

Property lists are very important to understand when discussing launch persistence because they describe the launch persistence to launchd. A plist is typically an XML document that contains key/value pairs. A plist can also be in JSON format, and in more rare cases a binary. We will just focus on the XML plists for this article.

Example Property List
  1. Key: Program or Program Arguments. Value: Path to launch persistence’s script or binary to execute.
  2. Key: RunAtLoad. Value: Contains a boolean that, if set to true, instructs launchd to automatically launch the launch persistence. The malware will be persistently restarted by macOS at system reboot at user re-login.
  3. Key: Label. Value: The job name. “This key is required for every job definition. It identifies the job and has to be unique for the launchd instance. Theoretically, it is possible for an agent to have the same label as a daemon, as daemons are loaded by the root launchd whereas agents are loaded by a user launchd, but it is not recommended by convention job names are written in reverse domain notation” (Soma-Zone, What is Launchd?). For example com.redteam.evil. For private agents, the domain local is a good choice: local.cleanup.

Launch Persistence and Malware

To persist as a launch agent, malware can create a plist in the LaunchAgents directories, and for a launch daemon, it can do the same in the LaunchDaemons directory. Malware can create its persistence in the same manner as Apple recommends developers create persistence for their legitimate applications.

Mitigation

To mitigate the adversarial launch persistence techniques on our home computers, we can use Patrick Wardle of Objective-See’s Free and Open Sourced tool, BlockBlock. BlockBlock monitors common persistence locations and alerts whenever a persistent component is added. BlockBlock is a fantastic tool and is excellent for use on your personal mac, but it isn’t scalable to the enterprise. We need something that will block an adversary from creating a plist in the launch persistence directories.

Enterprise Mitigation

What I have found in my research is that we can lock the root-level launch persistence directories (/Library/Launch(Agents || Daemons)) using a macOS mobile device management (MDM) solution. Assuming that the user of this managed machine does not have access to the local administrative (root) user account on this machine, the MDM admin(s) can lock these launch persistence directories using the root account. Now, only the owner of these directories (root) can unlock them. As it stands, malware, even running as root, would need to unlock the directory, and then create its launch persistence plist in the unlocked directory. I have not seen macOS malware with this bit of sophistication in the wild.

What do you mean by “locked?”

Files and directories on macOS have a flag called the uchg, or user immutable flag. If one sets this flag, then the file or directory is “locked.” If set, this immutable bit means that the associated file or folder cannot be changed. If we set this user immutable bit on the root launch persistence directories, nothing can add to or remove from the directories. Malware cannot add a plist, which means, without additional actions, no additional launch persistence files can be added.

LauchAgents directory is immutable
Unlocked LaunchAgents directory via File Information

Will this break application functionality?

When I told my colleagues about this mitigation method, they expressed concerns for legitimate applications that edit the plists they have created in the launch persistence directories. If an application loses its ability to write to its plist, this mitigation method might break application functionality. I agreed and did some testing.

What about the USER LaunchAgents directory?

So, the local user account doesn’t own the root launch persistence directories. Therefore it cannot unlock the folder. Still, since the user owns the /Users/<username>/Library/LaunchAgents directory, they can just unlock it, negating the mitigation in userland. This user LaunchAgent directory might not be necessary for an enterprise environment. Most approved software that I’ve seen lately in an enterprise environment doesn’t need to install persistence here, so we need a way to lock it down permanently.

How do we do this at scale?

Applications only need to add/create a plist to the root launch persistence directories at install time. If your IT administrators manage installations, they can install approved software using an MDM solution with scripting capabilities.

Protecting Launch Directories with Jamf Pro

The following is an example of how an admin might use Jamf Pro to lock the LaunchDaemon and LaunchAgent directories across a macOS fleet. This workflow highlights key Jamf Pro features, including scripting via the Jamf Pro binary, reporting and monitoring via custom inventory attributes, and scoping actions with a dynamic Smart Computer Group.

Reporting on uchg Status

The first step of the Jamf Pro workflow is to be able to determine whether the launch persistence directories have been uchg locked. We do this by creating a Jamf Pro Extension Attribute that is updated during the routine Jamf Pro inventory update. This custom inventory attribute checks the uchg status of the launch persistence directories and updates each computer’s inventory record.

Writing the Script

Next, we need to write the script that locks the launch persistence directories. Since the ~/Library/LaunchAgents/ directory does not exist by default, in this script, we check for its existence and create it if not present. If the user is a standard user, Antonio recommends creating an unprivileged user account on each machine and changing ownership of the ~/Library/LaunchAgents to this stand-in account. This avoids having the directory owned by root and prevents the local user from simply unlocking their directory as its owner.

Crafting the Policy

Finally, we create a policy that ties all of these elements together. After giving the policy an appropriate name, we set the policy to be triggered at the recurring check-in (every 15 minutes by default) on any computer that is in scope.

To Summarize

We can lock the root-level launch persistence directories using an MDM solution with scripting capabilities, like Jamf Pro. An administrator would only unlock it when installing approved software. They would then relock it after install.

Works Cited

Apple. (2016, September 13). Daemons and services programming guide. Creating Launch Daemons and Agents. Retrieved November 9, 2021, from https://developer.apple.com/library/archive/documentation/MacOSX/Conceptual/BPSystemStartup/Chapters/CreatingLaunchdJobs.html.