枫林在线论坛精华区>>信息安全
[6962] 主题: [入侵检测]snort源码分析
作者: little (渺小)
标题: [入侵检测]snort源码分析
来自: 203.95.*.*
发贴时间: 2002年07月02日 18:05:57
长度: 22440字
发信人: shuke (莫失莫忘), 信区: Security
标 题: snort源码分析
发信站: BBS 水木清华站 (Sun Oct 8 11:38:16 2000)
下面我写一点snort源代码的分析。
呵呵,我是面面,有理解的不对的地方,一起讨论。有些地方我读得比较

细,有些地方就比较粗糙,所以写起来也含糊其词。
我做事总拖拖拉拉,希望这次能尽快写完。
首先对snort做一个概括的评论。
从工作原理而言,snort是一个NIDS。 网络传输数据的采集
利用了工具包libpcap。snort对libpcap采集来的数据进行分析,从而判断
是否
存在可疑的网络活动。
从检测模式而言,snort基本上是误用检测(misuse detection)。具体实
现上,仅仅是对数据进行最直接最简单
的搜索匹配,并没有涉及更复杂的入侵检测办法。
尽管snort在实现上没有什么高深的检测策略,但是它给我们提供了一个非

优秀的公开源代码的入侵检测系统范例。我们可以通过对其代码的分析,
搞清IDS
究竟是如何工作的,并在此基础上添加自己的想法。
发信人: shuke (莫失莫忘), 信区: Security
标 题: snort源码分析(1)
发信站: BBS 水木清华站 (Sun Oct 8 11:39:33 2000)
首先对snort做一个概括的评论。
从工作原理而言,snort是一个NIDS。 网络传输数据的采集
利用了工具包libpcap。snort对libpcap采集来的数据进行分析,从而判断
是否
存在可疑的网络活动。
从检测模式而言,snort基本上是误用检测(misuse detection)。具体实
现上,仅仅是对数据进行最直接最简单
的搜索匹配,并没有涉及更复杂的入侵检测办法。
尽管snort在实现上没有什么高深的检测策略,但是它给我们提供了一个非

优秀的公开源代码的入侵检测系统范例。我们可以通过对其代码的分析,
搞清IDS
究竟是如何工作的,并在此基础上添加自己的想法。
异常检测(anomaly detection)。]具体实现上,仅仅是对数据进行最直
接最简单
的搜索匹配,并没有涉及更复杂的入侵检测办法。
尽管snort在实现上没有什么高深的检测策略,但是它给我们提供了一个非

优秀的公开源代码的入侵检测系统范例。我们可以通过对其代码的分析,
搞清IDS
究竟是如何工作的,并在此基础上添加自己的想法。
发信人: shuke (莫失莫忘), 信区: Security
标 题: snort源码分析(1)
发信站: BBS 水木清华站 (Sun Oct 8 11:39:33 2000)
首先对snort做一个概括的评论。
从工作原理而言,snort是一个NIDS。 网络传输数据的采集
利用了工具包libpcap。snort对libpcap采集来的数据进行分析,从而判断
是否
存在可疑的网络活动。
从检测模式而言,snort基本上是误用检测(misuse detection)。具体实
现上,仅仅是对数据进行最直接最简单
的搜索匹配,并没有涉及更复杂的入侵检测办法。
尽管snort在实现上没有什么高深的检测策略,但是它给我们提供了一个非

优秀的公开源代码的入侵检测系统范例。我们可以通过对其代码的分析,
搞清IDS
究竟是如何工作的,并在此基础上添加自己的想法。
snort的编程风格非常优秀,代码阅读起来并不困难,整个程序结构清晰,

数调用关系也不算复杂。但是,snort的源文件不少,函数总数也很多,所
以不太
容易讲清楚。因此,最好把代码完整看一两遍,能更清楚点。
下面看看snort的整体结构。展开snort压缩包,有约50个c程序和头文件,
另有
约30个其它文件(工程、数据或者说明文件)。下面对源代码文件分组
说明。
snort.c(.h)是主程序所在的文件,实现了main函数和一系列辅助函数。

decode.c(.h)把数据包层层剥开,确定该包属于何种协议,有什么特征。

