China Open source community
站内导航:

 
 
 
当前位置: 首页 >> 程序设计 >> Snort 源码分析
 

Snort 源码分析

作者:      来源:转自SMTH     发表时间:2006-09-12     浏览次数:      字号:    

Snort作为一个轻量级的网络入侵检测系统,在实际中应用可能会有些力不从心,但如果想了解研究IDS的工作原理,仔细研究一下它的源码到是非常不错.首先对snort做一个概括的评论。
从工作原理而言,snort是一个NIDS。[注:基于网络的入侵检测系统(NIDS)在网络的一点被动地检查原始的网络传输数据。通过分析检查的数据包,NIDS匹配入侵行为的特征或者从网络活动的角度检测异常行为。] 网络传输数据的采集利用了工具包libpcap。snort对libpcap采集来的数据进行分析,从而判断是否存在可疑的网络活动。

从检测模式而言,snort基本上是误用检测(misuse detection)。[注:该方法对已知攻击的特征模式进行匹配,包括利用工作在网卡混杂模式下的嗅探器被动地进行协议分析,以及对一系列数据包解释分析特征。顺便说一句,另一种检测是异常检测(anomaly detection)。]具体实现上,仅仅是对数据进行最直接最简单的搜索匹配,并没有涉及更复杂的入侵检测办法。

尽管snort在实现上没有什么高深的检测策略,但是它给我们提供了一个非常
优秀的公开源代码的入侵检测系统范例。我们可以通过对其代码的分析,搞清IDS 究竟是如何工作的,并在此基础上添加自己的想法。

snort的编程风格非常优秀,代码阅读起来并不困难,整个程序结构清晰,函
数调用关系也不算复杂。但是,snort的源文件不少,函数总数也很多,所以不太
容易讲清楚。因此,最好把代码完整看一两遍,能更清楚点。

*****************************************************
*****************************************************
下面看看snort的整体结构。展开snort压缩包,有约50个c程序和头文件,另有约30个其它文件(工程、数据或者说明文件)。[注:这里用的是snort-1.6-beta7。snort-1.6.3不在手边,就用老一点的版本了,差别不大。]下面对源代码文件分组说明。

snort.c(.h)是主程序所在的文件,实现了main函数和一系列辅助函数。

decode.c(.h)把数据包层层剥开,确定该包属于何种协议,有什么特征。并
标记到全局结构变量pv中。

log.c(.h)实现日志和报警功能。snort有多种日志格式,一种是按tcpdump二进制的格式存储,另一种按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_icmp_type_check针对icmp包的类型,sp_tcp_flag_check针对tcp包的标志位。不再详述。

spo_*.c(.h)实现输出(output)规则。spo_alert_syslog把事件记录到syslog中;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函数,创建守护进程,重定向标准输入输出,实现daamon状态,并结束父进程。

snort可以实时采集网络数据,也可以从文件读取数据进行分析。这两种情况并没有本质区别。如果是读取文件进行分析(并非直接从网卡实时采集来的),以该文件名作为libpcap的函数OpenPcap的参数,打开采集过程;如果是从网卡实时采集,就把网卡接口作为OpenPcap的参数,利用libpcap的函数打开该网卡接口。在unix中,设备也被看作是文件,所以这和读取文件分析没有多大的差别。

接着,指定数据包的拆包函数。不同的数据链路网络,拆包的函数也不同。利用函数SetPktProcessor,根据全局变量datalink的值,来设定不同的拆包函数。例如,以太网,拆包函数为DecodeEthPkt;令牌环网,拆包函数为DecodeTRPkt,等等。这些Decode*函数,在decode.c中实现。

如果使用了检测规则,那么下面就要初始化这些检测规则,并解析规则文件,转化成规则链表。规则有三大类:预处理(preprocessor),插件(plugin),输出插件(outputplugin)。这里plugin就是具体的检测规则,而outputplugin是定义日志和报警方式的规则。

然后根据报警模式,设定报警函数;根据日志模式,设定日志函数;如果指定了能够进行响应,就打开raw socket,准备用于响应。

最后进入读取数据包的循环,pcap_loop对每个采集来的数据包都用ProcessPacket函数进行处理,如果出现错误或者到达指定的处理包数(pv.pkt_cnt定义),就退出该函数。这里ProcessPacket是关键程序,

最后,关闭采集过程。

*****************************************************
现在看看snort如何实现对数据包的分析和检测入侵的。

在main函数的最后部分有如下语句,比较重要:

/* Read all packets on the device. Continue until cnt packets read */
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 *user)
{
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_char *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进行规则匹配实现检测,如果实现匹配,那么调用函数CallOutput
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] [2]

编辑 webmaster

 
 
 
评论
 
 
发表
 
姓名: QQ:
性别: MSN:
E-mail: 主页:
评分: 1 2 3 4 5
评论内容:
验证码:
  
  • 请遵守《互联网电子公告服务管理规定》及中华人民共和国其他各项有关法律法规。
  • 严禁发表危害国家安全、损害国家利益、破坏民族团结、破坏国家宗教政策、破坏社会稳定、侮辱、诽谤、教唆、淫秽等内容的评论 。
  • 用户需对自己在使用本站服务过程中的行为承担法律责任(直接或间接导致的)。
  • 本站管理员有权保留或删除评论内容。
  • 评论内容只代表网友个人观点,与本网站立场无关。
  •  
    中国源码网 - WWW.YUANMA.ORG - 中国开放源代码社区