Introduction

Let's look at a diagram from the Snort++ manual (the second diagram in the link).
This diagram shows how Snort++ processes a packet. In the upper level, decoders make sure that there are no anomalies in the protocol headers (e.g., Ethernet, IP, or TCP headers), stream inspectors make sure that the clients and servers that generated the UDP and TCP streams
Since a Flow is clearly defined (it's a class), we will soon talk in terms of Flows rather than "streams" or "sessions". For now, it's enough to understand that packets with the same endpoints, protocol, and ports belong to the same stream. I will discuss Flows in a moment.
are behaving properly, service inspectors make sure these same clients and servers are communicating with the application-layer protocols (e.g., HTTP) in a non-malicious manner, and the Rule Detection Engine ("detect")
"Rule Detection Engine" is my term. In my opinion, "Detect" is ambiguous. Depending on the context, "detect" (and the related term "DetectionEngine") in Snort++ can indicate the entity that looks in a packet (and the other packets in the stream) for matches with Snort++ rules or it can indicate the entity that looks for matches with the rules *and* the Inspectors that look for suspicious behavior. Therefore, to avoid ambiguity, I use the term "Rule Detection Engine" for the entity that looks for matches between a packet (and the other packets in the stream) and the Snort++ rules.
looks for matches between a packet (and its stream) and the Snort++ rules
a Snort++ rule typically (although there are exceptions) contains the signature of a malicious attack and the action that should be taken if there's a match with a packet
. In the lower level, AppId is a (fairly complex) inspector that attempts to determine which application (e.g., AnyPC) or web application (e.g., Facebook) generated the packet. (I discuss the "firewall" in a side note below.)
I don't like the diagram above. In my opinion, the diagram above is incomplete and arguably misleading. Let's improve the diagram.
Decoders are implemented as objects of Codec-derived classes. Codec's most important method is decode(). For example, EthCodec objects look for anomalies (e.g., an improper length) in an Ethernet header using EthCodec::decode().
Stream inspectors and service inspectors are only two types of inspectors. Since these are only two inspectors among many, I've combined them under a single "Inspectors" group (with the exception of the probe inspectors - the probe inspectors are later in the pipe). Other types of inspectors are packet, binder, wizard, network, and probe, which we'll discuss in the coming sections. These inspectors are implemented as objects of Inspector-derived classes. Inspector's most important method is eval(). An Inspector's eval() method typically looks for anomalies (e.g., Smtp::eval() looks for anomalies in SMTP streams) but can do other asks as well
For example, Wizard::eval() doesn't look for anomalies but instead tries to find the appropriate service Inspector for a given TCP or UDP stream. If Wizard::eval() sees the string "GET" early in a packet stream, it will match up the stream with the HttpInspect Inspector. We'll discuss the Wizard in depth in the TCP example.
.
The Rule Detection Engine ("detect") is implemented in part by Multi-Pattern Search Engines (MPSEs), which allow Snort++ to look for multiple (typically thousands) of rule contents simultaneously. We'll talk in detail about the Rule Detection Engine and MPSEs later.
The bottom level, AppId (implemented by AppIdInspector) and "Other", consists of "subscribers". The subscribers "subscribe" (e.g., AppIdInspector) to event types (e.g., HTTP_REQUEST_HEADER_EVENT_KEY) and the "publishers" (Inspectors and - at least theoretically - the Rule Detection Engine) "publish" events. This allows the publishers (e.g., HttpInspect) to pass data to subscribers so that the subscribers can analyze this data. For example, HttpInspect can extract (with help from the HttpCutter class) an HTTP header from an HTTP stream and pass this header (by publishing it) to AppIdInspector (and any other Inspector who subscribes to this specific event type). AppIdInspector can then determine the browser being used in the HTTP stream from the header's User-Agent field
You'll notice that "Firewall" has disappeared in the revised diagram. Although the manual doesn't explicitly say that the "Firewall" is a subscriber, the presence of the "Firewall" in the bottom level seems to me to imply that the "Firewall" is a subscriber. Firewall functionality exists in certain DAQ (Data AcQuisition) modules (which grab packets from the wire and optionally manipulate or drop them before handing the potentially modified packet off to the Decoders) and Snort++ is capable of dropping packets if Snort++ is run in "inline" (i.e., IPS - Intrusion Prevention System) mode, but there is no Snort++ code that has firewall functionality that subscribes to any event so I've left it out of the diagram.
. The diagram of the revised packet processing pipe above is a little better. Unfortunately, this diagram (as well as the diagram that it improved) give the impression that all of these entities (Decode, Inspectors, Detect, etc.) are invoked for every packet. This isn't the case, as we'll soon learn.
name:

website:

comment:


UDP Packet Processing Pipe - Client to Server

The packet processing pipe looks different for different protocols and even for different packets in the same protocol. Let's see how the pipe looks for a UDP stream. Specifically, let's look at a UDP DNS query packet and the corresponding DNS response packet.
First off, you'll notice that the subscribers are missing in the diagram directly above. I wanted to keep it as simple as possible in the beginning. As an example of a subscriber, I discuss the HttpInspect subscriber in detail elsewhere.
You'll also notice that there are specific Inspector types and a UdpSession that are in my diagram that are not in the revised diagram. We'll talk about these in the following sections.
Before we talk about the UDP packet processing pipe, let's briefly discuss DAQ.
name:

website:

comment:


DAQ (Data AcQuisition)

The DAQ library is used by Snort++ to grab packets (technically, frames) from a network interface or PCAP file and hand them off to Snort++. Before handing a packet off to Snort++, DAQ can modify it (and even drop it). In a typical setting when using Snort++ as an IDS (Intrusion Detection System) or IPS (Intrusion Prevention System)
I do not discuss Snort++'s IPS features in this documentation. If this documentation is well received and regarded as useful, I will cover this in the future.
, each time a packet is received by Snort++ from DAQ on an interface on which Snort++/DAQ is listening or from a packet capture (PCAP) file that Snort++/DAQ is processing, packet_callback() is called (with a pointer to the packet as its last argument). packet_callback() is the function that implements the packet processing pipe.
Snort++ is multi-threaded and each interface or PCAP file can be assigned its own thread. Note that if there are multiple interfaces on which Snort++ is listening or multiple PCAP files that Snort++ is processing, there can be multiple packet processing threads running concurrently (preferably on their own processor/core) and each thread will have its own packet processing pipe.
name:

website:

comment:


Decode

Codec objects "decode" packets that DAQ grabs on an interface or in a PCAP file
Instead of saying "objects of Codec-derived classes", I typically say "Codecs". (An EthCodec object is an example of an object of a Codec-derived class.) I do this with other classes as well. For example, instead of saying "Binder object" or "Wizard object", I simply use the terms "Binder" or "Wizard".
Codecs don't look at reassembled or "pseudo" packets - I'll discuss pseudo packets in the TCP example.
. That means that the Codecs look for anomalies in the headers (e.g., Ethernet, IP, TCP) at the beginning of a frame/packet. The Codecs inspect the packets fairly superficially; the inspection by the Codecs is not as thorough as the inspection by the Inspectors (e.g., service Inspectors), which happens later in the pipe.
Unlike the Inspectors, all Codecs are available to decode packets. This means that it is not necessary to specify the Codecs in the Snort++ configuration file (typically snort.lua) as you must do for Inspectors
If you wish to deviate from the default settings for a specific Codec, then you can specify the settings in the configuration file (typically snort.lua):
udp = { deep_teredo_inspection = true, }
Note that very few of the Codecs can be configured.
.
name:

website:

comment:


Codec Internals

During Snort++'s initialization, Snort++ creates (i.e., instantiates) the Codecs and then builds up the s_protocols[] array from the available Codecs
DefaultCodec is the Codec that is invoked (i.e., its decode() is called) if Snort++ can't determine an appropriate Codec.
.
Associated with each protocol is a registered protocol ID. Each Codec knows its protocol ID and can be queried for it. For example, the protocol ID for IPv4 is 0x0800 and so Ipv4Codec::get_protocol_ids() returns 0x0800 ( ETHERTYPE_IPV4 = 0x0800).
Snort++ queries every available Codec to build up the s_proto_map[] array. For example, since Ipv4Codec::get_protocol_ids() returns 0x0800 (decimal 2048), Snort++ puts Ipv4Codec's position (2) in s_protocols[] into element 2048 of s_proto_map[].
After initialization - when Snort++ is processing frames/packets - the decode method corresponding to each header of the packet is invoked. As an example, as Snort++ is inspecting an Ethernet frame
(as Snort++ will of course do frequently if DAQ is grabbing frames from an Ethernet interface), it will invoke EthCodec::decode(), which looks for irregularities in the Ethernet header. If EthCodec::decode() encounters 0x0800 in the frame's EtherType field, Ipv4Codec::decode() will then be invoked to inspect the IPv4 header.
And if Ipv4Codec::decode() encounters 6 in the Protocol field, TcpCodec::decode() will be invoked to inspect the TCP header.
So how does Snort++ know where to begin? In other words, how does it know to start with an Ethernet header? DAQ, the module that grabs packets from the wire, knows the protocol ID of the interface from which it's grabbing the packets and can be queried for it
The variable grinder is set to the index number of the interface's link layer protocol. For our example, grinder will equal 12, the position of EthCodec in s_protocols[].
The DECODE phase also produces the layers[] array, which is used as a convenience by a few of the Inspectors later in the pipe.

.
name:

website:

comment:


Inspectors

There are several
Every Inspector provides an API (Application Programming Interface). This API declares how to create and destroy the Inspector, the protocol (TCP, UDP, etc.) for which it is responsible, and its Inspector type as well as a few other things. For example, Smtp provides the smtp_api, which declares that smtp_ctor is the constructor for a Smtp object, smtp_dtor is the destructor, its Inspector type is IT_SERVICE (i.e., it's a service Inspector), and that the Inspector only inspects PktType::PDU (i.e., pseudo) packets. (We'll talk about pseudo packets in the TCP example).
types of Inspectors: packet
The name "packet" for an inspector type is unfortunate since the meaning relies on the context to determine if it is a packet inspector in the general sense or if its Inspector type is packet.
, stream, binder, wizard, network, service, and probe. Multiple packet, network, and probe Inspectors can inspect the packet. There is only one (meaningful) stream Inspector. There are multiple service Inspectors but only one service Inspector will inspect a given stream. There will typically (but not always) be a single binder Inspector (a Binder object) and a single wizard Inspector (a Wizard object). For a given packet, the Inspectors inspect the packet in more or less the following order of Inspector types: packet, stream, binder, wizard, network, service, and probe (which is invoked after the Rule Detection Engine - I'll discuss this later). I say "more or less" because all of the different types of Inspectors are not always invoked and Binder can be invoked more than once.
So what exactly do all of these different types of Inspectors do? Let's revisit our DNS query/response (UDP) example.
name:

website:

comment:


packet (IT_PACKET) Inspectors

Currently, the only packet Inspectors are the PacketCapture and Normalizer Inspectors. The packet Inspectors are invoked before any of the other Inspectors. So if you're writing an Inspector (which would be somewhat uncommon) and you want that Inspector to inspect
As you'll see below, the PacketCapture Inspector doesn't really "inspect" a packet. Snort++ uses the term "inspector" somewhat loosely.
the packet before the rest of the Inspectors, make it a packet Inspector.
The PacketCapture Inspector simply writes a packet to a log file before the other Inspectors have a chance to do anything with the packet (e.g., reject it). Indeed, PacketCapture should inspect the packet before the other Inspectors; if PacketCapture was later in the pipe, the inspection of the packet might be abandoned before the packet made it to the PacketCapture Inspector and the dropped packet would not make it to the log file (which obviously isn't what you want from a generic packet capture).
The Normalize Inspector, which only inspects a packet if Snort++ is in "inline" mode (i.e., Snort++ is acting as an Intrusion Prevention System rather than an Intrusion Detection System), scrubs a packet (i.e., removes anything offending) so that the receiving system won't see it. Since Snort++ is presumably more concerned with what a receiver receives than what the sender sends, it does this scrubbing early in the pipe so that the later Inspectors inspect the packet that the receiver will actually receive
Note that the packet Inspectors only inspect packets that Snort++ grabs on interfaces and PCAP files (i.e., "DAQ-acquired packets"). In other words, the packet Inspectors do not inspect "pseudo packets". "Pseudo packets" are packets that are packets that Snort++ itself generates. Pseudo packets are made up of DAQ-acquired packets but they are not packets that Snort++ grabbed from an interface/PCAP file. It makes sense that packet Inspectorss do not inspect pseudo packets. In the case of PacketCapture, it'd be unusual to log a packet that did not come off the wire. In the case of Normalize, a pseudo packet is made up of DAQ-acquired packets that have already been normalized. There's no need for further normalization. (I discuss pseudo packets in greater detail elsewhere).
.
I say that Inspectors are "invoked". The Inspectors are invoked by calling the Inspector's eval() method. For example, PacketCapture is invoked by calling PacketCapture::eval().
name:

website:

comment:


Stream, Binder, Wizard, Service

One of the principle responsibilities of StreamBase, the only stream Inspector that actually plays a role in inspecting packets
This means that StreamBase is the only stream Inspector whose eval() method does something. The other stream Inspectors (e.g., StreamIp) are used for configuration purposes but their eval() methods do nothing (e.g., StreamIp).
For TCP, StreamBase (which gets help from the relevant Session - e.g., TcpSession) also reassembles DAQ-acquired packets into "pseudo" packets. We will learn more about this when we study our second example, a TCP stream.
, is to ask Binder to identify an appropriate service Inspector for a given stream. In order to accomplish this, Binder looks in its small database, bindings[], for a match with the packet. bindings[] is built from the binder configuration element in the Snort++ configuration file (typically snort.lua). Here's the binder configuration element in the default snort.lua:
binder = { -- these protocols do not yet have wizard support { when = { proto = 'udp', ports = '53' }, use = { type = 'dns' } }, { when = { proto = 'tcp', ports = '111' }, use = { type = 'rpc_decode' } }, { when = { proto = 'tcp', ports = '502' }, use = { type = 'modbus' } }, { when = { proto = 'tcp', ports = '2123 2152 3386' }, use = { type = 'gtp' } }, { when = { service = 'dce_http_server' }, use = { type = 'dce_http_server' } }, { when = { service = 'dce_http_proxy' }, use = { type = 'dce_http_proxy' } }, { when = { service = 'dce_smb' }, use = { type = 'dce_smb' } }, { when = { service = 'dce_udp' }, use = { type = 'dce_udp' } }, { when = { service = 'dce_tcp' }, use = { type = 'dce_tcp' } }, { when = { service = 'dnp3' }, use = { type = 'dnp3' } }, { when = { service = 'dns' }, use = { type = 'dns' } }, { when = { service = 'ftp' }, use = { type = 'ftp_server' } }, { when = { service = 'ftp-data' }, use = { type = 'ftp_data' } }, { when = { service = 'gtp' }, use = { type = 'gtp_inspect' } }, { when = { service = 'imap' }, use = { type = 'imap' } }, { when = { service = 'http' }, use = { type = 'http_inspect' } }, { when = { service = 'modbus' }, use = { type = 'modbus' } }, { when = { service = 'pop3' }, use = { type = 'pop' } }, { when = { service = 'ssh' }, use = { type = 'ssh' } }, { when = { service = 'sip' }, use = { type = 'sip' } }, { when = { service = 'smtp' }, use = { type = 'smtp' } }, { when = { service = 'ssl' }, use = { type = 'ssl' } }, { when = { service = 'sunrpc' }, use = { type = 'rpc_decode' } }, { when = { service = 'telnet' }, use = { type = 'telnet' } }, { use = { type = 'wizard' } } }
And this is what the resulting bindings[] array looks like:
The first four binder entries are hard-coded since the corresponding protocols "do not have wizard support."
The Wizard looks for identifying tokens to determine a protocol. Most protocols have obvious identifying tokens. For example, HTTP has 48 commands that the Wizard can look for. (We discuss the Wizard in our TCP example.)
An example of a protocol that does not have obvious identifying tokens is DNS. Specifically, a DNS request doesn't have any tokens that indicate the protocol (as HTTP has "GET", for example). On the other hand, a DNS response contains 0x8180, which is probably unique enough to be an identifying token.
If one of the first four hard-coded entries matches, the service Inspector corresponding to the entry is used for the stream. For our example, the Dns service Inspector is invoked to inspect the stream since we'll assume that the packet had a destination UDP port of 53. The Dns Inspector checks for a few anomalies (e.g., obsolete record types and experimental types) and harvests some information from the request or response (although a Snort++ analyst is not aware of this information unless he/she has enabled debugging output and has access to this debugging output).
Snort++ assigns a service Inspector to a stream by setting a field within the packet's Flow, which we'll discuss next.
name:

website:

comment:


Flow

I've been using the word "stream" up to this point but it's better to talk in terms of Flows since Flow is the class that Snort++ uses to implement the concept of "stream". Each packet that Snort++/DAQ encounters from an interface or PCAP file is associated with a Flow. Packets that share source and destination IP addresses and source and destination ports (as well as a few other parameters) are associated with the same Flow (Flow's key field is built using these parameters and is unique for each Flow). In this way, the chore of determining the appropriate service Inspector must only be done once for all of the packets in a Flow. After all, if a DNS packet has a source IP address/port of 161.38.221.250/6222 and a destination IP address/port of 8.5.5.5/53 and Snort++ determines that the packet uses the DNS protocol, then why can't Snort++ assume that subsequent packets (obviously within a time window) that have the same IP addresses and ports also use the DNS protocol? The answer is that this is a valid assumption.
Ultimately, Binder will set Flow's gadget field to point to the service Inspector appropriate for the Flow. For example, if the packets in a Flow use the HTTP protocol, the Flow's gadget field will ultimately point to the HttpInspect object. Once gadget is set, Binder has nothing more to do for a Flow.
Above, we briefly mentioned the Wizard. In our example (a DNS Flow), Binder matched on a hard-coded entry for our example Flow. For this reason, the Wizard was not needed. In the TCP example, we'll see how Binder invokes the Wizard to determine the appropriate service Inspector for a Flow that does not have a corresponding hard-coded entry.
We've just covered the binder and service elements of the packet processing pipe. So what about UdpSession?
UdpSession does a few simple checks but its main duty is to invoke the Wizard. In the case of a DNS packet over port 53, this is not necessary. Later, when we study a TCP Flow, we'll see that TcpSession (similar to UdpSession, also derived from Session) is very important.
name:

website:

comment:


Probe

The probe Inspectors are invoked after the Rule Detection Engine (which is described below). The probe Inspectors are typically interested in statistics (e.g., PerfMonitor, PortScan).
I postpone the discussion of network Inspectors until the TCP example, where it is relevant.
name:

website:

comment:


Rule Detection Engine

For payload-carrying packets, the payload is sent through the Rule Detection Engine, which includes one or more Multi-Pattern Search Engines (MPSEs). An MPSE allows Snort++ to search for thousands of the rules' "fast-pattern" contents in a packet simultaneously
If a rule has more than one content, only the best content - the "fast-pattern" content - is chosen to be inserted into the MPSE. Typically, the longest content is chosen as the fast-pattern content but there are a few other factors that are important as well.
. Snort++ doesn't bother checking the other options of a rule unless a match is found between a packet and the rule's fast-pattern content in the MPSE. The fact that Snort++ can search for thousands of contents simultaneously represents an enormous savings of CPU cycles
The savings in CPU cycles can't be overstated. Without the ability to simultaneously search for thousands of contents, Snort++ in its present form would not be possible.
.
There are a few MPSE algorithms (see search_method in the link); the default is the Aho-Corasick algorithm. The Aho-Corasick algorithm requires that a state machine be built prior to any searches for a specific set of strings
For the case of Snort++, the "specific set of strings" is the fast-pattern contents from the rules in the rule files. (Snort++'s sole rule file is currently sample.rules.)
that can be compared against data
For the case of Snort++, the "data" is the payload of a packet or a subset of the payload (e.g., an HTTP header).
.
I describe the Aho-Corasick algorithm here and its implementation in Snort++. In each Snort++ install, there are potentially hundreds of MPSEs created. I defer the discussion of how the different rule fast-pattern contents are distributed between the different MPSEs until we study a TCP session. (The distribution of rule fast-pattern contents among the MPSEs is more complicated and interesting for TCP than for UDP.)
Let's study a (completely contrived) example. Let's say that we're looking within an arbitrary string for the following set of strings (called the "dictionary")
(These would be analogous to the fast-pattern contents of rules.)
:
{a, ab, bab, bc, bca, c, caa}
Out of this dictionary, the MPSE state machine below will be built (we'll see in a moment how Snort++ implements this). Let's look for these patterns in the following string
This would be analogous to the payload of a packet.
:
abccab
1 / 11
We always start in state 0. The first character in the payload is an "a" (abccab) so we follow the black line to state 1.
2 / 11
State 1 is blue so we have a match
In the case of Snort/Snort++, we must then check the rest of the rule's options to make sure that the other options (e.g., from_server, to_server) also match.
, which must be reported to the user (dictionary word "a").
Our next character is a "b" (abccab)so we follow the black arrow to state 2, which is also a match and must be reported (dictionary word "ab").
3 / 11
Our next character is a "c" (abccab). Unfortunately, there is no black arrow to a "c". However, instead of starting over (i.e., going to state 0), we follow the blue arrow to state 3, which is the "fail state" for state 2.
Why don't we go back to state 0? Because we just saw a "b" and should get credit for the "b".
4 / 11
Note that we moved to the fail state (state 3) but are still focused on finding a path for "c".
From state 3, our next character is still the first "c". There is a black arrow to state 6 so we follow it. State 6 is a match state and must be reported (dictionary word "bc").
5 / 11
Note that there is a green arrow from state 6 to state 8. The green arrow indicates other match states that coincide with the current state and must also be reported (dictionary word "c"). This makes sense because "c" is also in our dictionary. Unlike blue arrows, which we follow upon failure, we never follow green arrows.
Our next character is a "c" (abccab). Since there is no black arrow from state 6 to a "c", we must follow the blue arrow to state 8.
6 / 11
Again, since there is no black arrow from state 8 to a "c", we must follow the blue arrow to state 0 (i.e., we must start over).
7 / 11
From state 0, we follow the black arrow to a "c" (state 8), which is a match and must be reported (dictionary word "c").
8 / 11
Our next character is a "a" (abccab). From state 8, we follow the black arrow to "a".
9 / 11
State 9 isn't a match state but a green arrow points to state 1, which is a match state and must be reported (dictionary word "a"). Our next character is a "b" (abccab). We never follow green arrows so from state 9, we attempt to follow a black arrow to a "b". There is no such black arrow so we follow the blue arrow to state 1 and attempt following a black arrow to "b" again.
10 / 11
In this case, there is a black arrow to state 2, which is a match and must be reported (dictionary word "ab").
11 / 11
Since there are no more characters in the payload, we are finished.



In summary, our MPSE state machine found the dictionary words "a", "ab", "bc", "c", "c" (a second time), "ca", and "ab" (a second time) in the payload "abccab".
As mentioned in the side note () from frame 2 in the slide show above, this discussion is focused on the MPSE. The options other than the content must be checked as well. We discuss how the other options are checked below.
name:

website:

comment:


Snort++'s Implementation of Aho-Corasick

To study Snort++'s implementation of the Aho-Corasick algorithm, we'll use the same dictionary {a, ab, bab, bc, bca, c, caa} and payload ("abccab") as before.
1 / 29
Two arrays, bnfaTransList and bnfaMatchList, are used in Snort++s implemention of the Aho-Corasick algorithm. As in the example above, we'll use the dictionary and payload ("abccab").
2 / 29
We start (as always) at the beginning of the array, in state id = 0 (SID=0). The first state corresponds to the first 258 indexes - 0 through 257 - in bnfaTransList.
3 / 29
State id 0 is different from the other states. Since the "Full Storage Bit" is 1 (it is 0 for the other states), all transitions are provided, even if the Next State ID (NSI) is 0 (if the NSI is 0, the transition isn't possible). This is done for performance since the vast majority of transitions in a typical state lead to state 0.
4 / 29
Our first character is an "A" (ABCCAB) ...
In the context of the MPSE, both the dictionary words (during Snort++'s initialization) and the payload (during packet processing) in Snort++ are capitalized. This produces some initial matches but these false matches are discovered before an event is reported to the user so it's no big deal. On the other (positive) hand, this capitalization reduces the size of the bnfaTransList array.
5 / 29
... so we move to state id (SID) = 1 (index 258).
6 / 29
The match bit is 1 so we have a match, which we look up in bnfaMatchList[] (dictionary word "A"). Since it's a match, we must check the rule's other options, which are contained in "ROT" (on the diagram). This corresponds to the rule_option_tree, which we'll discuss in a little bit.
7 / 29
Our next character is a "B" (ABCCAB) ...
8 / 29
... so we move to state id (SID) = 2 (index 261).
9 / 29
Again, the match bit is 1 so we have a match (dictionary word "ab").
10 / 29
Our next character is a "C" (ABCCAB). Unfortunately, there is no transition to a "C" ...
11 / 29
so we move to the fail state (FSI), state id (SID) = 3 (index 263).
12 / 29
We still haven't processed the first "C" ...
13 / 29
so we move to state id (SID) = 6 (index 272).
14 / 29
State 6 is a match (dictionary words "bc" and "c").
15 / 29
Our next character is another "C" (ABCCCAB). There's no transition to a "C" from state 6 ...
16 / 29
... so we move to the fail state (FSI), state id (SID) = 8 (index 277).
17 / 29
Again, there's no transition to a "C" from state 8 ...
18 / 29
... so we again move to the fail state (FSI), state id (SID) = 0 (index 0).
19 / 29
From state 0, there is a transition to a "C" ...
20 / 29
so we move to state id (SID) = 8 (index 277).
21 / 29
State 8 is a match (dictionary words"c").
22 / 29
Our next character in the stream is an "A" (ABCCAB). From state 8, there is a transition to an "A" ...
23 / 29
... so we move to state id (SID) = 9 (index 280).
24 / 29
State 9 is a match (dictionary word "a").
25 / 29
Our next character is a "B" (ABCCAB). There is no transition to a "B" ...
26 / 29
... so we go to the fail state (FSI), state id (SID) = 1 (index 258).
27 / 29
From state 1, there is a transition to a "B" ...
28 / 29
... so we go to state id (SID) = 2 (index 261).
29 / 29
State 2 is a match (dictionary word "ab"). Since there are no more characters in the data stream (payload), we're done.

name:

website:

comment:


Rule Option Evaluation

Recall that Snort++'s implementation of the Aho-Corasick algorithm (as well as all of the other Multi-Pattern Search Engine algorithms) only looks for the fast-pattern contents from each rule in Snort++'s rule files (currently sample.rules). The other options in a rule (indicated by "ROT" above) are not checked unless this fast-pattern content is found in a packet.
Since the focus of the previous discussion was the fast-pattern contents, I simplified the diagrams. Here's the extra detail that shows the other options of the rule
In the diagram below, "Policy 0" points to a RuleTreeNode. A Snort++ installation can have multiple Policys. A Policy contains the configuration settings that apply to a subset of the traffic (if there is only one Policy - the default Policy - the Policy will apply to all of the traffic). Multiple Policys are present if the file command is used in the snort.lua binder configuration element.
{ when = { vlans = '1024' }, use = { file = 'vlan1024.lua' } },
In this case, a Policy will be created for VLAN1024 traffic, which will be handled according to the configuration settings in the vlan1024.lua file. For example, if the systems on VLAN1024 were susceptible to ARP spoofing, the ArpSpoof Inspector could be enabled for VLAN1024 traffic (with the arp_spoof configuration element in snort.lua).
Rule sets are unique for each Policy. In other words, each Snort++ configuration file (e.g., snort.lua, vlan1024.lua) specifies the rules that apply to its corresponding Policy (the default Policy for snort.lua and the VLAN1024 Policy for vlan1024.lua). Because a rule from one ruleset can differ from a rule from another ruleset only by its associated Policy, the arrangement above can allow the detection_option_tree_node structs to be shared between two rules that differ only by their Policy. This arrangement can save memory.
.
If there is a match with a rule's fast-pattern content, all of the rule's options (typically including the fast-pattern content
The fast-pattern content is not always checked a second time. If the nocase as well as a few other content modifiers are not used, the fast-pattern content doesn't need to be checked. This makes sense because the capitalized version of the content was already found (and case doesn't matter because of the nocase content modifier) in the packet (and it doesn't matter where it was found because no depth or other positional content modifiers were used).
) are checked against the packet.
Options are implemented by Snort++ as classes derived from IpsOption. These IpsOption-derived classes are pointed to by detection_option_tree_nodes, which are in a chain. The most important method of these IpsOption-derived classes are their eval() methods. The eval() methods check to see if a packet matches up with their respective options. For example, ContentOption::eval() checks to see if a packet contains a specific content.
For the example above, if the content "CWD incoming" exists in an MPSE and the string "CWD incoming" is somewhere in the section relevant to the MPSE, Snort++ checks flow (flow:from_client) as well as the content (content: "CWD incoming") against the packet by (indirectly) calling eval() of the specific IpsOption-derived class (for our example, FlowCheckOption::eval() and ContentOption::eval).
After the options are determined to match (and only if there are matches), the rule's IP addresses and ports (and directional operator - which is almost always "->") are checked against the packet. The last detection_option_tree_node in the chain of Options points to the OptTreeNode, which contains the unique identifier of the rule (sid) and the message of the alert ("cd incoming detected") that is potentially sent and (most importantly) the RuleTreeNode. The RuleTreeNode contains the IP addresses and ports
If the packet is a pseudo packet, the ports are ignored. After all, the Wizard and service-based MPSEs exist to deal with the possibility that a Flow using a specific protocol can be sent on a non-traditional port - e.g., HTTP over port 555. We'll discuss service-based MPSE in greater detail in the TCP example.
.
It's important to understand that the check against the options is MPSE algorithm-neutral. This means that the rule's options will be checked in exactly the same manner, regardless which algorithm (e.g., Aho-Corasick, Hyperscan) is being used.
name:

website:

comment:


Packet Processing Pipe - Server to Client

Let's look again at the packet processing pipe for UDP.
We just studied the pipe for the packet going from the client to the server. Since the Wizard was able to determine the appropriate service Inspector (in our example, the Dns Inspector), the work Snort++ must do for the server to client packet (i.e., the DNS response packet) is significantly easier. Neither Binder nor Wizard must be invoked again since the appropriate service Inspector was already determined (the Flow's gadget field points to this Inspector). The Codecs are called to look for anomalies in the headers, the stream and probe and (most importantly) service Inspectors inspect the packet, and the Rule Detection Engine looks for matches with the rules
I didn't remove "stream" and "UdpSession" from the diagram since they are both called but they do essentially nothing.
.
name:

website:

comment: