libpcap是一個網(wǎng)絡數(shù)據(jù)包捕獲函數(shù)庫,功能非常強大,Linux下著名的tcpdump就是以它為基礎的。今天我們利用它來完成一個我們自己的網(wǎng)絡嗅探器(sniffer)
首先先介紹一下本次實驗的環(huán)境:
Ubuntu 11.04,IP:192.168.1.1,廣播地址:192.168.1.255,子網(wǎng)掩碼:255.255.255.0
可以使用下面的命令設置:
sudo ifconfig eth0 192.168.1.1 broadcast 192.168.1.255 netmask 255.255.255.0
1.安裝
在//www.tcpdump.org/下載libpcap(tcpdump的源碼也可以從這個網(wǎng)站下載)
解壓
./configure
make
sudo make install
2.使用
安裝好libpcap后,我們要使用它啦,先寫一個簡單的程序,并介紹如何使用libpcap庫編譯它:
Makefile:
[plain] view plain copy
1. all: test.c
2. gcc -g -Wall -o test test.c -lpcap
3.
4. clean:
5. rm -rf *.o test
其后的程序的Makefile均類似,故不再重復
test1.c
[cpp] view plain copy
1. #include <pcap.h>
2. #include <stdio.h>
3.
4. int main()
5. {
6. char errBuf[PCAP_ERRBUF_SIZE], * device;
7.
8. device = pcap_lookupdev(errBuf);
9.
10. if(device)
11. {
12. printf("success: device: %s\n", device);
13. }
14. else
15. {
16. printf("error: %s\n", errBuf);
17. }
18.
19. return 0;
20. }
可以成功編譯,不過運行的時候卻提示找不到libpcap.so.1,因為libpcap.so.1默認安裝到了/usr/local/lib下,我們做一個符號鏈接到/usr/lib/下即可:
運行test的時候輸出"no suitable device found",原因是我們沒有以root權限運行,root權限運行后就正常了:
下面開始正式講解如何使用libpcap:
首先要使用libpcap,我們必須包含pcap.h頭文件,可以在/usr/local/include/pcap/pcap.h找到,其中包含了每個類型定義的詳細說明。
1.獲取網(wǎng)絡接口
首先我們需要獲取監(jiān)聽的網(wǎng)絡接口:
我們可以手動指定或讓libpcap自動選擇,先介紹如何讓libpcap自動選擇:
char * pcap_lookupdev(char * errbuf)
上面這個函數(shù)返回第一個合適的網(wǎng)絡接口的字符串指針,如果出錯,則errbuf存放出錯信息字符串,errbuf至少應該是PCAP_ERRBUF_SIZE個字節(jié)長度的。注意,很多l(xiāng)ibpcap函數(shù)都有這個參數(shù)。
pcap_lookupdev()一般可以在跨平臺的,且各個平臺上的網(wǎng)絡接口名稱都不相同的情況下使用。
如果我們手動指定要監(jiān)聽的網(wǎng)絡接口,則這一步跳過,我們在第二步中將要監(jiān)聽的網(wǎng)絡接口字符串硬編碼在pcap_open_live里。
2.釋放網(wǎng)絡接口
在操作為網(wǎng)絡接口后,我們應該要釋放它:
void pcap_close(pcap_t * p)
該函數(shù)用于關閉pcap_open_live()獲取的pcap_t的網(wǎng)絡接口對象并釋放相關資源。
3.打開網(wǎng)絡接口
獲取網(wǎng)絡接口后,我們需要打開它:
pcap_t * pcap_open_live(const char * device, int snaplen, int promisc, int to_ms, char * errbuf)
上面這個函數(shù)會返回指定接口的pcap_t類型指針,后面的所有操作都要使用這個指針。
第一個參數(shù)是第一步獲取的網(wǎng)絡接口字符串,可以直接使用硬編碼。
第二個參數(shù)是對于每個數(shù)據(jù)包,從開頭要抓多少個字節(jié),我們可以設置這個值來只抓每個數(shù)據(jù)包的頭部,而不關心具體的內(nèi)容。典型的以太網(wǎng)幀長度是1518字節(jié),但其他的某些協(xié)議的數(shù)據(jù)包會更長一點,但任何一個協(xié)議的一個數(shù)據(jù)包長度都必然小于65535個字節(jié)。
第三個參數(shù)指定是否打開混雜模式(Promiscuous Mode),0表示非混雜模式,任何其他值表示混合模式。如果要打開混雜模式,那么網(wǎng)卡必須也要打開混雜模式,可以使用如下的命令打開eth0混雜模式:
ifconfig eth0 promisc
第四個參數(shù)指定需要等待的毫秒數(shù),超過這個數(shù)值后,第3步獲取數(shù)據(jù)包的這幾個函數(shù)就會立即返回。0表示一直等待直到有數(shù)據(jù)包到來。
第五個參數(shù)是存放出錯信息的數(shù)組。
4.獲取數(shù)據(jù)包
打開網(wǎng)絡接口后就已經(jīng)開始監(jiān)聽了,那如何知道收到了數(shù)據(jù)包呢?有下面3種方法:
a)
u_char * pcap_next(pcap_t * p, struct pcap_pkthdr * h)
如果返回值為NULL,表示沒有抓到包
第一個參數(shù)是第2步返回的pcap_t類型的指針
第二個參數(shù)是保存收到的第一個數(shù)據(jù)包的pcap_pkthdr類型的指針
pcap_pkthdr類型的定義如下:
[cpp] view plain copy
1. struct pcap_pkthdr
2. {
3. struct timeval ts; /* time stamp */
4. bpf_u_int32 caplen; /* length of portion present */
5. bpf_u_int32 len; /* length this packet (off wire) */
6. };
注意這個函數(shù)只要收到一個數(shù)據(jù)包后就會立即返回
b)
int pcap_loop(pcap_t * p, int cnt, pcap_handler callback, u_char * user)
第一個參數(shù)是第2步返回的pcap_t類型的指針
第二個參數(shù)是需要抓的數(shù)據(jù)包的個數(shù),一旦抓到了cnt個數(shù)據(jù)包,pcap_loop立即返回。負數(shù)的cnt表示pcap_loop永遠循環(huán)抓包,直到出現(xiàn)錯誤。
第三個參數(shù)是一個回調(diào)函數(shù)指針,它必須是如下的形式:
void callback(u_char * userarg, const struct pcap_pkthdr * pkthdr, const u_char * packet)
第一個參數(shù)是pcap_loop的后一個參數(shù),當收到足夠數(shù)量的包后pcap_loop會調(diào)用callback回調(diào)函數(shù),同時將pcap_loop()的user參數(shù)傳遞給它
第二個參數(shù)是收到的數(shù)據(jù)包的pcap_pkthdr類型的指針
第三個參數(shù)是收到的數(shù)據(jù)包數(shù)據(jù)
c)
int pcap_dispatch(pcap_t * p, int cnt, pcap_handler callback, u_char * user)
這個函數(shù)和pcap_loop()非常類似,只是在超過to_ms毫秒后就會返回(to_ms是pcap_open_live()的第4個參數(shù))
例子:
test2:
[cpp] view plain copy
1. #include <pcap.h>
2. #include <time.h>
3. #include <stdlib.h>
4. #include <stdio.h>
5.
6. int main()
7. {
8. char errBuf[PCAP_ERRBUF_SIZE], * devStr;
9.
10. /* get a device */
11. devStr = pcap_lookupdev(errBuf);
12.
13. if(devStr)
14. {
15. printf("success: device: %s\n", devStr);
16. }
17. else
18. {
19. printf("error: %s\n", errBuf);
20. exit(1);
21. }
22.
23. /* open a device, wait until a packet arrives */
24. pcap_t * device = pcap_open_live(devStr, 65535, 1, 0, errBuf);
25.
26. if(!device)
27. {
28. printf("error: pcap_open_live(): %s\n", errBuf);
29. exit(1);
30. }
31.
32. /* wait a packet to arrive */
33. struct pcap_pkthdr packet;
34. const u_char * pktStr = pcap_next(device, &packet);
35.
36. if(!pktStr)
37. {
38. printf("did not capture a packet!\n");
39. exit(1);
40. }
41.
42. printf("Packet length: %d\n", packet.len);
43. printf("Number of bytes: %d\n", packet.caplen);
44. printf("Recieved time: %s\n", ctime((const time_t *)&packet.ts.tv_sec));
45.
46. pcap_close(device);
47.
48. return 0;
49. }
打開兩個終端,先ping 192.168.1.10,由于我們的ip是192.168.1.1,因此我們可以收到廣播的數(shù)據(jù)包,另一個終端運行test,就會抓到這個包。
下面的這個程序會把收到的數(shù)據(jù)包內(nèi)容全部打印出來,運行方式和上一個程序一樣:
test3:
[cpp] view plain copy
1. #include <pcap.h>
2. #include <time.h>
3. #include <stdlib.h>
4. #include <stdio.h>
5.
6. void getPacket(u_char * arg, const struct pcap_pkthdr * pkthdr, const u_char * packet)
7. {
8. int * id = (int *)arg;
9.
10. printf("id: %d\n", ++(*id));
11. printf("Packet length: %d\n", pkthdr->len);
12. printf("Number of bytes: %d\n", pkthdr->caplen);
13. printf("Recieved time: %s", ctime((const time_t *)&pkthdr->ts.tv_sec));
14.
15. int i;
16. for(i=0; i<pkthdr->len; ++i)
17. {
18. printf(" %02x", packet[i]);
19. if( (i + 1) % 16 == 0 )
20. {
21. printf("\n");
22. }
23. }
24.
25. printf("\n\n");
26. }
27.
28. int main()
29. {
30. char errBuf[PCAP_ERRBUF_SIZE], * devStr;
31.
32. /* get a device */
33. devStr = pcap_lookupdev(errBuf);
34.
35. if(devStr)
36. {
37. printf("success: device: %s\n", devStr);
38. }
39. else
40. {
41. printf("error: %s\n", errBuf);
42. exit(1);
43. }
44.
45. /* open a device, wait until a packet arrives */
46. pcap_t * device = pcap_open_live(devStr, 65535, 1, 0, errBuf);
47.
48. if(!device)
49. {
50. printf("error: pcap_open_live(): %s\n", errBuf);
51. exit(1);
52. }
53.
54. /* wait loop forever */
55. int id = 0;
56. pcap_loop(device, -1, getPacket, (u_char*)&id);
57.
58. pcap_close(device);
59.
60. return 0;
61. }
如果我們沒有按Ctrl+c,test會一直抓到包,因為我們將pcap_loop()設置為永遠循環(huán)
由于ping屬于icmp協(xié)議,并且發(fā)出icmp協(xié)議數(shù)據(jù)包之前必須先通過arp協(xié)議獲取目的主機的mac地址,因此我們抓到的包是arp協(xié)議的,而arp協(xié)議的數(shù)據(jù)包長度正好是42字節(jié)(14字節(jié)的以太網(wǎng)幀頭+28字節(jié)的arp數(shù)據(jù))。具體內(nèi)容請參考相關網(wǎng)絡協(xié)議說明。
5.分析數(shù)據(jù)包
我們既然已經(jīng)抓到數(shù)據(jù)包了,那么我們要開始分析了,這部分留給讀者自己完成,具體內(nèi)容可以參考相關的網(wǎng)絡協(xié)議說明。在本文的后,我會示范性的寫一個分析arp協(xié)議的sniffer,僅供參考。要特別注意一點,網(wǎng)絡上的數(shù)據(jù)是網(wǎng)絡字節(jié)順序的,因此分析前需要轉換為主機字節(jié)順序(ntohs()函數(shù))。
6.過濾數(shù)據(jù)包
我們抓到的數(shù)據(jù)包往往很多,如何過濾掉我們不感興趣的數(shù)據(jù)包呢?
幾乎所有的操作系統(tǒng)(BSD, AIX, Mac OS, Linux等)都會在內(nèi)核中提供過濾數(shù)據(jù)包的方法,主要都是基于BSD Packet Filter(BPF)結構的。libpcap利用BPF來過濾數(shù)據(jù)包。
過濾數(shù)據(jù)包需要完成3件事:
a) 構造一個過濾表達式
b) 編譯這個表達式
c) 應用這個過濾器
a)
BPF使用一種類似于匯編語言的語法書寫過濾表達式,不過libpcap和tcpdump都把它封裝成更高級且更容易的語法了,具體可以man tcpdump,以下是一些例子:
src host 192.168.1.177
只接收源ip地址是192.168.1.177的數(shù)據(jù)包
dst port 80
只接收tcp/udp的目的端口是80的數(shù)據(jù)包
not tcp
只接收不使用tcp協(xié)議的數(shù)據(jù)包
tcp[13] == 0x02 and (dst port 22 or dst port 23)
只接收SYN標志位置位且目標端口是22或23的數(shù)據(jù)包(tcp首部開始的第13個字節(jié))
icmp[icmptype] == icmp-echoreply or icmp[icmptype] == icmp-echo
只接收icmp的ping請求和ping響應的數(shù)據(jù)包
ehter dst 00:e0:09:c1:0e:82
只接收以太網(wǎng)mac地址是00:e0:09:c1:0e:82的數(shù)據(jù)包
ip[8] == 5
只接收ip的ttl=5的數(shù)據(jù)包(ip首部開始的第8個字節(jié))
b)
構造完過濾表達式后,我們需要編譯它,使用如下函數(shù):
int pcap_compile(pcap_t * p, struct bpf_program * fp, char * str, int optimize, bpf_u_int32 netmask)
fp:這是一個傳出參數(shù),存放編譯后的bpf
str:過濾表達式
optimize:是否需要優(yōu)化過濾表達式
metmask:簡單設置為0即可
c)
后我們需要應用這個過濾表達式:
int pcap_setfilter(pcap_t * p, struct bpf_program * fp)
第二個參數(shù)fp就是前一步pcap_compile()的第二個參數(shù)
應用完過濾表達式之后我們便可以使用pcap_loop()或pcap_next()等抓包函數(shù)來抓包了。
下面的程序演示了如何過濾數(shù)據(jù)包,我們只接收目的端口是80的數(shù)據(jù)包:
test4.c
[cpp] view plain copy
1. #include <pcap.h>
2. #include <time.h>
3. #include <stdlib.h>
4. #include <stdio.h>
5.
6. void getPacket(u_char * arg, const struct pcap_pkthdr * pkthdr, const u_char * packet)
7. {
8. int * id = (int *)arg;
9.
10. printf("id: %d\n", ++(*id));
11. printf("Packet length: %d\n", pkthdr->len);
12. printf("Number of bytes: %d\n", pkthdr->caplen);
13. printf("Recieved time: %s", ctime((const time_t *)&pkthdr->ts.tv_sec));
14.
15. int i;
16. for(i=0; i<pkthdr->len; ++i)
17. {
18. printf(" %02x", packet[i]);
19. if( (i + 1) % 16 == 0 )
20. {
21. printf("\n");
22. }
23. }
24.
25. printf("\n\n");
26. }
27.
28. int main()
29. {
30. char errBuf[PCAP_ERRBUF_SIZE], * devStr;
31.
32. /* get a device */
33. devStr = pcap_lookupdev(errBuf);
34.
35. if(devStr)
36. {
37. printf("success: device: %s\n", devStr);
38. }
39. else
40. {
41. printf("error: %s\n", errBuf);
42. exit(1);
43. }
44.
45. /* open a device, wait until a packet arrives */
46. pcap_t * device = pcap_open_live(devStr, 65535, 1, 0, errBuf);
47.
48. if(!device)
49. {
50. printf("error: pcap_open_live(): %s\n", errBuf);
51. exit(1);
52. }
53.
54. /* construct a filter */
55. struct bpf_program filter;
56. pcap_compile(device, &filter, "dst port 80", 1, 0);
57. pcap_setfilter(device, &filter);
58.
59. /* wait loop forever */
60. int id = 0;
61. pcap_loop(device, -1, getPacket, (u_char*)&id);
62.
63. pcap_close(device);
64.
65. return 0;
66. }
在下面的這一個例子中,客戶機通過tcp的9732端口連接服務器,發(fā)送字符'A',之后服務器將'A'+1即'B'返回給客戶機,具體實現(xiàn)可以參考://blog.csdn.net/htttw/article/details/7519964
服務器的ip是192.168.56.101,客戶機的ip是192.168.56.1
服務器:
Makefile:
[plain] view plain copy
1. all: tcp_client.c tcp_server.c
2. gcc -g -Wall -o tcp_client tcp_client.c
3. gcc -g -Wall -o tcp_server tcp_server.c
4.
5. clean:
6. rm -rf *.o tcp_client tcp_server
tcp_server:
[cpp] view plain copy
1. #include <sys/types.h>
2. #include <sys/socket.h>
3. #include <netinet/in.h>
4. #include <arpa/inet.h>
5. #include <unistd.h>
6. #include <stdlib.h>
7. #include <stdio.h>
8.
9. #define PORT 9832
10. #define SERVER_IP "192.168.56.101"
11.
12. int main()
13. {
14. /* create a socket */
15. int server_sockfd = socket(AF_INET, SOCK_STREAM, 0);
16.
17. struct sockaddr_in server_addr;
18. server_addr.sin_family = AF_INET;
19. server_addr.sin_addr.s_addr = inet_addr(SERVER_IP);
20. server_addr.sin_port = htons(PORT);
21.
22. /* bind with the local file */
23. bind(server_sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr));
24.
25. /* listen */
26. listen(server_sockfd, 5);
27.
28. char ch;
29. int client_sockfd;
30. struct sockaddr_in client_addr;
31. socklen_t len = sizeof(client_addr);
32. while(1)
33. {
34. printf("server waiting:\n");
35.
36. /* accept a connection */
37. client_sockfd = accept(server_sockfd, (struct sockaddr *)&client_addr, &len);
38.
39. /* exchange data */
40. read(client_sockfd, &ch, 1);
41. printf("get char from client: %c\n", ch);
42. ++ch;
43. write(client_sockfd, &ch, 1);
44.
45. /* close the socket */
46. close(client_sockfd);
47. }
48.
49. return 0;
50. }
tcp_client:
[cpp] view plain copy
1. #include <sys/types.h>
2. #include <sys/socket.h>
3. #include <netinet/in.h>
4. #include <arpa/inet.h>
5. #include <unistd.h>
6. #include <stdlib.h>
7. #include <stdio.h>
8.
9. #define PORT 9832
10. #define SERVER_IP "192.168.56.101"
11.
12. int main()
13. {
14. /* create a socket */
15. int sockfd = socket(AF_INET, SOCK_STREAM, 0);
16.
17. struct sockaddr_in address;
18. address.sin_family = AF_INET;
19. address.sin_addr.s_addr = inet_addr(SERVER_IP);
20. address.sin_port = htons(PORT);
21.
22. /* connect to the server */
23. int result = connect(sockfd, (struct sockaddr *)&address, sizeof(address));
24. if(result == -1)
25. {
26. perror("connect failed: ");
27. exit(1);
28. }
29.
30. /* exchange data */
31. char ch = 'A';
32. write(sockfd, &ch, 1);
33. read(sockfd, &ch, 1);
34. printf("get char from server: %c\n", ch);
35.
36. /* close the socket */
37. close(sockfd);
38.
39. return 0;
40. }
運行方法如下,首先在服務器上運行tcp_server,然后運行我們的監(jiān)聽器,然后在客戶機上運行tcp_client,注意,我們可以先清空arp緩存,這樣就可以看到整個通信過程(包括一開始的arp廣播)
在客戶機上運行下列命令來清空記錄服務器的arp緩存:
sudo arp -d 192.168.56.101
arp -a后發(fā)現(xiàn)已經(jīng)刪除了記錄服務器的arp緩存
抓包的結果如下所示,由于包太多了,無法全部截圖,因此我把所有內(nèi)容保存在下面的文本中了:
全部的包如下:
[plain] view plain copy
1. hutao@hutao-VirtualBox:~/test3$ sudo ./test
2. success: device: eth0
3. id: 1
4. Packet length: 60
5. Number of bytes: 60
6. Recieved time: Sat Apr 28 19:57:50 2012
7. ff ff ff ff ff ff 0a 00 27 00 00 00 08 06 00 01
8. 08 00 06 04 00 01 0a 00 27 00 00 00 c0 a8 38 01
9. 00 00 00 00 00 00 c0 a8 38 65 00 00 00 00 00 00
10. 00 00 00 00 00 00 00 00 00 00 00 00
11.
12. id: 2
13. Packet length: 42
14. Number of bytes: 42
15. Recieved time: Sat Apr 28 19:57:50 2012
16. 0a 00 27 00 00 00 08 00 27 9c ff b1 08 06 00 01
17. 08 00 06 04 00 02 08 00 27 9c ff b1 c0 a8 38 65
18. 0a 00 27 00 00 00 c0 a8 38 01
19.
20. id: 3
21. Packet length: 74
22. Number of bytes: 74
23. Recieved time: Sat Apr 28 19:57:50 2012
24. 08 00 27 9c ff b1 0a 00 27 00 00 00 08 00 45 00
25. 00 3c d4 af 40 00 40 06 74 55 c0 a8 38 01 c0 a8
26. 38 65 8e 20 26 68 79 e1 63 8c 00 00 00 00 a0 02
27. 39 08 d4 13 00 00 02 04 05 b4 04 02 08 0a 00 14
28. b7 23 00 00 00 00 01 03 03 06
29.
30. id: 4
31. Packet length: 74
32. Number of bytes: 74
33. Recieved time: Sat Apr 28 19:57:50 2012
34. 0a 00 27 00 00 00 08 00 27 9c ff b1 08 00 45 00
35. 00 3c 00 00 40 00 40 06 49 05 c0 a8 38 65 c0 a8
36. 38 01 26 68 8e 20 b6 c4 e6 e5 79 e1 63 8d a0 12
37. 38 90 f1 e5 00 00 02 04 05 b4 04 02 08 0a 00 57
38. a1 2c 00 14 b7 23 01 03 03 05
39.
40. id: 5
41. Packet length: 66
42. Number of bytes: 66
43. Recieved time: Sat Apr 28 19:57:50 2012
44. 08 00 27 9c ff b1 0a 00 27 00 00 00 08 00 45 00
45. 00 34 d4 b0 40 00 40 06 74 5c c0 a8 38 01 c0 a8
46. 38 65 8e 20 26 68 79 e1 63 8d b6 c4 e6 e6 80 10
47. 00 e5 fb c1 00 00 01 01 08 0a 00 14 b7 24 00 57
48. a1 2c
49.
50. id: 6
51. Packet length: 67
52. Number of bytes: 67
53. Recieved time: Sat Apr 28 19:57:50 2012
54. 08 00 27 9c ff b1 0a 00 27 00 00 00 08 00 45 00
55. 00 35 d4 b1 40 00 40 06 74 5a c0 a8 38 01 c0 a8
56. 38 65 8e 20 26 68 79 e1 63 8d b6 c4 e6 e6 80 18
57. 00 e5 ba b7 00 00 01 01 08 0a 00 14 b7 25 00 57
58. a1 2c 41
59.
60. id: 7
61. Packet length: 66
62. Number of bytes: 66
63. Recieved time: Sat Apr 28 19:57:50 2012
64. 0a 00 27 00 00 00 08 00 27 9c ff b1 08 00 45 00
65. 00 34 47 cb 40 00 40 06 01 42 c0 a8 38 65 c0 a8
66. 38 01 26 68 8e 20 b6 c4 e6 e6 79 e1 63 8e 80 10
67. 01 c5 f1 dd 00 00 01 01 08 0a 00 57 a1 2e 00 14
68. b7 25
69.
70. id: 8
71. Packet length: 67
72. Number of bytes: 67
73. Recieved time: Sat Apr 28 19:57:50 2012
74. 0a 00 27 00 00 00 08 00 27 9c ff b1 08 00 45 00
75. 00 35 47 cc 40 00 40 06 01 40 c0 a8 38 65 c0 a8
76. 38 01 26 68 8e 20 b6 c4 e6 e6 79 e1 63 8e 80 18
77. 01 c5 f1 de 00 00 01 01 08 0a 00 57 a1 2e 00 14
78. b7 25 42
79.
80. id: 9
81. Packet length: 66
82. Number of bytes: 66
83. Recieved time: Sat Apr 28 19:57:50 2012
84. 0a 00 27 00 00 00 08 00 27 9c ff b1 08 00 45 00
85. 00 34 47 cd 40 00 40 06 01 40 c0 a8 38 65 c0 a8
86.