Leveraging OSQuery for macOS Post-Exploitation

Cedric Owens
Red Teaming with a Blue Team Mentality
5 min readApr 19, 2020

In this blog post I will share some fun testing I did purely out of curiosity. From a defensive perspective, OSQuery is an awesome capability that can give defenders a ton of visibility and help answer important host based questions during an investigation. I personally am a fan of OSQuery and I found it to be so valuable that I asked the question “Can an attacker remotely run OSQuery searches on a macOS host and obtain useful host information?”. So this post shares the observations I have found with leveraging OSQuery remotely via a macOS post exploitation tool.

For anyone who may not be familiar with OSQuery, in a nutshell it is a SQL-based cross platform server/endpoint framework that allows organizations to query their servers/endpoints and ask all sorts of questions (ex: running processes, local users, last logged in users, etc.). OSQuery is open source and was developed by the awesome and talented security team at Facebook. You can find more info here: https://osquery.io/. I am not sure how many organizations currently leverage OSQuery but my guess is that more organizations will utilize OSQuery as time goes on given how well it works and with it being cross platform.

I tend to focus a lot on macOS as a personal interest of mine. Given the immense value that OSQuery provides in terms of data that you can pull back from servers/endpoints, I started running some tests to see if I could do the following from from a post exploitation tool on macOS:

  • remotely check and see if OSQuery is present
  • remotely run OSQuery commands and pull back host data
  • a look at how easy it is to detect this

First, after ensuring OSQuery was installed, I looked at the macOS installation docs (https://osquery.readthedocs.io/en/stable/installation/install-macos/) to get info on how to run OSQuery and some default file locations. In particular, the “osqueryi” binary (by default installed at: /usr/local/bin/osqueryi) was of interest since it could be used to locally run OSQuery searches. Running “osqueryi” will drop you into an interactive OSQuery shell:

$ osqueryi

osquery>

From there you can run “.tables” to list out all available OSQuery tables to query against:

osquery>.tables

Then from there you can simply run your SQL statements to start pulling data back:

osquery>select * from system_info;

Once I was comfortable with running the osqueryi binary and pulling host data via SQL statements, I then looked at how to run OSQuery searches remotely via a macOS post exploitation tool that can run shell commands. In essence I tested the ability to remotely run the following type of terminal command:

$ echo “<sql_query>” | osqueryi --json

An example would be:

$ echo “select pid,name,path,cmdline from processes;” | osqueryi --json

Below is more info on my test setup.

My Test Setup:

  • a test macOS Catalina host with OSQuery: victim
  • a remote command and control server: attacker
  • a post exploitation tool for macOS that I wrote (MacShellSwift: https://github.com/cedowens/MacShellSwift): tool (client code written in Swift)
  • network setup: simple 192.168.1.0/24 private network; host and victim were separate hosts with different IPs

Next I added Swift code to invoke OSQuery searches in the MacShellSwift mach-o binary. A few examples are below:

Check and see if OSQuery is present:

List out local users using OSQuery:

Get process info using OSQuery:

Pull back wifi info using OSQuery:

List running apps using OSQuery:

These are just some examples. Once I added the code above and corresponding server side code, I then tested it out to see if this would work remotely (I have added these examples above and a few more to my MacShellSwift project on github: https://github.com/cedowens/MacShellSwift).

Observations:

  • The test did work successfully. I was able to successfully remotely invoke OSQuery and pull host data back to the C2 server
  • As this was tested on Catalina and I did not sign or notarize the MacShellSwift binary, I had to clear the quarantine attribute via the command line on the target host ($xattr -c <binary_name>) otherwise gatekeeper would stop it from running.
  • I did not test remotely searching all OSQuery tables but I was unable to remotely get data back from this OSQuery search: “select * from shell_history;”. This was a bit strange since this search works fine from the command line locally by echoing it into osqueryi ($echo “select * from shell_history;” | osqueryi).

Detection:

I ran Patrick Wardle’s ProcessMonitor.app (can download from: https://bitbucket.org/objective-see/deploy/downloads/ProcessMonitor_1.3.0.zip) which leverages the Apple Endpoint Security Framework (ESF) for event monitoring. Essentially I ran this in the background while remotely executing OSQuery commands through MacShellSwift and was able to find this activity in the logs as:

  • ES_EVENT_TYPE_NOTIFY_EXEC
  • path: /usr/local/bin/osqueryd
  • arguments: /usr/local/bin/osqueryi --json
  • ppid: MacShellSwift macho’s pid

The SQL query itself wasn’t captured so you would not know what exact data was pulled via OSQuery but you would be able to at least see that osqueryi was invoked from a parent process belonging to a mach-o file (in this case the MacShellSwift mach-o binary). So one method for detection could be to search for osqueryi parent processes. I would guess in most organizations with OSQuery deployed on macOS hosts that few people are running osquery manually (maybe a few devs, engineers or IT staff members). So you could get a feel for what the common ways are for invoking osqueryi in your environment and look for anomalies (ex: a mach-o binary or a python script invoking osqueryi).

--

--

Cedric Owens
Red Teaming with a Blue Team Mentality

Red teamer with blue team roots🤓👨🏽‍💻 Twitter: @cedowens