What is the difference between signature detection and rule based heuristic identification?

Writing Your Own Preprocessor

In this section, we'll explore why and how you might write your own preprocessor plug-in. We'll accomplish the former by exploring the spp_telnet_negotiation.c preprocessor. We'll see the necessary components in a preprocessor, how it's plugged in to the Snort source code, and how it accomplishes its function. After this discussion, you'll be well on your way to writing your own preprocessor.

Over the course of this chapter, we've explored the following reasons to write your own preprocessor:

Reassembling packets

Decoding protocols

Nonrule or anomaly-based detection

In essence, you write your own preprocessor whenever you want to do something that straight rule-based detection can't do without help. Let's explore each of the previously listed reasons, to understand why they needed a preprocessor to fulfill the function.

Reassembling Packets

Signature-based detection matches well-defined patterns against the data in each packet, one at a time. It can't look at data across packets without help. By reassembling fragments into full packets with frag2, you can make sure that an attack doesn't successfully use fragmentation to evade detection. By reassembling each stream into one or more pseudo-packets with stream4, you attempt to ensure that the single-packet signature mechanism is able to match patterns across multiple packets in a TCP session. Finally, by adding state-keeping with stream4, you give this signature-matching some intelligence about which packets can be ignored and where a packet is in the connection. Packet reassembly preprocessors help to ensure that Snort detects attacks, even when the data to be matched is split across several packets.

Decoding Protocols

Rule-based detection generally gives you simple string/byte-matching against the data within a packet. It can't handle all the different versions of a URL in HTTP data without help, or at least without countably infinite rulesets. The http_decode preprocessor gives Snort the ability to canonicalize URLs before trying to match patterns against them. Straight rule-matching can also be foiled by protocol-based data inserted in the middle of data that would otherwise match a pattern. Both the rpc_decode and telnet_negotiation preprocessors remove data that could be extraneous to the pattern-matcher. The rpc_decode preprocessor consolidates all of the message fragments of a single RPC message into one fragment. The telnet_negotiation preprocessor removes Telnet negotiation sequences. Protocol-decoding preprocessors make string-matching possible primarily by forcing packet data into something less ambiguous, so that it can be more easily matched.

Nonrule or Anomaly-Based Detection

Rule-based detection performs well because of its simplicity. It's very deterministic, making it easy to tune for fewer false positives. It's also easy to optimize. However, there are functions that just can't be achieved under that model. Snort has gained protocol anomaly detection, but even this isn't enough to detect some types of attack. The portscan preprocessor allows Snort to keep track of the number of scan-style packets that it has received over a set time period, alerting when this number exceeds a threshold. The Back Orifice preprocessor allows Snort to detect encrypted Back Orifice traffic without creating a huge ruleset.

This third class of preprocessors expands Snort's detection model without completely redesigning it—Snort can gain any detection method flexibly. Preprocessors specifically, and plug-ins in general, give Snort the capability to be more than an IDS. They give it the capability to be an extensible intrusion detection framework onto which most any detection method can be built. Less spectacularly, they give Snort the capability to detect things for which there isn't yet a rule directive. For example, if you needed to have a rule that detected the word Marty being present in a packet between three and eight times (no more, no less), you'd probably need a preprocessor—Snort's rules language is flexible, but not quite that flexible. More usefully, what if you needed to detect a backdoor mechanism only identifiable by the fact that a single host sends your host/network UDP packets whose source and destination port consistently sum to the fixed number 777? (Note: this is a real tool.)

Without going quite that far, let's explore how a preprocessor is built.

Setting Up My Preprocessor

Every preprocessor is built from a common template, found in the Snort source code's templates/ directory. As you consider the Snort code, you should consider the following filename convention. We'll talk about the snort/ directory—this is the main directory you get when you expand the Snort source tarball or zipfile. Its contents look like this:

[[email protected] snort]$dollar; ls

acconfig.h config.h.in contribinstall-shmissing src

aclocal.m4 config.sub COPYINGLICENSE mkinstalldirsstamp-h.in

ChangeLog configuredoc Makefile.amrules templates

config.guess configure.in etc Makefile.insnort.8

The templates directory contains two sets of plug-in templates—to build a preprocessor plug-in, we want the spp_template.c and spp_template.h files.

[[email protected] snort]$dollar; ls templates/

Makefile.am spp_template.c sp_template.c

Makefile.in spp_template.h sp_template.h

You should take a look at these template files as you consider the Telnet negotiation preprocessor. This preprocessor is with the others in the snort/src/preprocessors directory.

[[email protected] preprocessors]$dollar; ls

Makefile.am spp_fnord.c spp_portscan2.h

Makefile.inspp_fnord.h spp_portscan.c

spp_arpspoof.c spp_frag2.c spp_portscan.h

