免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
最近访问板块 发新帖
查看: 2289 | 回复: 0
打印 上一主题 下一主题

Libnids-API(中文版) [复制链接]

论坛徽章:
0
跳转到指定楼层
1 [收藏(0)] [报告]
发表于 2005-12-01 13:48 |只看该作者 |倒序浏览

http://www.cnpaf.net/Forum/read.php?tid=1023&page=e&fpage=5
Libnids-API(中文版)

Libnids-API(中文版)
发表日期:01-3-28
====================
libnids-1.16
====================
1. 简介
2. IP碎片重组
3. TCP流还原
4. 例子程序
5. 基本的Libnids结构和函数
6. 其它有用的技巧
1. 简介
Libnids定义的数据结构和函数的声明集中在头文件nids.h中。使用Libnids的应用程序
必须包含这个文件,并且要与Libnids.a静态库进行连接。
应用程序的Main函数一般是这样的形式:
main()
{
应用程序的私有处理,与Libnids无关;
可选择的Libnids参数的修改;
if(!nids_init())那里出了问题,结束;
出测回调函数;
nids_run();
//正常情况下不可达;
}
另外的方法将在以后提及。
2.IP碎片重组
为了接收到Libnids看到的所有的IP包(包括分片的;带有无效校验和的IP包)程序员应该
定义一个如下类型的回调函数:
void ip_frag_func(struct ip * a_packet, int len)
在调用nids_init之后,这个函数应该使用Libnids进行注册:
nids_register_ip_frag(ip_frag_func);
函数ip_frag_func 将被Libnids调用;参数a_packet将指向一个收到的数据报,len是其长度。
类似的,为了接受到适当地数据包,这些包是要被目标主机接收的(就是,没有分片的包或者已经
完成重组的分片包;头部的正确性已经校验过),应该定义一个回调函数:
void ip_func(struct ip * a_packet)
并注册它:
nids_register_ip(ip_func);
3. TCP流的重组
为了接收到一个TCP流中交换的数据,必须定义一个回调函数:
void tcp_callback(struct tcp_stream * ns, void ** param)
tcp_stream结构包含了所有的关于一个TCP连接的信息,例如:它包含两个half_stream(名为
client和server)结构类型的域,每一个描述了一侧的连接,我们将在后面解释所有的域。
tcp_stream的一个域的名字为nids_state,tcp_callback的行为依赖于这个域的值。
* ns->nids_state==NIDS_JUST_EST
这里,ns描述了一个已经建立的连接。Tcp_callback必须判断当将来这个连接中
的数据到达的时候,它是否希望被通知。所有的连接参数都是可用的(ip地址,端
口号等等)。如果这个连接是我们感兴趣的,tcp_callback函数通知Libnids它希
望接收什么数据(发到客户端的数据;发到服务器的数据;发到客户端的紧急数据;
发到服务器的紧急数据),然后函数返回。
* ns->nids_state==NIDS_DATA
这里,新的数据已经到达,half_stream结构(tcp_stream结构的成员)包含数据
缓冲区。
* nids_state域的其它值(NIDS_CLOSE, NIDS_RESET,NIDS_TIMEOUT)意味着连接
已经被关闭。Tcp_callback应给释放其所分配的资源,即使它需要。
4. 例子程序
现在,让我们看看一个简单的例子程序,这个程序在stderr中显示Libnids所监视到的所
有TCP连接所交换的数据。
#include  
#include  
#include  
#include  
#include  
#include  
#include  
#include "nids.h"
#define int_ntoa(x) inet_ntoa(*((struct in_addr *)&x))
// struct tuple4 包含TCP连接的地址和端口号
// 下面的辅助函数生成一个类似10.0.0.1,1024,10.0.0.2,23的字符串
char *
adres (struct tuple4 addr)
{
static char buf[256];
strcpy (buf, int_ntoa (addr.saddr));
sprintf (buf + strlen (buf), ",%i,", addr.source);
strcat (buf, int_ntoa (addr.daddr));
sprintf (buf + strlen (buf), ",%i", addr.dest);
return buf;
}
void
tcp_callback (struct tcp_stream *a_tcp, void ** this_time_not_needed)
{
char buf[1024];
strcpy (buf, adres (a_tcp->addr)); // we put conn params into buf
if (a_tcp->nids_state == NIDS_JUST_EST)
{
// 由a_tcp描述的连接已经建立
// 这里我们决定是否希望跟踪这个流
// 例子条件: if (a_tcp->addr.dest!=23) return;
// 在本程序中我们跟踪所有的流所以。。。。。:
a_tcp->client.collect++; // 我们需要客户端接收到的数据.......
a_tcp->server.collect++; // 我们需要服务器接收到的数据.......
a_tcp->server.collect_urg++; // 我们需要服务器接收到的紧急数据.......
#ifdef WE_WANT_URGENT_DATA_RECEIVED_BY_A_CLIENT
a_tcp->client.collect_urg++; // 如果我们不增加这个值,当紧急数据到达
//时我们不会被通知。
#endif
fprintf (stderr, "%s establishedn", buf);
return;
}
if (a_tcp->nids_state == NIDS_CLOSE)
{
// 连接已经正常结束
fprintf (stderr, "%s closingn", buf);
return;
}
if (a_tcp->nids_state == NIDS_RESET)
{
// 连接已经通过RST关闭。
fprintf (stderr, "%s resetn", buf);
return;
}
if (a_tcp->nids_state == NIDS_DATA)
{
// 新的数据已经到达,必须判断其数据流向
// 判断其是否紧急数据
struct half_stream *hlf;
if (a_tcp->server.count_new_urg)
{
// 紧急数据的新字节已经到达
strcat(buf,"(urgent->)");
buf[strlen(buf)+1]=0;
buf[strlen(buf)]=a_tcp->server.urgdata;
write(1,buf,strlen(buf));
return;
}
// 我们不必必须检查是否客户端的紧急数据已经到达
// 因为我们没有增加a_tcp->client.collect_urg的值
// 因此,我们还有一些正常的数据关心
if (a_tcp->client.count_new)
{
//客户端的新数据
hlf = &a_tcp->client; // 现在我们将处理hlf变量
// 这个变量指向客户端一边的连接。
strcat (buf, "(server; // 类似的
strcat (buf, "(->)");
}
fprintf(stderr,"%s",buf); // 我们打印连接参数(saddr, daddr, sport, dport)
// 和数据流向(-> or data,hlf->count_new); // 我们打印最新到达的数据
}
return ;
}
int
main ()
{
// 这里我们可以改变Libnids的params,例如 nids_params.n_hosts=256;
if (!nids_init ())
{
fprintf(stderr,"%sn",nids_errbuf);
exit(1);
}
nids_register_tcp (tcp_callback);
nids_run ();
return 0;
}
5. 基本的Libnids结构和函数
现在是时候仔细描述一些提到的Libnids的结构的时候了,它们在头文件nids.h中声明。
struct tuple4 // TCP连接参数
{
unsigned short source,dest; // 客户端和服务器端口号
unsigned long saddr,daddr; // 客户端和服务器IP地址
};
struct half_stream // TCP连接一侧的描述结构
{
char state; // socket 状态 (例如:TCP_ESTABLISHED )
char collect; // if >0, 那么数据应该被存放到data缓冲区中。否则,
// 这个方向的数据流将被忽略
// 看一下samples/sniff.c文件如何使用这个域
char collect_urg; // 类似的,判断是否为紧急数据。
char * data; // 正常数据缓冲区
unsigned char urgdata; // one-byte buffer for urgent data
int count; // 自连接建立以来已经有多少字节已经发送到data缓冲区中
int offset; // offset (in data stream) of first byte stored in
// the "data" buffer; additional explanations
// follow
int count_new; // 多少字节将被发送到data缓冲区中last (this) time;
// if == 0, 没有新数据到达。
char count_new_urg; // if != 0,新的紧急数据到达
... // other fields are auxiliary for libnids
};
struct tcp_stream
{
struct tuple4 addr; // 连接参数(saddr, daddr, sport, dport)
char nids_state; // 连接的逻辑状态
struct half_stream client,server; // 描述连接的客户端和服务器端的结构
... // other fields are auxiliary for libnids
};
在上面的例子程序中,函数tcp_callback打印hlf->data缓冲区的数据到stderr上,
并且这些数据不再需要了。在tcp_callback返回后,默认情况下Libnids释放这些数
据占用的空间。hlf->offset域将增加丢弃的字节数,并接新的数据将存储到data缓冲区
的开始。如果上面的操作不是我们所希望的,(例如,数据处理器需要至少N字节的输入来
处理,并且迄今为止Libnids接受到的count_newdata包含
hlf->count_new字节的内容。一般情况下,缓冲区hlf->data中的字节数等于
hlf->count-hlf->offset.
感谢函数nids_discard,程序员不得不必须把接受到的数据拷贝到一个独立的缓冲区中。
hlf->data将一直包含尽可能多的数据。可是,经常发生对每一个ibnids_callback, tcp
stream对维护其辅助数据结构的请求。例如,如果我们希望检测一个针对wu-ftpd的攻击(这个
攻击包括在服务器上建立深层目录)我们需要将Ftpd守护进程的目录存储到某一个地方。
通过Ftp客户端的CWD指令怾改变。这就是tcp_callback的第二个参数的目的。它是一个指向
每一个(libnids_callback, tcp stream)对所私有的数据的指针。
典型情况如下:
void
tcp_callback_2 (struct tcp_stream * a_tcp, struct conn_param **ptr)
{
if (a_tcp->nids_state==NIDS_JUST_EST)
{
struct conn_param * a_conn;
如果连接是我们不感兴趣的,返回;
a_conn=malloc of some data structure//内存分配
init of a_conn//初始化
*ptr=a_conn // 这个值在将来的调用中将被传递给tcp_callback_2
增加一些 "collect" 域
return;
}
if (a_tcp->nids_state==NIDS_DATA)
{
struct conn_param *current_conn_param=*ptr;
using current_conn_param and the newly received data from the net
we search for attack signatures, possibly modyfying
current_conn_param
return ;
}
函数nids_register_tcp 和 nids_register_ip*可以被调用任意多的次数。两个与
tcp_callback相似的函数允许跟踪同一个TCP流。(with acertain non-default exception).
Libnids参数可以通过全局变量nids_params的域的改变来修改。如下声明:
struct nids_prm
{
int n_tcp_streams; // 用于存储tcp_stream结构信息的Hash表的大小
// libnis将只能同时跟踪 3/4 * n_tcp_streams连接的数据流
// 默认值:1040
// 如果设置为0,Libnids将不再进行TCP流的组装
int n_hosts; // 用于存储关于IP碎片重组的信息的Hash表的大小
// 默认值: 256
char * device; // Libnids用于监听数据包的设备接口
// 默认值 :NULL, 将通过调用pcap_lookupdev函数来接决定;
// 特殊值all将致使Libnids试图通过所有的设备接口截获数据包
// (这个参数在高于2.2.0Linux 核心版本有效)
// 参见 doc/NEW_LIBPCAP
int sk_buff_size; // 结构sk_buff的大小,这个结构是由Linux核心定义的,核心用于
// 数据包排列,如果这个参数和sizeof(struct sk_buff)地值不同,
// Libnids可以通过攻击其资源管理而被绕过。见TEST文件。
// 如果你是一个喜欢妄想的人,那么检查你网络中主机的sizeof(sk_buff)
// 并调整这个参数,默认值:168
int dev_addon; // 在sk_buff结构中保留了多少字节用于存储网络接口信息;如果dev_addon==-1,
// 将在nids_init()中根据Libnids监听的接口的类型进行改正。it
// 默认值: -1.
void (*syslog)(); // 参见nids_params定义部分的描述
int syslog_level; // 如果 nids_params.syslog==nids_syslog,那么这个域将决定
// 系统守护进程syslogd报告事件所使用的等级loglevel.
// 默认值: LOG_ALERT
int scan_num_hosts;// 用于存储关于端口扫描的信息的Hash表的大小;Libndis能够检测
// 到的同时发生的端口扫描企图。如果设置为0,端口扫描检测将被关闭
// 默认值:256
int scan_num_ports;// 多少个TCP端口必须被同一个源地址扫描
// 默认值: 10
int scan_delay; // 两个端口之间最大的扫描间隔
// 用于使Libnids可以报告端口扫描企图
// 默认值:3000
void (*no_mem)(); // 当Libndis的内存资源耗尽时调用此函数
// 它应该终止当前进程
int (*ip_filter)(struct ip*); // 这个参数当IP数据包到达时才会被考虑
// 如果ip_filter返回non-zero, 处理这个包否则忽略掉
// 通过这种方式,可以只监控所选中的主机,而不是整个子网
// 默认函数: (nids_ip_filter) 一般返回值为:1
char *pcap_filter; // 用于pcap地过滤字符串,默认情况下为NULL。
// 需要了解的是这强应用到link-layer,所以象"tcp dst port 23"
// 一样的过滤器无法控制碎片传输。
int promisc; // 如果非零,Libnids读取数据包的设备将被设置为混杂模式
// 默认为:1
int one_loop_less; // 默认情况下不可用
} nids_params;
nids_params变量中的syslog域在默认情况下包含函数nids_syslog的地址,如下声明:
void nids_syslog (int type, int errnum, struct ip *iph, void *data);
函数nids_params.syslog用于报告一些不寻常的情形,例如端口扫描请求,无效TCP头
标记等等。这个域应该被分配通常的事件记录函数的地址。
函数nids_syslog(在libnids.c中定义)举例说明了如何解读传递给nids_params.syslog
的参数。
Nids_syslog记录信息道系统守护进程syslogd中。忽视像消息速率和可用磁盘空间之类的事情。
如果对UDP数据报感兴趣,应该声明:
void udp_callback(struct tuple4 * addr, char * buf, int len, struct ip
* iph);
并注册它:
nids_register_udp(udp_callback)
参数addr包含地址信息,buf指针指向UDP数据包携带的数据,len是数据长度,iph是包
含此UDP数据报的Ip包的指针。校验和已经进行过。
6. 其他有用的技巧
void nids_killtcp(struct tcp_stream * a_tcp)
通过发送RST结束一个a_tcp描述的TCP连接。
使用nids_run()存在一个不利的因素,应用程序变成了完全的包驱动,有时在没有包到达的
时候执行某些操作也是必须得。代替nids_run(),我们可以使用函数:
int nids_next()
这个函数调用 pcap_next() 而不是 pcap_loop, 就是说它之处理一个包,如果没有包可用,
程序将处于睡眠状态,Nids_next()成功时返回1;出错返回0。(nids_errbuf 包含相关联
的信息)
标准情况下,当时用nids_next()时,因各应用程序将睡眠在一个select()函数中,同时在fd_set
中引入了一个监听得socket fd. 这个fd可以通过下面的调用获得:
int nids_getfd()
它返回一个文件描述符,失败的时候返回-1。(nids_errbuf包含出错信息)。
头文件nids.h定义了常量 NIDS_MAJOR (1) 和NIDS_MINOR (16), 这些常量用于判定Libnids的
现行版本。如果HAVE_NEW_PCAP(同样在nids.h中定义) 设置为1,则Libnids已被编译为支持
通过所有设备截获数据包。(见 NEW_LIBPCAP 文件)
典型情况下,TCP流携带的数据可以被分成协议依赖(protocol-dependent)记录(say, lines of
input)。一个tcp_callback函数可以接收一定数量的数据,这些数据可以包含多余一个地记录。因此
tcp_callback函数应该对所有接受到的数据重复进行协议解析程序。这增加了代码的复杂性。
如果nids_params.one_loop_less是非零的,Libnids的行为会发生轻微的变化。如果一个callback
消耗了部分而不是全部的新到达的数据,Libnids立刻再次调用它。只有在缓冲区中不再有未处理的保
留数据,并且rcv->count_new相应减少。因而,callback能一次处理一个记录,Libnids将再次调用
它,知道没有新的数据剩余或者没有数据可以处理。不幸的,在两个以上的callback函数读取同一个
TCP流的一半的时候,这一行为导致了可怕的语法问题。所以,如果nids_params.one_loop_less如果
为非零,你不允许分配两个或者更多callback函数处理同一个TCP流的一半。不幸的是现存的接口不能
向callback函数宣布这个错误,因此你必须自己小心,你已经被警告过了。
其它的使用Libnids的应用程序可以在samples目录中找到。


本文来自ChinaUnix博客,如果查看原文请点:http://blog.chinaunix.net/u/11897/showart_59564.html
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP