Osquery is my favorite open-source security tool and Python is my favorite programming language so fusing them together allows us to engineer tools to detect threats. This blog post will build an Osquery-python extension to calculate the CommunityID of a network connection utilizing the Osquery-polylogyx extension pack to monitor network connections. In blog posts to follow, we will correlate network-based events generated by Zeek and host-based events generated by Osquery using the CommunityID. So follow me as your adventure guide on this development journey to make an Osquery extension with osquery-python.
NOTICE
Osquery version 4.20 was released on February 10th 2019 which contains zwass PR to include communityID generation in the Osquery project. This blog post should be viewed as a learning exercise on how to create an Osquery extension with osquery-python.
NOTICE
Background
What is Threat Detection Engineering?
In a nutshell, Threat Detection Engineering is related but not limited to the following high-level activities:
- Drive proactive identification of threats to the environment with the rapid deployment of detection controls.
- Liaise with internal teams and especially data owners, establishing relationships focused on spotting new detection opportunities.
- Make threat intelligence consumable by integrating it with SIEM and other tools that are part of the security arsenal (SOAR, EDR, etc).
- Create custom code to aid the detection of malicious activity via security alerts (rules), reports (dashboards) or simply automation (scripting).
- Establish a continuous quality assurance process, with special attention to internal users/customers feedback (SOC, monitoring teams — in special).
What is Osquery?
Osquery exposes an operating system as a high-performance relational database. This allows you to write SQL-based queries to explore operating system data. With Osquery, SQL tables represent abstract concepts such as running processes, loaded kernel modules, open network connections, browser plugins, hardware events or file hashes.
What is communityID?
CommunityID is a new feature being implemented by networking security applications such as Zeek and Suricata. A CommunityID is a hash of the tuple (destination IP address, source IP address, destination port, source port, protocol) and this tuple defines a unique connection. For example, let’s say Suricata detects malicious activity and when you examine the details of the alert it will include a unique hash as the value of communityID.
What is osquery-python?
In Osquery, SQL tables, configuration retrieval, log handling, etc are implemented via a simple, robust plugin and extensions API. This capability provides the ability to create Osquery extensions in Python.
What is Osquery-polylogyx exetnsions?
PolyLogyx OSQuery Extension (plgx_win_extension.ext.exe) for Windows platform extends the core osquery on Windows by adding real time event collection capabilities to osquery on Windows platform. The capabilities are built using the kernel services library of PolyLogyx. The current release of the extension is a ‘community-only’ release. It is a step in the direction aimed at increasing osquery footprint and adoption on Windows platform. With the extension acting as a proxy into Windows kernel for osquery, the possibilities can be enormous.
Install/Setup Osquery-python environment on macOS
Create a directory and install Python packages
git clone https://github.com/CptOfEvilMinions/osquery-py-communityid
cd osquery-py-communityid
virtualenv -p python3 venv
source venv/bin/activate
pip3 install -r requirements.txt
Code breakdown
def name(self):
First, is the “@osquery.register_plugin” Python decorator which basically calls additional functionality to register this code as an Osquery plugin. Next, a class is defined for this plugin using the “osquery.TablePlugin” object and this class contains all the code necessary for this plugin. Lastly, for this section, we have the “name” function which simply returns the name of the table to be registered within Osquery.
def columns(self):
This section defines the columns of the table and the variable type of each column.
def generate(self, context):
This section contains all the logic for an Osquery extension. The context variable being passed into the function contains the values being passed into Osquery. For example, if we query the communityID table (which doesn’t exist yet) with the following query: select * from community_id WHERE src_ip='1.1.1.1' AND src_port=5858 AND dst_ip='8.8.8.8' AND dst_port=80 AND src_port=5858 and protocol=6;
the context variable will contain (first screenshot below) the table name, the key-value pairs of dst_ip, dst_port, src_ip, src_port, and protocol from the query.
First, our plugin converts the context variable which is a string into a Python dictionary using the builtin JSON module. Next, the code iterates over the dictionary and extracts the necessary values such as dst_ip, dst_port, src_ip, src_port, and protocol. If the dictionary contains a key-value pair that we don’t need, Python is instructed to skip it.
Next, our code initiates the CommunityID Python object. Then the code evaluates the protocol variable to numerical integers which represent a protocol type, 6 is TCP and 17 is UDP. If the protocol value matches an integer, the communityID is calculated based on the tuple of (src_ip, src_port, dst_ip, dst_port) and a specific function is called based on the protocol (make_udp(), make_tcp()). If the protocol is unknown an error is printed which can be used for debugging. Lastly, the code constructs a dictionary to be returned which consists of the key-values passed into the table and the newly calculated communityID.
if __name__ == “__main__”:
This section is pretty simple but the important piece is the name
and version
keys. The name key is the name used to register the extension by Osquery and the version key will specify the iteration of the extension.
Test extension
- Open a Terminal instance
osqueryi --nodisable_extensions
SELECT value FROM osquery_flags WHERE name = 'extensions_socket';
- Copy value returned
- Open a new Terminal instance
python ./osquery_community_id.py --socket <Value returned above by Osquery>
- Return back to the Osquery Terminal instance
SELECT DISTINCT p.local_address, p.local_port, p.remote_address, p.remote_port, p.protocol, p.state, c.community_id FROM process_open_sockets as p JOIN community_id as c ON c.src_ip= p.local_address AND c.src_port=p.local_port AND c.dst_ip=p.remote_address AND c.dst_port=p.remote_port AND p.protocol !=0 AND p.remote_port !=0 AND c.protocol=p.protocol;
Install/Setup Osquery and Osquery extension on Windows 10
Install Python3 and PIP modules
- Open browser and browse to https://www.python.org/ftp/python/3.8.1/python-3.8.1-amd64.exe
- Install Python3
- Check “Install launcher for all users”
- Check “Add Python to PATH”
- Select “Install Now”
- Open a Powershell instance
pip install osquery==3.0.6
pip install communityid=1.1
pip install pywin32
Install Osquery on Windows 10
- Open a browser and browse to https://pkg.osquery.io/windows/osquery-4.0.2.msi
- Polyglogyx – “It has been built and tested with 3.2.6. It also works with 3.3.0 and 4.0.x”
- Install Osquery
Test extension on Windows 10
- Open a Powershell instance as Administrator
cd 'C:\Program Files\osquery'
New-Item -Path "C:\Program Files\osquery" -Name "Extensions" -ItemType "directory"
Invoke-WebRequest -Uri https://raw.githubusercontent.com/CptOfEvilMinions/osquery-py-communityid/master/osquery_community_id.py -OutFile 'C:\Program Files\Extensions\osquery_community_id.py;
.\osqueryi.exe --nodisable_extensions
SELECT value FROM osquery_flags WHERE name = 'extensions_socket';
- Open a Powershell instance
cd 'C:\Program Files\osquery\Extensions'
python .\osquery_community_id.py --socket <value from query above>
- Go back to PowerShell instance running Osqueryi
SELECT * FROM community_id WHERE src_ip='1.1.1.1' AND src_port=5858 AND dst_ip='2.2.2.2' AND dst_port=80 AND protocol=6;
.quit
CommunityID and Polylogyx extensions on Windows 10
Add Osquery-polylogy extensions
- DISABLE WINDOWS DEFENDER
- Open Powershell instance as Administrator
cd $home\Downloads
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
Invoke-WebRequest -Uri https://github.com/polylogyx/osq-ext-bin/archive/master.zip -OutFile osq-ext-bin.zip -MaximumRedirection 3
Expand-Archive .\osq-ext-bin.zip -DestinationPath .
cd osq-ext-bin-master
Copy-Item .\plgx_win_extension.ext.exe -Destination 'C:\Program Files\osquery\Extensions\plgx_win_extension.ext.exe'
(Get-Content -path .\osquery.flags -Raw) -replace "ProgramData","Program Files" | Set-Content -Path .\osquery.flags
Copy-Item .\osquery.flags -Destination 'C:\Program Files\osquery\osquery.flags'
Set-Content -Path 'C:\Program Files\osquery\extensions.load' -Value 'C:\Program Files\osquery\Extensions\plgx_win_extension.ext.exe'
Test Osquery-polylogy and win_socket_events table
Stop-Service -Name osqueryd
cd 'C:\Program Files\osquery'
.\osqueryi.exe --flagfile .\osquery.flags
.tables
SELECT * FROM win_socket_events;
.quit
Joining win_socket_events and CommunityID tables
.\osqueryi.exe --nodisable_extensions --flagfile .\osquery.flags
SELECT value FROM osquery_flags WHERE name = 'extensions_socket';
- Open a Powershell instance
cd 'C:\Program Files\osquery\Extensions'
python .\osquery_community_id.py --socket <value from query above>
- Go back to Osquery Powershell instance
SELECT s.local_address, s.local_port, s.remote_address, s.remote_port, s.protocol, c.community_id, s.pid, s.process_name
FROM win_socket_events as s JOIN community_id as c
ON s.local_address=c.src_ip AND
s.local_port=c.src_port AND
s.remote_address=c.dst_ip AND
s.remote_port=c.dst_port AND
s.protocol=c.protocol
WHERE action='SOCKET_CONNECT';.quit
OsqueryD setup
Compile Python extension with PyInstaller for OsqueryD
- Open a Powershell instance as Administrator
pip install pyinstaller==3.6
cd 'C:\Program Files\osquery\Extensions'
pyinstaller osquery_community_id.py
ls
Open Notepad as Administrator
Open C:\Program Files\osquery\extensions.load
- Paste the following:
C:\Program Files\osquery\Extensions\dist\osquery_community_id\osquery_community_id.exe
C:\Program Files\osquery\Extensions\plgx_win_extension.ext.exe - Save changes
cd 'C:\Program Files\osquery'
icacls .\Extensions /setowner Administrators /t
icacls .\Extensions /grant Administrators:f /t
icacls .\Extensions /inheritance:r /t
icacls .\Extensions /inheritance:d /t
.\osqueryi.exe --nodisable_extensions --flagfile .\osquery.flags
SELECT s.local_address, s.local_port, s.remote_address, s.remote_port, s.protocol, c.community_id, s.pid, s.process_name FROM win_socket_events as s JOIN community_id as c ON s.local_address=c.src_ip AND s.local_port=c.src_port AND s.remote_address=c.dst_ip AND s.remote_port=c.dst_port AND s.protocol=c.protocol WHERE action='SOCKET_CONNECT';
.quit
Invoke-WebRequest -Uri https://raw.githubusercontent.com/CptOfEvilMinions/osquery-py-communityid/master/conf/osquery.conf -OutFile 'C:\Program Files\osquery\osquery.conf'
Start-Service -name osqueryd
Discussion
Github PR for a communityID table
On Tuesday February 4th 2020, I participated in the Osquery office hours where zwass debuted his new PR to the Osquery project for communityID generation. So the timing of my blog post and this PR are very timely. In the hardware resources section below, I show the performance of my extension with regards to system resources being utilized. Unfornately, my Osquery-python implementation of CommunityID on Windows utilzies alot of system resources, so my hope is the PR above will be more preformant. Nonetheless, this exercise was a good learning experience and I hope this blog post is helpful for developing Osquery extensions on Windows.
Not easily cross-platform
As you probably noticed above, to get this osquery-python extension working it requires Python on the machine. Luckily, Linux and macOS come with Python pre-installed so it’s a matter of installing the proper PIP libraries to run this Python module. However, for Windows + OsqueryD, you need Python on the machine and you need to compile the Python code. This may require an elaborate build pipeline containing Windows machines to build new Osquery extensions. However, an alternative, osquery-go exists which allows cross-platform compilation, on any platform for any platform.
Dependency on Polylogyx for Windows
This blog post relies on the Osquery-Polylogyx extension pack to monitor Windows socket events because Osquery does not provide this functionality. The Osquery-Polylogyx extension pack is a work of art in my opinion and a pinnacle example of threat detection engineering. However, the project only works with version 3.2.6 and 4.0.2 of Osquery and it requires Windows Defender to be disabled. I will make the assumption that if your environment has an EDR product like Carbon Black it will block this for the same reasons as Windows Defender. However, if you can’t afford an EDR product this will get you as close as possible for free but without the prevention capabilities.
Hardware resources
Below are a couple of screenshots of the Python extension and the AMOUNT of hardware resources it consumes on Windows and macOS.
Windows system resources
The first screenshot is how many resources the Python module consumers on a Windows machine at the start and that is before ANY query is executed. The second screenshot is how many resources are consumed on a Windows machine when we run a simple query using the “community_id” table. The last screenshot is showing how many resources are consumed on a Windows machine when the Python module is compiled and is an extension of OsqueryD.
For reference, this is a Windows 10 VM running on 2 virtual cores and 4 GBs of RAM on a 2019 Macbook Pro. The resource consumption of this Python module could be due to my implementation but even so, those numbers are frightening. My hope is that when I re-write this extension using osquery-go I will get better performance.
macOS system resources
Below is a single screenshot of the Python extension and the amount of hardware resources it consumes on macOS. The Python extension consumes considerably fewer resources than it’s Windows counterpart.
Nice work! Osquery 2.2.0 has been released and includes the Community ID support on all platforms. I would be very curious to know if it provides acceptable performance for you.
Hey Zach, I actually did a shoutout to your contribution in the discussion section! I haven’t been able to test your implementation yet but I would assume it’s better.
Also, the Polylogyx extension pack only supports certain versions of Osquerey and I haven’t tested them with Osquery v4.2.0.
And that’s 4.2.0 *whoops*