最近研究 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 中一致。