标记到全局结构变量pv中。
log.c(.h)实现日志和报警功能。snort有多种日志格式,一种是按tcpdu
mp
二进制的格式存储,另一种按snort编码的ascii格式存储在日志目录下,
日志目
录的名字根据"外"主机的ip地址命名。报警有不同的级别和方
式,可以记录到
syslog中,或者记录到用户指定的文件,另外还可以通过unix socket发送
报警
消息,以及利用SMB向Windows系统发送winpopup消息。
mstring.c(.h)实现字符串匹配算法。在snort中,采用的是Boyer-Moore算
法。
发信人: shuke (莫失莫忘), 信区: Security
标 题: snort源码分析(2)
发信站: BBS 水木清华站 (Sun Oct 8 11:40:44 2000)
下面看看snort的整体结构。展开snort压缩包,有约50个c程序和头文件,
另有
约30个其它文件(工程、数据或者说明文件)。下面对源代码文件分组
snort的编程风格非常优秀,代码阅读起来并不困难,整个程序结构清晰,

数调用关系也不算复杂。但是,snort的源文件不少,函数总数也很多,所
以不太
容易讲清楚。因此,最好把代码完整看一两遍,能更清楚点。
下面看看snort的整体结构。展开snort压缩包,有约50个c程序和头文件,
另有
约30个其它文件(工程、数据或者说明文件)。下面对源代码文件分组
说明。
snort.c(.h)是主程序所在的文件,实现了main函数和一系列辅助函数。

decode.c(.h)把数据包层层剥开,确定该包属于何种协议,有什么特征。

标记到全局结构变量pv中。
log.c(.h)实现日志和报警功能。snort有多种日志格式,一种是按tcpdu
mp
二进制的格式存储,另一种按snort编码的ascii格式存储在日志目录下,
日志目
录的名字根据"外"主机的ip地址命名。报警有不同的级别和方
式,可以记录到
syslog中,或者记录到用户指定的文件,另外还可以通过unix socket发送
报警
消息,以及利用SMB向Windows系统发送winpopup消息。
mstring.c(.h)实现字符串匹配算法。在snort中,采用的是Boyer-Moore算
法。
发信人: shuke (莫失莫忘), 信区: Security
标 题: snort源码分析(2)
发信站: BBS 水木清华站 (Sun Oct 8 11:40:44 2000)
下面看看snort的整体结构。展开snort压缩包,有约50个c程序和头文件,
另有
约30个其它文件(工程、数据或者说明文件)。下面对源代码文件分组
说明。
snort.c(.h)是主程序所在的文件,实现了main函数和一系列辅助函数。

decode.c(.h)把数据包层层剥开,确定该包属于何种协议,有什么特征。

标记到全局结构变量pv中。
log.c(.h)实现日志和报警功能。snort有多种日志格式,一种是按tcpdu
mp
二进制的格式存储,另一种按snort编码的ascii格式存储在日志目录下,
日志目
录的名字根据"外"主机的ip地址命名。报警有不同的级别和方
式,可以记录到
syslog中,或者记录到用户指定的文件,另外还可以通过unix socket发送
报警
消息,以及利用SMB向Windows系统发送winpopup消息。
mstring.c(.h)实现字符串匹配算法。在snort中,采用的是Boyer-Moore算
法。
算法书上一般都有。
plugbase.c(.h)实现了初始化检测以及登记检测规则的一组函数。snort中

