当前位置: 首页 > 工具软件 > libpcap > 使用案例 >

Libpcap二 libpcap抓包基本流程

公西季
2023-12-01

1、设置设备

这是很简单的。有两种方法设置想要嗅探的设备。

第一种,我们可以简单的让用户告诉我们。考察下面的程序:

#include
#include
int main(int argc, char *argv[])
{
   char *dev = argv[1];
   printf("Device: %s", dev);
   return(0);
}

用户通过传递给程序的第一个参数来指定设备。字符串“dev”以pcap能“理解”的格式保存了我们要嗅探的接口的名字(当然,用户必须给了我们一个真正存在的接口)。

另一种也是同样的简单。来看这段程序:

//gcc -o lookup lookup.c -I ./../include -L./../lib -lpcap
#include <stdio.h>
#include <pcap.h>
/*
char *pcap_lookupdev(char *errbuf)
   用于返回可被pcap_open_live()或pcap_lookupnet()函数调用的网络
   设备名指针。如果函数出错,则返回NULL,同时errbuf中存放相关的
   错误消息。
*/
int main()
{
	char *dev, errbuf[PCAP_ERRBUF_SIZE];
	dev = pcap_lookupdev(errbuf);
	printf("Device: %s\n", dev);
	return(0);
}

2、打开设备进行嗅探

创建一个嗅探会话的任务真的非常简单。为此,我们使用pcap_open_live()函数。此函数的原型(根据pcap的手册页)如下:

   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()来编译它,其原型是这样定义的:

   int pcap_compile(pcap_t *p, strUCt bpf_program *fp, char *str, int optimize, bpf_u_int32 netmask)

第一个参数是会话句柄。接下来的是我们存储被编译的过滤器版本的地址的引用。再接下来的则是表达式本身,存储在规定的字符串格式里。再下边是一个定义表达式是否被优化的整形量(0为false,1为true,标准规定)。最后,我们必须指定应用此过滤器的网络掩码。函数返回-1为失败,其他的任何值都表明是成功的。

表达式被编译之后就可以使用了。现在进入pcap_setfilter()。仿照我们介绍pcap的格式,先来看一看pcap_setfilter()的原型:

   int pcap_setfilter(pcap_t *p, struct bpf_program *fp)

这非常直观,第一个参数是会话句柄,第二个参数是被编译表达式版本的引用(可推测出它与pcap_compile()的第二个参数相同)。

下面的代码示例可能能使你更好的理解:

  

//gcc -o filter filter.c -I ./../include -L./../lib -lpcap

/*
pcap_t *pcap_open_live(char *device, int snaplen,
   int promisc, int to_ms, char *ebuf)
   获得用于捕获网络数据包的数据包捕获描述字。device参数为指定打开
   的网络设备名。snaplen参数定义捕获数据的最大字节数。promisc指定
   是否将网络接口置于混杂模式。to_ms参数指定超时时间(毫秒)。
   ebuf参数则仅在pcap_open_live()函数出错返回NULL时用于传递错误消
   息。
int pcap_lookupnet(char *device, bpf_u_int32 *netp,
   bpf_u_int32 *maskp, char *errbuf)
   获得指定网络设备的网络号和掩码。netp参数和maskp参数都是
   bpf_u_int32指针。如果函数出错,则返回-1,同时errbuf中存放相
   关的错误消息。
int pcap_compile(pcap_t *p, struct bpf_program *fp,
   char *str, int optimize, bpf_u_int32 netmask)
   将str参数指定的字符串编译到过滤程序中。fp是一个bpf_program结
   构的指针,在pcap_compile()函数中被赋值。optimize参数控制结果
   代码的优化。netmask参数指定本地网络的网络掩码。
int pcap_setfilter(pcap_t *p, struct bpf_program *fp)
   指定一个过滤程序。fp参数是bpf_program结构指针,通常取自
   pcap_compile()函数调用。出错时返回-1;成功时返回0。

*/
#include <pcap.h>
#include <stdio.h>
int main()
{
	int ret32 = -1;
	pcap_t *handle = NULL; /* 会话的句柄 */
	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地址 */
	ret32 = pcap_lookupnet(dev, &net, &mask, errbuf);
	if(ret32 < 0)
	{
		printf("pcap_lookupnet return %d, errbuf:%s\n", ret32, errbuf);
	}
	printf("sizeof(mask) = %d, mask:%#x, net:%#x\n",sizeof(mask), mask, net);
	handle = pcap_open_live(dev, 1024, 1, 0, errbuf);
	if(handle == NULL)
	{
		printf("pcap_open_live return err,errbuf:%s...\n", errbuf);
		return -1;
	}
	
	ret32 = pcap_compile(handle, &filter, filter_app, 0, net);
	if(ret32 < 0)
	{
		printf("pcap_compile return %d, errbuf:%s\n", ret32, errbuf);
		return -1;
	}
	ret32 = pcap_setfilter(handle, &filter);
	if(ret32 < 0)
	{
		printf("pcap_setfilter return %d, errbuf:%s\n", ret32, errbuf);
		return -1;
	}
	printf("libpcap API test ok...\n");
	
	return 0;
}

