Attack Detection Fundamentals 2021: macOS - Lab #2

By Calum Hall and Luke Roberts on 14 April, 2021

In the first lab of F-Secure Consulting's Attack Detection Fundamentals workshop focussing on macOS we played around with Office Macros. This lab demonstrated how macros can be abused within the macOS ecosystem, and namely focussed on the following areas:

  • Gain initial code execution via Office Macros using the Mythic framework
  • Breaking out of Apple's sandbox
  • Creating a login item to gain persistence

Perhaps more importantly however, we also investigated how we can gather and analyse the necessary telemetry to detect certain stages of this attack. Apple's Endpoint Security Framework (ESF) proved to be a key source of information for identifying the malicious behaviour of our Office document payload, and the Unified Log revealed certain telemetry that indicated a sandbox restricted event. 

A recording of this workshop series can be found here and the slides are available here.

To follow on from this, in this lab we're going to look at alternative means for which an adversary may persist on a device. This lab will discuss the well documented persistence technique that is abusing Apple's LaunchAgent functionality. Given that this attack is abusing legitimate macOS behaviour, the offensive walkthrough of this lab is rather short and focusses more on the fundamentals of how this technique is often abused. The key takeaways here will be focussing around the detection techniques that can be implemented to confidently identify this type of persistence on your devices. 


The macOS ecosystem provides a large array of persistence opportunities, both native to the operating system, as well as software dependent. A large number of these vectors resemble techniques that have been observed within *nix environments for many years. We only plan on delving into one of these techniques within this workshop, however there are some excellent resources out there, such as Csaba Fitzl's latest Beyond the good ol' LaunchAgents blog series discussion on macOS persistence techniques.


For the purpose of this workshop, we will focus on a technique that has been abused countless times in the wild by macOS malware - LaunchAgents. LaunchAgents have been used by malware for a number of years now, and are a common TTP used by offensive operators on macOS engagements. The Silver Sparrow malware for example, one of the first pieces of macOS malware that was compiled to run on Apple's latest silicon, was found to utilise a LaunchAgent for persistence.

A LaunchAgent can be used to employ launchd to run a given program periodically, or to keep that program alive indefinitely. This persistence mechanism can be abused by users of all privilege levels, as LaunchAgents can be configured on a per user basis within a specific user's home directory ($HOME/Library/LaunchAgents/). Hence, administrative privileges are not required to abuse this vector. It should be noted that a LaunchAgent will execute within the context of the user that created the LaunchAgent, with the exception of administrative users creating LaunchAgents on behalf of other users.

It is also worth noting at this point that in addition to LaunchAgents we also have LaunchDaemons. LaunchDaemons behave in a similar manner to LaunchAgents except that daemons run at a system level on startup, rather than within the context of a specific user's session. Given that you must be root on a device to register a new LaunchDaemon, they are not as commonly abused as LaunchAgents. Hence, for this document we will focus on the LaunchAgent side of things.

LaunchAgents exist in a .plist format similar to the following:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "<>">
<plist version="1.0">

This LaunchAgent will run the "/tmp/.malicious_binary" upon being loaded, and then every 20 seconds thereafter.


Creation of LaunchAgent

Using the Endpoint Security Framework (ESF), we can detect certain events of interest. In this instance, rather than monitoring processes, we are going to look at the file events occurring on the device. Using Objective-See's FileMonitor we can monitor for files that are created within the target directories:

  • /Library/LaunchAgents/
  • /Library/LaunchDaemons/
  • /Users/$USER_HOME/Library/LaunchAgents/

For example, here we are going to grep through the output of FileMonitor in an attempt to identify any file writes containing "/Library/Launch". Note that we are using JQ here to prettify the JSON output:

> sudo ./ > LaunchAgents
> cat LaunchAgents | grep -I /Library/Launch | jq

{ "event": "ES_EVENT_TYPE_NOTIFY_CREATE", "timestamp": "2021-04-06 08:55:05 +0000", "file": { "destination": "/Users/calumhall/Library/LaunchAgents/com.malicious.plist", "process": { "pid": 39665, "name": "touch", "path": "/usr/bin/touch", "uid": 501, "architecture": "unknown", "arguments": [], "ppid": 39279, "rpid": 32162, "ancestors": [ 32162, 1 ], "signing info (reported)": { "csFlags": 570522385, "platformBinary": 1, "signingID": "", "teamID": "", "cdHash": "1840467F3490AFAA5119AF54623768C1D8BE99E1" }, "signing info (computed)": { "signatureID": "", "signatureStatus": 0, "signatureSigner": "Apple", "signatureAuthorities": [ "Software Signing", "Apple Code Signing Certification Authority", "Apple Root CA" ] } } } }

At which point, we identify the LaunchAgent "com.malicious.plist" being created within this directory. At this point, we can confidently identify when a new LaunchAgent is created on the device, however we have one very obvious problem - LaunchAgents are a core component of macOS and many legitimate software packages. Hence, the creation of a LaunchAgent in itself does not indicate malicious behaviour. As such, we need to delve into the contents of the agents that are created.