spp_arpspoof.h spp_frag2.h spp_rpc_decode.c

spp_asn1.c #spp_http_decode.c#spp_rpc_decode.h

spp_asn1.h spp_http_decode.c spp_stream4.c

spp_bo.c spp_http_decode.h spp_stream4.h

spp_bo.h spp_perfmonitor.c spp_telnet_negotiation.c

spp_conversation.c spp_perfmonitor.h spp_telnet_negotiation.h

spp_conversation.h spp_portscan2.c

In the rest of this section, we'll explore the code in the file spp_telnet_negotiation.c, making references to the matching spp_telnet_negotiation.h header file as necessary. Remember, this book refers to the production Snort 2.0.0 code, which you can find on the CD accompanying this book. Let's start looking at this code:

/* Snort Preprocessor for Telnet Negotiation Normalization*/

/* $dollar;Id: spp_telnet_negotiation.c,v 1.14.2.1 2002/11/02 21:46:14 chrisgreen

Exp $dollar; */

/* spp_telnet_negotiation.c

*

* Purpose: Telnet and FTP sessions can contain telnet negotiation strings

* that can disrupt pattern matching. This plugin detects

* negotiation strings in stream and “normalizes” them much like

* the http_decode preprocessor normalizes encoded URLs

*

* Arguments: None

* Effect: The telnet nogiation data is removed from the payload

*

* Comments:

*

*/

The preprocessor starts out simply describing what its purpose is and how it can be called. You'll notice as we read through the code that the “Arguments” description in the previous comments is inaccurate—the code takes a space-delimited list of ports as an argument.

Before we continue reading code, we should talk about this preprocessor's purpose, so you understand what the code is doing. The best way to understand this thoroughly is to read the Requests for Comments (RFC) document describing the Telnet protocol.

OINK!

The Telnet protocol is described in detail in RFC 854, available via www.faqs.org/rfcs/rfc854.html. For even more comprehensive and easier-to-follow coverage, consider W Richard Stevens' TCP/IP Illustrated Volume 1. This is an essential and standard reference for understanding TCP/IP protocol implementations.

Telnet's creators knew that it would need to function between many devices, potentially with somewhat different levels of intelligence and flexibility. To this end, the Telnet protocol defines a Network Virtual Terminal (NVT), a “minimal” concept to which Telnet implementers could tailor their code. The protocol allows two NVTs to communicate to each other what options (extra features) they might or might not support. They communicate with escape sequences, which start with a special Interpret as Command (IAC) character. Following this character is a single-byte number, which codes a command. The command sent is usually a request that the other side activate/deactivate an option, if available, a request for permission to use an option, or an answer to a previous request from the other side. Most of these sequences, then, are three characters long, like this fictional one:

The protocol also allows for deleting the previous character sent via the Erase Character (EC) command and erasing the last line sent via the Erase Line (EL) command, both of which need to be accounted for in the preprocessor. It also allows for a No Operation (NOP) command, which tells it to do nothing—it's not clear why this is included in the protocol. Finally, it allows for complex negotiation of parameters of the options via a “subnegotiation” stream of characters, initiated with a Subnegotiation Begin (SB) character, followed by the option that it references, and terminated by a Subnegotiation End (SE) character. Such a sequence might look like this:

IAC SB SING HUMPTY-DUMPTY SE
255 250 53 1 240

There's more to Telnet than this, but this is enough to read and understand the preprocessor code. Let's get into that code now.

What Am I Given by Snort?

We'll now take an in-depth look at the preprocessor's code, exploring what each line of the code does. Commentary follows the lines of code lines that it references. If your C skills are rusty, don't worry—you'll probably find this discussion quite understandable. The Telnet negotiation preprocessor is one of the simplest preprocessors. Let's take a look at it together.

/* your preprocessor header file goes here */

#include <sys/types.h>

#ifdef HAVE_STRINGS_H

#include <strings.h>

#endif

The preceding lines just import standard C header files.

#include “decode.h”

#include “plugbase.h”

#include “parser.h”

#include “log.h”

#include “debug.h”

#include “util.h”

#include “mstring.h”

#include “snort.h”

The preceding lines import Snort's function prototypes, constants, and data structures, so that this plug-in can reference them. The plugbase.h header file, in particular, contains prototypes for the important functions that every preprocessor plug-in must call. Table 6.2 lists the other header files with their corresponding functions.

Table 6.2. Header Files and Their Corresponding Functions

Header FileFunction
sdecode.h Parses packets into data structures
parser.h Performs all input parsing (for example, snort.conf)
log.h Logs all packet-data, printing/formatting headers and data
debug.h Performs Snort's debugging, with enforcing granular levels of detail
util.h Miscellaneous utilitarian functions
mstring.h Provides string functions not pro vided by C standard libraries
snort.h Provides major data structures and Snort's primary functions

