This post describes process of building a custom dynamic preprocessor plugin for the Snort Network Intrusion Detection / Prevention System (IDS / IPS).
Snort is rules-based IDS. Although Snort rules have a simple structure, the number and variety of options within the Snort rule syntax allows reasonably complex analysis of packets under inspection to be performed. This is fine for situations where the symptoms of the threat being defended against can be
encapsulated within single logical expression (for example: “if the packet under inspection uses the TCP protocol, comes from the external network, is directed towards a database server within the internal network, and has the characters “0x02″ at an offset of 23 bytes within its payload, then drop the packet”). However, if more complex analysis is needed then a different approach is required.
A Snort preprocessor is a type of plugin which can be used to contribute additional processing functional to the core engine. Dynamic preprocessors are self contained libraries written in C which can be compiled independently of the main code base. The library is then included by placing it in a directory where Snort can find it at start-up, and updating the Snort configuration file so that Snort is aware of it.
In a previous post, I demonstrated a technique for exploiting a known vulnerability within a commercially available chat server application. The exploit involves delivering a payload containing reverse TCP shellcode which is then used to gain control over the compromised host.
The ultimate aim of this exercise is to develop a dynamic preprocessor which will be capable of detecting the pattern of data within a packet payload which indicates that a buffer overflow attack is in progress. I plan to do this in two parts:
The first part (this post) will describe the end-to-end process of putting together a template for the preprocessor, building it, checking that it gets loaded by Snort at runtime and then verifying that we can cause it to generate alerts by sending malicious traffic across the network segment it is monitoring, and checking that these alerts are then fowarded to an SIEM for analysis.
The second part (yet to be written at this point in time) will focus on the implementation detail of the main processing function within the preprocessor. The aim is devise an algorithm which can detect the tell-tale signs of a buffer overflow attack, without relying rigidly on the details of any specific vulnerability or known attack strategy. By understanding the general anatomy of a buffer overflow attack, I hope it will be possible to make informed judgements about the meaning of data values within the payload by looking at them within the context of the whole payload, rather than just determing their absence or presence, as a rule does. The approach taken by Stig Andersson, Andrew Clark, and George Mohay in their research paper “Network based buffer overflow detection by exploit code analysis” looks interesting and may provide some useful ideas.
It’s quite possible to achieve the functionality I’ve implemented here in Part 1 just using a rule; the reason I’ve split the development into two parts is partly because I’m doing this over a period of a few weeks and I’m using this blog as a kind of incremental work journal, and partly because I think this structure might of more use to people who just want to know how to get a template dynamic preprocessor project up and running but have some other purpose in mind for it.
The test lab was put together with the help of Tony Robinson’s excellent book “Building Virtual Machine Labs: A Hands-On Guide“. The network architecture is divided into a number of segments using the virtual networking facilities provided by VirtualBox’s VMs. This allows the guest VMs to be segregated logically according to their respective functions within the test lab. This approach enforces security by filtering traffic through a VM which acts as a firewall and a network gateway (pfSense). In this manner, traffic between the virtual and local physical networks, and traffic between the IPS (Snort) VM and the SIEM (Splunk) VM and the Snort management interface host, can be monitored and controlled separately.
In this setup, Snort is configured to be in-line between two network segments using a pair of virtual network adapters, rather than simply monitoring traffic passively. As well as allowing Snort to act as an IPS (ie by dropping packets) if required, a major advantage of this arrangement is that it allows one segment of the lab to be configured as a secure sandbox for examining malware if required. Shutting down the IPS VM will effectively isolate the segment containing the compromised host, which is a very useful “panic button” facility to have available, should things get out of control.
The nice people over at Snort headquarters have made a example dynamic preprocessor (Dymanic Preprocessor eXample) project available for download. The project provides the source code for an “empty” plugin, build scripts which take care of bringing all the required elements together and distributing the ouput binaries to the correct locations, plus a unit test script (bundled with some test data in the form of a .pcap file) to verify that the resultant plugin behaves as it should – in isolation, at least.
Although this project is really helpful in providing a head-start, the business of getting the plugin configured, registered with and loaded by Snort at start-up and called during execution takes a little more work, and not all of the steps involved are entirely intuitive. The remainder of this post explains how to get the preprocessor up and running in a test lab environment.
The steps involved in downloading, installing and building the example project as-is are listed here. Note that the source development header files for Snort itself are also needed to allow the project to be built. These won’t be present in an existing Snort installation unless it was built from source and so need will need to be downloaded separately.
As I mentioned previously, the aim in Part 1 of this post is just to add some simple functionality to the preprocessor and prove that it’s working, before moving on to more complex packet payload analysis functionality in Part 2. The following paragraphs describe what I did to acheive that objective.
Source Code Changes
Out of the box, the example preprocessor examines packets to determine whether the source or destination port number matches a pre-defined number. This number is defined in Snort configuration file, which needs to be modified to incude configuration values for the new preprocessor. I modified the source to allow the preprocessor to look for the presence of a sequence of hex values within the packet payload. This sequence represents the opcodes “JMP 06 NOP NOP” typically seen when an exploit which abuses Windows SEH is deployed.
Notice the call to DynamicPreprocessorData.alertAdd(…), which causes an alert to be generated when the required conditions are met. This method takes seven parameters: the ID of the preprocessor, the ID of the rule, the rule revision number, the alert classication number, the alert pritority, a message string and a reference to metadata pertinent to the alert (none in this case). NB: The use of term “rule” is used here even though we’re not dealing with a rule object; I suspect this is because alerts are normally generated by Snort rules, and the terminolgy used within the source probably isn’t generic enough to deal with alerts generated from other sources.
The alert classification number used in the above method call is derived from the alert classification file which maps classfication types to alert messages:
Constants defined in the preprocessor info header file sf_preproc_info.h determine the major and and minor version numbers, plus the name of the preprocessor (the name string value is important, as it is used to identify configuration values with the snort.conf config file):
Snort determines where to look for dynamic prepreocessors according to an entry in the snort.conf configuration file:
To ensure that the new preprocessor is loaded dynamically by Snort, either copy or symlink the libraries to the location specified in “dynamicpreprocessor directory” entry in the snort.conf configuration file:
Newer versions of Snort allows rules and events associated with preprocessors to be enabled and disabled individually via separate configuration file, however for the purposes of this exercise, we can just direct Snort to automatically enable all preprocessor rules with the following entry in the snort.conf configuration file:
While we’re modifying snort.conf, let’s add an entry to configure the preprocessor’s behaviour at runtime. The following line defines the value a port number; the packet being processed must have either a source or destination port value which matches this value or the packet will be ignored by the preprocessor:
To make debugging a little easier, let’s also ensure that Snort generates a vebose human-readable alert text file, as well as the standard unified binary format alert file:
The last step is to map the preprocessor GID and SID values to human-readable message strings which will be included in the generated alerts – this is achieved by adding entries to the gen-msg.map file:
Having made the required source code and config file changes, we can now rebuild the preprocessor and then restart Snort:
Examining the syslog log file, we can see that Snort appears to have loaded without errors:
…and has loaded the new dynamic preprocessor:
To test the functionality of the preprocessor, we launch an attack against the target host whose payload contains the shellcode for a Windows SEH exploit:
Detection and Alerting
By examining the Snort alert log file, we can see that the preprocessor has detected the SEH exploit opcode hex values and has generated an alert:
The last step in the verifcation process is to check that the Splunk universal forwarder correctly parses the alert and forwards the details as JSON formatted data to the Splunk SIEM for analysis. By issuing a simple search for the string “DPX” in the Splunk reporting interface, we can see that the alert has been received by the SIEM:
Expanding the alert JSON data shows that the alert contains the data we expect in each field:
Getting the example dynamic preprocessor example up and running took quite a bit of detective work and trial and error, but given the amount of extra processing scope and flexibility that preprocessors provide, over and above that provided by rules alone, I believe the investment in time and effort will prove to be worthwhile.
Hopefully the information presented here will be of help to anyone in a similar position. It’s worth noting that the post archives over at seclists.org are great source of information, as were some of the papers at the SANS UK Reading Room site.
In Part 2 of this post, I’ll be tackling the business of implementing the main processing method within the preprocessor, which will require devising an algorithm which can detect the tell-tale signs of a buffer overflow attack, without relying rigidly on the details of any specific vulnerability or known attack strategy.