1. 程式人生 > >使用libpcap庫用c編寫網路嗅探器

使用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);
}