While not all of the header file listed in Table 6.2 are necessary, they've probably been included to keep things simple and maintainable for the programmer.

/* external globals from rules.c */

extern char *file_name;

extern int file_line;

The previous three lines are also standard—they allow the preprocessor to describe where its configuration directives came from. An example would be “line 43 of the snort.conf file.”

extern u_int8_t DecodeBuffer[DECODE_BLEN]; /* decode.c */

As of Snort 1.9.1, this function is specific to the telnet_negotiation preprocessor. The preprocessor prunes negotiation code by copying all non-negotiation data from the packet it's examining into a globally available DecodeBuffer. It then signals that the packet has an alternate form, allowing the detection engine to look at either form of the packet data, based on whether the rules it evaluates specify “rawbytes.” Oddly, even though rawbytes sounds like a more general option, it's implemented strictly for the benefit of Telnet.

OINK!

Rawbytes signals that the rule should look at the non-negotiation-modified version of the Telnet packet.

/* define the telnet negotiation codes (TNC) that we're interested in */

#define TNC_IAC 0xFF

#define TNC_EAC 0xF7

#define TNC_SB 0xFA

#define TNC_NOP 0xF1

#define TNC_SE 0xF0

#define TNC_STD_LENGTH 3

The first five constants define the numerical versions of the codes that we explored earlier. The last constant simply codifies the fact that any negotiation sequences are at least three characters long.

/* list of function prototypes for this preprocessor */

extern void TelNegInit(u_char *);

As we'll explore soon, the TelNegInit() function initializes the preprocessor when Snort first starts. It calls a function to parse the preprocessors arguments from the snort.conf file and adds the main work function (NormalizeTelnet()) to the list of preprocessors called to examine every packet. Every preprocessor must have one of these functions to perform these two tasks. It must also have a Setup function to link this one to the Snort codebase—we'll explore SetupTelNeg() soon.

extern void NormalizeTelnet(Packet *);

As we'll explore later, this function performs the real task of the preprocessor. The previously discussed Init function will register this with Snort's main preprocessor engine.

static void SetTelnetPorts(char *portlist);

This function parses the Telnet negotiation preprocessor's arguments and is called by TelNegInit(). It parses a simple port list into a data structure that NormalizeTelnet() can reference before trying to work on a packet.

/* array containing info about which ports we care about */

static char TelnetDecodePorts[65536/8];

This array stores the TCP ports that the preprocessor will be paying attention to. Notice that it stores this via a single bit for every port between 0 and 65,536, not a byte.

/*

* Function: SetupTelNeg()

*

* Purpose: Registers the preprocessor keyword and initialization

* function into the preprocessor list.

*

* Arguments: None.

*

* Returns: void function

*

*/

void SetupTelNeg()

{

/* Telnet negotiation has many names, but we only implement this

* plugin for Bob Graham's benefit…

*/

RegisterPreprocessor(“telnet_decode”, TelNegInit);

DEBUG_WRAP(DebugMessage(DEBUG_PLUGIN, “Preprocessor: Telnet Decode

Decode is setup…\n”););

}

SetupTelNeg() links this preprocessor to the Snort code by registering its rules file keyword telnet_decode with its initiation function, TelNegInit(). The obvious reason for this registration is so that the initialization code isn't called if the keyword referring to the preprocessor isn't present in Snort's configuration file. This registration takes place via the RegisterPreprocessor() function from plugbase.c.

This is the first function in the preprocessor that Snort calls. It is called from plugbase.c, to which we must add it by hand. This process, which we'll describe after explaining this code, is also outlined in snort/doc/README.PLUGINS.

/*

* Function: TelNegInit(u_char *)

*

* Purpose: Calls the argument parsing function, performs

* final setup on data structs, links the preproc function

* into the function list.

*

* Arguments: args => ptr to argument string

*

* Returns: void function

*

*/

void TelNegInit(u_char *args)

{

DEBUG_WRAP(DebugMessage(DEBUG_PLUGIN, “Preprocessor: TelNegInitialized\n”););

SetTelnetPorts(args);

/* Set the preprocessor function into the function list */

AddFuncToPreprocList(NormalizeTelnet);

}

This function is called by Snort early in its run, as it parses the Snort rules file. It is a standard preprocessor Init() function, which is always registered by the preprocessor's Setup() function. The purpose of this function is to call an argument-parser and to add the preprocessor's main function to the preprocessor function list. Remember, a packet entering Snort goes through the decoder to be parsed, then each of the preprocessors in order, and then finally goes to the detection engine. AddFuncToPreprocList(), from plugbase.c, adds our preprocessor's main function to the linked list of preprocessor functions.

