Beyond good ol? LaunchAgent - part 0
First and foremost, let me start with a disclaimer. As probably most of you may have noticed the similarity, the title of this post, which I hope to be the first of a series, is inspired by the great and amazing work from Adam (@Hexacorn) [1] “Beyond good ol’ Run Key” (which, as of today, reached episode 93!!!), where he writes about all Windows persistence mechanisms he comes across/discovers in his research, which are, as the title suggest, much more than just the Run registry key we all love. In the rare case you have not read it yet, you should asap. Really.
Having said that, my motivation behind this is the intention to focus more on macOS analysis/research and macOS internals [2][3] (J. Levin’s book is The bible, if you have to pick one place where to start from on anything macOS related, there you go), and also because I’m a documentation maniac, I like to write things down, ideally in one location, to easily retrieve them when needed. This is a way to document and share my findings about macOS persistence mechanisms. And I could not but start with the most common mechanisms (hence part 0): LaunchAgents and LaunchDaemons.
In the macOS boot/startup process, immediately after the kernel initialization phase there is launchd. Launchd is “The process”. It is started directly by the kernel and is the first process “appearing” in user mode. It is responsible, among other things, for initializing and scheduling all system services and processes, and so for launching Agents and Daemons.
Both Agents and Daemons are described in their respective property list (.plist) files, containing the instructions of how and when they have to be launched. Daemons are system services, and are started in the boot process before any user logs in. Daemons may be created with administrator privileges, but are executed under root privileges, so an adversary may also use a service to escalate privileges from administrator to root. Daemons plist files are located at the following paths [4]:
- /System/Library/LaunchDaemons/
This is the location for Apple specific Daemon. This is a restricted location and is mounted as read-only. - /Library/LaunchDaemons/
This for all third-party daemons, therefore not restricted as the previous one (but requires root permission).
Agents, instead, are user’s services/processes and are started only after user logs in. When a user logs in, a per-user launchd process is started which loads the parameters for each launch-on-demand user agent from the property list (plist) files located at the following paths [4]:
- /System/Library/LaunchAgents/
As for the Daemons, this location is for Apple specific Agents and its access is restricted. - /Library/LaunchAgents/
Again, as for Daemons, this folder is for third party Agents. - ~/Library/LaunchAgents/
This folder contains the user installed Agents and are loaded by the user level launchd process. Because those are executed as soon as that specific user logs in and do not require administrator privileges, you can easily imagine why this is the (most) favourite malware persistence location.
Agents and Daemons plist files are just like any other property list file in the Apple universe, therefore you can easily review them with plutil -u from command line or use Xcode if you prefer GUI based. The following are some of the keywords of interest you may want to look for when analyzing a suspicious one.
Label: This key is required as it identifies the agent/daemon and has to be unique for the launchd instance (i.e. two agents or two daemons cannot have the same label, but an agent and a daemon can, since daemons are loaded by the root launchd whereas agents are loaded by a user launchd). This is typically the file name.
<dict>
<key>Label</key>
<string>com.apple.launchport.plist</string>
...
Program: This key defines actually what has to be run, the path to the specific binary/script.
ProgramArguments: This is also quite self explicative, and is defined as an array of arguments to be passed to the “Program” when launched
...
<key>Program</key>
<string>/path/to/my/script</string>
<key>ProgramArguments</key>
<array>
<string>/path/to/my/script</string>
<string>--config</string>
<string>~/.tmp/myconfig_file.json</string>
</array>
...
As you may have noticed, the name of the first argument is again the program itself. This is important to keep in mind as the first item will not be the first argument, much like argv[0]. If both keys are used, the value of Program is the executable to be launched and the first string in ProgramArguments will be ignored by launchd.
RunAtLoad: This key specifies that it has to be run right after it has been loaded, i.e. at boot time for daemons, and after user logs in for agents.
WatchPaths: launchd will start the program if the provided path is modified. If path points to a folder, modifying the folder or any of its content will trigger, as well as any modification to a file if path points to a specific one.
StartOnMount: The program is started whenever a file system is successfully mounted, i.e. a CD/DVD, USB drive, SD Card, etc. (haven’t checked with network drive to be honest, but I expect so).
Start[Calendar]Interval: StartInterval will tell launchd to start your program every n seconds, while StartCalendarInterval will tell to schedule the execution every day at a specific hour for example. This is pretty much like the cronjob in classic *NIX systems. The following example will tell launchd to run the program every monday morning at 7am:
<key>StartCalendarInterval</key>
<dict>
<key>Weekday</key>
<integer>1</integer>
<key>Hour</key>
<integer>7</integer>
<key>Minute</key>
<integer>0</integer>
</dict>
NetworkState: if set to True, it will run the program as soon as a network connection becomes available. SuccessfulExit: This key will take into account the exit code of the program in case of termination. If set to True, the program will be restarted until it fails. If set to False, it will restart the program every time it will terminate in a non “successful” way, i.e. exit code different from zero. Crashed: If set to True, the program will be restarted after it crashed. If set to false, it will restart the program unless it has crashed.
<dict>
<key>Label</key>
<string>com.apple.updates</string>
<key>ProgramArguments</key>
<array>
<string>/Users/Shared/.local/kextd</string>
</array>
<key>KeepAlive</key>
<false>
<key>RunAtLoad</key>
<true>
<key>StandardErrorPath</key>
<string>/dev/null</string>
<key>StandardOutPath</key>
<string>/dev/null</string>
</dict>
Pasquale
References:
[1] - Adam (@Hexacorn), “Beyond good ol’ Run Key”, http://www.hexacorn.com/blog/2017/01/28/beyond-good-ol-run-key-all-parts/
[2] - Jonathan Levin (@Technologeeks), “*OS Internals”, http://newosxbook.com/index.php
[3] - Jonathan Levin (@Technologeeks), “Mac OS X and iOS Internals - To the Apple’s Core”, http://newosxbook.com/MOXiI.pdf
[4] - Pasquale Stirparo (@pstirparo), “mac4n6 Artifacts Project”, https://github.com/pstirparo/mac4n6
[5] - “Launchd Info”, http://www.launchd.info/
[6] - Unit42, “Sofacy’s ‘Komplex’ OS X Trojan”, https://researchcenter.paloaltonetworks.com/2016/09/unit42-sofacys-komplex-os-x-trojan/
[7] - Kaspersky, “Unveiling Careto - The Masked APT”, https://www.securelist.com/en/downloads/vlpdfs/unveilingthemask_v1.0.pdf
[8] - Partick Warlde (@patrickwardle), “HackingTeam Reborn; A Brief Analysis of an RCS Implant Installer”, https://objective-see.com/blog/blog_0x0D.html
[9] - Patrick Warlde (@patrickwardle), “Objective See”, https://objective-see.com
[10] - Sarah Edwards (@iamevltwin), “Mac4n6 Blog”, https://www.mac4n6.com/
[11] - Pedro Vilaça (@osxreverser), “Reverse Engineering Mac OS X”, http://reverse.put.as/
[12] - MITRE ATT&CK (@MITREattack), “MacOS Techniques”, https://attack.mitre.org/wiki/MacOS_Techniques
----
Pasquale Stirparo, Ph.D.
@pstirparo
Comments