检测规则以链表的形式存储,每条规则通过登记(Register)过程添加到
链表中。
response.c(.h)进行响应,即向攻击方主动发送数据包。这里实现了两种
响应。
一种是发送ICMP的主机不可到达的假信息,另一种针对TCP,发送RST包,
断开连接。
rule.c(.h)实现了规则设置和入侵检测所需要的函数。规则设置主要的作
用是
把一个规则文件转化为实际运作中的规则链表。检测函数根据规则实施攻
击特征的
检测。
sp_*_check.c(.h)是不同类型的检测规则的具体实现。很容易就可以从文
件名
得知所实现的规则。例如,sp_dsize_check针对的是包的数据大小,sp_i
cmp_type
_check针对icmp包的类型,sp_tcp_flag_check针对tcp包的标志位。不再
详述。
spo_*.c(.h)实现输出(output)规则。spo_alert_syslog把事件记录到s
yslog
中;spo_log_tcpdump利用libpcap中的日志函数,进行日志记录。
spp_*.c(.h)实现预处理(preprocess)规则。包括http解码(即把http请
求中
的%XX这样的字符用对应的ascii字符代替,避免忽略了恶意的请求)、最
小片断检
查(避免恶意利用tcp协议中重组的功能)和端口扫描检测。
下面描述main函数的工作流程。先来说明两个结构的定义。
在snort.h中,定义了两个结构:PV和PacketCount。PV用来记录命令行参
数,
snort根据这些命令行参数来确定其工作方式。PV类型的全局变量pv用来实
际记录具体
工作方式。结构定义可以参看snort.h,在下边的main函数中,会多次遇到
pv中各个域
的设定,到时再一个一个解释。
结构PacketCount用来统计流量,每处理一个数据包,该结构类型的全局变
量pc
把对应的域加1。相当于一个计数器。
接下来解释main函数。
初始化设定一些缺省值;然后解析命令行参数,根据命令行参数,填充结
构变
量pv;根据pv的值(也就是解析命令行的结果)确定工作方式,需要注意

如果是运行在Daemon方式,通过GoDaemon函数,创建守护进程,重定向标
准输入
发信人: shuke (莫失莫忘), 信区: Security
标 题: snort源码分析(3)
发信站: BBS 水木清华站 (Sun Oct 8 15:51:03 2000)
下面描述main函数的工作流程。先来说明两个结构的定义。
在snort.h中,定义了两个结构:PV和PacketCount。PV用来记录命令行参
数,
snort根据这些命令行参数来确定其工作方式。PV类型的全局变量pv用来实
际记录具体
工作方式。结构定义可以参看snort.h,在下边的main函数中,会多次遇到
pv中各个域
的设定,到时再一个一个解释。
结构PacketCount用来统计流量,每处理一个数据包,该结构类型的全局变
量pc
把对应的域加1。相当于一个计数器。
接下来解释main函数。
初始化设定一些缺省值;然后解析命令行参数,根据命令行参数,填充结
构变
量pv;根据pv的值(也就是解析命令行的结果)确定工作方式,需要注意

如果是运行在Daemon方式,通过GoDaemon函数,创建守护进程,重定向标
准输入
输出,实现daamon状态,并结束父进程。
snort可以实时采集网络数据,也可以从文件读取数据进行分析。这两种情
况并
没有本质区别。如果是读取文件进行分析(并非直接从网卡实时采集来的
),以该文
件名作为libpcap的函数OpenPcap的参数,打开采集过程;如果是从网卡实
时采集,
就把网卡接口作为OpenPcap的参数,利用libpcap的函数打开该网卡接口。
在unix中,
设备也被看作是文件,所以这和读取文件分析没有多大的差别。
接着,指定数据包的拆包函数。不同的数据链路网络,拆包的函数也不同
。利用
函数SetPktProcessor,根据全局变量datalink的值,来设定不同的拆包函
数。例如,
以太网,拆包函数为DecodeEthPkt;令牌环网,拆包函数为DecodeTRPkt,
等等。这些
Decode*函数,在decode.c中实现。
如果使用了检测规则,那么下面就要初始化这些检测规则,并解析规则文
件,转
化成规则链表。规则有三大类:预处理(preprocessor),插件(plugin
),输出插
件(outputplugin)。这里plugin就是具体的检测规则,而outputplugin
是定义日志
和报警方式的规则。
然后根据报警模式,设定报警函数;根据日志模式,设定日志函数;如果
指定了
能够进行响应,就打开raw socket,准备用于响应。
最后进入读取数据包的循环,pcap_loop对每个采集来的数据包都用Proce
ssPacket
函数进行处理,如果出现错误或者到达指定的处理包数(pv.pkt_cnt定义
),就退出
该函数。这里ProcessPacket是关键程序,
最后,关闭采集过程。
现在看看snort如何实现对数据包的分析和检测入侵的。
在main函数的最后部分有如下语句,比较重要:
/* Read all packets on the device. Continue until cnt packets re
ad */
if(pcap_loop(pd, pv.pkt_cnt, (pcap_handler)ProcessPacket, NULL) 
< 0)
{
......
}
这里pcap_loop函数有4个参数,分别解释:
pd是一个全局变量,表示文件描述符,在前面OpenPcap的调用中已经被正
确地
赋值。前面说过,snort可以实时采集网络数据,也可以从文件读取数据进
行分析。
在不同情况打开文件(或设备)时,pd分别用来处理文件,或者网卡设备
接口。
pd是struct pcap类型的指针,该结构包括实际的文件描述符,缓冲区,等

