通过perl实现一个简单的NIDS

随着对网络安全需求的深入开发,基于网络的入侵检测技术已经成为一个重要且有意思的研究方向。想学习NIDS技术除了去读一些现成的资料和一些开源系统的源码,最好的办法莫过于自己去写一个NIDS程序,只有那样才能真正体会到一些NIDS的实现需求和设计妙处。

本质上说NIDS只是一种网络流量的分析工具,通过对网络流量的分析识别出一些已知或未知的攻击行为,一个最简单的NIDS完成的主要工作也就是抓包->协议解码->匹配,众所周知PERL是极其强大的脚本语言,尤其是它的字符串处理能力可以方便地实现对于网络流量中恶意特征进行匹配。当然PERL毕竟只是脚本语言,它的执行效率不允许用于真正大流量生产性环境,但PERL的简单易学及强大功能对于实现一个简单的NIDS达到学习的目的无疑是非常好的,下面我介绍一个用PERL实现的简单NIDS框架,我们将在Linux下实现它,在其他操作系统上类似。

PERL的一个强大特性就在于它海量的CPAN模块库,很多你想实现的功能都可以找到现成的模块,你所要做的只是安装上那些模块即可,关于PERL的模块及面向对象特性的管理和使用在这就不介绍了,请参看相关资料,比如O'REILLY出版的《高级Perl编程》。在用PERL编写网络流量分析脚本之前,需要安装一些底层的抓包及基本的数据包解码模块,包括如下这些:
http://www.tcpdump.org/release/libpcap-0.8.1.tar.gz
底层基本的抓包库。

http://www.cpan.org/authors/id/T/TI/TIMPOTTER/Net-Pcap-0.04.tar.gz
libpcap的PERL接口。

http://www.cpan.org/authors/id/T/TI/TIMPOTTER/Net-PcapUtils-0.01.tar.gz
Net-Pcap模块的wrapper,包装Net-Pcap的函数,可以更方便地在PERL里调用抓包。

http://www.cpan.org/authors/id/T/TI/TIMPOTTER/NetPacket-0.03.tar.gz
用于基本的IP/TCP/UDP等包解码的模块,剥除各种协议头,抽取各个字段。

下面的代码演示了一个带有基本SMB和FTP协议解码模块的最简单NIDS框架,此程序实现最简单的NIDS功能,面向单包,不关心包的状态,不具备高级的商业NIDS产品诸如流重组,包状态及应用层协议的跟踪等功能。为了提高检测的准确性,与Snort直接匹配数据区不同的是,这个脚本实现了两个应用层协议:SMB、FTP的简单解码,解码完全是面向NIDS的需要,代码也没有经过仔细的测试可能存在问题。

(一)perl-ids.pl 实现抓包及检测分析的主程序。



代码如下:

