使用libpcap库用c编写网络嗅探器

定义:网络嗅探也叫网络侦听,指的是使用特定的网络协议来分解捕获到的数据包,并根据对应的网络协议识别对应数据片断

作用:管理员可以用来监听网络的流量情况 

          开发网络应用的程序员可以监视程序的网络情况 

          黑客可以用来刺探网络情报

网卡的四种接收数据模式

①广播模式:该模式下的网卡能够接收网络中的广播信息;

②组播模式:设置在该模式下的网卡能够接收组播数据;

③直接模式:在这种模式下,只有目的网卡才能接收该数据;

④混杂模式:在这种模式下的网卡能够接收一切通过它的数据,而不管该数据是否是传给它的。

通常,网卡的缺省配置是支持前三种模式。 为了监听网络上的流量,必须设置为混杂模式

libpcap简介

Packet Capture library,即数据包捕获函数库。该库提供的C函数接口可用于需要捕获经过网络接口(只要经过该接口,目标地址不一定为本机)数据包的系统开发上。

程序流程

Libpcap API介绍

pcap_lookupdev( )

char *pcap_lookupdev(char *errbuf)

          用于返回可被pcap_open_live()或pcap_lookupnet()函数调用的网络

          设备名指针。如果函数出错,则返回NULL,同时errbuf中存放相关的

          错误消息。

pcap_lookupnet( )

int pcap_lookupnet(char *device, bpf_u_int32 *netp,bpf_u_int32 *maskp, char *errbuf)

          获得指定网络设备的网络号和掩码。netp参数和maskp参数都是

          bpf_u_int32指针。如果函数出错,则返回-1,同时errbuf中存放相

          关的错误消息。

pcap_open_live( ) 

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时用于传递错误消

          息。

pcap_setfilter( ) 

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

          指定一个过滤程序。fp参数是bpf_program结构指针,通常取自

          pcap_compile()函数调用。出错时返回-1;成功时返回0。

pcap_compile( ) 

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参数指定本地网络的网络掩码。

pcap_next( ) 

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

          返回指向下一个数据包的u_char指针。

pcap_dispatch( )

int pcap_dispatch(pcap_t *p, int cnt,

               pcap_handler callback, u_char *user)

          捕获并处理数据包。cnt参数指定函数返回前所处理数据包的最大值。

          cnt=-1表示在一个缓冲区中处理所有的数据包。cnt=0表示处理所有

          数据包,直到产生以下错误之一:读取到EOF;超时读取。callback

          参数指定一个带有三个参数的回调函数,这三个参数为:一个从

          pcap_dispatch()函数传递过来的u_char指针,一个pcap_pkthdr结构

          的指针,和一个数据包大小的u_char指针。如果成功则返回读取到的

          字节数。读取到EOF时则返回零值。出错时则返回-1,此时可调用

          pcap_perror()或pcap_geterr()函数获取错误消息。

pcap_loop( )

int pcap_loop(pcap_t *p, int cnt,

               pcap_handler callback, u_char *user)

          功能基本与pcap_dispatch()函数相同,只不过此函数在cnt个数据包

          被处理或出现错误时才返回,但读取超时不会返回。而如果为

          pcap_open_live()函数指定了一个非零值的超时设置,然后调用

          pcap_dispatch()函数,则当超时发生时pcap_dispatch()函数会返回。

          cnt参数为负值时pcap_loop()函数将始终循环运行,除非出现错误。

过滤规则

规则是由标识和修饰符与逻辑符组成的。

修饰符

确定方向的修饰符:src/dst; 

确定类型的修饰符:host/net/port; 

确定协议的修饰符:IP/TCP/UDP/ARP; 

逻辑符 and 或 && not 或! or  或 ||

打印程序思路

回调函数:回调函数,就是由你自己写的。你需要调用另外一个函数,而这个函数的其中一个参数,就是你的这个回调函数名。这样,系统在必要的时候,就会调用你写的回调函数,这样你就可以在回调函数里完成你要做的事。

程序能实现的功能:

使用Libpcap库捕获局域网中的IP包,要求:

打印数据包的源与目的物理地址;

打印源IP与目的IP地址; 

打印出上层协议类型;

如果上层协议为TCP或UDP协议,打印目的与源端口信息; 

如果上层协议为TCP或UDP协议,将数据以16进制与ASCII的两种方式同时打印出来,不可打印字符以‘.’代替; 

 00000   47 45 54 20 2f 20 48 54  54 50 2f 31 2e 31 0d 0a   GET / HTTP/1.1..

示例代码