发信人: shuke (莫失莫忘), 信区: Security
标 题: snort源码分析(4)
发信站: BBS 水木清华站 (Tue Oct 10 11:42:22 2000)
现在看看snort如何实现对数据包的分析和检测入侵的。
在main函数的最后部分有如下语句,比较重要:
/* Read all packets on the device. Continue until cnt packets re
ad */
if(pcap_loop(pd, pv.pkt_cnt, (pcap_handler)ProcessPacket, NULL) 
< 0)
{
......
}
这里pcap_loop函数有4个参数,分别解释:
pd是一个全局变量,表示文件描述符,在前面OpenPcap的调用中已经被正
确地
赋值。前面说过,snort可以实时采集网络数据,也可以从文件读取数据进
行分析。
在不同情况打开文件(或设备)时,pd分别用来处理文件,或者网卡设备
接口。
pd是struct pcap类型的指针,该结构包括实际的文件描述符,缓冲区,等

域,用来处理从相应的文件获取信息。
OpenPcap函数中对pd赋值的语句分别为:
/* get the device file descriptor,打开网卡接口 */
pd = pcap_open_live(pv.interface, snaplen,
pv.promisc_flag ? PROMISC : 0, READ_TIMEOUT, errorbuf);
或者
/* open the file,打开文件 */
pd = pcap_open_offline(intf, errorbuf);
于是,这个参数表明从哪里取得待分析的数据。
第2个参数是pv.pkt_cnt,表示总共要捕捉的包的数量。在main函数初始化
时,
缺省设置为-1,成为永真循环,一直捕捉直到程序退出:
/* initialize the packet counter to loop forever */
pv.pkt_cnt = -1;
或者在命令行中设置要捕捉的包的数量。前面ParseCmdLine(解析命令行
)函数
的调用中,遇到参数n,重新设定pv.pkt_cnt的值。ParseCmdLine中相关语
句如下:
case 'n': /* grab x packets and exit */
pv.pkt_cnt = atoi(optarg);
第3个参数是回调函数,该回调函数处理捕捉到的数据包。这里为函数
ProcessPacket,下面将详细解释该函数。
第4个参数是字符串指针,表示用户,这里设置为空。
在说明处理包的函数ProcessPacket之前,有必要解释一下pcap_loop的实
现。
我们看到main函数只在if条件判断中调用了一次pacp_loop,那么循环一定
是在
pcap_loop中做的了。察看pcap.c文件中pcap_loop的实现部分,我们发现
的确如此:
int
pcap_loop(pcap_t *p, int cnt, pcap_handler callback, u_char *use
r)
{
register int n;
for (;;) { //for循环
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循环
n = pcap_read(p, cnt, callback, user);
} while (n == 0);
}
if (n <= 0)
return (n); //遇到错误,返回
if (cnt > 0) {
cnt -= n;
if (cnt <= 0)
return (0); //到达指定数量,返回
}
//只有以上两种返回情况
}
}
现在看看ProcessPacket的实现了,这个回调函数用来处理数据包。该函数