#!/usr/bin/perl
#
# Comments/suggestions to stardust at xfocus dot org
#
#
# $Id: perl-ids.pl,v 1.16 2004/03/04 21:51:12 stardust Exp $
#
# 引用所有相关的模块
use Net::PcapUtils;
use NetPacket::Ethernet qw(:strip);
use NetPacket::TCP;
use NetPacket::IP qw(:protos);
use NetPacket::SMB;
use NetPacket::FTP;
# 定义日志文件名
$workingdir = "./";
$attacklog = "attack.log";
$monitorlog = "monitor.log";
# 以后台进程方式运行
daemon ();
sub daemon {
unless (fork) {
SniffLoop ();
exit 0;
}
exit 1;
}
# 抓包循环
sub SniffLoop {
# 进入工作目录
chdir ("$workingdir");
# 打开日志文件
open (ATTACKLOG,">> $attacklog");
open (MONITORLOG,">> $monitorlog");
# 设置文件读写为非缓冲模式
select(ATTACKLOG); $ ++; select(MONITORLOG); $ ++; select(STDOUT); $ ++;
# 设置信号处理函数,因为程序运行于后台,退出时需要利用信号处理函数做些清理工作
$SIG{"INT"} = 'HandleINT';
$SIG{"TERM"} = 'HandleTERM';
# 进入抓包回调函数
Net::PcapUtils::loop(&sniffit, SNAPLEN => 1800, Promisc => 1, FILTER => 'tcp or udp', DEV => 'eth0');
}
sub sniffit {
my ($args,$header,$packet) = @_;
# 解码IP包
$ip = NetPacket::IP->decode(eth_strip($packet));
# TCP协议
if ($ip->{proto} == IP_PROTO_TCP) {
# 解码TCP包
$tcp = NetPacket::TCP->decode($ip->{data});
# 检查来自SMB客户端的包
if (($tcp->{dest_port} == 139)    ($tcp->{dest_port} == 445)) {
# 如果目的端口是139或445,认为是SMB协议包,做相应的检查
SmbClientCheck ($ip->{src_ip},$tcp->{src_port},$ip->{dest_ip},$tcp->{dest_port},$tcp->{data});
} elsif ($tcp->{dest_port} == 21) {
# 如果目的端口是21,认为是FTP协议,做相应的检查
FtpClientCheck ($ip->{src_ip},$tcp->{src_port},$ip->{dest_ip},$tcp->{dest_port},$tcp->{data});
} else {}
# UDP协议
} elsif ($ip->{proto} == IP_PROTO_UDP) {
} else {}
}
sub SmbClientCheck {
my ($src_ip,$src_port,$dest_ip,$dst_port,$data) = @_;
# 调用SMB解码模块解码
$smb = NetPacket::SMB->decode($data);
# 如果解码成功
if ($smb->{valid}) {
# 示例检测新近公布eeye的那个ASN.1解码错误导致的堆破坏漏洞
# BID:9633,9635 CVEID:CAN-2003-0818 NSFOCUSID:6000
# 如果SMB命令是Session Setup AndX
if ($smb->{cmd} == 0x73) {
# 如果设置了Extended Security Negotiation位,表示有包里有Security Blob
if ($smb->{flags2} & F2_EXTSECURINEG) {
# 用正则表达式匹配通常会在攻击包里出现的OID及引发错误的畸形数据串
# 由于不是从原理上检测加之ASN.1编码的灵活性,这样的检测会导致漏报
if (($smb->{bytecount} > 0) && ($smb->{bytes} =~ m/x06x06x2bx06x01x05x05x02.*[xa1x05x23x03x03x01x07 x84xffxffxff]/)) {
# 记入日志文件
LogAlert ($src_ip,$src_port,$dest_ip,$dst_port,"ASN.1 malform encode attack!");
}
}
}
}
}
sub FtpClientCheck {
my ($src_ip,$src_port,$dest_ip,$dst_port,$data) = @_;
# 调用FTP解码模块解码
$ftp = NetPacket::FTP->decode($data);
# 如果解码成功
if ($ftp->{valid}) {
# 示例检测新近公布的Serv-U < 5.0.0.4版FTP服务器MDTM命令溢出攻击
# BID:9751 NSFOCUSID:6078
# 遍历从数据包里解码出来的FTP命令及其参数
for (my $i = 1;$i <= $ftp->{cmdcount};$i++) {
my $cmd = "cmd"."$i";
my $para = "para"."$i";
# 如果FTP命令是MDTM
if (uc($ftp->{$cmd}) eq "MDTM") {
# 用正则表达式匹配引发溢出的参数串,这里体现了正则
# 表达式的强大,用此匹配可以从原理上检测到畸形参数串
if ($ftp->{$para} =~ m/d{14}[+ -]S{5,}s+S{1,}/) {
LogAlert ($src_ip,$src_port,$dest_ip,$dst_port,"Serv-U < v5.0.0.4 MDTM command long timezone string overflow attack!");
}
}
}
}
}
# 记录攻击告警
sub LogAlert {
my ($src_ip,$src_port,$dest_ip,$dst_port,$message) = @_;
my $nowtime = localtime;
printf ATTACKLOG ("%s %s:%s -> %s:%s %s ",$nowtime,$src_ip,$src_port,$dest_ip,$dst_port,$message);
printf ("%s %s:%s -> %s:%s %s ",$nowtime,$src_ip,$src_port,$dest_ip,$dst_port,$message);
}
# 记录监控信息
sub LogMonitor {
my ($src_ip,$src_port,$dest_ip,$dst_port,$message) = @_;
my $nowtime = localtime;
printf MONITORLOG ("%s %s:%s -> %s:%s %s ",$nowtime,$src_ip,$src_port,$dest_ip,$dst_port,$message);
printf ("%s %s:%s -> %s:%s %s ",$nowtime,$src_ip,$src_port,$dest_ip,$dst_port,$message);
}
# INT信号处理例程
sub HandleINT {
CleanUp ();
exit (0);
}
# TERM信号处理例程
sub HandleTERM {
CleanUp ();
exit (0);
}
# 清理,主要工作是关闭文件句柄
sub CleanUp {
close (ATTACKLOG); close (MONITORLOG);
}

