序:
1.
包捕获模块
2.
包解码模块
3.
预处理模块
4.
检测模块(模式匹配)
5.
输出模块
第一部分:包捕获模块
本部分由
snort.c 文件中的
OpenPcap函数实现。该函数依次调用下图的
libpcap库中的函数
这里需要提醒的是本模块实现的功能实现到
libpcap库流程的循环抓包前为止,循环抓包是
到
snort.c 主流程的
InterfaceThread函数中实现。
以下是
libpcap库各个函数的作用:
1、
pcap_lookupdev pcap_lookupdev用来查找系统第一个可以使用的网络适配器,查找成功后,
返回该设备的名称;如果系统有多个网卡,也可以使用
pcap_findalldevs函数
来查找选取;
2、
pcap_open_live 函数用于打开指定网络适配器,准备截取数据。其调用形式为:
pd = pcap_open_live()
/* 以下是参数
*/ ( pv.interface, /*设备名称
*/ snaplen, /*捕获包的长度,通常设置为
65536,表示捕获链路层上的所有数据
*/ pv.promisc_flag ? PROMISC : 0, /*网卡是否工作于混杂模式
*/ READ_TIMEOUT, /*超时时间控制,单位毫秒
*/ Errorbuf ); /* 出错信息存储
*/ 3、
pcap_open_offline Libpcap 使用库函数
pcap_open_offline 进行脱机方式截获,即先将网络上的
数据截获下来,以文件形式储存到磁盘上,等事后方便时再从磁盘上读取数
据文件来做进一步分析。
4、
pcap_snapshot 函数用于获取数据链路层协议的类型;
5、
pcap_compile 和
pcap_setfilter 我们在进行数据包截获时,常常并不需要捕获所有的包,如只需要捕获
ARP 协议等,这就需要过滤规则。
Pcap_compile 用于将设置的过滤字符串编译成
一个过滤器程序。而
pcap_setfilter则用来设置过滤器的过滤规则;
6、
pcap_loop 前面的函数都可以看做捕获数据包的准备工作,
pcap_loop 用来从网络中捕
获数据包。需要注意的是函数的第三个参数,它是一个回调函数,捕获到的
数据包,就交由它来处理;
第二部分:包解码模块
本模块由
snort.c文件中的
SetPktProcessor函数实现。该函数根据
datalink(由上面的
li bpcap库函数得到)的值来判断并关联解码函数。解码结构如下图:
程序大致结构如下:
int SetPktProcessor() { switch(datalink) { case DLT_EN10MB: /* Ethernet */ grinder = DecodeEthPkt; break;
case DLT_IEEE802: /* Token Ring */ grinder = DecodeTRPkt; break;
/* 以下略
*/ } }
其中
grinder 是
snort.c 中的全局的函数指针,其定义如下:
typedef void (*grinder_t)(Packet *, struct pcap_pkthdr *, u_char *); /* ptr t o the packet processor */
|
第三部分:预处理模块
Snort系统在初始化(详见附录一)完成后,在进入检测引擎模块(处理前面两个模块传过
来的网络封包)前,需要对数据包进行预处理。这里请注意
SNORT所使用的插件的思想,
S
nort的插件结构允许开发者扩展
snort的功能。
需要再次说明的是,循环抓包是在
snort.c 主流程的
InterfaceThread函数中实现,该函数
调用
libpcap 库中的
pcap_loop循环抓包函数,这个函数有个回调函数
ProcessPacket,这
个回调函数先通过上一个包解码模块得到的相应的解码函数对抓到的数据包进行解码
(实现
语句是:
(*grinder) (&p, pkthdr, pkt); ),下一步判断
snort.c 文件中的
runMode全局
遍变量,若
SNORT工作在包记录模式,则调用输出插件
CallLogPlugins函数;否则工作在
IDS模式,继而进入预处理
Preprocess函数。
该预处理函数遍历在初始化预处理插件(详见附录一)后得到的
PreprocessKeywordList链
表,调用配置文件中要求的预处理插件函数对所抓的数据包进行预处理。函数实现如下:
idx = PreprocessList;
while(idx != NULL)
{
assert(idx->func != NULL);
idx->func(p);
idx = idx->next;
}
预处理器在调用检测引擎之前,在数据包被解码之后运行。通过这种机制,
snort可以以一
种
out of band的方式对数据包进行修改或者分析。以下是几个常用的预处理插件:
1.
HTTP解码预处理模块用来处理
HTTP URI字符串,把它们转换为清晰的
ASCII字符串。这
样就可以对抗
evasice web URL扫描程序和能够避开字符串内容分析的恶意攻击者。
2.
frag2模块
:使
snort能够消除
IP碎片包,给黑客使用
IP碎片包绕过系统的检测增加了
难度。
3.
stream4插件为
snort提供了
TCP数据包重组的功能。在配置的端口上,
stream4插件能
够对
TCP数据包的细小片段进行重组成为完整的
TCP数据包,然后
snort可以对其可疑行为进
行检查。
4.
Portscan预处理程序的用处:
向标准记录设备中记录从一个源
IP地址来的端口扫描的
开始和结束。端口扫描可以是对任一
IP地址的多个端口,也可以是对多个
IP地址的同一端
口进行。可以处理分布式的端口扫描(多对一或多对多)。端口扫描也包括单一的秘密扫
描(
stealth scan)数据包,比如
NULL,
FIN,
SYNFIN,
XMAS等。
5.
Portscan2模块将检测端口扫描。它要求包含
Conversation预处理器以便判定一个会话
是什么时间开始的。它的目的是能够检测快速扫描,例如,快速的
nmap扫描。
6.
Conversation 预处理器使
Snort 能够得到关于协议的基本的会话状态而不仅仅是由
s
pp_stream4处理的
TCP状态。当它接收到一个你的网络不允许的协议的数据包时,它也能产
生一个报警信息。要做到这一点,请在
IP协议列表中设置你允许的
IP协议,并且当它收到
一个不允许的数据包时,它将报警并记录这个数据包。
7.
Http Flow模块可以忽略
HTTP头后面的
HTTP服务响应。
由于
SNORT的各种预处理模块很有借鉴性,我将在另一章中给出详解。
第四部分
:检测模块(模式匹配)
SNORT系统的快速匹配模块有最重要的设计特色,其在初始规则链表的基础上,重新构造了
一套快速匹配的数据结构,并采用了多模式匹配搜索引擎。按其功能可分三个步骤:
1. 构造初始的规则链表结构,由
ParseRule函数实现。
(详见附录三
)
2. 读入规则链表各节点,并构造用快速匹配的新的数据结构,这是在初始化各个插件和建
立三维链表后,在调用处理模块函数
InterfaceThread前完成的。它是由
fpcreate.c文件中
的
fpCreateFastPacketDetection函数完成的。(详见附录四)
3. 对当前数据包执行具体的快速规则匹配任务,主要在
fpEvalPacket()上。(本模块实现
)
注意现在存在两个链表:一个是
RuleListNode->ListHead->RTN/OTN/OutputFuncNode链表
,表头是
parser.c文件中全局变量
RuleLists;另一个是
PORT_RULE_MAP -> PROT_GROUP -
> RULE_NODE链表,表头是
fpcreate.c文件中全局数据结构指针
prmTcpRTNX/prmUdpRTNX/p
rmIcmpRTNX/prmIpRTNX。
通过第一个链表中
OTN的规则内容建立第二个链表。
在
Preprocess函数中,在处理完预处理模块后,调用
Detect函数对捕获的
packet结构类型
参数的数据包内容进行特征规则匹配。该函数调用
fpEvalPacket函数,进而根据捕获数据
包的协议类型判断
Tcp/Udp/Icmp协议,若不能规为这三种协议的就算作
Ip协议,然后调用
相应的处理函数。以
Tcp协议为例,调用
fpEvalHeaderTcp函数。
下面以
fpEvalHeaderTcp 函数源码为例解析:
static INLINE int fpEvalHeaderTcp(Packet *p)
{
/*根据给定参数的源端口和目的端口决定应根据哪个
PORT_RULE_MAP->PORT_GROUP*/
/*结构进行匹配
(该链表的建立参照附录四
) */
prmFindRuleGroupTcp(p->dp, p->sp, &src, &dst, &gen);
/*遍历
Tcp类型的
PORT_RULE_MAP->PORT_GROUP 链表进行
http_decode模式特征匹配
*/
fpEvalHeaderSW(dst, p, 1);
}
继续对
fpEvalHeaderSW 函数进行解析。该函数遍历
PORT_RULE_MAP->PORT_GROUP链表,进
行
uri-conetnt/content/non-conetnt匹配:
int fpEvalHeaderSW(PORT_GROUP *port_group, Packet *p, int check_ports)
{
/*循环
HttpUri类型全局指针数组
UriBufs[URI_COUNT] */
for( i=0; iuri_count; i++)
{
/* 以下分别进行
uri-conetnt/content/non-conetnt匹配,仅以
content 为例
*/
stat = mpseSearch ( so, p->data, p->dsize, otnx_match, &omd );
fpLogEvent(otnx->rtn, otnx->otn, p);
}
}
下面对以上各函数分别解析:
1.
mpseSearch函数:
SNORT提供三种完全不同的模式匹配算法:
Aho-Corasick/Wu-Manber/Boyer-Moore,默认
使用第三种。注意
mpseSearch函数中有两个参数分别是:
所要匹配的数据包指针
(Packet
*)和先前为了快速匹配特征串而建立的对应的结构指针(
PORT_RULE_MAP->PORT_GROUP*)
,后面的所调用的函数参数都是从这两个参数中得到。
mpseSearch函数调用
mwmSearch,继续调用
mwmSearch,继续调用
hbm_match函数。这个函数
即是用
Boyer-Moore-Horspool模式匹配算法进行特征匹配。具体算法实现请参照
msting.c
文件。
2.
fpLogEvent函数:
若匹配成功,则调用本函数记录或报警。该函数的参数分别是:匹配的
RTN/ONT指针和数据
包指针
(Packet*),具体实现如下:
switch(rtn->type)
{
case RULE_PASS: PassAction();
case RULE_ACTIVATE: ActivateAction(p, otn, &otn->event_data);
case RULE_ALERT: AlertAction(p, otn, &otn->event_data);
case RULE_DYNAMIC: DynamicAction(p, otn, &otn->event_data);
case RULE_LOG: LogAction(p, otn, &otn->event_data);
}
若是应用
pass规则动作,则仅设置通过包的数目加一;
若是
dynamic/log规则动作,则调用
detect.c文件中的
CallLogFuncs函数进行记录;
若是
activate/alert规则动作,则调用
detect.c文件中的
CallAlertFuncs函数进行报警。
附录一:初始化插件函数
void InitPlugIns() void InitPreprocessors() void InitOutputPlugins()
三个函数都在plugbase.c中,并都转入RegisterXXX()函数调用。举个例子:
void RegisterOutputPlugin(char *keyword, int type, void (*func) (u_char *)) { OutputKeywordList *idx;
idx = OutputKeywords;
if(idx == NULL) { OutputKeywords = (OutputKeywordList *) calloc(sizeof(OutputKeywordList ),sizeof(char));
idx = OutputKeywords; } else { while(idx->next != NULL) { if(!strcasecmp(idx->entry.keyword, keyword)) { FatalError("%s(%d) => Duplicate output keyword!\n", file_name, file_line); } idx = idx->next; }
idx->next = (OutputKeywordList *) calloc(sizeof(OutputKeywordList), si zeof(char));
idx = idx->next; }
idx->entry.keyword = (char *) calloc(strlen(keyword) + 1, sizeof(char));
strncpy(idx->entry.keyword, keyword, strlen(keyword)+1);
idx->entry.node_type = (char) type;
idx->entry.func = (void *) func; }
本函数最终得到OutputKeywordList 链表指针,其他两个插件类似,得到 PreprocessLis t 链表指针和KeywordXlateList链表指针。这三个链表指针都是plugbase.c文件中的全局 变量,且都是对应的OutputKeyNode/PreprocessNode/KeywordXlate结构的链表头,这些结 构都在plugbase.h头文件中定义,它们都包括用来查找的插件名和要用到的处理从规则配 置文件中传过来参数的插件初始化函数。具体要用到那些插件是在parser.c文件中ParseR ule函数实现。
附录二: SNORT 的三维链表
处理规则文件后,SNORT使用一个三维链表来存储信息以便和所抓的封包进行数据查找匹配 。
RuleList是parser.c文件中的一个全局变量。该指针是一串RuleListNode结构的头指针。 每个RuleListNode结构中都有一个指向ListHead结构的指针。
每个ListHead结构又包括四个RuleTreeNode结构(IpList/TcpList/UdpList/IcmpList)的指 针和两个_OutputFuncNode结构(LogList/AlertList)的指针。
LogList/AlertList是OutputFuncNode结构链表的头指针,是在plugbase.c文件中AddFunc ToOutputList函数实现的。该函数被初始化输出插件函数InitOutputPlugins调用。这两个 链表被用于SNORT调用输出插件输出信息(记录日志或报警)。
四个RuleTreeNode结构的头指针详见下图:
以上就是三维链表结构,这是由parse.c文件中的ParseRule函数(调用ProcessHeadNode函 数和ParseRuleOptions函数)实现。详见附录三。
附录三:解析ParseRule初始化函数
ParseRule函数对配置文件的每一行(从ParseRuleFile 函数传过来)进行解析。可以分成 两大部分解析:规则头和规则体。举例来说:
alert tcp any 1234 -> 192.168.1.0/24 80 (content:”|00 01 86 a5|”; msg:”moun td access” |------------------- Header ------------------|---------------------- Options ------------------------|
规则头包括:规则动作(alert/log/pass/activate/dynamic),协议(tcp),源地址(any), 源端口(1234),方向操作符(“->”),目的地址(192.168.1.0/24),目的端口(80)。
规则选项组成了snort入侵检测引擎的核心,既易用又强大还灵活。snort中有42个规则选 项关键字,如 msg/logto/ipoption/content/classtype等,要提请注意的是选项content、 content-list和uricontent,它们允许用户设置规则在包的净荷中搜索指定的内容并根据 内容触发响应。当进行content选项模式匹配时,Boyer-Moore模式匹配函数被调用,并且 对包的内容进行检查(很花费计算能力)。如果包的净荷中包含的数据确切地匹配了参数 的内容,这个检查成功并且该规则选项的其他部分被执行。这就是模式匹配(基于特征的 规则匹配),也是检测模块的重点。
下面就源码作简要结束: void ParseRule(FILE *rule_file, char *prule, int inclevel) {
toks = mSplit(rule, " ", 10, &num_toks, 0); /* toks[0]对应于配置文件每一行的第一个关键字,可分为四类: */ /* 第一类:alert/log/pass/activate/dynamic (其实这由include语句引入) */ /* 第二类:preprocessor/output 分别对应预处理插件和输出插件,举例 */ /* 如:output database: log, mysql, user=root dbname=db host=localhost */ /* 第三类:设置网络参数如:var ORACLE_PORTS 1521 */ /* 第四类:配置和命令行选项参数 如:config logdir: /var/log/snort */ /* 第五类:自定义规则集,RULE_DECLARE/ RULE_UNKNOW在此不作介绍 */ rule_type = RuleType(toks[0]); switch(rule_type) { /* 先处理规则头 */ /* 以下五条语句ProcessHeadNode形成三维链表的表头 */ case RULE_ALERT: ProcessHeadNode(&proto_node, &Alert, protocol); case RULE_LOG: ProcessHeadNode(&proto_node, &Log, protocol); case RULE_PASS: ProcessHeadNode(&proto_node, &Pass, protocol); case RULE_ACTIVATE: ProcessHeadNode(&proto_node, &Activation, protocol); case RULE_DYNAMIC: ProcessHeadNode(&proto_node, &Dynamic, protocol);
case RULE_PREPROCESS: ParsePreprocessor(rule); //处理预处理插件(附录一 ) case RULE_OUTPUT: ParseOutputPlugin(rule); //处理输出插件(附录一)
/* 以下的parse函数详见源码 */ case RULE_CONFIG: ParseConfig(rule); case RULE_DECLARE: ParseRuleTypeDeclaration(rule_file, rule); case RULE_INCLUDE: ParseRulesFile(tmp, inclevel + 1); case RULE_VAR: VarDefine(toks[1], toks[2]); case RULE_UNKNOWN: ParseDeclaredRuleType(rule); }
ProcessIP(); //解析地址 ParsePort(); //解析端口 strcmp("->", toks[4]) && !strcmp("<>", toks[4]); //解析方向
/* 再处理规则选项*/ ParseRuleOptions(rule, rule_type, protocol);
} 1.ProcessHeadNode函数的第二个参数Alert/Log/Pass/Activation/Dynamic是parser.c文 件中定义的五个ListHead全局指针(分别对应五个规则动作),它们都包含RuleTreeNode 结构的四个链表IpList/TcpList/UdpList/IcmpList,RTN链表这是三维链表的第一维。
ProcessHeadNode函数遍历RTN表头(如:Alert->IpList ),调用SetupRTNFuncList函数 初始化RTN链表,该函数是通过调用不同参数的AddRuleFuncToList函数来实现的,最后得 到规则函数指针链表,表头是RTN->RuleFpList类型。这是三维链表的第三维之一。
注意:各个RTN/OTN结构中的函数指针链表即是三维链表的第三维。
2.ParsePreprocessor函数对预处理选项进行初始化,它遍历附录一初始化后所得到的Pr eprocessKeywords链表,找到配置文件指定要加入的预处理器插件,用相应的插件函数对 配置文件中传入的参数进行初始化。 ParseOutputPlugin函数与此类似。
3.ParseRuleOptions函数处理规则选项。它遍历括号内的规则选项。所有的snort规则选 项用分号";"隔开。规则选项关键字和它们的参数用冒号":"分开。通过匹配规则选项关 键字来调用相关的解析函数。大致结构如下: if(!strcasecmp(opts[0], "msg") ParseMessage(opts[1]); else if(!strcasecmp(opts[0], "logto") ParseLogto(opts[1]); else if /*略*/
然后调用AddOptFuncToList函数,得到三维链表的第三维之一:RTN->OptRuleFpList类型 的链表。
注意:至此三维链表建立。
|
附录四:初始化快速包检测引擎
由
fpCreateFastPacketDetection函数实现,该函数遍历
RuleListNode->ListHead->RTN/O
TN类型的链表(如:
rule->RuleList->TcpList),在此每一条规则根据其内容
(Content/
UriContent/NoContent)被分类,内容信息存储在
OTN中:在
OTN中有一项指针数组
ds_list
,指向不同的根据规则设置的数据结构。
根据每一条规则的
ds_list内容类型分别调用
prmAddRule/prmAddRuleUri/prmAddRuleNC函
数。
fpCreateFastPacketDetection函数根据各条规则中给出的源端口和目的端口把规则排
序加入到合适的
PORT_RULE_MAP -> PROT_GROUP -> RULE_NODE链表中。这样做的目的是为
了能够快速和捕获包进行特征匹配。下图是用到的数据结构:
我们在
fpcreate.c文件中定义了四个
PORT_RULE_MAP类型的全局数据结构指针
prmTcpRTNX/
prmUdpRTNX/prmIcmpRTNX/prmIpRTNX,该结构主要包含三个
PORT_GROUP类型的结构的指针
数组表:
prmSrcPort[MAX_PORTS]/prmDstPort[MAX_PROTS]/prmGeneric,其中
MAX_PORTS对
应端口号
0到
65535,
prmGeneric通用表被用作源端口和目的端口都为
ANY的情况。
SNORT系统快速匹配规则引擎的设计思想是如何更有效地划分规则集合具体实现是通过规则
中的目的端口和源断口值来划分类别。
1。
如果源端口值为特定值,目的端口为任意值
(ANY),则该规则加入到源端口值对应的子
集合中。如果目的端口的值为特定值,则该规则同时加入到目的端口对应的子集合中。
2。
如果源端口值为特定值,目的端口为任意值
(ANY),则该规则加入到源端口值对应的子
集合中。如果目的端口的值也为任意值
(ANY),则该规则同时加入到目的端口对应的子集合
中。
3。
如果目的端口和源端口都为任意值
(ANY),则该规则加入到通用子集合中。
4。
对于规则中端口值求反操作或者指定值范围的情况,等于端口值为
ANY情况。
以下是源码解析:
int fpCreateFastPacketDetection()
{
/* 以
TCP为例
*/
/* 遍历
RuleListNode->ListHead->RTN->OTN 链表
*/
if(rule->RuleList->TcpList) {
for(rtn = rule->RuleList->TcpList; rtn != NULL; rtn = rtn->right){
for( otn = rtn->down; otn; otn=otn->next ) {
/* 以下把
Content、
UriContent、
NoContent 加入到端口规则映射表
*/
if( OtnHasContent( otn ) )
prmAddRule(prmTcpRTNX, dport, sport, otnx);
if( OtnHasUriContent( otn ) )
prmAddRuleUri(prmTcpRTNX, dport, sport, otnx);
if( !OtnHasContent( otn ) && !OtnHasUriContent( otn ) )
prmAddRuleNC(prmTcpRTNX, sport, dport, otnx);
prmCompileGroups(prmTcpRTNX);
BuildMultiPatternGroups(prmTcpRTNX);
}
下面分别对
prmAddRule/ prmCompileGroups/ BuildMultiPatternGroups三个函数分别解析
:
1。以
OtnHasContent为例:调用
prmAddRule函数,该函数根据源端口和目的端口来判断,
把
OTNX中相应的信息加入到对应的
PORT_RULE_MAP结构中。
int prmAddRule( PORT_RULE_MAP * p, int dport, int sport, RULE_PTR rd )
{
/* 若根据目的端口(
0 -> 65535)来汇聚信息
*/
if( dport != ANYPORT && dport < MAX_PORTS )
prmxAddPortRule( p->prmDstPort[ dport ], rd );
/* 若根据目的端口(
0 -> 65535)来汇聚信息
*/
if( sport != ANYPORT && sport < MAX_PORTS)
prmxAddPortRule( p->prmSrcPort[ sport ], rd );
/* generic(-1)指本规则可应用于任何值
*/
if( sport == ANYPORT && dport == ANYPORT) //ANYPORT==-1
prmxAddPortRule( p->prmGeneric, rd );
}
继续调用
prmxAddPortRule函数最终把
RULE_NODE节点加入到
PORT_GROUP形成规则内容链表
:
static int prmxAddPortRule( PORT_GROUP *p, RULE_PTR rd )
{
p->pgTail->rnNext = (RULE_NODE*)malloc( sizeof(RULE_NODE) );
p->pgTail = p->pgTail->rnNext;
p->pgTail->rnNext = 0;
p->pgTail->rnRuleData = rd;
}
需要注意的是:
RULE_PTR类型的结构定义是
typedef void * RULE_PTR,其实就对应的
OTN
X结构类型的数据
——这是从
RuleListNode->ListHead->RTN/OTN传过来的规则内容数据。
2。
fpCreateFastPacketDetection函数再调用
prmCompileGroups函数。
对于通用规则节点链表而言,在构建快速规则匹配引擎中,它仅是作为一个过渡性的函数
结构。对于未来的规则检测任务而言,每个数据包都有特定的源
/目的端口值。
为了达成根据端口进行快速规则匹配的任务,函数
fpCreateFastPacketDetection在完成遍
历操作后,对各个
PORT_RULE_MAP结构中的通用规则链表进行了进一步处理,调用
prmCom
pileGroups函数。该函数功能将通用规则链表中的各个规则节点加入到另外对应于特定端
口值的两个规则链表中,前提条件是目标规则链表中已经存在节点,既在
Port_Rule_Map结
构中的
Port_Group数组中每个非空元素中加入。
3。最后为适应多模式引擎算法,
fpCreateFastPacketDetection再调用
BuildMultiPatern
Groups()在每个
Port_Group结构中构建多模式搜索引擎所需的数据结构。
函数
fpCreateFastPacketDetection在完成规则链表的遍历操作后,既完成了各个规则子集
合的划分,所有的规则节点都已经加入了如下三种类型之一的子集合里
:
1。对应于特定源端口值的
PORT_GROUP结构的规则链表
2。对应于特定目的端口值的
PORT_GROUP结构的规则链表
3。通用一般的规则节点链表
总结:
可以把
SNORT功能实现分成两大块:一是
SNORT系统的初始化,二是在
SNORT系统功能框架建
立起来后,抓包并进行模式匹配。
初始化分两步:第一步把所有的三类插件(
output/preprocess/plugins)串成三个链表(
OutputKeywordList/PreprocessKeywordList/KeywordXlateList)以供下一步调用;第二
步是读取配置文件,并从上一步中插件链表中挑出配置文件中相应的要用到的插件建立
Ru
leListNode->ListHead->RTN/OTN/OutputFuncNode,这其中的
RTN/OTN链表构成
SNORT的有
特色的三维链表,供模式匹配用。
第二步:模式匹配。分两步:第一步,在模式匹配初始化时另外建立一个
PORT_RULE_MAP
-> PROT_GROUP -> RULE_NODE链表以供快速匹配用;第二步循环抓包并用
Boyer-Moore -H
orspool算法匹配。
1. 包捕获模块
2. 包解码模块
3. 预处理模块
4. 检测模块(模式匹配)
5. 输出模块