在Unix系统平台上的网络安全工具开发中,目前最为流行的C API library有libnet、
libpcap、libnids和libicmp等。它们分别从不同层次和角度提供了不同的功能函数。使
网络开发人员能够忽略网络底层细节的实现,从而专注于程序本身具体功能的设计与开发
。其中,
* libnet提供的接口函数主要实现和封装了数据包的构造和发送过程。
* libpcap提供的接口函数主要实现和封装了与数据包截获有关的过程。
* libnids提供的接口函数主要实现了开发网络入侵监测系统所必须的一些结构框架。
* libicmp等相对较为简单,它封装的是ICMP数据包的主要处理过程(构造、发送、接收等
)。
利用这些C函数库的接口,网络安全工具开发人员可以很方便地编写出具有结构化强、健
壮性好、可移植性高等特点的程序,如scanner、sniffer、firewall、IDS等。
---[[ libnet ]]------------------------------------------
libnet库的最新版本为1.0.0,它一共约7600行C源代码,33个源程序文件,12个C头文件
,50余个自定义函数,提供的接口函数包含15种数据包生成器和两种数据包发送器(IP层
和数据链路层)。目前只支持IPv4,不支持IPv6。已经过测试的系统平台包括:
* OpenBSD 2.6snap, 2.5, 2.4, 2.3, 2.2 (i386)
* FreeBSD 4.0-STABLE, 3.3-STABLE, 3.2-RELEASE, 3.1-CURRENT, 3.0, 2.2 (i386)
* NetBSD 1.3.2 (i386)
* BSD/OS 3.x (i386)
* BSDi 3.0 (i386)
* Linux 2.2.x, 2.0.3x, 2.1.124 (i386, alpha) (libc: 2.4.x, glibc: 2.0.x)
* Solaris 7 (SPARC, gcc 2.7.2[13], 2.8.2), 2.6 (SPARC, gcc 2.8.2),
2.5.x (SPARC, gcc 2.7.2[13])
* IRIX 6.2
* MacOS 5.3rhapsody (powerpc)
libnet提供的接口函数按其作用可分为四类:
* 内存管理(分配和释放)函数
* 地址解析函数
* 数据包构造函数
* 数据包发送函数
以下分别列出这些接口函数及其功能(其参数含义简单易懂,不再解释):
★ 内存管理函数
单数据包内存初始化:
int libnet_init_packet(u_short packet_size, u_char **buf);
单数据包内存释放:
void libnet_destroy_packet(u_char **buf);
多数据包内存初始化:
int libnet_init_packet_arena(struct libnet_arena **arena,
u_short packet_num, u_short packet_size);
访问多数据包内存中的下一个数据包:
u_char *libnet_next_packet_from_arena(struct libnet_arena **arena,
u_short packet_size);
多数据包内存释放:
void libnet_destroy_packet_arena(struct libnet_arena **arena);
★ 地址解析函数
解析主机名:
u_char *libnet_host_lookup(u_long ip, u_short use_name);
解析主机名(可重入函数):
void libnet_host_lookup_r(u_long ip, u_short use_name, u_char *buf);
域名解析:
u_long libnet_name_resolve(u_char *ip, u_short use_name);
获取接口设备IP地址:
u_long libnet_get_ipaddr(struct libnet_link_int *l,
const u_char *device, const u_char *ebuf);
获取接口设备硬件地址:
struct ether_addr *libnet_get_hwaddr(struct libnet_link_int *l,
const u_char *device,
const u_char *ebuf);
★ 数据包构造函数
ARP协议数据包:
int libnet_build_arp(u_short hrdw, u_short prot, u_short h_len,
u_short p_len, u_short op, u_char *s_ha,
u_char *s_pa, u_char *t_ha, u_char *t_pa,
const u_char *payload, int payload_len,
u_char *packet_buf);
DNS协议数据包:
int libnet_build_dns(u_short id, u_short flags, u_short num_q,
u_short num_answ_rr, u_short num_auth_rr,
u_short num_add_rr, const u_char * payload,
int payload_len, u_char *packet_buf);
以太网协议数据包:
int libnet_build_ethernet(u_char *daddr, u_char *saddr, u_short id,
const u_char *payload, int payload_len,
u_char *packet_buf);
ICMP协议数据包(ICMP_ECHO / ICMP_ECHOREPLY):
int libnet_build_icmp_echo(u_char type, u_char code, u_short id,
u_short seq, const u_char *payload,
int payload_len, u_char *packet_buf);
ICMP协议数据包(ICMP_MASKREQ / ICMP_MASKREPLY):
int libnet_build_icmp_mask(u_char type, u_char code, u_short id,
u_short seq, u_long mask,
const u_char *payload, int payload_len,
u_char *packet_buf);
ICMP协议数据包(ICMP_UNREACH):
int libnet_build_icmp_unreach(u_char type, u_char code,
u_short orig_len, u_char orig_tos,
u_short orig_id, u_short orig_frag,
u_char orig_ttl, u_char orig_prot,
u_long orig_saddr, u_long orig_daddr,
const u_char *payload, int payload_len,
u_char *packet_buf);
ICMP协议数据包(ICMP_TIMEXCEED):
int libnet_build_icmp_timeexceed(u_char type, u_char code,
u_short orig_len, u_char orig_tos,
u_short orig_id, u_short orig_frag,
u_char orig_ttl, u_char orig_prot,
u_long orig_saddr, u_long orig_daddr,
const u_char *payload, int payload_len,
u_char *packet_buf);
ICMP协议数据包(ICMP_REDIRECT):
int libnet_build_icmp_redirect(u_char type, u_char code, u_long gateway,
u_short orig_len, u_char orig_tos,
u_short orig_id, u_short orig_frag,
u_char orig_ttl, u_char orig_prot,
u_long orig_saddr, u_long orig_daddr,
const u_char *payload, int payload_len,
u_char *packet_buf);
ICMP协议数据包(ICMP_TSTAMP / ICMP_TSTAMPREPLY):
int libnet_build_icmp_timestamp(u_char type, u_char code, u_short id,
u_short seq, n_time otime, n_time rtime,
n_time ttime, const u_char *payload,
int payload_len, u_char *packet_buf);
IGMP协议数据包:
int libnet_build_igmp(u_char type, u_char code, u_long ip,
const u_char *payload, int payload_len,
u_char *packet_buf);
IP协议数据包:
int libnet_build_ip(u_short len, u_char tos, u_short ip_id, u_short frag,
u_char ttl, u_char protocol, u_long saddr,
u_long daddr, const u_char *payload, int payload_len,
u_char *packet_buf);
OSPF路由协议数据包:
int libnet_build_ospf(u_short len, u_char type, u_long router_id,
u_long area_id, u_short auth_type,
const char *payload, int payload_s, u_char *buf);
OSPF路由协议数据包(Hello):
int libnet_build_ospf_hello(u_long netmask, u_short interval,
u_char options, u_char priority,
u_int dead_interval, u_long des_router,
u_long backup, u_long neighbor,
const char *payload, int payload_s,
u_char *buf);
OSPF路由协议数据包(DataBase Description (DBD)):
int libnet_build_ospf_dbd(u_short len, u_char options, u_char type,
u_int sequence_num, const char *payload,
int payload_s, u_char *buf);
OSPF路由协议数据包(Link State Request (LSR)):
int libnet_build_ospf_lsr(u_int type, u_int ls_id, u_long adv_router,
const char *payload, int payload_s,
u_char *buf);
OSPF路由协议数据包(Link State Update (LSU)):
int libnet_build_ospf_lsu(u_int num, const char *payload,
int payload_s, u_char *buf);
OSPF路由协议数据包(Link State Acknowledgement (LSA)):
int libnet_build_ospf_lsa(u_short age, u_char options, u_char type,
u_int ls_id, u_long adv_router,
u_int sequence_num, u_short len,
const char *payload, int payload_s,
u_char *buf);
OSPF路由协议数据包(OSPF Link Sate NetworkLink State Router):
int libnet_build_ospf_lsa_net(u_long netmask, u_int router_id,
const char *payload, int payload_s,
u_char *buf);
OSPF路由协议数据包(Link State Router):
int libnet_build_ospf_lsa_rtr(u_short flags, u_short num, u_int id,
u_int data, u_char type, u_char tos,
u_short metric, const char *payload,
int payload_s, u_char *buf);
OSPF路由协议数据包(Link State Summary):
int libnet_build_ospf_lsa_sum(u_long netmask, u_int metric, u_int tos,
const char *payload, int payload_s,
u_char *buf);
OSPF路由协议数据包(Link State AS External):
int libnet_build_ospf_lsa_as(u_long netmask, u_int metric,
u_long fwd_addr, u_int tag,
const char *payload, int payload_s,
u_char *buf);
RIP路由协议数据包:
int libnet_build_rip(u_char cmd, u_char ver, u_short domain,
u_short addr_fam, u_short route_tag, u_long ip,
u_long mask, u_long next_hop, u_long metric,
const u_char *payload, int payload_len,
u_char *packet_buf);
TCP协议数据包:
int libnet_build_tcp(u_short th_sport, u_short th_dport, u_long th_seq,
u_long th_ack, u_char th_flags, u_short th_win,
u_short th_urg, const u_char *payload,
int payload_len, u_char *packet_buf);
UDP协议数据包:
int libnet_build_udp(u_short sport, u_short dport, const u_char *payload,
int payload_len, u_char *packet_buf);
IP协议数据包选项:
int libnet_insert_ipo(struct ipoption *opt, u_char opt_len,
u_char *packet_buf);
TCP协议数据包选项:
int libnet_insert_tcpo(struct tcpoption *opt, u_char opt_len,
u_char *packet_buf);
★ 数据包发送函数
打开raw socket:
int libnet_open_raw_sock(int protocol);
关闭raw socket:
int libnet_close_raw_sock(int socket);
选择接口设备:
int libnet_select_device(struct sockaddr_in *sin,
u_char **device, u_char *ebuf);
打开链路层接口设备:
struct libnet_link_int *libnet_open_link_interface(char *device,
char *ebuf);
关闭链路层接口设备:
int libnet_close_link_interface(struct libnet_link_int *l);
发送IP数据包:
int libnet_write_ip(int socket, u_char *packet, int packet_size);
发送链路层数据包:
int libnet_write_link_layer(struct libnet_link_int *l,
const u_char *device, u_char *packet,
int packet_size);
检验和计算:
int libnet_do_checksum(u_char *packet, int protocol, int packet_size);
★ 相关的支持函数
随机数种子生成器:
int libnet_seed_prand();
获取随机数:
u_long libnet_get_prand(int modulus);
16进制数据输出:
void libnet_hex_dump(u_char * buf, int len, int swap, FILE *stream);
端口列表链初始化:
int libnet_plist_chain_new(struct libnet_plist_chain **plist,
char *token_list);
获取端口列表链的下一项(端口范围):
int libnet_plist_chain_next_pair(struct libnet_plist_chain *plist,
u_short *bport, u_short *eport);
端口列表链输出显示:
int libnet_plist_chain_dump(struct libnet_plist_chain *plist);
获取端口列表链:
u_char *libnet_plist_chain_dump_string(struct libnet_plist_chain *plist);
端口列表链内存释放:
void libnet_plist_chain_free(struct libnet_plist_chain *plist);
---[[ libnids ]]------------------------------------------
一、简介
libnids的英文意思是 Network Intrusion Detect System library,即网络入侵监
测系统函数库。它是在前面介绍的两种C函数接口库libnet和libpcap的基础上开发的,封
装了开发NIDS所需的许多通用型函数。linids提供的接口函数监视流经本地的所有网络通
信,检查数据包等。除此之外,还具有重组TCP数据段、处理IP分片包和监测TCP端口扫描
的功能。利用libnids接口函数库,NIDS开发者不需要再编写底层的网络处理代码,只需
专注于NIDS本身功能的实现即可。
libnids支持Linux、Solaris和*BSD系统平台,目前最新版本为1.13。
二、IP分片数据包
为了使libnids能接收所有的IP数据包(包括分片包、畸形包等),程序员需要定义如
下的回调函数:
void ip_frag_func(struct ip * a_packet)
在调用nids_init()函数初始化后,使用nids的函数进行注册:
nids_register_ip_frag(ip_frag_func);
这样回调函数ip_frag_func会在适当的时候由libnids调用,参数a_packet指针将指向接
收到的数据报。
类似地,如果仅接收目标主机会接受的数据包(如非碎片包、重组包或头部校验正确
的数据包等),需要定义如下回调函数:
void ip_func(struct ip * a_packet)
然后注册:
nids_register_ip(ip_func);
三、TCP数据流重组
要接收TCP流在交换的数据,必须定义如下回调函数:
void tcp_callback(struct tcp_stream * ns, void ** param)
tcp_stream结构提供了一个TCP连接的所有信息。例如,它包含了客户端与服务器端的
half_stream结构。下文会对该结构的字段进行解释。
tcp_stream结构有一个名为nids_state的字段。此字段的数值将决定tcp_callback的
操作。
(a) ns->nids_state==NIDS_JUST_EST时,ns表示一个刚刚建立的连接。
tcp_callback可以据此决定是否对该连接的后续数据进行检查。如
需要检查,tcp_callback回调函数将通知libnids它希望接收哪些
数据(如到客户端的数据、到服务器端的数据、到客户端的紧急数
据或到服务器端的紧急数据等),然后返回。
(b) ns->nids_state==NIDS_DATA时,表示ns连接接收到新的数据。
half_stream结构中的缓冲区用于存放这些数据。
(c) nids_state字段为其它数值(NIDS_CLOSE、NIDS_RESET、
NIDS_TIMEOUT)时,表示该连接已经关闭了。tcp_callback函数应
释放相关资源。
四、一个简单的实例
下面的源代码是一个非常简单的程序,它将libnids捕获的所有TCP连接交换的数据输
出显示到标准输出设备上。
-----------------------BEGINING OF CODE--------------------------------
#include "nids.h"
#include <string.h>
#include <stdio.h>
extern char * inet_ntoa(unsigned long);
// tuple4结构包含了TCP连接两端的IP地址和端口,以下函数将它们转换为字符串
// 格式,如10.0.0.1,1024, 10.0.0.2,23
char *
adres (struct tuple4 addr)
{
static char buf[256];
strcpy (buf, inet_ntoa (addr.saddr));
sprintf (buf + strlen (buf), ",%i,", addr.source);
strcat (buf, inet_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;表
// 示不处理目标端口为23的数据包。
// 本例需要处理(显示)所有数据包,故:
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 established\n", buf);
return;
}
if (a_tcp->nids_state == NIDS_CLOSE)
{
// TCP连接正常关闭
fprintf (stderr, "%s closing\n", buf);
return;
}
if (a_tcp->nids_state == NIDS_RESET)
{
// TCP连接因RST数据包而关闭
fprintf (stderr, "%s reset\n", 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;
}
#ifdef WE_WANT_URGENT_DATA_RECEIVED_BY_A_CLIENT
if (a_tcp->client.count_new_urg)
{
// 客户端接收的紧急数据
strcat(buf,"(urgent->)");
buf[strlen(buf)+1]=0;
buf[strlen(buf)]=a_tcp->server.urgdata;
write(1,buf,strlen(buf));
return;
}
#endif
if (a_tcp->client.count_new)
{
// 客户端接收的数据
hlf = &a_tcp->client; // 准备显示客户端接收的数据
strcat (buf, "(<-)"); // 指示数据流方向
}
else
{
hlf = &a_tcp->server; // 准备显示服务器端接收的数据
strcat (buf, "(->)"); // 指示数据流方向
}
fprintf(stderr,"%s",buf); // 首先输出显示连接双方的IP地址、端口
// 和数据流方向
write(2,hlf->data,hlf->count_new); // 输出显示接收到的新数据
}
return ;
}
int
main ()
{
// 此处可自定义libnids的全局变量,如:
// nids_params.n_hosts=256;
if (!nids_init () )
{
fprintf(stderr,"%s\n",nids_errbuf);
exit(1);
}
nids_register_tcp (tcp_callback);
nids_run ();
// NOT REACHED
return 0;
}
---------------------------END OF CODE------------------------------------
五、libnids的数据结构及接口函数
libnids库的所有数据结构及接口函数都在"nids.h"头文件中声明。
struct tuple4 // TCP连接参数
{
unsigned short source,dest; // 客户端和服务器端的端口号
unsigned long saddr,daddr; // 客户端和服务器端的IP地址
};
struct half_stream // TCP连接一端的数据结构
{
char state; // 套接字状态(如TCP_ESTABLISHED)
char collect; // 如果大于0,则保存其数据到缓冲区中,否则忽略
char collect_urg; // 如果大于0,则保存紧急数据,否则忽略
char * data; // 正常数据的缓冲区
unsigned char urgdata; // 紧急数据缓冲区
int count; // 自从连接建立以来保存到"data"缓冲区的数据字节
// 数总和
int offset; // 保存到"data"缓冲区的首字节数据偏移量
int count_new; // 最近一次接收到的数据字节数;如果为0,则无数
// 到达
char count_new_urg; // 如果非0,表示有新的紧急数据到达
... // libnids库使用的辅助字段
};
struct tcp_stream
{
struct tuple4 addr; // TCP连接参数(saddr, daddr, sport, dport)
char nids_state; // TCP连接的逻辑状态
struct half_stream client,server; // 描述客户端与服务器端的数据结构
... // libnids库使用的辅助字段
};
在上面的实例程序中,回调函数tcp_callback输出显示hlf->data缓冲区中的数据到
标准输出设备上。这些数据在tcp_callback函数返回后,由libnids自动释放这些数据所
占用的内存空间。同时,hlf->offset字段将增加被丢弃数据的字节数,而新接收到的数
据则存放到"data"缓冲区的起始处。
如果在其它应用中不进行如上例的操作(例如,数据处理过程至少需要N个字节的输入
数据,而libnids只接收到的数据字节数count_new<N),则需要在tcp_callback函数返回
前调用如下函数:
void nids_discard(struct tcp_stream * a_tcp, int num_bytes)
此时,当回调函数tcp_callback返回后linids将"data"缓冲区的前num_bytes字节数据,
同时计算调整offset字段的数值,并将剩余数据移动到缓冲区的起始处。
如果始终不调用nids_discard()函数(如上面实例),hlf->data缓冲区中将包含
hlf-> count_new字节数据。通常情况下,在hlf->data缓冲区中的数据字节数等于
hlf->count - hlf->offset。
有了nids_discard()函数,程序员就不必拷贝接收到的数据到另外的缓冲区中,
hlf->data缓冲区将总是尽可能保存足够的数据。然后,有时会有保留数据包特定数据的
需要。例如,我们希望能监测到针对wu-ftpd服务器的"CWD"溢出攻击,就需要跟踪检查
ftp客户端发送的"CWD"命令。此时就需要tcp_callback回调函数具有第二个参数了。此参
数是某TCP连接私有数据的指针。处理过程如下:
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;
if the connection is uninteresting, return;
a_conn=malloc of some data structure
init of a_conn
*ptr=a_conn // this value will be passed to tcp_callback_2 in future
// calls
increase some of "collect" fields
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连接中
使用两种不同的回调函数是允许的。
libnids库定义了一个全局变量结构nids_params,其声明如下:
struct nids_prm
{
int n_tcp_streams; // 存放tcp_stream结构的hash表大小。
// 缺省值:1024
int n_hosts; // 存放IP分片信息的hash表大小
// 缺省值:256
char * device; // libnids监听的接口设备名
// 缺省值 == NULL,即由pcap_lookupdev函数确定
int sk_buff_size; // (Linux内核)sk_buff结构大小
// 缺省值:168
int dev_addon; // sk_buff为网络接口保留的字节数
// 如果dev_addon==-1,则由nids_init函数确定
// 缺省值:-1
void (*syslog)(); // 日志函数指针
int syslog_level; // 如果nids_params.syslog==nids_syslog,则此字段值
// 将确定日志等级loglevel
// 缺省值:LOG_ALERT
int scan_num_hosts;// 存放端口扫描信息的hash表大小。
// 如果为0,则关闭端口扫描监测功能。
// 缺省值:256
int scan_num_ports;// 来自同一IP地址所扫描的TCP端口数上限
// 缺省值:10
int scan_delay; // 在两次端口扫描中的间隔时间上限(毫秒)
// 缺省值:3000
void (*no_mem)(); // 内存不足时被调用,此时应终止当前进程
int (*ip_filter)(struct ip*); // 当接收到一个IP数据包时调用。如返回值
// 非零,则处理该数据包,否则忽略。
// 缺省为(nids_ip_filter)且总返回1
char *pcap_filter; // 传递给pcap过滤器的字符串。
// 缺省值:NULL
} 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()仅作为一个例子。nids_syslog()函
数向系统守护服务syslogd发送日 志消息。
使用nids_run有一个缺陷:应用程序将完全由数据包驱动(运行)。有时需要在没有数
据包到达时也能处理一些任务,则作为nids_run()函数的替代,程序员可使用如下函数:
int nids_next()
此函数将调用pcap_next()函数(而不是pcap_loop()函数)。(详细资料请参阅《网络安全
工具开发函数库介绍之二 ――libpcap》。) nids_next()函数成功时返回1,出错时返回
0,且nids_errbuf缓冲区存放相应错误消息。
典型地,当使用nids_next()函数时,应用程序调用I/O复用函数select()阻塞,监听
套接字fd在“读”描述字集合fd_set中设置。该套接字可通过如下函数获得:
int nids_getfd()
成功时返回一个文件描述字,出错时返回-1,且nids_errbuf缓冲区存放相应错误消息。
libpcap、libnids和libicmp等。它们分别从不同层次和角度提供了不同的功能函数。使
网络开发人员能够忽略网络底层细节的实现,从而专注于程序本身具体功能的设计与开发
。其中,
* libnet提供的接口函数主要实现和封装了数据包的构造和发送过程。
* libpcap提供的接口函数主要实现和封装了与数据包截获有关的过程。
* libnids提供的接口函数主要实现了开发网络入侵监测系统所必须的一些结构框架。
* libicmp等相对较为简单,它封装的是ICMP数据包的主要处理过程(构造、发送、接收等
)。
利用这些C函数库的接口,网络安全工具开发人员可以很方便地编写出具有结构化强、健
壮性好、可移植性高等特点的程序,如scanner、sniffer、firewall、IDS等。
---[[ libnet ]]------------------------------------------
libnet库的最新版本为1.0.0,它一共约7600行C源代码,33个源程序文件,12个C头文件
,50余个自定义函数,提供的接口函数包含15种数据包生成器和两种数据包发送器(IP层
和数据链路层)。目前只支持IPv4,不支持IPv6。已经过测试的系统平台包括:
* OpenBSD 2.6snap, 2.5, 2.4, 2.3, 2.2 (i386)
* FreeBSD 4.0-STABLE, 3.3-STABLE, 3.2-RELEASE, 3.1-CURRENT, 3.0, 2.2 (i386)
* NetBSD 1.3.2 (i386)
* BSD/OS 3.x (i386)
* BSDi 3.0 (i386)
* Linux 2.2.x, 2.0.3x, 2.1.124 (i386, alpha) (libc: 2.4.x, glibc: 2.0.x)
* Solaris 7 (SPARC, gcc 2.7.2[13], 2.8.2), 2.6 (SPARC, gcc 2.8.2),
2.5.x (SPARC, gcc 2.7.2[13])
* IRIX 6.2
* MacOS 5.3rhapsody (powerpc)
libnet提供的接口函数按其作用可分为四类:
* 内存管理(分配和释放)函数
* 地址解析函数
* 数据包构造函数
* 数据包发送函数
以下分别列出这些接口函数及其功能(其参数含义简单易懂,不再解释):
★ 内存管理函数
单数据包内存初始化:
int libnet_init_packet(u_short packet_size, u_char **buf);
单数据包内存释放:
void libnet_destroy_packet(u_char **buf);
多数据包内存初始化:
int libnet_init_packet_arena(struct libnet_arena **arena,
u_short packet_num, u_short packet_size);
访问多数据包内存中的下一个数据包:
u_char *libnet_next_packet_from_arena(struct libnet_arena **arena,
u_short packet_size);
多数据包内存释放:
void libnet_destroy_packet_arena(struct libnet_arena **arena);
★ 地址解析函数
解析主机名:
u_char *libnet_host_lookup(u_long ip, u_short use_name);
解析主机名(可重入函数):
void libnet_host_lookup_r(u_long ip, u_short use_name, u_char *buf);
域名解析:
u_long libnet_name_resolve(u_char *ip, u_short use_name);
获取接口设备IP地址:
u_long libnet_get_ipaddr(struct libnet_link_int *l,
const u_char *device, const u_char *ebuf);
获取接口设备硬件地址:
struct ether_addr *libnet_get_hwaddr(struct libnet_link_int *l,
const u_char *device,
const u_char *ebuf);
★ 数据包构造函数
ARP协议数据包:
int libnet_build_arp(u_short hrdw, u_short prot, u_short h_len,
u_short p_len, u_short op, u_char *s_ha,
u_char *s_pa, u_char *t_ha, u_char *t_pa,
const u_char *payload, int payload_len,
u_char *packet_buf);
DNS协议数据包:
int libnet_build_dns(u_short id, u_short flags, u_short num_q,
u_short num_answ_rr, u_short num_auth_rr,
u_short num_add_rr, const u_char * payload,
int payload_len, u_char *packet_buf);
以太网协议数据包:
int libnet_build_ethernet(u_char *daddr, u_char *saddr, u_short id,
const u_char *payload, int payload_len,
u_char *packet_buf);
ICMP协议数据包(ICMP_ECHO / ICMP_ECHOREPLY):
int libnet_build_icmp_echo(u_char type, u_char code, u_short id,
u_short seq, const u_char *payload,
int payload_len, u_char *packet_buf);
ICMP协议数据包(ICMP_MASKREQ / ICMP_MASKREPLY):
int libnet_build_icmp_mask(u_char type, u_char code, u_short id,
u_short seq, u_long mask,
const u_char *payload, int payload_len,
u_char *packet_buf);
ICMP协议数据包(ICMP_UNREACH):
int libnet_build_icmp_unreach(u_char type, u_char code,
u_short orig_len, u_char orig_tos,
u_short orig_id, u_short orig_frag,
u_char orig_ttl, u_char orig_prot,
u_long orig_saddr, u_long orig_daddr,
const u_char *payload, int payload_len,
u_char *packet_buf);
ICMP协议数据包(ICMP_TIMEXCEED):
int libnet_build_icmp_timeexceed(u_char type, u_char code,
u_short orig_len, u_char orig_tos,
u_short orig_id, u_short orig_frag,
u_char orig_ttl, u_char orig_prot,
u_long orig_saddr, u_long orig_daddr,
const u_char *payload, int payload_len,
u_char *packet_buf);
ICMP协议数据包(ICMP_REDIRECT):
int libnet_build_icmp_redirect(u_char type, u_char code, u_long gateway,
u_short orig_len, u_char orig_tos,
u_short orig_id, u_short orig_frag,
u_char orig_ttl, u_char orig_prot,
u_long orig_saddr, u_long orig_daddr,
const u_char *payload, int payload_len,
u_char *packet_buf);
ICMP协议数据包(ICMP_TSTAMP / ICMP_TSTAMPREPLY):
int libnet_build_icmp_timestamp(u_char type, u_char code, u_short id,
u_short seq, n_time otime, n_time rtime,
n_time ttime, const u_char *payload,
int payload_len, u_char *packet_buf);
IGMP协议数据包:
int libnet_build_igmp(u_char type, u_char code, u_long ip,
const u_char *payload, int payload_len,
u_char *packet_buf);
IP协议数据包:
int libnet_build_ip(u_short len, u_char tos, u_short ip_id, u_short frag,
u_char ttl, u_char protocol, u_long saddr,
u_long daddr, const u_char *payload, int payload_len,
u_char *packet_buf);
OSPF路由协议数据包:
int libnet_build_ospf(u_short len, u_char type, u_long router_id,
u_long area_id, u_short auth_type,
const char *payload, int payload_s, u_char *buf);
OSPF路由协议数据包(Hello):
int libnet_build_ospf_hello(u_long netmask, u_short interval,
u_char options, u_char priority,
u_int dead_interval, u_long des_router,
u_long backup, u_long neighbor,
const char *payload, int payload_s,
u_char *buf);
OSPF路由协议数据包(DataBase Description (DBD)):
int libnet_build_ospf_dbd(u_short len, u_char options, u_char type,
u_int sequence_num, const char *payload,
int payload_s, u_char *buf);
OSPF路由协议数据包(Link State Request (LSR)):
int libnet_build_ospf_lsr(u_int type, u_int ls_id, u_long adv_router,
const char *payload, int payload_s,
u_char *buf);
OSPF路由协议数据包(Link State Update (LSU)):
int libnet_build_ospf_lsu(u_int num, const char *payload,
int payload_s, u_char *buf);
OSPF路由协议数据包(Link State Acknowledgement (LSA)):
int libnet_build_ospf_lsa(u_short age, u_char options, u_char type,
u_int ls_id, u_long adv_router,
u_int sequence_num, u_short len,
const char *payload, int payload_s,
u_char *buf);
OSPF路由协议数据包(OSPF Link Sate NetworkLink State Router):
int libnet_build_ospf_lsa_net(u_long netmask, u_int router_id,
const char *payload, int payload_s,
u_char *buf);
OSPF路由协议数据包(Link State Router):
int libnet_build_ospf_lsa_rtr(u_short flags, u_short num, u_int id,
u_int data, u_char type, u_char tos,
u_short metric, const char *payload,
int payload_s, u_char *buf);
OSPF路由协议数据包(Link State Summary):
int libnet_build_ospf_lsa_sum(u_long netmask, u_int metric, u_int tos,
const char *payload, int payload_s,
u_char *buf);
OSPF路由协议数据包(Link State AS External):
int libnet_build_ospf_lsa_as(u_long netmask, u_int metric,
u_long fwd_addr, u_int tag,
const char *payload, int payload_s,
u_char *buf);
RIP路由协议数据包:
int libnet_build_rip(u_char cmd, u_char ver, u_short domain,
u_short addr_fam, u_short route_tag, u_long ip,
u_long mask, u_long next_hop, u_long metric,
const u_char *payload, int payload_len,
u_char *packet_buf);
TCP协议数据包:
int libnet_build_tcp(u_short th_sport, u_short th_dport, u_long th_seq,
u_long th_ack, u_char th_flags, u_short th_win,
u_short th_urg, const u_char *payload,
int payload_len, u_char *packet_buf);
UDP协议数据包:
int libnet_build_udp(u_short sport, u_short dport, const u_char *payload,
int payload_len, u_char *packet_buf);
IP协议数据包选项:
int libnet_insert_ipo(struct ipoption *opt, u_char opt_len,
u_char *packet_buf);
TCP协议数据包选项:
int libnet_insert_tcpo(struct tcpoption *opt, u_char opt_len,
u_char *packet_buf);
★ 数据包发送函数
打开raw socket:
int libnet_open_raw_sock(int protocol);
关闭raw socket:
int libnet_close_raw_sock(int socket);
选择接口设备:
int libnet_select_device(struct sockaddr_in *sin,
u_char **device, u_char *ebuf);
打开链路层接口设备:
struct libnet_link_int *libnet_open_link_interface(char *device,
char *ebuf);
关闭链路层接口设备:
int libnet_close_link_interface(struct libnet_link_int *l);
发送IP数据包:
int libnet_write_ip(int socket, u_char *packet, int packet_size);
发送链路层数据包:
int libnet_write_link_layer(struct libnet_link_int *l,
const u_char *device, u_char *packet,
int packet_size);
检验和计算:
int libnet_do_checksum(u_char *packet, int protocol, int packet_size);
★ 相关的支持函数
随机数种子生成器:
int libnet_seed_prand();
获取随机数:
u_long libnet_get_prand(int modulus);
16进制数据输出:
void libnet_hex_dump(u_char * buf, int len, int swap, FILE *stream);
端口列表链初始化:
int libnet_plist_chain_new(struct libnet_plist_chain **plist,
char *token_list);
获取端口列表链的下一项(端口范围):
int libnet_plist_chain_next_pair(struct libnet_plist_chain *plist,
u_short *bport, u_short *eport);
端口列表链输出显示:
int libnet_plist_chain_dump(struct libnet_plist_chain *plist);
获取端口列表链:
u_char *libnet_plist_chain_dump_string(struct libnet_plist_chain *plist);
端口列表链内存释放:
void libnet_plist_chain_free(struct libnet_plist_chain *plist);
---[[ libnids ]]------------------------------------------
一、简介
libnids的英文意思是 Network Intrusion Detect System library,即网络入侵监
测系统函数库。它是在前面介绍的两种C函数接口库libnet和libpcap的基础上开发的,封
装了开发NIDS所需的许多通用型函数。linids提供的接口函数监视流经本地的所有网络通
信,检查数据包等。除此之外,还具有重组TCP数据段、处理IP分片包和监测TCP端口扫描
的功能。利用libnids接口函数库,NIDS开发者不需要再编写底层的网络处理代码,只需
专注于NIDS本身功能的实现即可。
libnids支持Linux、Solaris和*BSD系统平台,目前最新版本为1.13。
二、IP分片数据包
为了使libnids能接收所有的IP数据包(包括分片包、畸形包等),程序员需要定义如
下的回调函数:
void ip_frag_func(struct ip * a_packet)
在调用nids_init()函数初始化后,使用nids的函数进行注册:
nids_register_ip_frag(ip_frag_func);
这样回调函数ip_frag_func会在适当的时候由libnids调用,参数a_packet指针将指向接
收到的数据报。
类似地,如果仅接收目标主机会接受的数据包(如非碎片包、重组包或头部校验正确
的数据包等),需要定义如下回调函数:
void ip_func(struct ip * a_packet)
然后注册:
nids_register_ip(ip_func);
三、TCP数据流重组
要接收TCP流在交换的数据,必须定义如下回调函数:
void tcp_callback(struct tcp_stream * ns, void ** param)
tcp_stream结构提供了一个TCP连接的所有信息。例如,它包含了客户端与服务器端的
half_stream结构。下文会对该结构的字段进行解释。
tcp_stream结构有一个名为nids_state的字段。此字段的数值将决定tcp_callback的
操作。
(a) ns->nids_state==NIDS_JUST_EST时,ns表示一个刚刚建立的连接。
tcp_callback可以据此决定是否对该连接的后续数据进行检查。如
需要检查,tcp_callback回调函数将通知libnids它希望接收哪些
数据(如到客户端的数据、到服务器端的数据、到客户端的紧急数
据或到服务器端的紧急数据等),然后返回。
(b) ns->nids_state==NIDS_DATA时,表示ns连接接收到新的数据。
half_stream结构中的缓冲区用于存放这些数据。
(c) nids_state字段为其它数值(NIDS_CLOSE、NIDS_RESET、
NIDS_TIMEOUT)时,表示该连接已经关闭了。tcp_callback函数应
释放相关资源。
四、一个简单的实例
下面的源代码是一个非常简单的程序,它将libnids捕获的所有TCP连接交换的数据输
出显示到标准输出设备上。
-----------------------BEGINING OF CODE--------------------------------
#include "nids.h"
#include <string.h>
#include <stdio.h>
extern char * inet_ntoa(unsigned long);
// tuple4结构包含了TCP连接两端的IP地址和端口,以下函数将它们转换为字符串
// 格式,如10.0.0.1,1024, 10.0.0.2,23
char *
adres (struct tuple4 addr)
{
static char buf[256];
strcpy (buf, inet_ntoa (addr.saddr));
sprintf (buf + strlen (buf), ",%i,", addr.source);
strcat (buf, inet_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;表
// 示不处理目标端口为23的数据包。
// 本例需要处理(显示)所有数据包,故:
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 established\n", buf);
return;
}
if (a_tcp->nids_state == NIDS_CLOSE)
{
// TCP连接正常关闭
fprintf (stderr, "%s closing\n", buf);
return;
}
if (a_tcp->nids_state == NIDS_RESET)
{
// TCP连接因RST数据包而关闭
fprintf (stderr, "%s reset\n", 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;
}
#ifdef WE_WANT_URGENT_DATA_RECEIVED_BY_A_CLIENT
if (a_tcp->client.count_new_urg)
{
// 客户端接收的紧急数据
strcat(buf,"(urgent->)");
buf[strlen(buf)+1]=0;
buf[strlen(buf)]=a_tcp->server.urgdata;
write(1,buf,strlen(buf));
return;
}
#endif
if (a_tcp->client.count_new)
{
// 客户端接收的数据
hlf = &a_tcp->client; // 准备显示客户端接收的数据
strcat (buf, "(<-)"); // 指示数据流方向
}
else
{
hlf = &a_tcp->server; // 准备显示服务器端接收的数据
strcat (buf, "(->)"); // 指示数据流方向
}
fprintf(stderr,"%s",buf); // 首先输出显示连接双方的IP地址、端口
// 和数据流方向
write(2,hlf->data,hlf->count_new); // 输出显示接收到的新数据
}
return ;
}
int
main ()
{
// 此处可自定义libnids的全局变量,如:
// nids_params.n_hosts=256;
if (!nids_init () )
{
fprintf(stderr,"%s\n",nids_errbuf);
exit(1);
}
nids_register_tcp (tcp_callback);
nids_run ();
// NOT REACHED
return 0;
}
---------------------------END OF CODE------------------------------------
五、libnids的数据结构及接口函数
libnids库的所有数据结构及接口函数都在"nids.h"头文件中声明。
struct tuple4 // TCP连接参数
{
unsigned short source,dest; // 客户端和服务器端的端口号
unsigned long saddr,daddr; // 客户端和服务器端的IP地址
};
struct half_stream // TCP连接一端的数据结构
{
char state; // 套接字状态(如TCP_ESTABLISHED)
char collect; // 如果大于0,则保存其数据到缓冲区中,否则忽略
char collect_urg; // 如果大于0,则保存紧急数据,否则忽略
char * data; // 正常数据的缓冲区
unsigned char urgdata; // 紧急数据缓冲区
int count; // 自从连接建立以来保存到"data"缓冲区的数据字节
// 数总和
int offset; // 保存到"data"缓冲区的首字节数据偏移量
int count_new; // 最近一次接收到的数据字节数;如果为0,则无数
// 到达
char count_new_urg; // 如果非0,表示有新的紧急数据到达
... // libnids库使用的辅助字段
};
struct tcp_stream
{
struct tuple4 addr; // TCP连接参数(saddr, daddr, sport, dport)
char nids_state; // TCP连接的逻辑状态
struct half_stream client,server; // 描述客户端与服务器端的数据结构
... // libnids库使用的辅助字段
};
在上面的实例程序中,回调函数tcp_callback输出显示hlf->data缓冲区中的数据到
标准输出设备上。这些数据在tcp_callback函数返回后,由libnids自动释放这些数据所
占用的内存空间。同时,hlf->offset字段将增加被丢弃数据的字节数,而新接收到的数
据则存放到"data"缓冲区的起始处。
如果在其它应用中不进行如上例的操作(例如,数据处理过程至少需要N个字节的输入
数据,而libnids只接收到的数据字节数count_new<N),则需要在tcp_callback函数返回
前调用如下函数:
void nids_discard(struct tcp_stream * a_tcp, int num_bytes)
此时,当回调函数tcp_callback返回后linids将"data"缓冲区的前num_bytes字节数据,
同时计算调整offset字段的数值,并将剩余数据移动到缓冲区的起始处。
如果始终不调用nids_discard()函数(如上面实例),hlf->data缓冲区中将包含
hlf-> count_new字节数据。通常情况下,在hlf->data缓冲区中的数据字节数等于
hlf->count - hlf->offset。
有了nids_discard()函数,程序员就不必拷贝接收到的数据到另外的缓冲区中,
hlf->data缓冲区将总是尽可能保存足够的数据。然后,有时会有保留数据包特定数据的
需要。例如,我们希望能监测到针对wu-ftpd服务器的"CWD"溢出攻击,就需要跟踪检查
ftp客户端发送的"CWD"命令。此时就需要tcp_callback回调函数具有第二个参数了。此参
数是某TCP连接私有数据的指针。处理过程如下:
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;
if the connection is uninteresting, return;
a_conn=malloc of some data structure
init of a_conn
*ptr=a_conn // this value will be passed to tcp_callback_2 in future
// calls
increase some of "collect" fields
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连接中
使用两种不同的回调函数是允许的。
libnids库定义了一个全局变量结构nids_params,其声明如下:
struct nids_prm
{
int n_tcp_streams; // 存放tcp_stream结构的hash表大小。
// 缺省值:1024
int n_hosts; // 存放IP分片信息的hash表大小
// 缺省值:256
char * device; // libnids监听的接口设备名
// 缺省值 == NULL,即由pcap_lookupdev函数确定
int sk_buff_size; // (Linux内核)sk_buff结构大小
// 缺省值:168
int dev_addon; // sk_buff为网络接口保留的字节数
// 如果dev_addon==-1,则由nids_init函数确定
// 缺省值:-1
void (*syslog)(); // 日志函数指针
int syslog_level; // 如果nids_params.syslog==nids_syslog,则此字段值
// 将确定日志等级loglevel
// 缺省值:LOG_ALERT
int scan_num_hosts;// 存放端口扫描信息的hash表大小。
// 如果为0,则关闭端口扫描监测功能。
// 缺省值:256
int scan_num_ports;// 来自同一IP地址所扫描的TCP端口数上限
// 缺省值:10
int scan_delay; // 在两次端口扫描中的间隔时间上限(毫秒)
// 缺省值:3000
void (*no_mem)(); // 内存不足时被调用,此时应终止当前进程
int (*ip_filter)(struct ip*); // 当接收到一个IP数据包时调用。如返回值
// 非零,则处理该数据包,否则忽略。
// 缺省为(nids_ip_filter)且总返回1
char *pcap_filter; // 传递给pcap过滤器的字符串。
// 缺省值:NULL
} 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()仅作为一个例子。nids_syslog()函
数向系统守护服务syslogd发送日 志消息。
使用nids_run有一个缺陷:应用程序将完全由数据包驱动(运行)。有时需要在没有数
据包到达时也能处理一些任务,则作为nids_run()函数的替代,程序员可使用如下函数:
int nids_next()
此函数将调用pcap_next()函数(而不是pcap_loop()函数)。(详细资料请参阅《网络安全
工具开发函数库介绍之二 ――libpcap》。) nids_next()函数成功时返回1,出错时返回
0,且nids_errbuf缓冲区存放相应错误消息。
典型地,当使用nids_next()函数时,应用程序调用I/O复用函数select()阻塞,监听
套接字fd在“读”描述字集合fd_set中设置。该套接字可通过如下函数获得:
int nids_getfd()
成功时返回一个文件描述字,出错时返回-1,且nids_errbuf缓冲区存放相应错误消息。