是pcap_handler类型的,pcap.h中类型的定义如下:
typedef void (*pcap_handler)(u_char *, const struct pcap_pkthdr 
*,
const u_char *);
第1个参数这里没有什么用;
第2个参数为pcap_pkthdr结构指针,记录时间戳、包长、捕捉的长度;
第3个参数字符串指针为数据包。
函数如下:
void ProcessPacket(char *user, struct pcap_pkthdr *pkthdr, u_cha
r *pkt)
{
Packet p; //Packet结构在decode.h中定义,用来记录数据包的各种信息

/* call the packet decoder,调用拆包函数,这里grinder是一个全局

函数指针,已经在main的SetPktProcessor调用中设置为正确的拆包函数 
*/
(*grinder)(&p, pkthdr, pkt);
/* print the packet to the screen,如果选择了详细显示方式,
那么把包的数据,显示到标准输出 */
if(pv.verbose_flag)
{
...... //省略
}
/* check or log the packet as necessary
如果工作在使用检测规则的方式,就调用Preprocess进行检测,
否则,仅仅进行日志,记录该包的信息*/
if(!pv.use_rules)
{
... //进行日志,省略
}
else
{
Preprocess(&p);
}
//清除缓冲区
ClearDumpBuf();
}
这里Preprocess函数进行实际检测。
Proprocess函数很短,首先调用预处理规则处理数据包p,然后调用检测

函数Detect进行规则匹配实现检测,如果实现匹配,那么调用函数CallOu
tput
Plugins根据输出规则进行报警或日志。函数如下:
void Preprocess(Packet *p)
{
PreprocessFuncNode *idx;
do_detect = 1;
idx = PreprocessList; //指向预处理规则链表头
while(idx != NULL) //调用预处理函数处理包p
{
idx->func(p);
idx = idx->next;
}
发信人: shuke (莫失莫忘), 信区: Security
标 题: snort源码分析(5)
发信站: BBS 水木清华站 (Fri Oct 13 11:20:55 2000)
Proprocess函数很短,首先调用预处理规则处理数据包p,然后调用检测

函数Detect进行规则匹配实现检测,如果实现匹配,那么调用函数CallOu
tput
Plugins根据输出规则进行报警或日志。函数如下:
void Preprocess(Packet *p)
{
PreprocessFuncNode *idx;
do_detect = 1;
idx = PreprocessList; //指向预处理规则链表头
while(idx != NULL) //调用预处理函数处理包p
{
idx->func(p);
idx = idx->next;
}
if(!p->frag_flag && do_detect)
{
if(Detect(p)) //调用检测函数
{
CallOutputPlugins(p); //如果匹配,根据规则输出
}
}
}
尽管这个函数很简洁,但是在第1行我们看到定义了ProprocessFuncNode

结构类型的指针,所以下面,我们不得不开始涉及到snort的各种复杂
的数据结构。前面的分析,我一直按照程序运行的调用顺序,忽略了许多

数(其实有不少非常重要),以期描述出snort执行的主线,避免因为程序

大量的调用关系而产生混乱。到现在,我们还没有接触到snort核心的数据
结构
和算法。有不少关键的问题需要解决:规则是如何静态描述的?运行时这

规则按照什么结构动态存储?每条规则的处理函数如何被调用?snort给了

我们提供了非常好的方法。
snort一个非常成功的思想是利用了plugin机制,规则处理函数并非固定在

源程序中,而是根据每次运行时的参数设定,从规则文件中读入规则,再
把每个
规则所需要的处理函数挂接到链表上。实际检测时,遍历这些链表,调用
链表上
相应的函数来分析。
snort主要的数据结构是链表,几乎都是链表来链表去。我们下面做个总的

介绍。
我们有必要先回过头来,看一看main函数中对规则初始化时涉及到的一些

数据结构。
在main函数初始化规则的时候,先建立了几个链表,全局变量定义如下
(plugbase.c中):
KeywordXlateList *KeywordList;
PreprocessKeywordList *PreprocessKeywords;
PreprocessFuncNode *PreprocessList;
OutputKeywordList *OutputKeywords;
OutputFuncNode *OutputList;
这几种结构的具体定义省略。这一初始化的过程把snort中预定义的关键

字和处理函数按类别连接在不同的链表上。然后,在解析规则文件的时候

如果一条规则的选项中包含了某个关键字,就会从上边初始化好的对应的
链表
中查找,把必要的信息和处理函数添加到表示这条规则的节点(用RuleTre
eNode
类型来表示,下面详述)的特定域(OptTreeNode类型)中。
同时,main函数中初始化规则的最后,对指定的规则文件进行解析。在最

高的层次上,有3个全局变量保存规则(rules.c):
ListHead Alert; /* Alert Block Header */
ListHead Log; /* Log Block Header */
ListHead Pass; /* Pass Block Header */
这几个变量是ListHead类型的,正如名称所说,指示链表头。Alert中登记

了需要报警的规则,Log中登记了需要进行日志的规则,Pass中登记的规则
在处
理过程忽略(不进行任何处理)。ListHead定义如下:
typedef struct _ListHead
{
RuleTreeNode *TcpList;
RuleTreeNode *UdpList;
RuleTreeNode *IcmpList;
} ListHead;
可以看到,每个ListHead结构中有三个指针,分别指向处理Tcp/Udp/Icmp

规则的链表头。这里又出现了新的结构RuleTreeNode,为了说明链表的层
次关系,
下面列出RuleTreeNode的定义,但是忽略了大部分域:
typedef struct _RuleTreeNode
{
RuleFpList *rule_func;
...... //忽略
struct _RuleTreeNode *right;
OptTreeNode *down; /* list of rule options to associate with thi
s
rule node */
} RuleTreeNode;
RuleTreeNode中包含上述3个指针域,分别又能形成3个链表。RuleTreeNo
de*
类型的right指向下一个RuleTreeNode,相当于普通链表中的next域,只不
过这里
用right来命名。这样就形成了规则链表。
RuleFpList类的指针rule_func记录的是该规则的处理函数的链表。一条规

有时候需要调用多个处理函数来分析。所以,有必要做成链表。我们看看
下面的
定义,除了next域,还有一个函数指针:
typedef struct _RuleFpList
{
/* rule check function pointer */
int (*RuleHeadFunc)(Packet *, struct _RuleTreeNode *, struct _Ru
leFpList *);
/* pointer to the next rule function node */
struct _RuleFpList *next;
} RuleFpList;
第3个指针域是OptTreeNode类的指针down,该行后面的注释说的很清楚,
这是
与这个规则节点相联系的规则选项的链表。很不幸,OptTreeNode的结构也
相当复
杂,而且又引出了几个新的链表。忽略一些域,OptTreeNode定义如下:

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" da
ta structures
with the rule system, letting them link anything they can come u
p
with to the rules list */
void *ds_list; /* list of plugin data struct pointers */
.......//省略了一些域
struct _OptTreeNode *next;
} OptTreeNode;
next指向链表的下一个节点,无需多说。OptFpList类型的指针opt_func指