/*

* Function: PreprocFunction(Packet *)

*

* Purpose: Perform the preprocessor' s intended function. This can be

* simple (statistics collection) or complex (IP defragmentation)

* as you like. Try not to destroy the performance of the whole

* system by trying to do too much….

*

* Arguments: p => pointer to the current packet data struct

*

* Returns: void function

*

*/

void NormalizeTelnet(Packet *p)

{

This is the real workhorse of the preprocessor. In essence, this is the function for which SetupTelNeg() and InitTelNeg() exist to provide to Snort. This structure of functions is standard, as you'll note when reading the other preprocessors and the preprocessor template.

The function starts out receiving a simple pointer to the packet currently being considered. (You can find the structure definition for Packet in snort/src/decode.h.) Let's look at the variables that it defines.

char *read_ptr;

char *start = (char *) DecodeBuffer; /* decode.c */

char *write_ptr

char *end;

int normalization_required = 0

read_ptr points to the current byte being considered in the incoming packet data.

start points to the beginning of the destination buffer (DecodeBuffer).

write_ptr points to the current position to which we're writing in DecodeBuffer.

end points to the end of the incoming packet data.

normalization_required tells us whether we need to normalize this packet.

/* check for TCP traffic that's part of an established session */

if(!PacketIsTCP(p))

{

return;

}

Like every preprocessor function, this one must decide whether it should even be looking at this packet. If the packet isn't a TCP packet, the preprocessor needs to exit.

/* check the port list */

if(!(TelnetDecodePorts[(p->dp/8)] & (1<<(p->dp%8))))

{

return

}

p->dp is the packet's destination port. If this port was not among those that this preprocessor should affect, we need to exit.

Again, note that the port is being checked in this array using a bitwise check. For example, if dp=14, then p->dp/8 will be 1, thus referring to the second byte in the array. 1<<(p->dp%8) means “shift the binary number 00000001 by the remainder of dp/8.” 14%8 is 6, so 1<<(p->dp%8) is, in binary, 0100 0000. By AND-ing the second byte in the array with this number, we get the status of the sixth byte.

/* negotiation strings are at least 3 bytes long */

if(p->dsize < TNC_STD_LENGTH)

{

return;

}

Finally, we're looking at something specific to the Telnet protocol. This if statement just says that, since any Telnet negotiation sequence must be at least 3 bytes long, it doesn't need to see any packet whose data is less than 3 bytes.

/* setup the pointers */

read_ptr = p->data;

end = p->data + p->dsize;

This sets our start and end points on the incoming packet data:

/* look to see if we have any telnet negotiaion codes in the payload */

while(!normalization_required && (read_ptr++ < end))

{

/* look for the start of a negotiation string */

if(*read_ptr == (char) TNC_IAC)

{

/* set a flag for stage 2 normalization */

normalization_required = 1;

}

}

This code runs through the incoming packet data looking for the start of a Telnet negotiation code sequence. This code doesn't perform any modifications—it's just here to quickly determine if the packet will need normalization. As soon as it finds a single IAC character, it flags that normalization is required and halts.

/*

* if we found telnet negotiation strings OR backspace characters,

* we're going to have to normalize the data

*

* Note that this is always (now: 2002-08-12) done to a

* alternative data buffer.

*/

\n

if(normalization_required)

{

If we found an IAC character, then this routine normalizes the data:

/* rewind the data stream to p->data */

read_ptr = p->data;

/* setup for overwriting the negotaiation strings with

* the follow-on data

*/

write_ptr = (char *) DecodeBuffer;

We set the read_ptr to the beginning of the incoming packet data, and the write_ptr to the start of the output buffer. Remember, DecodeBuffer is a global variable that the detection engine will look in for our alternative version of the packet.

/* walk thru the remainder of the packet */

while((read_ptr < end) && (write_ptr < ((char *) DecodeBuffer) +DECODE_BLEN))

{

DECODE_BLEN is the constant length of the DecodeBuffer. The while loop allows us to copy data from the packet data to the DecodeBuffer, skipping negotiation sequences.

/* if the following byte isn't a subnegotiation initialization*/

if(((read_ptr + 1) < end) &&

(*read_ptr == (char) TNC_IAC) &&

(*(read_ptr + 1) != (char) TNC_SB))

{

This code looks for negotiation sequences (initiated by IAC) and skips the read_ptr forward the appropriate number of bytes. Remember, skipping read_ptr forward without doing a copy ensures that the skipped data doesn't make it into DecodeBuffer. Note that this code doesn't want to handle the suboption negotiation case; hence, its decision not to branch if the second byte in the sequence is a Subnegotiation Begin (TNC_SB) character.

/* NOPs are two bytes long */

switch(* ((unsigned char *)(read_ptr + 1)))

{

case TNC_NOP:

read_ptr += 2;

break;

If the sequence is just an IAC, NOP, then it's only two characters long.

case TNC_EAC:

read_ptr += 2;

/* wind it back a character */

if(write_ptr > start)

{

write_ptr—;

}

break;

EAC is a backspace. When we see one, we skip the two characters of negotiation (IAC, EAC), but also decrement write_ptr, so that the byte that was at write_ptr is overwritten on our next character write.

default:

/* move the read ptr up 3 bytes */

read_ptr += TNC_STD_LENGTH;

}

In all other non-subnegotiation cases, we need to skip exactly three characters.

}

/* check for subnegotiation */

else if(*(read_ptr+1) == (char) TNC_SB)

{

/* move to the end of the subneg */

do

{

read_ptr++;

} while((*read_ptr != (char) TNC_SE) && (read_ptr < end));

Remember that our last if branch refused to handle subnegotiation. This one handles them—it simply moves the read_ptr forward until it gets past the terminating Subnegotiation End (SE) character, thus omitting the entire sequence from DecodeBuffer.

}

else

{

DEBUG_WRAP(DebugMessage(DEBUG_PLUGIN, “overwriting %2X(%c)with %2X(%c)\n”,

(char)(*write_ptr&0xFF), *write_ptr,

(char)(*read_ptr & 0xFF), *read_ptr););

/* overwrite the negotiation bytes with the follow-onbytes */

*write_ptr++ = *read_ptr++;

}

This is the case where we weren't at the start of a negotiation code. We just copy another character from the packet data to DecodeBuffer.

}

p->packet_flags |= PKT_ALT_DECODE;

p->alt_dsize = write_ptr - start;

The code now sets two variables on the original packet's data structure. The first tells the detection engine that the telnet_negotiation preprocessor has created a second, altered version of the packet data by using a bitwise-OR to set a Snort internal packet flag. Don't worry; this is changing data that Snort keeps on the packet, not in the original data collected from the packet. The second variable stores the length of the data placed in DecodeBuffer.

DEBUG_WRAP(DebugMessage(DEBUG_PLUGIN,

“Converted buffer after telnet normalization:\n”);

PrintNetData(stdout, (char *) DecodeBuffer, p->alt_dsize););

DebugMessage() now logs the results of the telnet_negotiation preprocessor's handiwork. If Snort is at the appropriate level of debug, this will come out.

}

}