Suspicious LaunchAgent Behaviour

Using the ESF we can detect when a LaunchAgent is created, yet we are provided with no indication as to the behaviour of the agent. As a result, we must employ alternative methods of retrieving this information such as osquery.

Using osquery we are able to not only read the contents of the file, but also link certain attributes with more contextual information. As Patrick Wardle discusses in his talk "What’s Your Game Plan? Leveraging Apple’s Game Engine to Detect Threats" we can create a number of predicates that will flag suspicious behaviour within LaunchAgents. Namely, in this instance we're going to focus on Apple's Code Signing, or - in our case - lack thereof!

For those that aren't familiar - osquery is a tool that can be deployed across your estate, enabling you to query your organisation's devices in a SQL database-like manner. There are plenty of resources out there that will provide everything you need to know about using osquery and as such we're going to stick to discussing what we need from it.

Specifically, we want to detect LaunchAgents that execute an unsigned binary on the device, which we can achieve with the following query provided by Guillaume Ross:

> select * FROM signature s JOIN launchd d ON d.program_arguments = s.path WHERE signed=0 AND d.run_at_load=1;
+-----------------------------------------------------------------+----------------+--------+--------+------------+--------+-----------------+-----------+-----------------------------------------------------------------+-----------------------------------+-----------------------------+---------+-------------+------------+-----------+----------+----------+-----------+-------------+-------------+----------------+---------------------------------------+-------------+-------------------+---------------------+----------------+----------------+-------------------+--------------+ | path | hash_resources | arch | signed | identifier | cdhash | team_identifier | authority | path | name | label | program | run_at_load | keep_alive | on_demand | disabled | username | groupname | stdout_path | stderr_path | start_interval | program_arguments | watch_paths | queue_directories | inetd_compatibility | start_on_mount | root_directory | working_directory | process_type | +-----------------------------------------------------------------+----------------+--------+--------+------------+--------+-----------------+-----------+-----------------------------------------------------------------+-----------------------------------+-----------------------------+---------+-------------+------------+-----------+----------+----------+-----------+-------------+-------------+----------------+---------------------------------------+-------------+-------------------+---------------------+----------------+----------------+-------------------+--------------+ | /Users/calumhall/Library/LaunchAgents/com.malicious.plist | 1 | | 0 | | | | | /Users/calumhall/Library/LaunchAgents/com.malicious.plist | com.malicious.plist | malicious_plist | | 1 | | | | | | | | 20 | /tmp/.malicious_binary | | | | | | | | | /Users/calumhall/Library/LaunchAgents/com.malicious.plist | 1 | x86_64 | 0 | | | | | /Users/calumhall/Library/LaunchAgents/com.malicious.plist | com.malicious.plist | malicious_plist | | 1 | | | | | | | | 20 | /tmp/.malicious_binary | | | | | | | | +-----------------------------------------------------------------+----------------+--------+--------+------------+--------+-----------------+-----------+-----------------------------------------------------------------+-----------------------------------+-----------------------------+---------+-------------+------------+-----------+----------+----------+-----------+-------------+-------------+----------------+---------------------------------------+-------------+-------------------+---------------------+----------------+----------------+-------------------+--------------+

From this output we can see that the "/tmp/.malicious_binary" binary is being called as a "Program Argument" from within the LaunchAgent. By joining the data held within osquery we can see that this file is unsigned, which in itself is suspect. We can also implement further predicates that are likely to flag additional suspicious behaviour, e.g. the fact that we're executing a hidden binary from the "/tmp/" directory is bound to raise some eyebrows. There are too many potential indicators to cover in this workshop, however for those interested, Patrick Wardle's talk discussed previously is a great place to start.

Osquery only returns information that has not previously been gathered from the device, and hence we can focus on analysing new LaunchAgents that are added to the device. On the flip side however, it is worth noting a few limitations of osquery:

  • Not real-time reporting - osquery will periodically send new events, rather than as they happen
  • Runs in user-land - Anything that gains root access to the device will be able to bypass osquery detection


Throughout this lab we have demonstrated the simplistic nature of LaunchAgents and consequently why they are so commonly employed as a persistence technique. Thankfully for the good guys, we have also demonstrated the following methods that we can employ to detect such an attack:

  • Identify the creation of new LaunchAgents via the ESF
  • Discover conspicuous LaunchAgents on target devices using osquery

The detection techniques we have described are just the tip of the iceberg of the detection capability we could develop. We have demonstrated that whilst the ESF can provide real-time detection of new LaunchAgents, it is the further analysis of these LaunchAgents that provide us with the ability to accurately detect malicious behaviour. 


Special shoutout to the following researchers whose work has formed the basis of this workshop series:

  • Patrick Wardle
  • Cody Thomas
  • Michael Jack
  • Cedric Owens
  • Csaba Fitzl
  • Jaron Bradley
  • Guillaume Ross
  • Howard Oakley
  • Phil Stokes
  • Madhav Bhatt
  • Adam Chester

And to anyone else that we have inevitably forgotten to mention.