选项函数链表,同前面说的RuleFpList没什么大差别。值得注意的是指针
数组
ds_list,用来记录该条规则中涉及到的预定义处理过程。每个元素的类型
是void*。
在实际表示规则的时候,ds_list被强制转换成不同的预定义类型。
--
垆边人似月,皓腕凝霜雪
※ 来源:·BBS 水木清华站 smth.org·
发信人: shuke (莫失莫忘), 信区: Security
标 题: snort源码分析(5)
发信站: BBS 水木清华站 (Fri Oct 13 11:20:55 2000)
Proprocess函数很短,首先调用预处理规则处理数据包p,然后调用检测

函数Detect进行规则匹配实现检测,如果实现匹配,那么调用函数CallOu
tput
Plugins根据输出规则进行报警或日志。函数如下:
void Preprocess(Packet *p)
{
PreprocessFuncNode *idx;
do_detect = 1;
idx = PreprocessList; //指向预处理规则链表头
while(idx != NULL) //调用预处理函数处理包p
{
idx->func(p);
idx = idx->next;
}
if(!p->frag_flag && do_detect)
{
if(Detect(p)) //调用检测函数
{
CallOutputPlugins(p); //如果匹配,根据规则输出
}
}
}
尽管这个函数很简洁,但是在第1行我们看到定义了ProprocessFuncNode

结构类型的指针,所以下面,我们不得不开始涉及到snort的各种复杂
的数据结构。前面的分析,我一直按照程序运行的调用顺序,忽略了许多

数(其实有不少非常重要),以期描述出snort执行的主线,避免因为程序

大量的调用关系而产生混乱。到现在,我们还没有接触到snort核心的数据
结构
和算法。有不少关键的问题需要解决:规则是如何静态描述的?运行时这

