由于工作上的需要,最近简单学习了抓包函数库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工作流程图(摘自官网)

初识函数库libpcap_抓包

下面我们看一下具体的步骤实施:

(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的说明文档,讲的非常详细;