因爲工做上的須要,最近簡單學習了抓包函數庫libpcap,順便記下筆記,方便之後查看linux
1、libpcap簡介
libpcap(Packet Capture Library),即數據包捕獲函數庫,是Unix/Linux平臺下的網絡數據包捕獲函數庫。它是一個獨立於系統的用戶層包捕獲的API接口,爲底層網絡監測提供了一個可移植的框架.
Libpcap能夠在絕大多數類unix平臺下工做,libpcap庫安裝也很簡單,Libpcap 軟件包可從 http://www.tcpdump.org/ 下載,而後依此執行下列三條命令便可安裝
./configure
make
make install
2、pcap基本工做流程
(1)肯定將要嗅探的接口,在linux下是相似eth0的東西。在BSD下是相似xll的東西。能夠在一個字符串中聲明設備,也可讓pcap提供備選接口(咱們想要嗅探的接口)的名字。
(2)初始化pcap,此時才真正告訴pcap咱們要嗅探的具體接口,只要咱們願意,咱們能夠嗅探多個接口。可是如何區分多個接口呢,使用文件句柄。就像讀寫文件時使用文件句柄同樣。咱們必須給嗅探任務命名,以致於區分不一樣的嗅探任務。
(3)指定過濾規則,當咱們只想嗅探特殊的流量時(例如,僅僅嗅探TCP/IP包、僅僅嗅探通過端口80的包,等等)咱們必須設定一個規則集,「編譯」並應用它。這是一個三相的而且緊密聯繫的過程,規則集存儲與字符串中,在「編譯」以後會轉換成pcap能夠讀取的格式。「編譯過程」其實是調用自定義的函數完成的,不涉及外部的函數。而後咱們能夠告訴pcap在咱們想要過濾的任何任務上實施。
(4)抓包,最後,告訴pcap進入主要的執行循環中,在此階段,在接收到任何咱們想要的包以前pcap將一直循環等待。在每次抓取到一個新的數據包時,它將調用另外一個自定義的函數,咱們能夠在這個函數中肆意妄爲,例如,解析數據包並顯示數據內容、保存到文件或者什麼都不作等等。
當嗅探完美任務完成時,記得關掉任務。正則表達式
下面是pcap工做流程圖(摘自官網)express
下面咱們看一下具體的步驟實施:
(1)肯定咱們將要嗅探的接口
這一步操做咱們能夠手動指定接口或者調用pcap庫提供的接口來查找網絡設備
手動指定:網絡
1 #include <stdio.h> 2 #include <string.h> 3 #include <stdlib.h> 4 5 int main(int argc, char **argv) 6 { 7 char *dev = argv[1]; 8 9 printf("Device: %s\n", dev); 10 11 return 0; 12 }
使用pcap API查找網絡設備session
pcap_lookupdev()函數用於查找網絡設備app
1 #include <stdio.h> 2 #include <string.h> 3 #include <stdlib.h> 4 #include <pcap.h> 5 6 int main(int argc, char *argv[]) 7 { 8 char *dev, errbuf[PCAP_ERRBUF_SIZE]; 9 10 dev = pcap_lookupdev(errbuf); 11 if (dev == NULL) { 12 fprintf(stderr, "Couldn't find default device: %s\n", errbuf); 13 return(2); 14 } 15 16 printf("Device: %s\n", dev); 17 return(0); 18 }
編譯時須要鏈接pcap庫 -lpcap
(2)打開嗅探設備框架
pcap_open_live()函數用於打開網絡設備,而且返回用於捕獲網絡數據包的數據包捕獲描述字。對於此網絡設備的操做都要基於此網絡設備描述字。tcp
函數原型以下:函數
1 pcap_t *pcap_open_live(char *device, int snaplen, int promisc, int to_ms, char *errbuf); 2 3 /*函數說明:該函數用於打開一個嗅探設備 4 參數:device 須要打開的設備 5 snaplen int型,表示pcap能夠捕獲的最大字節數(最大爲65535) 6 promisc 是否開啓混雜模式(1打開,0關閉),設置開啓混雜模式,須要對應的網卡也開啓混雜模式 7 to_ms 是讀取時間溢出,單位爲毫秒(ms), 0表示沒有時間溢出 8 errbuf 保存錯誤的返回值 9 */
下面是具體實現:oop
1 #include <stdio.h> 2 #include <string.h> 3 #include <stdlib.h> 4 #include <pcap.h> 5 6 int main(int argc, char *argv[]) 7 { 8 char *dev, errbuf[PCAP_ERRBUF_SIZE]; 9 pcap_t *handle; 10 11 dev = pcap_lookupdev(errbuf); 12 if (dev == NULL) { 13 fprintf(stderr, "Couldn't find default device: %s\n", errbuf); 14 return(2); 15 } 16 printf("Device: %s\n", dev); 17 18 handle = pcap_open_live(dev, BUFSIZ, 1, 1000, errbuf); 19 if (handle == NULL) { 20 fprintf(stderr, "Couldn't open device %s: %s\n", dev, errbuf); 21 return(2); 22 } 23 printf("Open Device success!\n"); 24 25 return(0); 26 }
(3)過濾指定流量
不少時候,我只須要咱們指定的流量,好比咱們須要劫持http請求(80端口),劫持DNS服務(53端口),所以,咱們大多數時候都不會盲目的抓取所有的報文。
相關過濾函數pcap_compile()and pcap_setfilter(),當咱們調用pcap_open_live()後,咱們會獲得一個創建的嗅探會話,此時咱們就能夠開始過濾咱們想要流量了;
過濾器表達式是基於正則表達式來編寫的,官網tcpdump有詳細的規則說明,在使用過濾器以前咱們必須」編譯「.
函數原型以下:
1 int pcap_compile(pcap_t *p, struct bpf_program *fp, char *str, int optimize, bpf_u_int32 netmask) 2 /*函數說明:將str參數指定的字符串編譯到過濾程序中。 3 參數: p是嗅探器回話句柄 4 fp是一個bpf_program結構的指針,在pcap_compile()函數中被賦值。o 5 ptimize參數控制結果代碼的優化。 6 netmask參數指定本地網絡的網絡掩碼。 7 */
編譯完過濾表達式後,咱們就能夠應用它了,下面是int pcap_setfilter(),具體用法看man手冊:
1 int pcap_setfilter(pcap_t *p, struct bpf_program *fp) 2 //第一個參數嗅探器回話句柄,第二參數是存儲過濾器編譯版本的結構體指針(跟pcap_compile 一個參數同樣)
下面是簡單實例:
1 #include <stdio.h> 2 #include <string.h> 3 #include <stdlib.h> 4 #include <pcap.h> 5 6 int main(int argc, char *argv[]) 7 { 8 char *dev, errbuf[PCAP_ERRBUF_SIZE]; 9 struct bpf_program fp; /* The compiled filter expression */ 10 char filter_exp[] = "port 53"; /* The filter expression (filter 53 port)*/ 11 pcap_t *handle; 12 bpf_u_int32 mask; /* The netmask of our sniffing device */ 13 bpf_u_int32 net; /* The IP of our sniffing device */ 14 15 dev = pcap_lookupdev(errbuf); 16 if (dev == NULL) { 17 fprintf(stderr, "Couldn't find default device: %s\n", errbuf); 18 return(2); 19 } 20 printf("Device: %s\n", dev); 21 22 /*get network mask*/ 23 if (pcap_lookupnet(dev, &net, &mask, errbuf) == -1) { 24 fprintf(stderr, "Can't get netmask for device %s\n", dev); 25 net = 0; 26 mask = 0; 27 } 28 /*Open the session in promiscuous mode*/ 29 handle = pcap_open_live(dev, BUFSIZ, 1, 1000, errbuf); 30 if (handle == NULL) { 31 fprintf(stderr, "Couldn't open device %s: %s\n", dev, errbuf); 32 return(2); 33 } 34 /* Compile and apply the filter */ 35 if (pcap_compile(handle, &fp, filter_exp, 0, net) == -1) { 36 fprintf(stderr, "Couldn't parse filter %s: %s\n", filter_exp, pcap_geterr(handle)); 37 return(2); 38 } 39 if (pcap_setfilter(handle, &fp) == -1) { 40 fprintf(stderr, "Couldn't install filter %s: %s\n", filter_exp, pcap_geterr(handle)); 41 return(2); 42 } 43 44 return(0); 45 }
以上代碼中的pcap_lookupnet()函數得到指定網絡設備的網絡號和掩碼。函數原型以下:
1 int pcap_lookupnet(const char *device, bpf_u_int32 *netp, 2 bpf_u_int32 *maskp, char *errbuf); 3 /* 參數:device 指定的嗅探設備名稱 4 netp 指定設備的網絡號 5 maskp 掩碼 6 errbuf 保存錯誤信息 7 */
下面是具體實例:
1 #include <stdio.h> 2 #include <string.h> 3 #include <netinet/in.h> 4 #include <arpa/inet.h> 5 #include <pcap.h> 6 7 #define DEVICE "enp0s3" 8 9 int main() 10 { 11 char errBuf[PCAP_ERRBUF_SIZE]; 12 struct pcap_pkthdr packet; 13 pcap_t *dev; 14 bpf_u_int32 netp, maskp; 15 char *net, *mask; 16 struct in_addr addr; 17 int ret; 18 19 if(pcap_lookupnet(DEVICE, &netp, &maskp, errBuf)) { 20 printf("get net failure\n"); 21 return -1; 22 } 23 addr.s_addr = netp; 24 net = inet_ntoa(addr); 25 printf("network: %s\n", net); 26 27 addr.s_addr = maskp; 28 mask = inet_ntoa(addr); 29 printf("mask: %s\n", mask); 30 31 return 0; 32 } 33 //運行結果 34 [root@localhost pacp_1st]# ./pacp 35 network: 192.168.16.0 36 mask: 255.255.255.0
(4)進行抓包處理
經過以上內容,咱們已經知道了如何指定獲取以及初始化一個嗅探器設備,如何編譯及使用過濾器;下面咱們就開始進行抓包,抓包程序有抓一次包(pcap_next())和循環一直抓包幾個函數;
下面咱們咱們先用pcap_next()進行一次抓包
函數原型:
1 const u_char *pcap_next(pcap_t *p, struct pcap_pkthdr *h); 2 3 /*參數:p是嗅探器會話句柄 4 h是一個指向存儲數據包概略信息結構體的指針 5 */ 6 struct pcap_pkthdr { 7 struct timeval ts; /* time stamp */ 8 bpf_u_int32 caplen; /* length of portion present */ 9 bpf_u_int32 len; /* length this packet (off wire) */ 10 }; 11 //ts——時間戳 12 //caplen——真正實際捕獲的包的長度 13 //len——這個包的長度 14 15 由於在某些狀況下你不能保證捕獲的包是完整的,例如一個包長1480,可是你捕獲到1000的時候, 16 可能由於某些緣由就停止捕獲了,因此caplen是記錄實際捕獲的包長,也就是1000,而len就是1480。
下面是使用pcap_next()抓包程序
#include <stdio.h> #include <string.h> #include <stdlib.h> #include <time.h> #include <pcap.h> int main(int argc, char *argv[]) { pcap_t *handle; /* Session handle */ char *dev; /* The device to sniff on */ char errbuf[PCAP_ERRBUF_SIZE]; /* Error string */ struct bpf_program fp; /* The compiled filter */ char filter_exp[] = "port 53"; /* The filter expression */ bpf_u_int32 mask; /* Our netmask */ bpf_u_int32 net; /* Our IP */ struct pcap_pkthdr header; /* The header that pcap gives us */ const u_char *packet; /* The actual packet */ /* Define the device */ dev = pcap_lookupdev(errbuf); if (dev == NULL) { fprintf(stderr, "Couldn't find default device: %s\n", errbuf); return(2); } /* Find the properties for the device */ if (pcap_lookupnet(dev, &net, &mask, errbuf) == -1) { fprintf(stderr, "Couldn't get netmask for device %s: %s\n", dev, errbuf); net = 0; mask = 0; } /* Open the session in promiscuous mode */ handle = pcap_open_live(dev, BUFSIZ, 1, 100, errbuf); if (handle == NULL) { fprintf(stderr, "Couldn't open device %s: %s\n", dev, errbuf); return(2); } /* Compile and apply the filter */ if (pcap_compile(handle, &fp, filter_exp, 0, net) == -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); } /* Grab a packet */ packet = pcap_next(handle, &header); /* Print header info */ printf("Packet length: %d\n", header.len); printf("Number of bytes: %ud\n", header.caplen); printf("Recieved time: %s\n", ctime((const time_t *)&header.ts.tv_sec)); /* And close the session */ pcap_close(handle); return(0); } //運行結果 [root@localhost pacp_5th]# ./pacp Packet length: 32603 Number of bytes: 3372236960d Recieved time: Sat Aug 16 07:45:20 4461732
上面的代碼在promisc模式下嗅探全部由pcap_lookupdev()返回的設備。它發現第一個通過端口53(DNS)的數據包並打印包的相關信息。
在大多數狀況下咱們不多的嗅探器使用pcap_next(),更多的是使用pcap_loop()或者pcap_dispatch()(pcap_dispatch()內部調用pcap_next())
pcap_loop()及pcap_dispatch()的具體使用在下篇博客中介紹
參考:http://www.tcpdump.org/ libpcap 官方有不少關於libpcap的說明文檔,講的很是詳細;