(二)FTP.pm FTP协议解码模块,抽取数据包里的FTP命令及相应的参数,此文件需要拷贝到NetPacket系列模块所在的目录,通常是在/usr/lib/perl5/site_perl/5.x.x/NetPacket/

代码如下:

#
# NetPacket::FTP - Decode FTP packets
#
# Comments/suggestions to stardust at xfocus dot org
#
#
# $Id: FTP.pm,v 1.16 2004/03/03 l1:16:20 stardust Exp $
#
package NetPacket::FTP;
use strict;
use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS);
use NetPacket;
my $myclass;
BEGIN {
$myclass = __PACKAGE__;
$VERSION = "0.01";
}
sub Version () { "$myclass v$VERSION" }
BEGIN {
@ISA = qw(Exporter NetPacket);
# Items to export into callers namespace by default
# (move infrequently used names to @EXPORT_OK below)
@EXPORT = qw(
);
# Other items we are prepared to export if requested
@EXPORT_OK = qw(
);
# Tags:
%EXPORT_TAGS = (
ALL => [@EXPORT, @EXPORT_OK],
);
}
#
# Decode the packet
#
# FTP协议文本参看RFC959,http://www.ietf.org/rfc/rfc0959.txt
# 常见的FTP命令
my @ftp_cmds = qw(ABOR ACCT ALLO APPE CDUP CWD DELE HELP LIST MKD MODE NLST
NOOP PASS PASV PORT PWD QUIT REIN REST RETR RMD RNFR RNTO
SITE SMNT STAT STOR STOU STRU SYST TYPE USER XCUP XCWD XMKD
XPWD XRMD LPRT LPSV ADAT AUTH CCC CONF ENC MIC PBSZ PROT
FEAT OPTS EPRT EPSV LANG MDTM MLSD MLST SIZE DIGT CLNT MACB
);
sub decode {
my $class = shift;
my($data) = @_;
my $self = {};
my $cmdhead = 0;
my $cmdtail = 0;
my @parts = ();
my $cmdcount = 0;
my $returnindex = 0;
my $data_len = length($data);
# 如果数据长度过短则不处理
if ($data_len >= 4) {
# 一个包里的FTP命令个数
$self->{cmdcount} = 0;
# 搜索回车,之前认为是一个命令行,需要注意的是一个包里可能包含多个FTP命令
while ( (($returnindex = index ($data,"x0a",$cmdhead)) >=0)    (($returnindex < 0) && (($data_len - $cmdhead) >= 4))) {
# 调整一个命令行串尾指针
if ($returnindex < 0) {
$cmdtail = $data_len -1;
} else {
$cmdtail = $returnindex;
}
if ((my $cmdlen = ($cmdtail - $cmdhead + 1)) >= 4) {
# 取出命令行串
my $cmdline = substr($data,$cmdhead,$cmdlen);
# 从命令行里拆分出命令名和它的参数串
if (splitcmd($cmdline,@parts)) {
$self->{cmdcount}++;
my $cmdindex = "cmd"."$self->{cmdcount}";
my $paraindex = "para"."$self->{cmdcount}";
# 记录到要返回到主程序的对象
$self->{$cmdindex} = $parts[0];
$self->{$paraindex} = $parts[1];
}
}
# 调整命令行串头指针
$cmdhead = $cmdtail + 1;
}
# 如果命令个数大于0,则说明解码是有效的
if ($self->{cmdcount} == 0) {
$self->{valid} = 0;
} else {
$self->{valid} = 1;
}
} else {
$self->{valid} = 0;
}
# 返回对象
bless($self, $class);
return $self;
}
sub splitcmd {
my ($cmdline,$parts) = @_;
# 去除行尾的回车
chomp($cmdline);
# 用正则表达式抽取出命令名字和参数,既然效率不是考虑的主要问题就“毫无顾忌”地使用正则表达式,因为方便
if ($cmdline =~ m/^s*([a-zA-Z]{3,4})s+(.*)/) {
my $valid_cmd = 0;
# 检查抽出来的命令名字是否是一个已知的合法FTP命令
for (my $i=0;$i<@ftp_cmds;$i++) {
if ($ftp_cmds[$i] eq uc($1)) {
$valid_cmd = 1;
last;
}
}
# 如果是合法的命令则返回给调用函数
if ($valid_cmd) {
${$parts}[0] = $1;
${$parts}[1] = $2;
return 1;
} else {
return 0;
}
} else {
return 0;
}
}
#
# Module initialisation
#
1;
# autoloaded methods go after the END token (&& pod) below
__END__

