As a lightweight network intrusion detection system, Snort can be used in practice. However, if you want to learn how IDs works, the source code is very good. first, give a general comment on snort.
In terms of working principle, Snort is an NIDs. [Note: The network-based intrusion detection system (NIDS) passively checks the original data transmitted over the network. By analyzing the checked data packets, NIDs match the characteristics of intrusion behavior or detect abnormal behavior from the perspective of network activity.] The tool kit libpcap is used to collect network transmission data. Snort analyzes the data collected by libpcap to determine whether suspicious network activities exist.
In terms of the detection mode, Snort is basically a misuse detection (Misuse Detection ). [Note: This method matches the feature modes of known attacks, including passive protocol analysis using the sniffer working in Nic hybrid mode, and interpreting and analyzing features of a series of data packets. By the way, another kind of detection is anomaly detection.] The specific implementation is only the most direct and simple search matching of data, and there is no more complex intrusion detection method.
Although Snort has no advanced detection strategy in implementation, it provides a very
Examples of excellent open source code intrusion detection systems. We can analyze the code to find out how IDs works, and add our own ideas on this basis.
The Snort programming style is very good, and it is not difficult to read the code. The entire program structure is clear
The call relationship is not complex. However, there are many Snort source files and a large number of functions.
It is easy to clarify. Therefore, it is better to read the code one or two times to make it clearer.
**************************************** *************
**************************************** *************
Next let's take a look at the overall structure of snort. Expand the Snort compressed package. There are about 50 C Programs and header files, and about 30 other files (engineering, data, or description files ). [Note: snort-1.6-beta7 is used here. If snort-1.6.3 is not at hand, an earlier version will be used. The difference is not big.] The following describes the source code file grouping.
Snort. C (. h) is the file where the main program is located. It implements the main function and a series of auxiliary functions.
Decode. C (. h) detaches data packets layer by layer to determine which Protocol the packet belongs to and what features it has. And
Mark it in the global structure variable PV.
Log. C (. h) enables logging and alarm. Snort supports multiple log formats. One is stored in the tcpdump binary format, and the other is stored in the log directory in the ASCII format encoded by snort, the name of the log directory is based on the IP address of the "external" host. There are different alarm levels and methods, which can be recorded in syslogs or in user-specified files. In addition, you can also send alarm messages through UNIX socket, and use SMB to send winpopup messages to Windows systems.
Mstring. C (. h) implements the string matching algorithm. In snort, Boyer-Moore algorithm is used. Generally, algorithm books are used.
Plugbase. C (. h) implements a set of functions for initialization detection and registration detection rules. The detection rules in Snort are stored in the form of a linked list. Each rule is added to the linked list through the registration process.
Response. C (. h) responds by actively sending data packets to the attacker. Two responses are implemented here. One is the non-reachable false information of the host sending ICMP messages, and the other is the RST packet sent to TCP to disconnect the connection.
Rule. C (. h) implements the functions required for rule setting and intrusion detection. The main function of rule setting is
Converts a rule file to a list of rules in actual operation. The detection function detects attack features based on rules.
SP _ * _ Check. C (. h) is the specific implementation of different types of detection rules. You can easily learn the implemented rules from the file name. For example, sp_dsize_check is for the packet data size. sp_icmp_type_check is for the ICMP packet type. sp_tcp_flag_check is for the TCP packet flag. Not detailed.
SpO _ *. C (. h) implements output rules. Spo_alert_syslog records events to syslogs. spo_log_tcpdump records logs by using the log functions in Libpcap.
SPP _ *. C (. h) implements preprocess rules. Including HTTP decoding (replace the characters such as % XX in the HTTP request with the corresponding ASCII characters to avoid ignoring malicious requests), minimum segment check (to avoid malicious use of the function of restructuring in the TCP protocol) and Port Scan detection.
**************************************** **************************************** * ************************* The workflow of the main function is described below. First, describe the definitions of the two structures.
In snort. H, two structures are defined: PV and packetcount. PV is used to record command line parameters. Snort determines how it works based on these command line parameters. The PV type global variable PV is used to actually record the specific working method. For the structure definition, see snort. H. In the main function below, the settings of each field in PV will be met multiple times, and then explained one by one.
The structure packetcount is used to count traffic. Each time a packet is processed, the global variable PC of this structure adds 1 to the corresponding domain. It is equivalent to a counter.
Next we will explain the main function.
Initialize and set some default values. parse the command line parameters and fill in the Structure Variable PV based on the command line parameters. Determine the working mode based on the PV value (that is, the result of parsing the command line, note:
If it is running in the daemon mode, use the godaemon function to create a daemon process, redirect the standard input and output, implement the daamon status, and end the parent process.
Snort can collect network data in real time or read data from files for analysis. There is no essential difference between the two cases. If the file is read for analysis (not directly collected from the network card in real time), use the file name as a parameter of the Libpcap function openpcap to open the collection process. If the file is collected from the network card in real time, use the interface of the NIC as the parameter of openpcap and use the function of libpcap to open the interface of the NIC. In UNIX, devices are also seen as files, so this is not much different from reading file analysis.
Next, specify the packet unpacking function. Different data link networks have different packet splitting functions. The setpktprocessor function is used to set different unpacking functions based on the value of the global variable datalink. For example, for Ethernet, the package splitting function is decodeethpkt; for Ring-based network, the package splitting function is decodetrpkt, and so on. These decode * functions are implemented in Decode. C.
If the detection rules are used, initialize these detection rules, parse the rule file, and convert them into a rule linked list. There are three main types of rules: Preprocessor, plugin, and outputplugin ). Here, plugin is a specific detection rule, while outputplugin is a rule that defines the log and alarm method.
Then, set the alarm function according to the alarm mode. Set the log function according to the log mode. If the function can respond, open the raw socket and prepare for the response.
Finally, it enters the loop of reading data packets. pcap_loop processes each collected data packet using the processpacket function. If an error occurs or the number of processed packets reaches the specified value (Pv. pkt_cnt) to exit the function. Processpacket is a key program,
Finally, disable the collection process.
**************************************** *************
Now let's take a look at how snort can analyze and detect data packets.
The last part of the main function has the following statements, which are important:
/* Read all packets on the device. Continue until CNT packets read */
If (pcap_loop (PD, pv. pkt_cnt, (pcap_handler) processpacket, null) <0)
{
......
}
Here, the pcap_loop function has four parameters, which are explained separately:
PD is a global variable that indicates the file descriptor. It has been correctly assigned a value in the previous openpcap call. As mentioned above, Snort can collect network data in real time or read data from files for analysis. When a file (or device) is opened in different situations, PD is used to process the file or interface of the NIC device.
PD is a pointer of the struct pcap type. The structure includes the actual file descriptor, buffer zone, and other fields, which are used to process the information obtained from the corresponding file.
The PD assignment statements in the openpcap function are:
/* Get the device file descriptor and open the NIC interface */
Pd = pcap_open_live (Pv. Interface, snaplen,
PV. promisc_flag? Promisc: 0, read_timeout, errorbuf );
Or
/* Open the file and open the file */
Pd = pcap_open_offline (INTF, errorbuf );
Therefore, this parameter indicates where to obtain the data to be analyzed.
The 2nd parameter is PV. pkt_cnt, indicating the total number of packages to be captured. When the main function is initialized, the default value is-1, which becomes a permanent loop and is captured until the program exits:
/* Initialize the packet counter to loop forever */
PV. pkt_cnt =-1;
You can also set the number of packages to capture in the command line. In the call to the preceding parsesponline (PARSE command line) function, if the parameter n is encountered, reset the PV. pkt_cnt value. The related statements in parsecmdline are as follows:
Case 'N':/* Grab x packets and exit */
PV. pkt_cnt = atoi (optarg );
The first parameter is the callback function that processes the captured data packets. Here is the function
Processpacket. The function is described in detail below.
The first parameter is a string pointer, indicating the user. This parameter is set to null.
Before describing the processpacket function of the processing package, it is necessary to explain the implementation of pcap_loop. We can see that the main function only calls the pacp_loop once in the IF condition judgment, so the loop must be done in pcap_loop. Check the pcap_loop implementation in the pcap. c file. We find that:
Int
Pcap_loop (pcap_t * P, int CNT, pcap_handler callback, u_char * User)
{
Register int N;
For (; {// For Loop
If (p-> SF. rfile! = NULL)
N = pcap_offline_read (p, CNT, callback, user );
Else {
/*
* XXX keep reading until we get something
* (Or an error occurs)
*/
Do {// do loop
N = pcap_read (p, CNT, callback, user );
} While (n = 0 );
}
If (n <= 0)
Return (n); // if an error occurs, return
If (CNT> 0 ){
CNT-= N;
If (CNT <= 0)
Return (0); // return the specified number
}
// There are only two returned results
}
}
Now let's look at the implementation of processpacket. This callback function is used to process data packets. This function is of the pcap_handler type. The type definition in pcap. H is as follows:
Typedef void (* pcap_handler) (u_char *, const struct pcap_pkthdr *,
Const u_char *);
1st parameters are useless here;
The first parameter is the pcap_pkthdr structure pointer, which records the timestamp, package length, and capture length;
The 3rd parameter string pointer is a data packet.
The function is as follows:
Void processpacket (char * user, struct pcap_pkthdr * pkthdr, u_char * Pkt)
{
Packet P; // The packet structure is defined in Decode. h to record various information about data packets.
/* Call the packet decoder and call the package splitting function. Grinder is a global
Function pointer, which has been set to the correct package splitting function in the setpktprocessor call of main */
(* Grinder) (& P, pkthdr, Pkt );
/* Print the packet to the screen. If you select the detailed display mode,
Display the package data to the standard output */
If (Pv. verbose_flag)
{
... // Omitted
}
/* Check or log the packet as necessary
If you are using detection rules, call preprocess for detection,
Otherwise, log only to record the package information */
If (! PV. use_rules)
{
... // Log, omitted
}
Else
{
Preprocess (& P );
}
// Clear the buffer
Cleardumpbuf ();
}
Here, the preprocess function is used for actual detection.
**************************************** ************************************
The proprocess function is short. First, call the preprocessing rule to process the data packet P, and then call the detection
The detect function is used for rule matching. If this function is used, the calloutput function is called.
Plugins generates alarms or logs based on the output rules. The function is as follows:
Void preprocess (packet * P)
{
Preprocessfuncnode * idx;
Do_detect = 1;
Idx = preprocesslist; // point to the pre-processing rule linked list Header
While (idx! = NULL) // call the preprocessing function Processing Package P
{
Idx-> func (P );
Idx = idx-> next;
}
If (! P-> frag_flag & do_detect)
{
If (detect (p) // call the detection function
{
Calloutputplugins (p); // If matched, output according to the rule
}
}
} Despite the simplicity of this function, the proprocessfuncnode is defined in row 1st.
Structure Type pointer, so we have to begin to involve various snort complexity.
. According to the previous analysis, I have always ignored many letters according to the calling sequence of the program running.
Number (in fact there are many very important), in order to describe the main line of Snort execution, avoid
A large number of calling relationships lead to confusion. Till now, we have no access to the core data structure of snort.
And algorithms. There are many key issues that need to be solved: How are rules statically described? These
What structure does the rule dynamically store? How is the processing function of each rule called? Snort gave
We provide a very good method.
A very successful idea of Snort is to use the plug-in mechanism, and the rule processing function is not fixed in
In the source program, read the rules from the rule file according to the parameter settings at each runtime, and then
The processing function required by the rule is mounted to the linked list. In actual detection, traverse these linked lists and call the linked list
Function.
The main data structure of Snort is the linked list. Almost all data structures are linked lists. Let's make a summary
Introduction.
It is necessary to go back and take a look at some of the things involved in rule initialization in the main function.
Data structure.
When the main function initializes the rule, several linked lists are created first. The global variables are defined as follows:
(Plugbase. C ):
Keywordxlatelist * keywordlist;
Preprocesskeywordlist * preprocesskeywords;
Preprocessfuncnode * preprocesslist;
Outputkeywordlist * outputkeywords;
Outputfuncnode * outputlist;
The specific definitions of these structures are omitted. This initialization process highlights the pre-defined key in Snort
Words and processing functions are connected to different linked lists by category. Then, when parsing the rule file,
If a rule option contains a keyword, the corresponding linked list initialized from the above
To add the necessary information and processing functions to the node that represents the rule (using ruletreenode
Type, which is detailed below) in the specific domain (opttreenode type.
At the end of the initialization rule in the main function, parse the specified rule file. In
At a high level, there are three global variable storage rules (rules. C ):
Listhead alert;/* alert block header */
Listhead log;/* log block header */
Listhead pass;/* pass block header */
These variables are of the listhead type. As the name suggests, they indicate the chain table header. Register in alert
The alarm rules are required. The log registers the rules that need to be logged. The rules registered in pass are located
Ignore the processing (without any processing ). Listhead is defined as follows:
Typedef struct _ listhead
{
Ruletreenode * tcplist;
Ruletreenode * udplist;
Ruletreenode * icmplist;
} Listhead;
You can see that each listhead structure has three pointers pointing to the chain table header that processes TCP, UDP, and ICMP packet rules respectively. Here, a new structure ruletreenode is introduced. To illustrate the hierarchical relationship of the linked list, the following lists the definition of ruletreenode, but most of the fields are ignored:
Typedef struct _ ruletreenode
{
Rulefplist * rule_func;
... // Ignore
Struct _ ruletreenode * right;
Opttreenode * down;/* List of Rule options to associate with this
Rule node */
} Ruletreenode;
Ruletreenode contains the above three pointer fields, which can form three linked lists respectively. The right of the ruletreenode * type points to the next ruletreenode, which is equivalent to the next field in the common linked list, but the right field is used for naming. In this way, the rule linked list is formed.
The rulefplist class pointer rule_func records the linked list of the processing functions of the rule. A rule sometimes needs to call multiple processing functions for analysis. Therefore, it is necessary to create a linked list. Let's take a look at the following definition. In addition to the next field, there is also a function pointer:
Typedef struct _ rulefplist
{
/* Rule check function pointer */
INT (* ruleheadfunc) (packet *, struct _ ruletreenode *, struct _ rulefplist *);
/* Pointer to the next rule function node */
Struct _ rulefplist * next;
} Rulefplist;
The first pointer field is the pointer down of the opttreenode class. The comments behind this row are clear. This is the linked list of Rule options associated with this rule node. Unfortunately, the opttreenode structure is quite complex, and several new linked lists are introduced. Ignore some fields. The opttreenode is defined as follows:
Typedef struct _ opttreenode
{
/* Plugin/detection functions go here */
Optfplist * opt_func;
/* The ds_list is absolutely essential for the plugin system to work,
It allows the plugin authors to associate "dynamic" Data Structures
With the rule system, lew.them link anything they can come up
With to the Rules list */
Void * ds_list [1, 512];/* List of plugin data struct pointers */
Some fields are omitted.
Struct _ opttreenode * next;
} Opttreenode;
Next points to the next node of the linked list. Optfplist pointer opt_func points
The option function linked list is similar to the previously mentioned rulefplist. It is worth noting that the pointer Array
Ds_list, used to record the predefined processing process involved in this rule. The type of each element is void *. ds_list is forcibly converted to different predefined types during actual expression of rules.
Bytes --------------------------------------------------------------------------------------
The proprocess function is short. First, call the preprocessing rule to process the data packet P, and then call the detection
The detect function is used for rule matching. If this function is used, the calloutput function is called.
Plugins generates alarms or logs based on the output rules. The function is as follows:
Void preprocess (packet * P)
{
Preprocessfuncnode * idx;
Do_detect = 1;
Idx = preprocesslist; // point to the pre-processing rule linked list Header
While (idx! = NULL) // call the preprocessing function Processing Package P
{
Idx-> func (P );
Idx = idx-> next;
}
If (! P-> frag_flag & do_detect)
{
If (detect (p) // call the detection function
{
Calloutputplugins (p); // If matched, output according to the rule
}
}
}
Despite the simplicity of this function, in row 1st we can see that proprocessfuncnode is defined.
Structure Type pointer, so we have to begin to involve various snort complexity.
. According to the previous analysis, I have always ignored many letters according to the calling sequence of the program running.
Number (in fact there are many very important), in order to describe the main line of Snort execution, avoid
A large number of calling relationships lead to confusion. Till now, we have no access to the core data structure of snort.
And algorithms. There are many key issues that need to be solved: How are rules statically described? These
What structure does the rule dynamically store? How is the processing function of each rule called? Snort gave
We provide a very good method.
A very successful idea of Snort is to use the plug-in mechanism, and the rule processing function is not fixed in
In the source program, read the rules from the rule file according to the parameter settings at each runtime, and then
The processing function required by the rule is mounted to the linked list. In actual detection, traverse these linked lists and call the linked list
Function.
The main data structure of Snort is the linked list. Almost all data structures are linked lists. Let's make a summary
Introduction.
It is necessary to go back and take a look at some of the things involved in rule initialization in the main function.
Data structure.
When the main function initializes the rule, several linked lists are created first. The global variables are defined as follows:
(Plugbase. C ):
Keywordxlatelist * keywordlist;
Preprocesskeywordlist * preprocesskeywords;
Preprocessfuncnode * preprocesslist;
Outputkeywordlist * outputkeywords;
Outputfuncnode * outputlist;
The specific definitions of these structures are omitted. This initialization process highlights the pre-defined key in Snort
Words and processing functions are connected to different linked lists by category. Then, when parsing the rule file,
If a rule option contains a keyword, the corresponding linked list initialized from the above
To add the necessary information and processing functions to the node that represents the rule (using ruletreenode
Type, which is detailed below) in the specific domain (opttreenode type.
At the end of the initialization rule in the main function, parse the specified rule file. In
At a high level, there are three global variable storage rules (rules. C ):
Listhead alert;/* alert block header */
Listhead log;/* log block header */
Listhead pass;/* pass block header */
These variables are of the listhead type. As the name suggests, they indicate the chain table header. Register in alert
The alarm rules are required. The log registers the rules that need to be logged. The rules registered in pass are located
Ignore the processing (without any processing ). Listhead is defined as follows:
Typedef struct _ listhead
{
Ruletreenode * tcplist;
Ruletreenode * udplist;
Ruletreenode * icmplist;
} Listhead;
You can see that each listhead structure has three pointers pointing to the chain table header that processes TCP, UDP, and ICMP packet rules respectively. Here, a new structure ruletreenode is introduced. To illustrate the hierarchical relationship of the linked list, the following lists the definition of ruletreenode, but most of the fields are ignored:
Typedef struct _ ruletreenode
{
Rulefplist * rule_func;
... // Ignore
Struct _ ruletreenode * right;
Opttreenode * down;/* List of Rule options to associate with this
Rule node */
} Ruletreenode;
Ruletreenode contains the above three pointer fields, which can form three linked lists respectively. Ruletreenode *
The right type points to the next ruletreenode, which is equivalent to the next field in the common linked list, but the right field is used for naming. In this way, the rule linked list is formed.
The rulefplist class pointer rule_func records the linked list of the processing functions of the rule. A rule sometimes needs to call multiple processing functions for analysis. Therefore, it is necessary to create a linked list. Let's take a look at the following
Definition, in addition to the next field, there is also a function pointer:
Typedef struct _ rulefplist
{
/* Rule check function pointer */
INT (* ruleheadfunc) (packet *, struct _ ruletreenode *, struct _ rulefplist *);
/* Pointer to the next rule function node */
Struct _ rulefplist * next;
} Rulefplist;
The first pointer field is the pointer down of the opttreenode class. The comments behind this row are clear. This is the linked list of Rule options associated with this rule node. Unfortunately, the opttreenode structure is quite complex, and several new linked lists are introduced. Ignore some fields. The opttreenode is defined as follows:
Typedef struct _ opttreenode
{
/* Plugin/detection functions go here */
Optfplist * opt_func;
/* The ds_list is absolutely essential for the plugin system to work,
It allows the plugin authors to associate "dynamic" Data Structures
With the rule system, lew.them link anything they can come up
With to the Rules list */
Void * ds_list [1, 512];/* List of plugin data struct pointers */
Some fields are omitted.
Struct _ opttreenode * next;
} Opttreenode;
Next points to the next node of the linked list. The optfplist type pointer opt_func points to the option function linked list, which is similar to the previously mentioned rulefplist. It is worth noting that the pointer Array
Ds_list, used to record the predefined processing process involved in this rule. The type of each element is void *. When a rule is actually represented, ds_list is forcibly converted to different predefined types. Address: http://www.yuanma.org/data/2006/0912/article_1515.htm