这个程序使嗅探器嗅探经由端口23的所有通信,使用混杂模式,设备是eth0。

4、实际的嗅探

有两种手段捕捉包。我们可以一次只捕捉一个包,也可以进入一个循环,等捕捉到多个包再进行处理。我们将先看看怎样去捕捉单个包,然后再看看使用循环的方法。为此,我们使用函数pcap_next()。

pcap_next()的原型及其简单:

   u_char *pcap_next(pcap_t *p, struct pcap_pkthdr *h)

第一个参数是会话句柄,第二个参数是指向一个包括了当前数据包总体信息(被捕捉时的时间,包的长度,其被指定的部分长度)的结构体的指针(在这里只有一个片断,只作为一个示例)。pcap_next()返回一个u_char指针给被这个结构体描述的包。我们将稍后讨论这种实际读取包本身的手段。

   这里有一个演示怎样使用pcap_next()来嗅探一个包的例子:

  

/*
//gcc -o pcap pcap.c -I ./../include -L./../lib -lpcap
u_char *pcap_next(pcap_t *p, struct pcap_pkthdr *h)
   返回指向下一个数据包的u_char指针。
*/
#include <pcap.h>
#include <stdio.h>
int main()
{
	int ret32 = -1;
	pcap_t *handle = NULL; /* 会话的句柄 */
	char dev[] = "eth0"; /* 执行嗅探的设备 */
	char errbuf[PCAP_ERRBUF_SIZE]; /* 存储错误 信息的字符串 */
	struct bpf_program filter; /*已经编译好的过滤表达式*/
	char filter_app[] = "port 22"; /* 过滤表达式*/
	bpf_u_int32 mask; /* 执行嗅探的设备的网络掩码 */
	bpf_u_int32 net; /* 执行嗅探的设备的IP地址 */
	u_char *packet; /* 实际的包 */
	struct pcap_pkthdr header; /* 由pcap.h定义 */
	
	ret32 = pcap_lookupnet(dev, &net, &mask, errbuf);
	if(ret32 < 0)
	{
		printf("pcap_lookupnet return %d, errbuf:%s\n", ret32, errbuf);
	}
	printf("sizeof(mask) = %d, mask:%#x, net:%#x\n",sizeof(mask), mask, net);
	handle = pcap_open_live(dev, 1024, 1, 0, errbuf);
	if(handle == NULL)
	{
		printf("pcap_open_live return err,errbuf:%s...\n", errbuf);
		return -1;
	}
	
	#if 0
	ret32 = pcap_compile(handle, &filter, filter_app, 0, net);
	if(ret32 < 0)
	{
		printf("pcap_compile return %d, errbuf:%s\n", ret32, errbuf);
		return -1;
	}
	ret32 = pcap_setfilter(handle, &filter);
	if(ret32 < 0)
	{
		printf("pcap_setfilter return %d, errbuf:%s\n", ret32, errbuf);
		return -1;
	}
	#endif 
	
	/* 截获一个包 */
	packet = pcap_next(handle, &header);
	if(packet)
	{
		/* 打印它的长度 */
		printf("Jacked a packet with length of [%d]\n", header.len);
	}
	
	/* 关闭会话 */
	pcap_close(handle);
	
	return 0;
}

这个程序嗅探被pcap_lookupdev()返回的设备并将它置为混杂模式。它发现第一个包经过端口23(telnet)并且告诉用户此包的大小(以字 节为单位)。这个程序又包含了一个新的调用pcap_close(),我们将在后面讨论(尽管它的名字就足够证实它自己的作用)。

实际上很少有嗅探程序会真正的使用pcap_next()。通常,它们使用pcap_loop()或者 pcap_dispatch()。

 

 类似资料: