zoukankan      html  css  js  c++  java
  • 使用 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 中一致。

  • 相关阅读:
    【python接口自动化】httpUtils
    mac上安装chromedriver
    python自动化测试报告(excel篇)
    Fiddler 手机抓包介绍
    Fiddler 简单介绍
    Python splinter 环境搭建
    Python pip 常用命令
    Python Yaml 学习
    Jmeter
    Python3 操作Excel
  • 原文地址:https://www.cnblogs.com/ningci/p/15677170.html
Copyright © 2011-2022 走看看