(三)SMB.pm 对SMB包头结构的简单解码模块,此文件需要拷贝到NetPacket系列模块所在的目录,通常是在/usr/lib/perl5/site_perl/5.x.x/NetPacket/

代码如下:

#
# NetPacket::SMB - Decode SMB packets
#
# Comments/suggestions to stardust at xfocus dot org
#
#
# $Id: SMB.pm,v 1.16 2004/02/23 12:25:17 stardust Exp $
#
package NetPacket::SMB;
use strict;
use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS);
use NetPacket;
my $myclass;
# SMB flags
use constant F2_LONGNAMEALLW => 0x0001;
use constant F2_EXTATTRIBUTE => 0x0002;
use constant F2_SECURITYSIGN => 0x0004;
use constant F2_LONGNAMEUSED => 0x0040;
use constant F2_EXTSECURINEG => 0x0800;
use constant F2_DONTRESOLDFS => 0x1000;
use constant F2_EXECONLYREAD => 0x2000;
use constant F2_ERRORCODTYPE => 0x4000;
use constant F2_UNICODSTRING => 0x8000;
use constant F_LOCKANDREAD => 0x01;
use constant F_RCVBUFFPOST => 0x02;
use constant F_CASESENSITV => 0x08;
use constant F_CANONICPATH => 0x10;
use constant F_OPLOCKSREQU => 0x20;
use constant F_NOTIFYONOPN => 0x40;
use constant F_REQUERESPON => 0x80;
BEGIN {
$myclass = __PACKAGE__;
$VERSION = "0.01";
}
sub Version () { "$myclass v$VERSION" }
BEGIN {
@ISA = qw(Exporter NetPacket);
# Items to export into callers namespace by default
# (move infrequently used names to @EXPORT_OK below)
@EXPORT = qw(F2_LONGNAMEALLW F2_EXTATTRIBUTE F2_SECURITYSIGN
F2_LONGNAMEUSED F2_EXTSECURINEG F2_DONTRESOLDFS
F2_EXECONLYREAD F2_ERRORCODTYPE F2_UNICODSTRING
F_LOCKANDREAD F_RCVBUFFPOST F_CASESENSITV
F_CANONICPATH F_OPLOCKSREQU F_NOTIFYONOPN
F_REQUERESPON
);
# Other items we are prepared to export if requested
@EXPORT_OK = qw(smb_strip
);
# Tags:
%EXPORT_TAGS = (
ALL => [@EXPORT, @EXPORT_OK],
strip => [qw(smb_strip)],
);
}
#
# Strip header from packet and return the data contained in it
#
undef &smb_strip;
*smb_strip = &strip;
# 剥除SMB头的函数
sub strip {
my ($data) = @_;
my $smb_obj = NetPacket::SMB->decode($data);
return $smb_obj->{data};
}
#
# Decode the packet
#
sub decode {
my $class = shift;
my($data) = @_;
my $self = {};
my $data_len = 0;
my $temp = "";
$data_len = length ($data);
# 如果数据区长度小于39字节(4+32+3),则认为不是一个可解码的SMB包
if ($data_len < 39) {
$self->{valid} = 0;
} else {
# 取SMB的标志串
my $smb_mark = substr ($data,4,4);
# 是否符合标志串
if ($smb_mark ne "xffx53x4dx42") {
$self->{valid} = 0;
} else {
$self->{valid} = 1;
# Decode SMB packet
if (defined($data)) {
# 用PERL的unpack函数解码32字节长的SMB头结构,头结构可
# 参考 http://www.cs.uml.edu/~bill/cs592/cifs.chm
# 感谢小四(scz at nsfocus dot com)对于SMB头结构中字段字节序的提醒
($self->{nbt_type}, $self->{nbt_flag}, $self->{nbt_len},
$self->{mark}, $self->{cmd}, $self->{status},
$self->{flags}, $self->{flags2}, $self->{ext},
$self->{ext2}, $self->{ext3}, $self->{tid},
$self->{pid}, $self->{uid}, $self->{mid},
$self->{data}) = unpack("CCna4CVCvVVVvvvva*", $data);
($self->{wordcount},$temp) = unpack("Ca*",$self->{data});
if ((36 + 1 + $self->{wordcount} * 2) <= ($data_len - 2)) {
# 解码SMB结构下的wordcount字节及bytecount字节数据
my $wordbytes = $self->{wordcount} * 2;
($self->{wordcount},$self->{words},$self->{bytecount},$self->{bytes}) = unpack("C"."a"."$wordbytes"."va*",$self->{data});
} else {
($self->{wordcount},$self->{words}) = unpack("Ca*",$self->{data});
$self->{bytecount} = -1; $self->{bytes} = "";
}
}
}
}
# 返回对象
bless($self, $class);
return $self;
}
#
# Module initialisation
#
1;
# autoloaded methods go after the END token (&& pod) below
__END__

