初识函数库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\n", 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\n", errbuf);
13         return(2);
14     }   
15 
16     printf("Device: %s\n", 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\n", errbuf);
14         return(2);
15     }   
16     printf("Device: %s\n", 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\n", dev, errbuf);
21         return(2);
22     }   
23     printf("Open Device success!\n");   
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\n", errbuf);
18         return(2);
19     }   
20     printf("Device: %s\n", 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\n", 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\n", 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\n", 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\n", 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\n");
21         return -1; 
22     }   
23     addr.s_addr = netp;
24     net = inet_ntoa(addr);
25     printf("network: %s\n", net);
26     
27     addr.s_addr = maskp;
28     mask = inet_ntoa(addr);
29     printf("mask: %s\n", 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\n", 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\n", 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\n", 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\n", filter_exp, pcap_geterr(handle));
        return(2);
    }   
    if (pcap_setfilter(handle, &fp) == -1) {
        fprintf(stderr, "Couldn't install filter %s: %s\n", filter_exp, pcap_geterr(handle));
        return(2);
    }   
    /* Grab a packet */
    packet = pcap_next(handle, &header);
    /* Print header info */
    printf("Packet length: %d\n", header.len);  
    printf("Number of bytes: %ud\n", header.caplen);  
    printf("Recieved time: %s\n", 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的说明文档,讲的非常详细;

 

 

posted @ 2016-07-27 17:36  zhangwju  阅读(1910)  评论(0编辑  收藏  举报