使用 libpcap 在路由上捕捉 http 請求網址 自制 SWF

最近研究 kernel netfilter BPF 又想起來以前用 tcpdump 抓包的快樂時光。

就想着做一個,抓包軟件名稱想好了,就是大名鼎鼎的,哥WF 現在有正名了叫《數據安全網關》,所以取名爲 SWF 即,簡易版哥WF。

首先是基礎知識,這些很有用,其實使用原始的 BPF 也能很方便的抓取數據包,但是語法怪怪的,使用 libpcap 能簡化開發。

OSI參考模型
7層模型
應用層
表示層
會話層
傳輸層
網絡層
數據鏈路層
物理層

5層模型
應用層 --》 對應7層前3層 ->> HTTP FTP
傳輸層 ->> TCP
網絡層 ->> IP ICMP IGMP RIP
數據鏈路層 ->> ARP RARP IEEE802.3 PPP CSMA/CD
物理層 ->> FE 自協商 Manchester MLT-3 4A PAM5

使用 wireshark 抓包一個普通的 http 包

1,配置過波規則 tcp.dstport == 80

2,使用 curl http://www.baidu.com 發出請求

1 Frame 33: 141 bytes on wire (1128 bits), 141 bytes captured (1128 bits) on interface 0 -------------> 以太網 鏈路層 總長度
2 Ethernet II, Src: Vmware_b0:f1:9c (00:0c:29:b0:f1:9c), Dst: Vmware_fe:94:1d (00:50:56:fe:94:1d) ------> 發送接收 MAC 和 幀類型 長度 14
3 Internet Protocol Version 4, Src: 192.168.80.129, Dst: 35.232.111.17 ------> IP 長度 20
4 Transmission Control Protocol, Src Port: 39266, Dst Port: 80, Seq: 1, Ack: 1, Len: 87 ------> TCP
5 Hypertext Transfer Protocol ------> HTTP 真實數據

IP 中保存 發送長度 減去 TCP 頭大小後,即真實數據

但 TCP 頭部大小會變化,例:握手連接時

 1 Frame 7: 74 bytes on wire (592 bits), 74 bytes captured (592 bits) on interface 0
 2 Ethernet II, Src: Vmware_b0:f1:9c (00:0c:29:b0:f1:9c), Dst: Vmware_fe:94:1d (00:50:56:fe:94:1d)
 3 Internet Protocol Version 4, Src: 192.168.80.129, Dst: 182.61.200.7
 4 Transmission Control Protocol, Src Port: 45486, Dst Port: 80, Seq: 0, Len: 0
 5 Source Port: 45486
 6 Destination Port: 80
 7 [Stream index: 0]
 8 [TCP Segment Len: 0]
 9 Sequence number: 0 (relative sequence number)
10 [Next sequence number: 0 (relative sequence number)]
11 Acknowledgment number: 0
12 1010 .... = Header Length: 40 bytes (10) --------------> 40Byte
13 Flags: 0x002 (SYN)
14 Window size value: 64240
15 [Calculated window size: 64240]
16 Checksum: 0xadd3 [unverified]
17 [Checksum Status: Unverified]
18 Urgent pointer: 0
19 Options: (20 bytes), Maximum segment size, SACK permitted, Timestamps, No-Operation (NOP), Window scale
20 [Timestamps]
21 
22 Frame 9: 54 bytes on wire (432 bits), 54 bytes captured (432 bits) on interface 0
23 Ethernet II, Src: Vmware_b0:f1:9c (00:0c:29:b0:f1:9c), Dst: Vmware_fe:94:1d (00:50:56:fe:94:1d)
24 Internet Protocol Version 4, Src: 192.168.80.129, Dst: 182.61.200.7
25 Transmission Control Protocol, Src Port: 45486, Dst Port: 80, Seq: 1, Ack: 1, Len: 0
26 Source Port: 45486
27 Destination Port: 80
28 [Stream index: 0]
29 [TCP Segment Len: 0]
30 Sequence number: 1 (relative sequence number)
31 [Next sequence number: 1 (relative sequence number)]
32 Acknowledgment number: 1 (relative ack number)
33 0101 .... = Header Length: 20 bytes (5) ------------> 20Byte
34 Flags: 0x010 (ACK)
35 Window size value: 64240
36 [Calculated window size: 64240]
37 [Window size scaling factor: -2 (no window scaling used)]
38 Checksum: 0x1526 [unverified]
39 [Checksum Status: Unverified]
40 Urgent pointer: 0
41 [SEQ/ACK analysis]
42 [Timestamps]

