SNORT源码分析说课讲解.docx

上传人:b****7 文档编号:24963652 上传时间:2023-06-03 格式:DOCX 页数:18 大小:22.48KB
下载 相关 举报
SNORT源码分析说课讲解.docx_第1页
第1页 / 共18页
SNORT源码分析说课讲解.docx_第2页
第2页 / 共18页
SNORT源码分析说课讲解.docx_第3页
第3页 / 共18页
SNORT源码分析说课讲解.docx_第4页
第4页 / 共18页
SNORT源码分析说课讲解.docx_第5页
第5页 / 共18页
点击查看更多>>
下载资源
资源描述

SNORT源码分析说课讲解.docx

《SNORT源码分析说课讲解.docx》由会员分享,可在线阅读,更多相关《SNORT源码分析说课讲解.docx(18页珍藏版)》请在冰豆网上搜索。

SNORT源码分析说课讲解.docx

SNORT源码分析说课讲解

 

SNORT源码分析

SNORT源码分析(转自SMTH)

Snort作为一个轻量级的网络入侵检测系统,在实际中应用可能会有些力不从心,但如果想了解研究IDS的工作原理,仔细研究一下它的源码到是非常不错.首先对snort做一个概括的评论。

从工作原理而言,snort是一个NIDS。

[注:

基于网络的入侵检测系统(NIDS)在网络的一点被动地检查原始的网络传输数据。

通过分析检查的数据包,NIDS匹配入侵行为的特征或者从网络活动的角度检测异常行为。

]网络传输数据的采集利用了工具包libpcap。

snort对libpcap采集来的数据进行分析,从而判断是否存在可疑的网络活动。

从检测模式而言,snort基本上是误用检测(misusedetection)。

[注:

该方法对已知攻击的特征模式进行匹配,包括利用工作在网卡混杂模式下的嗅探器被动地进行协议分析,以及对一系列数据包解释分析特征。

顺便说一句,另一种检测是异常检测(anomalydetection)。

]具体实现上,仅仅是对数据进行最直接最简单的搜索匹配,并没有涉及更复杂的入侵检测办法。

尽管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中,或者记录到用户指定的文件,另外还可以通过unixsocket发送报警消息,以及利用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是定义日志和报警方式的规则。

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

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

这里ProcessPacket是关键程序,

最后,关闭采集过程。

*****************************************************

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

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

/*Readallpacketsonthedevice.Continueuntilcntpacketsread*/

if(pcap_loop(pd,pv.pkt_cnt,(pcap_handler)ProcessPacket,NULL)<0)

{

......

}

这里pcap_loop函数有4个参数,分别解释:

pd是一个全局变量,表示文件描述符,在前面OpenPcap的调用中已经被正确地赋值。

前面说过,snort可以实时采集网络数据,也可以从文件读取数据进行分析。

在不同情况打开文件(或设备)时,pd分别用来处理文件,或者网卡设备接口。

pd是structpcap类型的指针,该结构包括实际的文件描述符,缓冲区,等等域,用来处理从相应的文件获取信息。

OpenPcap函数中对pd赋值的语句分别为:

/*getthedevicefiledescriptor,打开网卡接口*/

pd=pcap_open_live(pv.interface,snaplen,

pv.promisc_flag?

PROMISC:

0,READ_TIMEOUT,errorbuf);

或者

/*openthefile,打开文件*/

pd=pcap_open_offline(intf,errorbuf);

于是,这个参数表明从哪里取得待分析的数据。

第2个参数是pv.pkt_cnt,表示总共要捕捉的包的数量。

在main函数初始化时,缺省设置为-1,成为永真循环,一直捕捉直到程序退出:

/*initializethepacketcountertoloopforever*/

pv.pkt_cnt=-1;

或者在命令行中设置要捕捉的包的数量。

前面ParseCmdLine(解析命令行)函数的调用中,遇到参数n,重新设定pv.pkt_cnt的值。

ParseCmdLine中相关语句如下:

case'n':

/*grabxpacketsandexit*/

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,intcnt,pcap_handlercallback,u_char*user)

