使用 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 中一致。

 

上一篇:【Java基础】java集合体系汇总


下一篇:SELinux导致PHP连接MySQL异常Can't connect to MySQL server的解决方法