Now, for the sake of brevity, we're not going to explain the argument-parsing function much. This function, as is standard with most of the preprocessors, is a mostly optional routine called by the preprocessor Init() function, which is InitTelNeg() in this case.

/*

* Function: SetTelnetPorts(char *)

*

* Purpose: Reads the list of port numbers from the argument string and

* parses them into the port list data struct

*

* Arguments: portlist => argument list

*

* Returns: void function

*

*/

static void SetTelnetPorts(char *portlist)

{

char portstr[STD_BUF];

char **toks;

int is_reset = 0;

int num_toks = 0;

int num = 0;

if(portlist == NULL || *portlist == ‘\0’)

{

portlist = “21 23 25 119”;

}

If this function does not get a list of ports in the Snort configuration file, it chooses ports 21, 23, 25, and 119.

/* tokenize the argument list */

toks = mSplit(portlist, “ ”, 31, &num_toks, ‘\\rsquo;);

mSplit is one of the functions in mstring.c, Snort's string-handling functions.

LogMessage(“telnet_decode arguments:\n”);

/* convert the tokens and place them into the port list */

for(num = 0; num < num_toks; num++)

{

if(isdigit((int)toks[num][0]))

{

char *num_p = NULL; /* used to determine last position instring */

long t_num;

t_num = strtol(toks[num], &num_p, 10);

if(*num_p != ‘\0’)

{

FatalError(“ERROR => Port Number invalid format: %s\n”, toks[num]);

}

else if(t_num < 0 || t_num > 65335)

{

FatalError(“ERROR => Port Number out of range: %ld\n”, t_num);

}

/* user specified a legal port number and it should override the

default */

port list, so reset it unless already done */

if(!is_reset)

{

bzero(&TelnetDecodePorts, sizeof(TelnetDecodePorts));

portstr[0] = ‘\0’;

is_reset = 1;

}

/* mark this port as being interesting using some

portscan2-type voodoo, and also add it to the port

list string while we're at it so we can later

print out all the ports with a single LogMessage() */

TelnetDecodePorts[(t_num/8)] |= 1<<(t_num%8);

strlcat(portstr, toks[num], STD_BUF − 1);

strlcat(portstr, “ ”, STD_BUF − 1);

}

else

{

FatalError(“ERROR %s(%d) => Unknown argument to telnet_decode”

“preprocessor: \”%s\“\n”,

file_name, file_line, toks[num]);

}

}

/* print out final port list */

LogMessage(“ Ports to decode telnet on: %s\n”, portstr);

}

