How does one begin attempting to identify spoiled apples in a large bin full of apples? To start, you have accept that there may be at least one bad apple in the bin. As a defender, you may be tasked to protect a large population of macOS systems. Let’s operate under the assumption that there is at least one compromised system in your population at any given time. This is the first post in a four part series that will discuss methodologies and techniques to proactively find compromised Macs in a enterprise environment.

Osquery will be used exclusively in this series for several reasons: it is free and open source, which promotes being able to follow along in your own lab or testing environment. In addition, it provides us with the majority of data needed to create robust host-based detections for macOS systems.

In this post, we’ll use Empire to simulate malicious activity. A lot of Empire’s capabilities are used in modern Mac malware today.

There are a number of ways an attacker could gain initial access into a macOS system. Let’s focus on how we can identify a compromised system assuming that an attacker has already established persistence.

Mitre ATT&CK shows us many of the techniques used in the tactic of “Persistence”. The go-to technique for persistence on macOS systems today is the installation of a LaunchAgent or LaunchDaemon. Thus, we can form the following hypothesis: At least one of the systems in my environment is compromised, and persisting via a LaunchAgent or Daemon.

Let’s generate a malicious LaunchDaemon via Empire. We’ll start by generating a stager:

After getting the user to execute the stager code (e.g. through a malicious attachment, etc.), we can see the agent check in.

Using the agent we have available, we can use the sudo_spawn module to elevate our privileges with previously acquired credentials.

Now that we have elevated privileges on the system, we can install a malicious launchDaemon to establish persistence.

Through osquery we have the ability to query all of the LaunchAgents and LaunchDaemons on a given macOS system. Let’s begin our hunt by starting osquery in interactive mode.

The osquery schema shows the tables that are available for use for a given operating system and the data type for each column in a table. The table that contains LaunchAgents/LaunchDaemons information is launchd. Below is a simple query that demonstrates the data osquery exposes to us.

select * from launchd;

It is common for attackers to create labels starting with “com.apple.” to blend in with legitimate LaunchAgents and LaunchDaemons on a system with the same label prefix. The binaries associated with legitimate com.apple.* LaunchAgents and LaunchDaemons are signed by Apple, however. We’ll build on our initial query to look for user-level LaunchAgents & Daemons, which are located in /Library/LaunchAgents and /Library/LaunchDaemons directories. This narrows our dataset to exclude launchAgents & Daemons that are in the “/System” folder, which is protected by System Integrity Protection.

select path,name,label,run_at_load,program_arguments from launchd where path like ‘/Library/Launch%’;

com.apple.BluetoothService stands out a bit, as it is unusual for a service to be running under a user context (e.g. “Users/mike/” directory). Osquery also allows us to check the signature information for a binary via the signature table. To do so, you must provide the path to the binary of interest. Let’s use the signature table to gather signing information for the binary associated with the suspicious plist.

select * from signature where path = ‘/Users/mike/Library/BluetoothService’;

Legitimate applications that install a LaunchAgent or Daemon are tied to a binary that is signed. At this point, we can conclude that the LaunchDaemon is suspicious as it is unsigned and lives in the user context with a “com.apple.” label. Such a discovery warrants additional investigation and likely incident response. Our query is a good candidate for a detection rule. We can join the launchd & signature tables to identify all unsigned LaunchAgents/Daemons starting with “com.apple.”.

select * FROM signature s JOIN launchd d ON d.program_arguments = s.path WHERE d.name LIKE ‘com.apple.%’ AND signed=0 AND d.run_at_load=1;

Let’s walk through this query. We start by selecting all columns (*) from the signature table and assign that table a variable of “s”. Next, we assign variable “d” to the launchd table. We join the program_arguments column from launchd with the path column of the signature table. Last, we filter our results such that it only includes LaunchAgents & Daemons with a name starting with com.apple. that are unsigned (0) with it’s flag set to run at startup.

Finally, what if the attacker signed their malicious code using a developer account? This would bypass our detection as the signed value would show as 1. Let’s look at an example below.

In this case, the signed value is 1, with a corresponding authority value of “Developer ID Application: VMWare. Inc (EG7KH642X6). EG7KH642X6 is the unique team identifier for VMWare used to identify all apps signed by VMWare. As a real world example, Mughthesec malware was signed with a developer id of 9G2J3967H9.

An additional rule to account for this may look like this:

select * from signature s join launchd d ON d.program_arguments = s.path where d.name like ‘com.apple.%’ and signed=1 and authority!=’Software Signing’and d.run_at_load=1;

Binaries signed by Apple Inc, will have an authority value of “Software Signing” with no team identifier. Attackers may also attempt to bypass code signature checks using the technique outlined in this blog post by Josh Pitts. In osquery (version 3.2.6 and prior), it is possible for adhoc or unsigned code to show an authority value of “Software Signing” when this technique is used. To account for this, future versions of osquery will provide us with the architectures used within FAT bundled executables.

The example below using osquery pre-release version 3.2.9, shows an attempt to bypass code signature checks. On the second line, we see a adhoc signed binary (bash_adhoc), with a i386 architecture, an identifier of com.apple.bash, yet no authority. This is suspicious as bash on a macOS system (as shown in the first line), should be signed and have an authority value of “Software Signing”.

Executables using the i386 architecture should be scrutinized as this a major condition needed to bypass code signature checks. Also, Apple has prioritized the use of x86_64 binaries, which makes i386 code running on a system unusual. An example of a legitimate Apple signed binary is shown below. Be sure to notice the matching authority and cdhash (SHA1 hash of the application Code Directory) values in addition to a x86_64 architecture value.

Here is an updated rule that accounts for attempts to bypass code signing checks:

select * from signature s join launchd d ON d.program_arguments = s.path where d.name like ‘com.apple.%’ and signed=1 and authority!=’Software Signing’ and d.run_at_load=1 and arch=’i386’;

The rules presented in this post can be added to a query schedule in osquery, to run at a set interval across your fleet. As always, test your rules before use in production to cut down on false positives that may occur as a result of your unique environment.

Happy hunting!