先是 以太網幀,然後是 ip 幀,然後是 tcp 頭,然後是數據。

下面是實現代碼:

  1 /**
  2  * soft: smple WF
  3  * author: nejidev
  4  * date: 2021-12-11 21:19
  5  */
  6 #define _GNU_SOURCE         /* See feature_test_macros(7) */
  7 #include <stdio.h>
  8 #include <string.h>
  9 #include <time.h>
 10 
 11 #include <arpa/inet.h>
 12 #include <net/ethernet.h>
 13 #include <linux/ip.h>
 14 #include <linux/tcp.h>
 15 
 16 #include <sys/socket.h>
 17 #include <netinet/in.h>
 18 #include <arpa/inet.h>
 19 
 20 #include <pcap/pcap.h>
 21 
 22 //ubuntu 18.0.4 sudo apt-get install libpcap-dev
 23 //gcc pcap_test.c -lpcap -o pcap_test
 24 //sudo ./pcap_test
 25 
 26 #define HTTP_HOST_FILE "http_host.txt"
 27 
 28 #define LOG_D(fmt, ...) printf("[D: fun:%s line:%d] " fmt "\n", __func__, __LINE__, ##__VA_ARGS__);
 29 #define LOG_I(fmt, ...) printf("[I: fun:%s line:%d] " fmt "\n", __func__, __LINE__, ##__VA_ARGS__);
 30 
 31 static void write_date(FILE *file)
 32 {
 33     time_t now_time;
 34     struct tm *tm;
 35     char now_date[32] = {0};
 36 
 37     time (&now_time);
 38     tm = gmtime(&now_time);
 39 
 40     snprintf(now_date, sizeof(now_date), "\n%d-%d-%d %d:%d:%d ",
 41         1900 + tm->tm_year,
 42         1 + tm->tm_mon,
 43         tm->tm_mday,
 44         8 + tm->tm_hour,
 45         tm->tm_min,
 46         tm->tm_sec
 47     );
 48     fwrite(now_date, 1, strlen(now_date), file);
 49 }
 50 
 51 void capture_one_test()
 52 {
 53     char error_buf[PCAP_ERRBUF_SIZE] = {0};
 54     int  ret           = 0;
 55     char *net_dev      = NULL;
 56     FILE *file         = NULL;
 57     unsigned char *mac = NULL;
 58     //保存接收到的數據包
 59     const unsigned char *packet_content    = NULL; 
 60     struct pcap_pkthdr protocol_header     = {0};
 61     // 分析以太網中的 源mac、目的mac  
 62     struct ether_header *ethernet_protocol = NULL;
 63 
 64     net_dev = pcap_lookupdev(error_buf);
 65 
 66     LOG_D("default net_dev:%s", net_dev);
 67 
 68     /*
 69     device:網絡接口的名字,爲第一步獲取的網絡接口字符串(pcap_lookupdev() 的返回值 ),也可人爲指定,如“eth0”。
 70     snaplen:捕獲數據包的長度,長度不能大於 65535 個字節。
 71     promise:“1” 代表混雜模式,其它非混雜模式。什麼爲混雜模式,請看《原始套接字編程》。
 72     to_ms:指定需要等待的毫秒數,超過這個數值後,獲取數據包的函數就會立即返回(這個函數不會阻塞,後面的抓包函數纔會阻塞)。
 73            0 表示一直等待直到有數據包到來。
 74     ebuf:存儲錯誤信息。
 75     */
 76     pcap_t *pcap_handle = pcap_open_live(net_dev, 65535, 1, 0, error_buf);
 77 
 78     if (! pcap_handle)
 79     {
 80         LOG_I("pcap_open_live failed");
 81         return ;
 82     }
 83 
 84     LOG_D("wait capture");
 85 
 86     packet_content = pcap_next(pcap_handle, &protocol_header);
 87 
 88     //數據包的實際長度
 89     LOG_D("protocol_header.len:%d", protocol_header.len);
 90 
 91     //以太網幀頭部
 92     ethernet_protocol = (struct ether_header *)packet_content;
 93 
 94     //獲取源mac
 95     mac = (unsigned char *)ethernet_protocol->ether_shost;  
 96     LOG_D("Mac Source Address is %02x:%02x:%02x:%02x:%02x:%02x",
 97         *(mac+0),*(mac+1),*(mac+2),*(mac+3),*(mac+4),*(mac+5));
 98 
 99     //獲取目的mac
100     mac = (unsigned char *)ethernet_protocol->ether_dhost;  
101     LOG_D("Mac Destination Address is %02x:%02x:%02x:%02x:%02x:%02x",
102         *(mac+0),*(mac+1),*(mac+2),*(mac+3),*(mac+4),*(mac+5));
103 
104     //save to file
105     file = fopen("test_once.pcap", "w");
106     fwrite(packet_content, 1, protocol_header.len, file);
107     fclose(file);
108 
109     pcap_close(pcap_handle);
110 }
111 
112 void ethernet_protocol_callback(unsigned char *argument, const struct pcap_pkthdr *packet_heaher,
113                                 const unsigned char *packet_content) 
114 {
115     int offset = 0;
116     unsigned char *mac = NULL;
117     char *host         = NULL;
118     char *host_end     = NULL;
119     int   host_len     = 0;
120     FILE *cap_file     = NULL;
121     //以太網幀頭部 分析以太網中的 源mac、目的mac  
122     struct ether_header *ethernet_protocol = NULL;
123     //以太網類型
124     unsigned short ethernet_type           = 0;
125     //IP
126     struct iphdr *ip_header                = NULL;
127     //socket ip
128     struct in_addr addr                    = {0};
129     //TCP
130     struct tcphdr *tcp_header              = NULL;
131 
132     LOG_I("new package len:%d", packet_heaher->len);
133 
134     LOG_D("pkthdr len:%ld", sizeof(struct pcap_pkthdr)); //24
135     LOG_D("ip len:%ld",     sizeof(struct iphdr)); //20
136     LOG_D("tcp len:%ld",    sizeof(struct tcphdr)); //20 ?
137 
138     ethernet_protocol = (struct ether_header *)packet_content;
139 
140     //獲取源mac
141     mac = (unsigned char *)ethernet_protocol->ether_shost;
142     LOG_D("Mac Source Address is %02x:%02x:%02x:%02x:%02x:%02x\n",*(mac+0),*(mac+1),*(mac+2),
143                                         *(mac+3),*(mac+4),*(mac+5));
144 
145     //獲取目的mac
146     mac = (unsigned char *)ethernet_protocol->ether_dhost; 
147     LOG_D("Mac Destination Address is %02x:%02x:%02x:%02x:%02x:%02x\n",*(mac+0),*(mac+1),*(mac+2),
148                                         *(mac+3),*(mac+4),*(mac+5));
149 
150     //獲得以太網的類型 
151     ethernet_type = ntohs(ethernet_protocol->ether_type);
152 
153     LOG_D("Ethernet type is :%04x\n", ethernet_type);
154 
155     switch(ethernet_type)
156     {
157         case 0x0800: LOG_D("IP protocol");  break;
158         case 0x0806: LOG_D("ARP protocol"); break;
159         case 0x0835: LOG_D("RARP protocol"); break;
160         default: break;
161     }
162 
163     if (0x0800 != ethernet_type)
164     {
165         return;
166     }
167 
168     ip_header = (struct iphdr *)(packet_content + sizeof(struct ether_header));
169 
170     addr.s_addr  = ip_header->saddr;
171     LOG_D("ip saddr:%s", inet_ntoa(addr));
172 
173     addr.s_addr  = ip_header->daddr;
174     LOG_D("ip daddr:%s", inet_ntoa(addr));
175 
176     switch (ip_header->protocol)
177     {
178         case 1: LOG_D("ICMP protocol"); break;
179         case 2: LOG_D("ICMP protocol"); break;
180         case 6: LOG_D("TCP protocol");  break;
181         case 17: LOG_D("UDP protocol"); break;
182     }
183 
184     if (6 != ip_header->protocol)
185     {
186         return ;
187     }
188 
189     tcp_header = (struct tcphdr *)(packet_content + sizeof(struct ether_header) + sizeof(struct iphdr));
190 
191     LOG_D("tcp source port:%d", ntohs(tcp_header->source));
192     LOG_D("tcp dest port:%d", ntohs(tcp_header->dest));
193     LOG_D("tcp header size:%ld", (tcp_header->doff)*sizeof(int));
194 
195     offset += sizeof(struct pcap_pkthdr);
196     offset += sizeof(struct iphdr);
197     offset += (tcp_header->doff)*sizeof(int);
198 
199     LOG_D("package offset:%d", offset);
200     LOG_D("package data:%s", packet_content + offset);
201 
202     host = strcasestr((const char *)packet_content + offset, "host");
203 
204     if (host)
205     {
206         host += strlen("host:");
207 
208         LOG_I("capture host:%s", host);
209 
210         host_end = strstr(host, "\n");
211         //host_end--;
212         host_len = host_end - host;
213 
214         LOG_D("host_len:%d", host_len);
215 
216         //eat space
217         while (' ' == *host)
218         {
219             host++;
220             host_len--;
221         }
222 
223         //save to file
224         cap_file = fopen(HTTP_HOST_FILE, "a");
225         write_date(cap_file);
226         fwrite(host, 1, host_len, cap_file);
227         fclose(cap_file);
228     }
229 }
230 
231 void capture_all_test()
232 {
233     char error_buf[PCAP_ERRBUF_SIZE] = {0};
234     int  ret                         = 0;
235     char *net_dev                    = NULL;
236     pcap_t *pcap_handle              = NULL;
237     struct bpf_program filter        = {0};
238     FILE *cap_file                   = NULL;
239 
240     net_dev = pcap_lookupdev(error_buf);
241 
242     LOG_D("default net_dev:%s", net_dev);
243 
244     pcap_handle = pcap_open_live(net_dev, 65535, 1, 0, error_buf);
245 
246     if (! pcap_handle)
247     {
248         LOG_I("pcap_open_live failed");
249         return ;
250     }
251 
252     cap_file = fopen(HTTP_HOST_FILE, "w");
253     fclose(cap_file);
254 
255     //wireshark tcp.dstport == 80
256     pcap_compile(pcap_handle, &filter, "dst port 80", 1, 0);  
257     pcap_setfilter(pcap_handle, &filter);
258 
259     if (0 > pcap_loop(pcap_handle, -1, ethernet_protocol_callback, NULL))
260     {
261         LOG_I("pcap_loop failed");
262         return ;
263     }
264 
265     LOG_I("wait loop exit");
266 }
267 
268 int main(int argc, char **argv)
269 {
270     //capture_one_test();
271     capture_all_test();
272     return 0;
273 }

測試環境:ubuntu 18.0.4 sudo apt-get install libpcap-dev
編譯:gcc pcap_test.c -lpcap -o pcap_test
運行:sudo ./pcap_test

使用 curl 或 firefox 隨便上,http 的網站,然後就會被記錄下來。

capture_one_test(); 這個能抓包原始數據,經過對比和 Wireshark 中一致。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章