As promised, this function was fairly simple.

Examining the Argument Parsing Code

Let's look at SetTelnetPorts(), the only function in this preprocessor that we haven't examined yet. This simple function just takes a port list from Snort and parses it into a data structure usable by the main preprocessor function that we just explored.

/*

* Function: SetTelnetPorts(char *)

*

* Purpose: Reads the list of port numbers from the argument string and

* parses them into the port list data struct

*

* Arguments: portlist => argument list

*

* Returns: void function

*

*/

static void SetTelnetPorts(char *portlist)

{

The SetTelnetPorts() function takes a pointer to a string as an argument, this string is the space delimited list of ports that Snort determines from the preprocessor telnet_decode line its configuration file. More specifically, Snort passes everything after the colon (:) on that line as a string to TelNegInit(), which passed it to the SetTelnetPorts() function. TelNegInit() receives that pointer as its only argument (the initiation functions of all preprocessor plug-ins receive that same one argument), a pointer to the string of text that followed the colon in their preprocessor directive lines in Snort.conf.

char portstr[STD_BUF];

char **toks;

int is_reset = 0;

int num_toks = 0;

int num = 0;

Let's detail what each of these variables do.

portstr This is a string that the function constructs specifically so that it can report a list of ports that it found in the log.

**toks This is a two-dimensional character array (an array of pointers to strings) that will point to the tokenized (separated) strings, which each encode a port.

portstr A flag describing whether the default port list has been replaced by a user-supplied one.

num_toks The number of ports parsed by the function.

num A simple integer counter used in a for loop.

if(portlist == NULL || *portlist == ‘\0’)

{

portlist = “ 21 23 25 119”;

}

In the default Snort 1.9.1 configuration file, there's no port list specified. This is accomplished with the line:

preprocessor telnet_decode

You'll note that this line does not contain a colon, and thus contains no arguments. In this case, the preprocessor (and thus this function) will receive a string pointer with NULL as its contents. This may seem equivalent to the situation where you include a colon in the syntax, but do not add any text after the colon, like this:

preprocessor telnet_decode:

In this case, the preprocessor receives a pointer to a string of zero length as an argument, which is basically the string \0. This is the case even if you added some spaces after the colon, because Snort strips terminating whitespace off the end of the lines in snort.conf. Basically, this if {} construct tells the preprocessor to use its default port list of “21 23 25 119” if it receives no input.

The preprocessor calls the Snort function mSplit(), from mstring.c, which can be thought of as the “Marty String” library.

/* tokenize the argument list */

toks = mSplit(portlist, “ ”, 31, &num_toks, ‘\\’);

Here is the definition of mSplit and the comments that describe it:

char **mSplit(char *str, char *sep, int max_strs, int *toks, char meta)

* char *str < the string to be split

* char *sep < a string of token seperaters

* int max_strs < how many tokens should be returned

* int *toks < place to store the number of tokens found in str

* char meta < the “escape metacharacter”, treat the character

* after this character as a literal and “escape” a

* seperator

*

* Returns:

* 2D char array with one token per “row” of the returned

* array.

This function parses the string portlist into 0-31 shorter strings, called tokens, using space as the separator and allowing that separator to be escaped by preceding it with \\. Each one of these strings should be an ASCII representation of a port number.

LogMessage, another Snort function, writes information by default to the console via or to a log facility, if configured to do so. You'll see this output at the end of this subsection, when we're done exploring the code.

LogMessage(“telnet_decode arguments:\n”);

Now the code loops through each of the strings (tokens) that mSplit() created, converting them to long integers storing them.

/* convert the tokens and place them into the port list */

for(num = 0; num < num_toks; num++)

{

First, it checks to see if the first character in our string is an ASCII representation of a digit (0-9) with the isdigit() C library function:

if(isdigit((int)toks[num][0]))

{

This following lines are where things begin to get a bit more tricky.

char *num_p = NULL; /* used to determine last position in string */

long t_num;

This defines two new variables:

num_p This is a pointer to terminating, non-decimal part of the port string

t_num This is a long integer which stores the port number that gets pulled out of the string.

t_num = strtol(toks[num], &num_p, 10);

This converts the numth token (string) into a long integer using the C standard library strtol() function. strtol(), which converts strings to long ints, takes a pointer to the string, a pointer to store a result in, and a numerical base as its arguments. Normal decimal numbers are base 10, while binary numbers are base 2 (the Snort configuration file uses base 10 port numbers). strtol() returns the integer form of the number that it finds and sets num_p to point to the part of the string that is after the decimal number. If our string is, as Snort expects, simply a string of ASCII digits between zero and nine, terminated by a \0, this pointer should just point to the terminating \0 character.

The if statement checks to see if the first character pointed to by num_p is a \0. If it is not, then this particular string was not made up strictly of ASCII characters between zero and nine, and an error occurs. It calls FatalError(), which prints the message ERROR => Port Number invalid format, along with the particular string that it was parsing, and then causes Snort to exit. The error message is either printed to the console or to the system log. The output is similar to what you will see here:

if(*num_p != ‘\0’)

{

FatalError(“ERROR => Port Number invalid format: %s\n”, toks[num]);

}

If our string is fine, but the number to which it converts is either negative or too large to be a valid TCP port, it causes Snort to exit, printing ERROR => Port Number out of range: and the port number to the console or system log:

else if(t_num < 0 || t_num > 65335)

{

FatalError(“ERROR => Port Number out of range: %ld\n”, t_num);

}

Now, if neither of these error conditions comes up, the string is fine and the function can store it in the list of ports.

Contrary to the comment and to the is_reset structure, this block of code runs both when the user has input a specific port list on the preprocessor telnet_negotiation snort.conf directive and when the user has left one off. If you're very interested in how this particular function works, it's important that you understand this misrepresentation, if you're not so interested, don't worry, because this doesn't really generalize to the other preprocessors.

For the most part, the is_reset variable keeps track of whether the function has initialized its two important output data structures yet.

First, it zeroes out the TelnetDecodePorts data structure. This structure is a 65,536/8 byte array that stores the ports the preprocessor should examine in a bit-wise true/false fashion. This was described earlier, when we were examining the NormalizeTelnet () function:

{

bzero(&TelnetDecodePorts, sizeof(TelnetDecodePorts));

It also blanks the portstr string by setting its first character to the \0 string terminator character:

portstr[0] = ‘\0’;

Finally, it sets is_reset so that it doesn't re-initialize these values now that it's populating them with data:

is_reset = 1;

}

Now, whether or not the data structures just got initialized, the function now has to store the port number that got translated from the string that it's currently handling.

First, it activates the t_numth bit in the TelnetDecodePorts array. Remember from the NormalizeTelnet() function that this activates the (t_num%8+1)th bit of the (t_num/8+1)th byte. To make this more concrete, think of the example where t_num is 14. Then t_num/8 will be 1 and t_num%8 will be 6. Therefore, this will activate the seventh bit of the second byte in the array. If this is confusing, you might want to reread the explanation for the code walkthrough of NormalizeTelnet()

/* mark this port as being interesting using some portscan2-type voodoo,

and also add it to the port list string while we're at it so we can

later print out all the ports with a single LogMessage() */

TelnetDecodePorts[(t_num/8)] |= 1<<(t_num%8);

Finally, the function adds the string representation of the port number to its portstr string, which gets logged at the end of this function.

strlcat(portstr, toks[num], STD_BUF − 1);

strlcat(portstr, “ ”, STD_BUF − 1);

}

This next else block corresponds to the if(isdigit((int)toks[num][0])) test at the beginning of this loop. The code internal to the block gets executed if the first character of the string it is evaluating is not a numerical digit (between zero and nine).

else

{

FatalError(“ERROR %s(%d) =< Unknown argument to telnet_decode ”

“preprocessor: \”%s\“\n”,

file_name, file_line, toks[num]);

}

The loop ends here and logs the list of ports that it parsed (stored in portstr) out to the console or the system logs.

}

/* print out final port list */

LogMessage(“Ports to decode telnet on: %s\n”, portstr);

}