#include<stdio.h>
#include<string.h>
#include<sys/types.h>
#include<sys/time.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<netinet/in_systm.h>
#include<netinet/ip.h>
#include<netinet/if_ether.h>
#include<netinet/tcp.h>
#include<netinet/udp.h>
#include<pcap.h>
#include<netdb.h>
#include<time.h>
#include<sys/time.h>
#include<stdlib.h>
#include<ctype.h>

//打印16进制和ascii
void print(u_char*payload,int len,int offset,int maxlen)
{
	printf("%.5d  ",offset);	//打印偏移量(宽度为5)
	int max=maxlen;	//数据包的有效载荷和长度
	int i;
	for(i=0;i<16;i++)	//打印16个字节的16进制payload
	{
		if((len-i)>0)	//还没打完
		{
		printf("%.2x ",payload[max-(len-i)]);
		}
		else	//已打完,最后一个后面有空格
		{
		printf("   ");
		}
	}
	printf("       ");
	for(i=0;i<16;i++)	//打印16个字节的asciipayload
	{
		if(isprint(payload[max-(len-i)]))	//为可打印字符
		{
			printf("%c",payload[max-(len-i)]);
		}
		else		//打印不出来的用"."表示
		{
			printf(".");
		}
	}
}

//打印数据包
void print_data(u_char *payload,int len)
{
	int line_width=16;	//一行16个字节
	int len_rem=len;	//剩余长度
	int maxlen=len;		//数据包的有效载荷和长度
	int offset=0;		//偏移量
	while(1)
	{
		if(len_rem<line_width)	//最后一次打印
		{
			if(len_rem==0)	//已打印完
				break;
			else {	//还没打印完
			print(payload,len_rem,offset,maxlen);	//调用print函数,传入payload地址、剩余长度、偏移量和数据包的有效载荷和长度
			offset=offset+len_rem;	//偏移量后移
			printf("\n");
			break;
			}}
		else	//不是最后一次打印
		{	
			print(payload,len_rem,offset,maxlen);	//调用print函数,传入payload地址、剩余长度、偏移量和数据包的有效载荷和长度
			offset=offset+16;	//偏移量后移(由于非最后一次打印,所以固定打16个字节 - 偏移量后移16个字节)
			printf("\n");
		}
		len_rem=len_rem-line_width;	//剩余长度减少
	}
}

//打印mac地址
void print_mac(u_char* macadd){
	int i;
	for(i=0;i<5;i++){
		printf("%.2x:",macadd[i]);	//16进制,两位宽度
		}
	printf("%.2x",macadd[i]);
}

