c语言基于libpcap实现一个抓包程序过程
基于pcap的嗅探器程序的总体架构,其流程如下:(1)首先要决定用哪一个接口进行嗅探开始。在Linux中,这可能是eth0,而在BSD系统中则可能是xl1等等。我们也可以用一个字符串来定义这个设备,或者采用pcap提供的接口名来工作。(...
基于pcap的嗅探器程序的总体架构,其流程如下:
(1)首先要决定用哪一个接口进行嗅探开始。在Linux中,这可能是eth0,而在BSD系统中则可能是xl1等等。我们也可以用一个字符串来定义这个设备,或者采用pcap提供的接口名来工作。
(2)初始化pcap。在这里需要告诉pcap对什么设备进行嗅探。假如愿意的话,我们还可以嗅探多个设备。怎样区分它们呢?使用 文件句柄。就像打开一个文件进行读写一样,必须命名我们的嗅探“会话”,以此使它们各自区别开来。
(3)如果只想嗅探特定的传输(如TCP/IP包,发往端口23的包等等),我们必须创建一个规则集合,编译并且使用它。这个过程分为三个相互紧密关联的阶段。 规则集合被置于一个字符串内,并且被转换成能被pcap读的格式(因此编译它)。编译实际上就是在我们的程序里调用一个不被外部程序使用的函数。接下来我们要告诉 pcap使用它来过滤出我们想要的那一个会话。(此步骤可选)
(4)最后,我们告诉pcap进入它的主体执行循环。在这个阶段内pcap一直工作到它接收了所有我们想要的包为止。每当它收到一个包就调用另一个已经定义好的函数,这个函数可以做我们想要的任何工作,它可以剖析所部获的包并给用户打印出结果,它可以将结果保存为一个文件,或者什么也不作。
(5)在嗅探到所需的数据后,我们要关闭会话并结束。
编程实现过程:
(1)设置设备
这是很简单的。有两种方法设置想要嗅探的设备。
第一种,我们可以简单的让用户告诉我们。考察下面的程序:
#include <stdio.h> #include <pcap.h> int main(int argc, char *argv[]) { char *dev = argv[1]; printf("Device: %s", dev); return(0); }
运行:
[email protected] /h/jiabei# vim a1.c
[email protected] /h/jiabei# gcc a1.c -lpcap
[email protected] /h/jiabei# ./a.out
Device : (null) (输出)
用户通过传递给程序的第一个参数来指定设备。字符串“dev”以pcap能“理解”的格式保存了我们要嗅探的接口的名字(当然,用户必须给了我们一个真正存在的接口)。
另一种也是同样的简单。来看这段程序:
#include <stdio.h> #include <pcap.h> int main() { char *dev, errbuf[PCAP_ERRBUF_SIZE]; dev = pcap_lookupdev(errbuf); printf("Device: %s", dev); return(0); }
运行:
[email protected] /h/jiabei# vim a2.c
[email protected] /h/jiabei# gcc a2.c -lpcap
error :‘errbuf‘ undeclared .... (输出)
我们发现 errbuf[]数组未定义。
于是:
#include <stdio.h> #include <pcap.h> char errbuf[PCAP_ERRBUF_SIZE]; int main() { char *dev, errbuf[PCAP_ERRBUF_SIZE]; dev = pcap_lookupdev(errbuf); printf("Device: %s", dev); return(0); }
再次运行:
[email protected] /h/jiabei# vim a2.c
[email protected] /h/jiabei# gcc a2.c -lpcap
Device: eth0 (输出,即Linux 下默认为eth0端口)
(2)打开设备进行嗅探
创建一个嗅探会话的任务真的非常简单。为此,我们使用pcap_open_live()函数。此函数的原型(根据pcap的手册页)如下:
1 |
pcap_t *pcap_open_live(char *device, int snaplen, int promisc, int to_ms, char *ebuf) |
其第一个参数是我们在上一节中指定的设备,snaplen是整形的,它定义了将被pcap捕捉的最大字节数。当promisc设为true时将置指定接口为混杂模式(然而,当它置为false时接口仍处于混杂模式的非凡情况也是有可能的)。to_ms是读取时的超时值,单位是毫秒(假如为0则一直嗅探直到错误发生,为-1则不确定)。最后,ebuf是一个我们可以存入任何错误信息的字符串(就像上面的errbuf)。此函数返回其会话句柄。
混杂模式与非混杂模式的区别:这两种方式区别很大。一般来说,非混杂模式的嗅探器中,主机仅嗅探那些跟它直接有关的通信,如发向它的,从它发出的,或经它路由的等都会被嗅探器捕捉。而在混杂模式中则嗅探传输线路上的所有通信。在非交换式网络中,这将是整个网络的通信。这样做最明显的优点就是使更多的包被嗅探到,它们因你嗅探网络的原因或者对你有帮助,或者没有。但是,混杂模式是可被探测到的。一个主机可以通过高强度的测试判定另一台主机是否正在进行混杂模式的嗅探。其次,它仅在非交换式的网络环境中有效工作(如集线器,或者交换中的ARP层面)。再次,在高负荷的网络中,主机的系统资源将消耗的非常严重。
(3)过滤通信
实现这一过程由pcap_compile()与pcap_setfilter()这两个函数完成。
在使用我们自己的过滤器前必须编译它。过滤表达式被保存在一个字符串中(字符数组)。其句法在tcpdump的手册页中被证实非常好。我建议你亲自阅读它。但是我们将使用简单的测试表达式,这样你可能很轻易理解我的例子。
我们调用pcap_compile()来编译它,其原型是这样定义的:
1 |
int pcap_compile(pcap_t |
第一个参数是会话句柄。接下来的是我们存储被编译的过滤器版本的地址的引用。再接下来的则是表达式本身,存储在规定的字符串格式里。再下边是一个定义表达式是否被优化的整形量(0为false,1为true,标准规定)。最后,我们必须指定应用此过滤器的网络掩码。函数返回-1为失败,其他的任何值都表明是成功的。
表达式被编译之后就可以使用了。现在进入pcap_setfilter()。仿照我们介绍pcap的格式,先来看一看pcap_setfilter()的原型:
1 |
int pcap_setfilter(pcap_t |
这非常直观,第一个参数是会话句柄,第二个参数是被编译表达式版本的引用(可推测出它与pcap_compile()的第二个参数相同)。
下面的代码示例可能能使你更好的理解:
#include <pcap.h> int main() { pcap_t *handle; /* 会话的句柄 */ char dev[] = "eth0"; /* 执行嗅探的设备 */ char errbuf[PCAP_ERRBUF_SIZE]; /* 存储错误 信息的字符串 */ struct bpf_program filter; /*已经编译好的过滤表达式*/ char filter_app[] = "port 23"; /* 过滤表达式*/ bpf_u_int32 mask; /* 执行嗅探的设备的网络掩码 */ bpf_u_int32 net; /* 执行嗅探的设备的IP地址 */ pcap_lookupnet(dev, &net, &mask, errbuf); handle = pcap_open_live(dev, BUFSIZ, 1, 0, errbuf); pcap_compile(handle, &filter, filter_app, 0, net); pcap_setfilter(handle, &filter); }
这个程序使嗅探器嗅探经由端口23的所有通信,使用混杂模式,设备是eth0。
(4)实际的嗅探
有两种手段捕捉包。我们可以一次只捕捉一个包,也可以进入一个循环,等捕捉到多个包再进行处理。我们将先看看怎样去捕捉单个包,然后再看看使用循环的方法。为此,我们使用函数pcap_next()。
pcap_next()的原型及其简单:
u_char *pcap_next(pcap_t *p, struct pcap_pkthdr *h)
第一个参数是会话句柄,第二个参数是指向一个包括了当前数据包总体信息(被捕捉时的时间,包的长度,其被指定的部分长度)的结构体的指针(在这里只有一个片断,只作为一个示例)。pcap_next()返回一个u_char指针给被这个结构体描述的包。我们将稍后讨论这种实际读取包本身的手段。
这里有一个演示怎样使用pcap_next()来嗅探一个包的例子:
#include <pcap.h> #include <stdio.h> int main() { pcap_t *handle; /* 会话句柄 */ char *dev; /* 执行嗅探的设备 */ char errbuf[PCAP_ERRBUF_SIZE]; /* 存储错误信息的字符串 */ struct bpf_program filter; /* 已经编译好的过滤器 */ char filter_app[] = "port 23"; /* 过滤表达式 */ bpf_u_int32 mask; /* 所在网络的掩码 */ bpf_u_int32 net; /* 主机的IP地址 */ struct pcap_pkthdr header; /* 由pcap.h定义 */ const u_char *packet; /* 实际的包 */ /* Define the device */ dev = pcap_lookupdev(errbuf); /* 探查设备属性 */ pcap_lookupnet(dev, &net, &mask, errbuf); /* 以混杂模式打开会话 */ handle = pcap_open_live(dev, BUFSIZ, 1, 0, errbuf); /* 编译并应用过滤器 */ pcap_compile(handle, &filter, filter_app, 0, net); pcap_setfilter(handle, &filter); /* 截获一个包 */ packet = pcap_next(handle, &header); /* 打印它的长度 */ printf("Jacked a packet with length of [%d] ", header.len); /* 关闭会话 */ pcap_close(handle); return(0); }
运行:
[email protected] /h/jiabei# vim a3.c
[email protected] /h/jiabei# gcc a3.c -lpcap
Jacked a packet with length of [1]
(输出)
这个程序嗅探被pcap_lookupdev()返回的设备并将它置为混杂模式。它发现第一个包经过端口23(telnet)并且告诉用户此包的大小(以字节为单位)。