• 初识函数库libpcap


    由于工作上的需要,最近简单学习了抓包函数库libpcap,顺便记下笔记,方便以后查看

    一、libpcap简介
        libpcap(Packet Capture Library),即数据包捕获函数库,是Unix/Linux平台下的网络数据包捕获函数库。它是一个独立于系统的用户层包捕获的API接口,为底层网络监测提供了一个可移植的框架.
    Libpcap可以在绝大多数类unix平台下工作,libpcap库安装也很简单,Libpcap 软件包可从 http://www.tcpdump.org/ 下载,然后依此执行下列三条命令即可安装
    ./configure
    make
    make install
    二、pcap基本工作流程
    (1)确定将要嗅探的接口,在linux下是类似eth0的东西。在BSD下是类似xll的东西。可以在一个字符串中声明设备,也可以让pcap提供备选接口(我们想要嗅探的接口)的名字。
    (2)初始化pcap,此时才真正告诉pcap我们要嗅探的具体接口,只要我们愿意,我们可以嗅探多个接口。但是如何区分多个接口呢,使用文件句柄。就像读写文件时使用文件句柄一样。我们必须给嗅探任务命名,以至于区分不同的嗅探任务。
    (3)指定过滤规则,当我们只想嗅探特殊的流量时(例如,仅仅嗅探TCP/IP包、仅仅嗅探经过端口80的包,等等)我们必须设定一个规则集,“编译”并应用它。这是一个三相的并且紧密联系的过程,规则集存储与字符串中,在“编译”之后会转换成pcap可以读取的格式。“编译过程”实际上是调用自定义的函数完成的,不涉及外部的函数。然后我们可以告诉pcap在我们想要过滤的任何任务上实施。
    (4)抓包,最后,告诉pcap进入主要的执行循环中,在此阶段,在接收到任何我们想要的包之前pcap将一直循环等待。在每次抓取到一个新的数据包时,它将调用另一个自定义的函数,我们可以在这个函数中肆意妄为,例如,解析数据包并显示数据内容、保存到文件或者什么都不做等等。
     当嗅探完美任务完成时,记得关掉任务。

    下面是pcap工作流程图(摘自官网)


    下面我们看一下具体的步骤实施:
    (1)确定我们将要嗅探的接口
    这一步操作我们可以手动指定接口或者调用pcap库提供的接口来查找网络设备
    手动指定:

     1 #include <stdio.h>  
     2 #include <string.h>
     3 #include <stdlib.h>
     4   
     5 int main(int argc, char **argv)  
     6 {  
     7     char *dev = argv[1];
     8 
     9     printf("Device: %s
    ", dev);
    10  
    11   return 0;  
    12 }

    使用pcap API查找网络设备

    pcap_lookupdev()函数用于查找网络设备

     1 #include <stdio.h>
     2 #include <string.h>
     3 #include <stdlib.h>
     4 #include <pcap.h>
     5 
     6 int main(int argc, char *argv[])
     7 {
     8     char *dev, errbuf[PCAP_ERRBUF_SIZE];
     9 
    10     dev = pcap_lookupdev(errbuf);
    11     if (dev == NULL) {
    12         fprintf(stderr, "Couldn't find default device: %s
    ", errbuf);
    13         return(2);
    14     }   
    15 
    16     printf("Device: %s
    ", dev);
    17     return(0);
    18 }

    编译时需要连接pcap库 -lpcap
    (2)打开嗅探设备

    pcap_open_live()函数用于打开网络设备,并且返回用于捕获网络数据包的数据包捕获描述字。对于此网络设备的操作都要基于此网络设备描述字。

    函数原型如下:

    1     pcap_t *pcap_open_live(char *device, int snaplen, int promisc, int to_ms, char *errbuf);
    2     
    3 /*函数说明:该函数用于打开一个嗅探设备
    4     参数:device 需要打开的设备
    5           snaplen int型,表示pcap可以捕获的最大字节数(最大为65535)
    6           promisc 是否开启混杂模式(1打开,0关闭),设置开启混杂模式,需要对应的网卡也开启混杂模式
    7           to_ms 是读取时间溢出,单位为毫秒(ms), 0表示没有时间溢出
    8           errbuf 保存错误的返回值
    9 */    

    下面是具体实现:

     1 #include <stdio.h>
     2 #include <string.h>
     3 #include <stdlib.h>
     4 #include <pcap.h>
     5 
     6 int main(int argc, char *argv[])
     7 {
     8     char *dev, errbuf[PCAP_ERRBUF_SIZE];
     9     pcap_t *handle;
    10 
    11     dev = pcap_lookupdev(errbuf);
    12     if (dev == NULL) {
    13         fprintf(stderr, "Couldn't find default device: %s
    ", errbuf);
    14         return(2);
    15     }   
    16     printf("Device: %s
    ", dev);
    17     
    18     handle = pcap_open_live(dev, BUFSIZ, 1, 1000, errbuf);
    19     if (handle == NULL) {
    20         fprintf(stderr, "Couldn't open device %s: %s
    ", dev, errbuf);
    21         return(2);
    22     }   
    23     printf("Open Device success!
    ");   
    24 
    25     return(0);
    26 }

     (3)过滤指定流量
          很多时候,我只需要我们指定的流量,比如我们需要劫持http请求(80端口),劫持DNS服务(53端口),因此,我们大多数时候都不会盲目的抓取全部的报文。
        相关过滤函数pcap_compile()and pcap_setfilter(),当我们调用pcap_open_live()后,我们会得到一个建立的嗅探会话,此时我们就可以开始过滤我们想要流量了;
        过滤器表达式是基于正则表达式来编写的,官网tcpdump有详细的规则说明,在使用过滤器之前我们必须”编译“.

    函数原型如下:

    1     int pcap_compile(pcap_t *p, struct bpf_program *fp, char *str, int optimize, bpf_u_int32 netmask)
    2 /*函数说明:将str参数指定的字符串编译到过滤程序中。
    3     参数:  p是嗅探器回话句柄
    4             fp是一个bpf_program结构的指针,在pcap_compile()函数中被赋值。o
    5             ptimize参数控制结果代码的优化。
    6             netmask参数指定本地网络的网络掩码。
    7 */

    编译完过滤表达式后,我们就可以应用它了,下面是int pcap_setfilter(),具体用法看man手册:

    1     int pcap_setfilter(pcap_t *p, struct bpf_program *fp)
    2 //第一个参数嗅探器回话句柄,第二参数是存储过滤器编译版本的结构体指针(跟pcap_compile 一个参数一样)

    下面是简单实例:

     1 #include <stdio.h>
     2 #include <string.h>
     3 #include <stdlib.h>
     4 #include <pcap.h>
     5 
     6 int main(int argc, char *argv[])
     7 {
     8     char *dev, errbuf[PCAP_ERRBUF_SIZE];
     9     struct bpf_program fp;      /* The compiled filter expression */
    10     char filter_exp[] = "port 53";  /* The filter expression (filter 53 port)*/
    11     pcap_t *handle;
    12     bpf_u_int32 mask;       /* The netmask of our sniffing device */
    13     bpf_u_int32 net;        /* The IP of our sniffing device */
    14 
    15     dev = pcap_lookupdev(errbuf);
    16     if (dev == NULL) {
    17         fprintf(stderr, "Couldn't find default device: %s
    ", errbuf);
    18         return(2);
    19     }   
    20     printf("Device: %s
    ", dev);
    21 
    22     /*get network mask*/    
    23     if (pcap_lookupnet(dev, &net, &mask, errbuf) == -1) {
    24         fprintf(stderr, "Can't get netmask for device %s
    ", dev);
    25         net = 0;
    26         mask = 0;
    27     }   
    28     /*Open the session in promiscuous mode*/
    29     handle = pcap_open_live(dev, BUFSIZ, 1, 1000, errbuf);
    30     if (handle == NULL) {
    31         fprintf(stderr, "Couldn't open device %s: %s
    ", dev, errbuf);
    32         return(2);
    33     }   
    34     /* Compile and apply the filter */
    35     if (pcap_compile(handle, &fp, filter_exp, 0, net) == -1) {
    36         fprintf(stderr, "Couldn't parse filter %s: %s
    ", filter_exp, pcap_geterr(handle));
    37         return(2);
    38     }   
    39     if (pcap_setfilter(handle, &fp) == -1) {
    40         fprintf(stderr, "Couldn't install filter %s: %s
    ", filter_exp, pcap_geterr(handle));
    41         return(2);
    42     }   
    43 
    44     return(0);
    45 }

    以上代码中的pcap_lookupnet()函数获得指定网络设备的网络号和掩码。函数原型如下:

    1 int pcap_lookupnet(const char *device, bpf_u_int32 *netp,
    2                bpf_u_int32 *maskp, char *errbuf);
    3 /*    参数:device 指定的嗅探设备名称
    4           netp 指定设备的网络号
    5           maskp 掩码
    6           errbuf 保存错误信息
    7 */

    下面是具体实例:

     1 #include <stdio.h>  
     2 #include <string.h>
     3 #include <netinet/in.h>
     4 #include <arpa/inet.h>
     5 #include <pcap.h> 
     6 
     7 #define DEVICE "enp0s3" 
     8  
     9 int main()  
    10 {  
    11     char errBuf[PCAP_ERRBUF_SIZE];
    12     struct pcap_pkthdr packet;  
    13     pcap_t *dev;
    14     bpf_u_int32 netp, maskp;
    15     char *net, *mask;
    16     struct in_addr addr;
    17     int ret;
    18 
    19     if(pcap_lookupnet(DEVICE, &netp, &maskp, errBuf)) {
    20         printf("get net failure
    ");
    21         return -1; 
    22     }   
    23     addr.s_addr = netp;
    24     net = inet_ntoa(addr);
    25     printf("network: %s
    ", net);
    26     
    27     addr.s_addr = maskp;
    28     mask = inet_ntoa(addr);
    29     printf("mask: %s
    ", mask);
    30 
    31     return 0;
    32 } 
    33 //运行结果
    34 [root@localhost pacp_1st]# ./pacp 
    35 network: 192.168.16.0
    36 mask: 255.255.255.0

    (4)进行抓包处理  

      通过以上内容,我们已经知道了如何指定获取以及初始化一个嗅探器设备,如何编译及使用过滤器;下面我们就开始进行抓包,抓包程序有抓一次包(pcap_next())和循环一直抓包几个函数;
    下面我们我们先用pcap_next()进行一次抓包
    函数原型:

     1     const u_char *pcap_next(pcap_t *p, struct pcap_pkthdr *h);
     2     
     3 /*参数:p是嗅探器会话句柄
     4         h是一个指向存储数据包概略信息结构体的指针
     5     */
     6 struct pcap_pkthdr {
     7     struct timeval ts;  /* time stamp */ 
     8     bpf_u_int32 caplen; /* length of portion present */
     9     bpf_u_int32 len;    /* length this packet (off wire) */
    10 };
    11 //ts——时间戳
    12 //caplen——真正实际捕获的包的长度
    13 //len——这个包的长度
    14 
    15 因为在某些情况下你不能保证捕获的包是完整的,例如一个包长1480,但是你捕获到1000的时候,
    16 可能因为某些原因就中止捕获了,所以caplen是记录实际捕获的包长,也就是1000,而len就是1480。

    下面是使用pcap_next()抓包程序

    #include <stdio.h>
    #include <string.h>
    #include <stdlib.h>
    #include <time.h>
    #include <pcap.h>
    
    int main(int argc, char *argv[])
    {
        pcap_t *handle;         /* Session handle */
        char *dev;          /* The device to sniff on */
        char errbuf[PCAP_ERRBUF_SIZE];  /* Error string */
        struct bpf_program fp;      /* The compiled filter */
        char filter_exp[] = "port 53";  /* The filter expression */
        bpf_u_int32 mask;       /* Our netmask */
        bpf_u_int32 net;        /* Our IP */
        struct pcap_pkthdr header;  /* The header that pcap gives us */
        const u_char *packet;       /* The actual packet */
        
        /* Define the device */
        dev = pcap_lookupdev(errbuf);
        if (dev == NULL) {
            fprintf(stderr, "Couldn't find default device: %s
    ", errbuf);
            return(2);
        }   
        /* Find the properties for the device */
        if (pcap_lookupnet(dev, &net, &mask, errbuf) == -1) {
            fprintf(stderr, "Couldn't get netmask for device %s: %s
    ", dev, errbuf);
            net = 0;
            mask = 0;
        }   
        /* Open the session in promiscuous mode */
        handle = pcap_open_live(dev, BUFSIZ, 1, 100, errbuf);
        if (handle == NULL) {
            fprintf(stderr, "Couldn't open device %s: %s
    ", dev, errbuf);
            return(2);
        }   
        /* Compile and apply the filter */
        if (pcap_compile(handle, &fp, filter_exp, 0, net) == -1) {
            fprintf(stderr, "Couldn't parse filter %s: %s
    ", filter_exp, pcap_geterr(handle));
            return(2);
        }   
        if (pcap_setfilter(handle, &fp) == -1) {
            fprintf(stderr, "Couldn't install filter %s: %s
    ", filter_exp, pcap_geterr(handle));
            return(2);
        }   
        /* Grab a packet */
        packet = pcap_next(handle, &header);
        /* Print header info */
        printf("Packet length: %d
    ", header.len);  
        printf("Number of bytes: %ud
    ", header.caplen);  
        printf("Recieved time: %s
    ", ctime((const time_t *)&header.ts.tv_sec)); 
        /* And close the session */
        pcap_close(handle);
    
        return(0);
    }
    //运行结果
    [root@localhost pacp_5th]# ./pacp     
    Packet length: 32603
    Number of bytes: 3372236960d
    Recieved time: Sat Aug 16 07:45:20 4461732

         上面的代码在promisc模式下嗅探所有由pcap_lookupdev()返回的设备。它发现第一个经过端口53(DNS)的数据包并打印包的相关信息。
    在大多数情况下我们很少的嗅探器使用pcap_next(),更多的是使用pcap_loop()或者pcap_dispatch()(pcap_dispatch()内部调用pcap_next())
    pcap_loop()及pcap_dispatch()的具体使用在下篇博客中介绍

    参考:http://www.tcpdump.org/
    libpcap 官方有很多关于libpcap的说明文档,讲的非常详细;

     

  • 相关阅读:
    Swift学习二
    Swift学习一
    iOS--点击地图上某一点获取该点的经纬度
    iOS--新特性一览
    搭建LNAMP环境(三)- 源码安装Apache2.4
    搭建LNAMP环境(四)- 源码安装PHP7
    搭建LNAMP环境(五)- PHP7源码安装Redis和Redis拓展
    搭建LNAMP环境(六)- PHP7源码安装MongoDB和MongoDB拓展
    搭建LNAMP环境(七)- PHP7源码安装Memcached和Memcache拓展
    PHP安装mysql.so扩展
  • 原文地址:https://www.cnblogs.com/wenqiang/p/5711871.html
Copyright © 2020-2023  润新知