时间: 2013-02-08

Python实现一个简单的验证码程序

老师讲完random函数,自己写的,虽然和老师示例的不那么美观,智能,但是也自己想出来的,所以记录一下,代码就需要自己不断的自己练习,实战,才能提高啊!不然就像我们这些大部分靠自学的人,何时能学会.还有就是,这次听老师的,把自己的代码添加注释,所以这次把很简单的代码都写上了注释,而且很大白话,不管有没有接触过python的,我相信仔细看了,肯定能看懂.如果看完,再自己尝试着默写出来,那就是更好到了,好了进入正题: 自己写的: __Author__ = "Zhang Peng" impo

python使用线程封装的一个简单定时器类实例

本文实例讲述了python使用线程封装的一个简单定时器类.分享给大家供大家参考.具体实现方法如下: from threading import Timer class MyTimer: def __init__(self): self._timer= None self._tm = None self._fn = None def _do_func(self): if self._fn: self._fn() self._do_start() def _do_start(self): self.

使用Spring来创建一个简单的工作流引擎

文章来源:matrix 作者:Steve Dodge 摘要 spring是支持控制反转编程机制的一个相对新的框架.本文把spring作为简单工作流引擎,将它用在了更加通用的地方.在对工作流简单介绍之后,将要介绍在基本工作流场景中基于Spring的工作流API的使用. 许多J2EE应用程序要求在一个和主机分离的上下文中执行处理过程.在许多情况下,这些后台的进程执行多个任务,一些任务依赖于以前任务的状态.由于这些处理任务之间存在相互依赖的关系,使用一套基于过程的方法调用常常不能满足要求.开发人员能够

