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