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