{

registerintn;

for(;{//for循环

if(p->sf.rfile!

=NULL)

n=pcap_offline_read(p,cnt,callback,user);

else{

/*

*XXXkeepreadinguntilwegetsomething

*(oranerroroccurs)

*/

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中类型的定义如下:

typedefvoid(*pcap_handler)(u_char*,conststructpcap_pkthdr*,

constu_char*);

第1个参数这里没有什么用;

第2个参数为pcap_pkthdr结构指针,记录时间戳、包长、捕捉的长度;

第3个参数字符串指针为数据包。

函数如下:

voidProcessPacket(char*user,structpcap_pkthdr*pkthdr,u_char*pkt)

{

Packetp;//Packet结构在decode.h中定义,用来记录数据包的各种信息

/*callthepacketdecoder,调用拆包函数,这里grinder是一个全局

函数指针,已经在main的SetPktProcessor调用中设置为正确的拆包函数*/

(*grinder)(&p,pkthdr,pkt);

/*printthepackettothescreen,如果选择了详细显示方式,

那么把包的数据,显示到标准输出*/

if(pv.verbose_flag)

{

......//省略

}

/*checkorlogthepacketasnecessary

如果工作在使用检测规则的方式,就调用Preprocess进行检测,

否则,仅仅进行日志,记录该包的信息*/

if(!

pv.use_rules)

{

...//进行日志,省略

}

else

{

Preprocess(&p);

}

//清除缓冲区

ClearDumpBuf();

}

这里Preprocess函数进行实际检测。

****************************************************************************

Proprocess函数很短,首先调用预处理规则处理数据包p,然后调用检测

函数Detect进行规则匹配实现检测,如果实现匹配,那么调用函数CallOutput

Plugins根据输出规则进行报警或日志。

函数如下:

voidPreprocess(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中预定义的关键

字和处理函数按类别连接在不同的链表上。

然后,在解析规则文件的时候,

如果一条规则的选项中包含了某个关键字,就会从上边初始化好的对应的链表

中查找,把必要的信息和处理函数添加到表示这条规则的节点(用RuleTreeNode

类型来表示,下面详述)的特定域(OptTreeNode类型)中。

同时,main函数中初始化规则的最后,对指定的规则文件进行解析。

在最

高的层次上,有3个全局变量保存规则(rules.c):

ListHeadAlert;/*AlertBlockHeader*/

ListHeadLog;/*LogBlockHeader*/

ListHeadPass;/*PassBlockHeader*/

这几个变量是ListHead类型的,正如名称所说,指示链表头。

Alert中登记

了需要报警的规则,Log中登记了需要进行日志的规则,Pass中登记的规则在处

理过程忽略(不进行任何处理)。

ListHead定义如下:

typedefstruct_ListHead

{

RuleTreeNode*TcpList;

RuleTreeNode*UdpList;

RuleTreeNode*IcmpList;

}ListHead;

可以看到,每个ListHead结构中有三个指针,分别指向处理Tcp/Udp/Icmp包规则的链表头。

这里又出现了新的结构RuleTreeNode,为了说明链表的层次关系,下面列出RuleTreeNode的定义,但是忽略了大部分域:

typedefstruct_RuleTreeNode

{

RuleFpList*rule_func;

......//忽略

struct_RuleTreeNode*right;

OptTreeNode*down;/*listofruleoptionstoassociatewiththis

rulenode*/

}RuleTreeNode;

RuleTreeNode中包含上述3个指针域,分别又能形成3个链表。

RuleTreeNode*类型的right指向下一个RuleTreeNode,相当于普通链表中的next域,只不过这里用right来命名。

这样就形成了规则链表。

RuleFpList类的指针rule_func记录的是该规则的处理函数的链表。

一条规则有时候需要调用多个处理函数来分析。

所以,有必要做成链表。

我们看看下面的定义,除了next域,还有一个函数指针:

typedefstruct_RuleFpList

{

/*rulecheckfunctionpointer*/

int(*RuleHeadFunc)(Packet*,struct_RuleTreeNode*,struct_RuleFpList*);

/*pointertothenextrulefunctionnode*/

struct_RuleFpList*next;

}RuleFpList;

 

第3个指针域是OptTreeNode类的指针down,该行后面的注释说的很清楚,这是与这个规则节点相联系的规则选项的链表。

很不幸,OptTreeNode的结构也相当复杂,而且又引出了几个新的链表。

忽略一些域,OptTreeNode定义如下:

typedefstruct_OptTreeNode

{

/*plugin/detectionfunctionsgohere*/

OptFpList*opt_func;

/*theds_listisabsolutelyessentialforthepluginsystemtowork,

itallowsthepluginauthorstoassociate"dynamic"datastructures

withtherulesystem,lettingthemlinkanythingtheycancomeup

withtotheruleslist*/

void*ds_list[512];/*listofplugindatastructpointers*/

.......//省略了一些域

struct_OptTreeNode*next;

}OptTreeNode;

next指向链表的下一个节点,无需多说。

OptFpList类型的指针opt_func指向

选项函数链表,同前面说的RuleFpList没什么大差别。

值得注意的是指针数组

ds_list,用来记录该条规则中涉及到的预定义处理过程。

每个元素的类型是void*.在实际表示规则的时候,ds_list被强制转换成不同的预定义类型。

--------------------------------------------------------------------------------------

Proprocess函数很短,首先调用预处理规则处理数据包p,然后调用检测

函数Detect进行规则匹配实现检测,如果实现匹配,那么调用函数CallOutput

Plugins根据输出规则进行报警或日志。

函数如下:

voidPreprocess(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机制,规则处理函数并非固定在

源程序中,而是根据

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 幼儿教育 > 家庭教育

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1