规则按照什么结构动态存储?每条规则的处理函数如何被调用?snort给了

我们提供了非常好的方法。
snort一个非常成功的思想是利用了plugin机制,规则处理函数并非固定在

源程序中,而是根据每次运行时的参数设定,从规则文件中读入规则,再
把每个
规则所需要的处理函数挂接到链表上。实际检测时,遍历这些链表,调用
链表上
相应的函数来分析。
snort主要的数据结构是链表,几乎都是链表来链表去。我们下面做个总的

介绍。
我们有必要先回过头来,看一看main函数中对规则初始化时涉及到的一些

数据结构。
在main函数初始化规则的时候,先建立了几个链表,全局变量定义如下
(plugbase.c中):
KeywordXlateList *KeywordList;
PreprocessKeywordList *PreprocessKeywords;
PreprocessFuncNode *PreprocessList;
OutputKeywordList *OutputKeywords;
OutputFuncNode *OutputList;
这几种结构的具体定义省略。这一初始化的过程把snort中预定义的关键

字和处理函数按类别连接在不同的链表上。然后,在解析规则文件的时候

如果一条规则的选项中包含了某个关键字,就会从上边初始化好的对应的
链表
中查找,把必要的信息和处理函数添加到表示这条规则的节点(用RuleTre
eNode
类型来表示,下面详述)的特定域(OptTreeNode类型)中。
同时,main函数中初始化规则的最后,对指定的规则文件进行解析。在最

高的层次上,有3个全局变量保存规则(rules.c):
ListHead Alert; /* Alert Block Header */
ListHead Log; /* Log Block Header */
ListHead Pass; /* Pass Block Header */
这几个变量是ListHead类型的,正如名称所说,指示链表头。Alert中登记

了需要报警的规则,Log中登记了需要进行日志的规则,Pass中登记的规则
在处
理过程忽略(不进行任何处理)。ListHead定义如下:
typedef struct _ListHead
{
RuleTreeNode *TcpList;
RuleTreeNode *UdpList;
RuleTreeNode *IcmpList;
} ListHead;
可以看到,每个ListHead结构中有三个指针,分别指向处理Tcp/Udp/Icmp

规则的链表头。这里又出现了新的结构RuleTreeNode,为了说明链表的层
次关系,
下面列出RuleTreeNode的定义,但是忽略了大部分域:
typedef struct _RuleTreeNode
{
RuleFpList *rule_func;
...... //忽略
struct _RuleTreeNode *right;
OptTreeNode *down; /* list of rule options to associate with thi
s
rule node */
} RuleTreeNode;
RuleTreeNode中包含上述3个指针域,分别又能形成3个链表。RuleTreeNo
de*
类型的right指向下一个RuleTreeNode,相当于普通链表中的next域,只不
过这里
用right来命名。这样就形成了规则链表。
RuleFpList类的指针rule_func记录的是该规则的处理函数的链表。一条规

有时候需要调用多个处理函数来分析。所以,有必要做成链表。我们看看
下面的
定义,除了next域,还有一个函数指针:
typedef struct _RuleFpList
{
/* rule check function pointer */
int (*RuleHeadFunc)(Packet *, struct _RuleTreeNode *, struct _Ru
leFpList *);
/* pointer to the next rule function node */
struct _RuleFpList *next;
} RuleFpList;
第3个指针域是OptTreeNode类的指针down,该行后面的注释说的很清楚,
这是
与这个规则节点相联系的规则选项的链表。很不幸,OptTreeNode的结构也
相当复
杂,而且又引出了几个新的链表。忽略一些域,OptTreeNode定义如下:

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" da
ta structures
with the rule system, letting them link anything they can come u
p
with to the rules list */
void *ds_list; /* list of plugin data struct pointers */
.......//省略了一些域
struct _OptTreeNode *next;
} OptTreeNode;
next指向链表的下一个节点,无需多说。OptFpList类型的指针opt_func指

选项函数链表,同前面说的RuleFpList没什么大差别。值得注意的是指针
数组
ds_list,用来记录该条规则中涉及到的预定义处理过程。每个元素的类型
是void*。
在实际表示规则的时候,ds_list被强制转换成不同的预定义类型。


========== * * * * * ==========
返回