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