In default detection mode, Snort will display this message and the upcoming one with its portlist on the screen at start-up, before its version announcement, similar to the following:

telnet_decode arguments:

Ports to decode telnet on: 21 23 25 119

1310 Snort rules read…

1310 Option Chains linked into 139 Chain Headers

0 Dynamic rules

+++++++++++++++++++++++++++++++++++++++++++++++++++

Rule application order: ->activation->dynamic->alert->pass->log

—== Initialization Complete ==—

-*> Snort! <*-

Version 1.9.1 (Build 231)

By Martin Roesch ([email protected] sourcefire.com, www.snort.org)

This is all of the preprocessor code that we'll need to look at. In the next section, you'll learn how preprocessor code is placed into Snort. Now, since Marty designed the preprocessor architecture to be simple and modular through plug-ins, this is a pretty easy process.

Getting the Preprocessor's Data Back into Snort

The telnet_negotiation preprocessor works much like other preprocessors, with the exception of its unique method of getting data back to the detection engine. Different preprocessors do this in different ways. For example, frag2 sends the packet it just reconstructed back through the same detection engine that gave it all the fragments of the packet. It avoids an infinite loop by setting a flag on the packet noting that said packet is a rebuilt fragment packet. Another example is http_decode, which creates a canonical URL from the data in an HTTP packet and then passes that URL by itself into a separate variable. You can perform this process in whatever way makes the most sense, unless the Snort developers create a standard and required API for passing back preprocessed data.

