2022-06-09- 1 -
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, /*超时时间控制,单位毫秒*/
/* 以下是参数 */
2022-06-09- 2 -
); /* 出错信息存储 */
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(由上面的 libpcap 库函数得到)
的值来判断并关联解码函数。解码结构如下图:
程序大致结构如下:
int SetPktProcessor()
{
switch(datalink)
{
case DLT_EN10MB:
/* Ethernet */
2022-06-09- 3 -
/* Token Ring */
grinder = DecodeEthPkt;
break;
case DLT_IEEE802:
grinder = DecodeTRPkt;
break;
/* 以下略 */
}
}
/* ptr to the packet processor */
其中 grinder 是 snort.c 中的全局的函数指针,其定义如下:
typedef void (*grinder_t)(Packet *, struct pcap_pkthdr *, u_char *);
第三部分:预处理模块
Snort 系统在初始化(详见附录一)完成后,在进入检测引擎模块(处理前面两个模块传过来的网络封包)
前,需要对数据包进行预处理。这里请注意 SNORT 所使用的插件的思想,Snort 的插件结构允许开发者扩
展 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 能够得到关于协议的基本的会话状态而不仅仅是由 spp_stream4 处理的
TCP 状态。当它接收到一个你的网络不允许的协议的数据包时,它也能产生一个报警信息。要做到这一点,
请在 IP 协议列表中设置你允许的 IP 协议,并且当它收到一个不允许的数据包时,它将报警并记录这个数
据包。
2022-06-09- 4 -
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/prmIcmpRTNX/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; i
uri_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 函数:
2022-06-09- 5 -
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 函数进行报警。
2022-06-09- 6 -
snort 源代码情形分析
snort 基本框架
snort 初始化情形分析
本文有以下几部分组成:
1.
2.
3. 获取一个数据包后预处理和检测情形分析
4. 附录 一 源码中库函数和具体函数详解
5. 附录 二 设计自己的检测算法
一 snort 基本框架
Snort 包括主控模块(snort.c),包捕获模块,包解码模块(decode.c),规则处理模块(rule.c), 预
处理模块(spp_*.c),处理模块(sp_ *.c), 输出模块。
主控模块实现模块和全局变量的初始化,通过读取命令行参数进行一些设置。然后调用包捕获函数,
之后进行解码,处理,匹配等操作。下面是 snort 运行的流程:
二 snort 初始化情形分析
下面就从程序的入口分析,具体说明程序如何完成初始化的。
在说明初始化过程之前,我们要明确什么是需要初始化的。
1.
2.
3.
要初始化 snort 运行的模式,pass、log、or alert。
要确定网络的链路
要形成规则树用于匹配
2022-06-09- 7 -
4.
要设定好响应模式
下面具体看一些初始化:
我们从 main.c(snort.c)开始。(请对照源码看)
开始是初始化信号处理,子网掩码,抓包数量(/* initialize the packet counter to loop forever */
pv.pkt_cnt
= -1; ) 等 。 接 下 来 调 用 的 是 处 理 命 令 行 参 数 , 根 据 用 户 输 入 的 命 令 行 参 数 具 体 初 始 化
--------ParseCmdLine(argc, argv);----------- 该函数解析命令行参数,然后设置全局变量 pv,这个函数十分的
简单,大家可以看看源代码
然 后 打 开 raw socket : libnet_open_raw_sock(IPPROTO_RAW) 其 中 IPPROTO_RAW 意 味 着
IP_HDRINCL 处于激活状态,也意味着接收所有 IP 协议. 这个可以不管,只要知道这个就是打开接受 IP
包的开关就好了。
接下来是通过对 pv.readmode_flag 的判断决定是从网络接口还是从文件接受数据包,具体表现为 语句 /*
open up our libpcap packet capture interface */ OpenPcap(pv.interface); 和语句 OpenPcap(pv.readfile); (二者
通过 if 语句选其一)。
然后/*创建一个保存进程标识等相关信息的文件*/ CreatePidFile(pv.interface); 接下来做一些相关目
录检查,是否记录日志,是否进入 daemon 模式 等。
SetPktProcessor();设置端口的解码函数,然后如果应用规则(if (pv.use_rules) ) 就安装相关插件
InitPreprocessors(); 安装预处理插件
InitPlugIns(); 安处理插件
InitOutputPlugins(); 安装输出插件
通过 ParseRulesFile 处理规则文件,然后是加入相关 log ,alert 的处理函数(通过 AddFuncToOutputList 函
数加入)。
呵呵,到这里初始化工作就结束了。
接 下 来 就 是 pcap_loop(pd, pv.pkt_cnt, (pcap_handler)ProcessPacket, NULL) 捕 获 并 处 理 数 据 包 了 ,
ProcessPacket 就是处理数据包的回调函数。所有的检测过程都是在这个函数中完成的。首先 ProcessPacket
调用解码和预处理函数,然后调用规则检测函数 Detect(),然后通过规则树查找函数找到相应的规则列表,
规则头匹配,规则选项匹配,产生告警,记录日志….
三 获取一个数据包后预处理和检测情形分析
附录一:初始化插件函数
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)
2022-06-09- 8 -
{
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), sizeof(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 链表指针,其他两个插件类似,得到 PreprocessList 链表指针和
KeywordXlateList 链 表 指 针 。 这 三 个 链 表 指 针 都 是 plugbase.c 文 件 中 的 全 局 变 量 , 且 都 是 对 应 的
OutputKeyNode/PreprocessNode/KeywordXlate 结构的链表头,这些结构都在 plugbase.h 头文件中定义,它
们都包括用来查找的插件名和要用到的处理从规则配置文件中传过来参数的插件初始化函数。具体要用到
那些插件是在 parser.c 文件中 ParseRule 函数实现。
附录二: SNORT 的三维链表
处理规则文件后,SNORT 使用一个三维链表来存储信息以便和所抓的封包进行数据查找匹配
RuleList 是 parser.c 文件中的一个全局变量。该指针是一串 RuleListNode 结构的头指针。每个 RuleListNode