//打印ip地址	
void print_ip(u_char* ipadd){
	int i;
	for(i=0;i<3;++i)
		{
		printf("%d.",ipadd[i]);
		}
	printf("%d",ipadd[i]);
}
pcap_handler callback(u_char *user,const struct pcap_pkthdr *h,const u_char *p)//一个从pcap_dispatch()函数传递过来的u_char指针,一个pcap_pkthdr结构的指针,和一个数据包大小的u_char指针。
{
    struct ether_header *eth;	//以太网帧头部
	static long int packet_num=0;	//当前包编号(包数量),为静态分配
	struct ether_arp *arppkt;	//arp帧头部(以arp包的源以太地址是RTSG, 目标地址是全以太网段)
	struct ip *iph;	//IP包头部
	struct icmphdr *icmp;	//ICMP包头部
	struct tcphdr *tcph;	//tcp头部
	struct udphdr *udph;	//udp头部
	int m;
	char *buf;

	printf("...............................................................................\n");
	printf("\n");	
	printf("Recieved at ----- %s",ctime((const time_t*)&(h->ts).tv_sec));	//显示时间
	printf("Packet  number:%d\n",++packet_num);	//显示当前包编号
	printf("Packet  length:%d\n",h->len);	//显示包长度(脱机长度)
	int i;
	eth=(struct ether_header *)p;
	printf("Source Mac Address: ");	
	print_mac(eth->ether_shost);	//调用print_mac函数,传入源主机的mac地址
	printf("\n");
	printf("Destination Mac Address:");
	print_mac(eth->ether_dhost);	//调用print_mac函数,传入目的主机的mac地址
	printf("\n");
	
	//判断网络层协议
	unsigned int typeno;
	typeno=ntohs(eth->ether_type);
	printf("network layer protocal:");
	switch(typeno){
	case ETHERTYPE_IP:
		printf("IPV4\n");
		break;
	case ETHERTYPE_PUP:
		printf("PUP\n");
		break;
	case ETHERTYPE_ARP:
		printf("ARP\n");
		break;
	default:
		printf("unknown network layer types\n");
	}	
	if(typeno==ETHERTYPE_IP)	//为IP协议
		{
			iph=(struct ip*)(p+sizeof(struct ether_header));	//获得ip包头部地址
			
			printf("Source Ip Address:");
			print_ip((u_char*)&(iph->ip_src));	//调用print_ip函数,传入源主机的ip地址
			printf("\n");
			
			printf("Destination Ip address:");
			print_ip((u_char *)&(iph->ip_dst));	//调用print_ip函数,传入目的主机的ip地址
			printf("\n");

			//判断传输层协议
			printf("Transport layer protocal:");
			if(iph->ip_p==1)
			{
				printf("ICMP\n");
			}
			else if(iph->ip_p==2)
			{
				printf("IGMP\n");
			}		
			else if(iph->ip_p==6)	//为TCP协议
			{
				printf("TCP\n");
				tcph=(struct tcphdr*)(p+sizeof(struct ether_header)+sizeof(struct ip));	//获得tcp头部地址
				printf("destport :%d\n",ntohs(tcph->dest));	//打印目的端口号
				printf("sourport:%d\n",ntohs(tcph->source));	//打印源端口号
				printf("Payload");
				printf("(%d bytes): \n",h->len);	
				print_data(p,h->len);
			}
			else if(iph->ip_p==17)	//为UDP协议
			{
				printf("UDP\n");
				udph=(struct udphdr*)(p+sizeof(struct ether_header)+sizeof(struct ip));	//获得udp头部地址
				printf("dest port:%d\n",ntohs(udph->dest));	//打印目的端口号
				printf("source port:%d\n",ntohs(udph->source));	//打印源端口号
				printf("Payload");
				printf("(%d bytes): \n",h->len);	
				print_data(p,h->len);	
			}
			else 
			{
				printf("unknown protocol\n");
			}
		}
} 
int main(int argc,char** argv)
{
	char * dev;
	char *net_c; //字符串形式网络地址,用于打印输出
	char *mask_c; //字符串形式的网络掩码地址
	char errbuf[PCAP_ERRBUF_SIZE];
	struct in_addr addr;
	struct pcap_pkthdr header;//libpcap包头结构,包含捕获时间,捕获长度与数据包实际长度
	const u_char *packet;//捕获到的实际数据包内容
	pcap_t *handle;//libpcap设备描述符号

	struct bpf_program fp;//过滤器
	char filter_exp[] = "tcp port 80";//实际的过滤规则
	 	
	struct tm *now_tm;
	time_t now;
	
	bpf_u_int32 net = 0;
	bpf_u_int32 mask = 0;

	dev = NULL;
	memset(errbuf,0,PCAP_ERRBUF_SIZE);

	//pcap_lookupdev 返回设备名称 
	dev = pcap_lookupdev(errbuf);
	if (dev == NULL) {
		fprintf(stderr, "Couldn't find default device: %s\n", errbuf);
		return(2);
	}
	printf("Device: %s\n", dev);
	//lookupnet获得指定网络设备的网络号和掩码 
	if (pcap_lookupnet(dev, &net, &mask, errbuf) == -1) {
		fprintf(stderr, "Can't get netmask for device %s\n", dev);
		net_c = 0;
		mask_c = 0;
		return(2);
 	}
 	//转换网络地址 
	addr.s_addr = net;
	net_c = inet_ntoa(addr);
	printf("Net: %s\n", net_c);

	addr.s_addr = mask;
	mask_c = inet_ntoa(addr);
	printf("Mask: %s\n",mask_c);

	printf("==================================================\n");
	//pcap_open_live打开设备文件准备读取数据 
	handle = pcap_open_live(dev, BUFSIZ, 1, 1000, errbuf);
	if (handle == NULL) {
		fprintf(stderr, "Couldn't open device %s: %s\n", dev, errbuf);
		return(2);
	}
	//编译过滤规则 
	if (pcap_compile(handle, &fp, "ip", 1, mask) == -1) {
		fprintf(stderr, "Couldn't parse filter %s: %s\n", filter_exp, pcap_geterr(handle));
		return(2);
	}
	//设置过滤规则 
	if (pcap_setfilter(handle, &fp) == -1) {
	 	fprintf(stderr, "Couldn't install filter %s: %s\n", filter_exp, pcap_geterr(handle));
	 	return(2);
 	}
 	//捕获数据包
 	//pcap_loop循环抓取网络数据报文 采用回调函数实现 
	if(pcap_loop(handle,-1,callback,NULL)<0)
	{
		(void)fprintf(stderr,"pcap_loop:%s\n",pcap_geterr(handle));
		exit(0);
	}
	//关闭库 
	pcap_close(handle);
	return(0);
}
		

猜你喜欢

转载自blog.csdn.net/lr_viola/article/details/83689143