Adding the Preprocessor into Snort

Snort's plug-ins are linked into it in a fairly static way. In essence, you need to do the following to link in a new plug-in:

1

Insert an include directive in plugbase.c for your plug-ins header file.

2

Insert a call to your plug-ins Setup() function in plugbase.c's InitPreprocessors().

3

Add your plug-ins code and header file to the preprocessors/Makefile.am.

Let's practice doing this for the telnet_negotiation preprocessor, as if it hadn't been done yet. First, we need to add our telnet_negotiation.h header file into plugbase.c. Here's the relevant portion of plugbase.c:

#include “detect.h“

/* built-in preprocessors */

#include “preprocessors/spp_http_decode.h”

#include “preprocessors/spp_portscan.h”

#include “preprocessors/spp_rpc_decode.h”

#include “preprocessors/spp_bo.h”

#include “preprocessors/spp_stream4.h”

#include “preprocessors/spp_frag2.h”

#include “preprocessors/spp_arpspoof.h”

#include “preprocessors/spp_asn1.h”

#include “preprocessors/spp_fnord.h”

#include “preprocessors/spp_conversation.h”

#include “preprocessors/spp_portscan2.h”

We can just add a single line to the end of this list:

#include “preprocessors/spp_telnet_negotiation.h”

Second, let's insert our Setup() function into plugbase.c, so that our plug-in has a chance to register itself. We're adding this call to InitPreprocessors():

void InitPreprocessors()

{

if(!pv.quiet_flag)

{

printf(“Initializing Preprocessors!\n”);

{

SetupHttpDecode();

SetupPortscan();

SetupPortscanIgnoreHosts();

SetupRpcDecode();

SetupBo();

SetupStream4()

SetupFrag2();

SetupARPspoof();

SetupASN1Decode();

SetupFnord();

SetupConv();

SetupScan2();

}

Now we can add the Telnet negotiation plug-ins Setup() function, called SetupTelNeg():

SetupTelNeg();

Finally, we need only add our preprocessor's source files to:

snort/src/preprocessors/Makefile.am:

libspp_a_SOURCES = spp_arpspoof.c spp_arpspoof.h spp_bo.c spp_bo.h \

spp_frag2.c spp_frag2.h spp_http_decode.c spp_http_decode.h \

spp_portscan.c spp_portscan.h spp_rpc_decode.c spp_rpc_decode.h \

spp_stream4.c spp_stream4.h spp asn1.c spp_asn1.h spp_fnord.c spp_\

fnord.h spp_conversation.c spp_conversation.h spp_portscan2.c spp_\

portscan2.h spp_perfmonitor.c spp_perfmonitor.h

We can add our Telnet negotiation preprocessor like so:

libspp_a_SOURCES = spp_arpspoof.c spp_arpspoof.h spp_bo.c spp_bo.h \

spp_frag2.c spp_frag2.h spp_http_decode.c spp_http_decode.h \

spp_portscan.c spp_portscan.h spp_rpc_decode.c spp_rpc_decode.h \

spp_stream4.c spp_stream4.h spp asn1.c spp_asn1.h spp_fnord.c spp_\

fnord.h spp_conversation.c spp_conversation.h spp_portscan2.c spp_\

portscan2.h spp_perfmonitor.c spp_perfmonitor.h spp_telnet_negotiation.c \

spp_telnet_negotiation.h

That's all there is to it—adding a Snort preprocessor is pretty easy!

What is the difference between anomaly detection and signature or heuristic intrusion detection?

What it is: Signature-based and anomaly-based detections are the two main methods of identifying and alerting on threats. While signature-based detection is used for threats we know, anomaly-based detection is used for changes in behavior.

What is the difference between behavior signature anomaly and heuristic based monitoring and detection?

The difference is simple: signature-based IDS rely on a database of known attacks, while anomaly-based observe the behavior of the network, profile the normal behavior, and in the case of any anomalies, these anomalies cause deviations on which it alerts.

What is signature

What is signature detection? Signature-based detection is one of the most common techniques used to address software threats levelled at your computer. These threats include viruses, malware, worms, Trojans, and more. Your computer must be protected from an overwhelmingly large volume of dangers.

What is the difference between the rule

What is the difference between the rule-based detection when compared to behavioral detection? A. Rule-Based detection is searching for patterns linked to specific types of attacks, while behavioral is identifying per signature.