一个简单Ajax类库及使用方法实例分析

本文实例讲述了一个简单Ajax类库及使用方法.分享给大家供大家参考,具体如下: ajax.js function Ajax(recvType){ var aj=new Object(); aj.recvType=recvType ? recvType.toUpperCase() : 'HTML' //HTML XML aj.targetUrl=''; aj.sendString=''; aj.resultHandle=null; aj.createXMLHttpRequest=function(

一个简单的XML Schema的例子

我们可以看到,DTD的语法相当复杂,并且它不符合XML文件的标准,自成一个体系.也就是说DTD文档本身并不是一个良好形式的XML文档,上面的关于DTD的介绍也仅仅是作了一个简介,目的是帮助大家能读懂DTD文件以及在必要时创建简单的DTD文件,因为现在很多的XML应用是建立在DTD之上的. 另外一个代替DTD的就是W3C定义的Schema,Schema从字面意义上来说,可以翻译成模式.大纲.计划.规划等等.它的基本意思就是说为XML文档制定一种模式. Schema相对于DTD的明显好处

实现一个简单的vue无限加载指令方法

vue 中的自定义指令是对底层 dom 进行操作,下面以实现滚动到底部加载数据,实现无限加载来介绍如何自定义一个简单的指令. 无限加载的原理是通过对滚动事件的监听,每一次滚动都要获取到已滚动的距离,如果滚动的距离加上浏览器窗口高度,会大于等于内容高度,就触发函数加载数据. 先介绍不使用 vue 的情况如何实现无限加载. 不使用框架 首先是html: <!DOCTYPE html><html lang="en"> <head><meta char

Angular实现一个简单的多选复选框的弹出框指令实例

之前的文章有写过包含树结构下拉框的. 要实现一个包含多个复选框的下拉框该如何做呢? 先上个效果图吧: 代码: <!DOCTYPE html> <html ng-app="app"> <head> <meta charset="UTF-8"> <title></title> <link rel="stylesheet" type="text/css"

使用Lucene实现一个简单的布尔搜索功能

什么是lucene Lucene是apache软件基金会jakarta项目组的一个子项目,是一个开放源代码的全文检索引擎工具包,但它不是一个完整的全文检索引擎,而是一个全文检索引擎的架构,提供了完整的查询引擎和索引引擎,部分文本分析引擎(英文与德文两种西方语言). Lucene是一个全文搜索框架,而不是应用产品.因此它并不像www.baidu.com 或者google Desktop那么拿来就能用,它只是提供了一种工具让你能实现这些产品. 在布尔查询的对象中,包含一个子句的集合,各个子句间都是如

Spring boot实现一个简单的ioc(1)

前言 跳过废话,直接看正文 之前参与开发的几个spring的项目,用的版本都3.x, 最近忽然发现spring 5 都快上线了,于是赶紧去关注了下spring的最新动态.发现了spring-boot这个好东西(终于可以从各种错综复杂的xml配置文件中解放出来了!). 在学习了目前最新的1.5.2.RELEASE版spring-boot官方文档之后,我决定仿照spring-boot的项目结构以及部分注解,写一个简单的ioc容器,一方面为了加深自己对ioc的理解,另一方面也为了以后在开发一些个人项目