Enforcing IT Compliance with osquery: Monitoring Third-Party System Extensions

Blog Author
Eric Kaiser

Monitoring system-level kernel extensions, modules, and drivers across all three major desktop platforms is a great way to ensure IT compliance, with a sprinkling of security investigation if you uncover something interesting.

 

In this tutorial we’re going to look at a few example queries and pivots for system-level extensions across Windows, Mac, and Linux. All of the examples below assume you’re using osqueryi in interactive mode, on a machine of the appropriate type.

 

Windows

There are no kernel extensions on Windows, but there are drivers that perform similar functions, and a drivers table in osquery. Let’s begin with a simple 'SELECT *' query:

SELECT * FROM drivers;

Not too bad, depending on what you have installed, but a lot of system devices are still listed. Let’s narrow it down:

 

SELECT * FROM drivers
WHERE provider != 'Microsoft';

 

Again, much better. There are still some strange entries such as the Document XPS Writer, but, let’s narrow the query further:

 

SELECT device_name, description, service, inf, class, provider FROM drivers
WHERE provider != 'Microsoft'
AND device_name != ''
ORDER BY device_name ASC;

 

Now, normally there would be one last step to get digital signature information. The drivers table has the image column, which is the path to the actual exe or bin. Joining against the authenticode table should get digital signature information for the drivers. Unfortunately, due to a bug in osquery that has been fixed but not pushed to a release, the image column is empty, and joining against an empty column won’t get us very far. Once the next release is pushed (4.5.2), this final step should be eminently possible.

 

Linux

Next up is Linux, and the kernel_modules table. This is a very simple table, essentially outputting the contents of /proc/modules. Let’s take a look at a simple query first:

 

SELECT * FROM kernel_modules;

 

And then filtering by ‘Live’, i.e. loaded:

 

SELECT name, used_by FROM kernel_modules
WHERE status = 'Live'
ORDER BY name ASC;

 

There is no way that I have found to show proprietary or “tainted” modules as opposed to upstream/OSS modules—at least with this table. One possible interesting additional use case might be to join this table against the augeas table, pointed at /etc/modules, to determine blacklisted modules and the like.

 

MacOS

On macOS, the osquery table that you will find most useful is kernel_extensions. This includes (almost) all kernel extensions on the system, both those that are actively loaded and those within the load search path or part of the pre-linked kernel. Extensions can also live nearly anywhere on the system; VMware Fusion keeps theirs in /Library/Application Support, while Google Drive File Stream’s fuse kext is inside the application bundle.

 

Be aware! Kernel extensions have changed a great deal over the past several releases of macOS, starting with user approval in High Sierra. They are also no longer ending with the deprecation of them in Big Sur. System extensions are the way forward, but the work to add them to osquery is not yet in progress.

Let’s start with a simple query:

 

SELECT * FROM kernel_extensions;

This is apt to return a lot of results. Adding count(*) to the query shows more than 200 results on my machine. Most of these are Apple kexts, and not terribly useful as a result, since they’re on every machine. Let’s narrow the results to only third-party kexts. Excluding both the kernel itself, and anything loaded from the SIP-protected /System/Library directory, and sorting by name for readability:

 

SELECT * FROM kernel_extensions
WHERE path NOT LIKE '/System/Library/%'
AND name != '__kernel__'
ORDER BY name ASC;

This should offer a much smaller set of results, and it also reveals an interesting potential pivot. On Catalina, loaded third-party extensions are copied to /Library/StagedExtensions. Let’s try filtering by that:


SELECT * FROM kernel_extensions WHERE path LIKE '/Library/StagedExtensions/%' ORDER BY name ASC;

Same results. Note that because we are filtering on StagedExtensions, this could theoretically miss unloaded but still present kernel extensions. Try running this query before and after you open a recent version of VMware Fusion, for example.

 

The kernel_extensions table is very useful, but it doesn’t provide an important piece of information: Who signed the kernel extension? For that we turn to the signature table. We’ll join on the path, and substring just the important bits:

 

SELECT DISTINCT kx.name, substr(kx.path, 26) AS orig_path,
substr(s.authority, 27) AS authority
FROM kernel_extensions kx
JOIN signature s USING (path)
WHERE kx.path LIKE '/Library/StagedExtensions/%'
ORDER BY kx.name ASC;

Some explanation:

  • When joining multiple tables, you can shortname the tables to allow for easier reference: kernel_extensions becomes kx in this example.
  • The path column in kernel_extensions has /Library/StagedExtensions in front of every kext; the substr selection removes that (“select all characters starting from 26, going to the end”).

There are of course even further tables that could be joined against; the hash table for example would provide MD5 or SHA-256 sums of the kext binaries for checking against VirusTotal. Here’s an updated version of a query I wrote for QueryCon in 2019, that does just that:

 

WITH kext_bins AS (SELECT path FROM file
   WHERE directory IN
       (SELECT path || '/Contents/MacOS/'
       FROM kernel_extensions
       WHERE path LIKE '/Library/StagedExtensions/%')
)
SELECT substr(s.path, 26) AS orig_path, 
substr(s.authority, 27) AS authority, h.sha256
FROM signature s
JOIN hash h USING (path)
WHERE path IN kext_bins
AND s.arch = 'x86_64';

Again, some explanation:

  • kext_bins is a subquery that appends the actual binary path inside the kext bundle
  • Joining to hash with that path results in the SHA-256 hash of the actual binary
  • signature reports both 32-bit and 64-bit kexts, so we exclude that by ignoring any non-x64 binaries

As I mentioned in the beginning, all of these examples were done using the osqueryi tool. For IT compliance and other investigative work, setting these as scheduled queries to run using osqueryd would give you historical reference, allowing you to track changes over time, and even run data analysis on outliers in your fleet. How many users have installed any kexts at all? How many users are running unique kexts? How has that changed over time?

 

I hope you find these queries useful, and a springboard for your own investigations. Good hunting!

 

Learn how to use osquery for threat hunting in our free on-demand webinar.

 

Image by Jürgen Jester from Pixabay.