WindowsAPI编程核心技术-NPcap网络抓包入门

环境搭建

NPcap是WinPcap的改进版,后者已停止维护。在官网https://npcap.com/ 获取安装包和SDK,并运行安装包。

新建Visual Studio工程,添加SDK的Include目录为包含目录,添加Lib\x64目录为库目录,链接器附加依赖项添加wpcap.lib、packet.lib和ws2_32.lib文件。

PCAP文件格式

文件格式大致为:

1
2
3
4
文件头24字节
数据报头16字节 + 数据包
数据报头16字节 + 数据包
...

文件头格式为:

1
2
3
4
5
6
7
8
9
struct pcap_file_header {
bpf_u_int32 magic; //PCAP文件标识 如d4 c3 b2 a1
u_short version_major; //主版本号
u_short version_minor; //次版本号
bpf_int32 thiszone; /* 时区修正 未使用 总为0 */
bpf_u_int32 sigfigs; /* 精确时间戳 未使用 总为0 */
bpf_u_int32 snaplen; /* 抓包最大长度 默认68 抓全要改为0xffff */
bpf_u_int32 linktype; /* 链路类型 一般为1(ethernet) */
};

常用结构与函数

结构

NPcap的网络接口地址结构为:

1
2
3
4
5
6
7
struct pcap_addr {
struct pcap_addr *next; //指向下一个地址节点
struct sockaddr *addr; /* 网络接口地址 */
struct sockaddr *netmask; /* 掩码 */
struct sockaddr *broadaddr; /* 广播地址 */
struct sockaddr *dstaddr; /* P2P目标地址 */
};

数据报头格式如下。ts成员中包含8字节的抓包时间,4字节的秒数,4字节的微秒数。caplen为保存下来的包长度,len为数据包真实长度。若文件保存的不是完整数据包,则len可能比caplen大。

1
2
3
4
5
6
7
8
9
struct pcap_pkthdr {
struct timeval ts; /* time stamp */
bpf_u_int32 caplen; /* length of portion present */
bpf_u_int32 len; /* length of this packet (off wire) */
};
struct timeval {
long tv_sec; /* seconds */
long tv_usec; /* and microseconds */
};

pcap_findalldevs

获取网卡列表:

1
2
3
4
5
6
7
8
9
10
11
12
PCAP_API int pcap_findalldevs(
pcap_if_t **alldevsp,
char *errbuf //错误信息缓冲区
); //成功0 否则PCAP_ERROR
typedef struct pcap_if pcap_if_t;
struct pcap_if {
struct pcap_if *next; //多个网卡时用来显示各个网卡信息
char *name; /* name to hand to "pcap_open_live()" */
char *description; /* 网卡型号、名字等 */
struct pcap_addr *addresses;
bpf_u_int32 flags; /* PCAP_IF_ interface flags */
};

不再需要网卡列表时用pcap_freealldevs释放空间:

1
2
3
PCAP_API void pcap_freealldevs(
pcap_if_t *alldevs
);

pcap_open_live

打开网络设备:

1
2
3
4
5
6
7
PCAP_API pcap_t    *pcap_open_live(
const char * device, //网卡名称
int snaplen, //捕获的最大字节数 超出该值后面全为0
int promisc, //是否开启混杂模式
int to_ms, //读取超时时间 单位毫秒 0则等足够多数据包再一起返回
char *errbuf //错误信息缓冲区
); //失败NULL

例如:

1
2
3
4
if ((adhandle = pcap_open_live(d->name, 65535, 1, 1000, errbuf)) == NULL) {
pcap_freealldevs(alldevs);
return -1;
};

pcap_loop

捕获数据包,且不响应pcap_open_live设置的超时时间:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
PCAP_API int pcap_loop(
pcap_t *p, //网卡
int cnt, //捕获数据包个数
pcap_handler callback, //回调函数
u_char *user //一般NULL
);
typedef void (*pcap_handler)( //回调函数原型 必须为全局函数或静态函数
u_char *argument, //pcap_loop的user参数
const struct pcap_pkthdr *packet_header, //数据包基本信息 如时间、长度等
const u_char *packet_content //捕获到的数据包内容
);
struct pcap_pkthdr {
struct timeval ts; /* 时间戳 */
bpf_u_int32 caplen; /* 实际捕获的包长度 */
bpf_u_int32 len; /* 该包在发送端发出时的长度 */
};

使用方法例如:

1
2
3
4
5
6
7
8
void packet_handler(uchar* param, const struct pcap_pkthdr* header, const u_char* pkt_data) {
struct tm* ltime;
char timestr[16];
ltime = localtime(&header->ts.tv_sec);
strftime(timestr, sizeof(timestr), "%H:%M:%S", ltime);
printf("%s,%.6d len:%d\n", timestr, header->ts.tv_usec, header->len);
};
pcap_loop(adhandle, 0, packet_handler, NULL);

pcap_dispatch

捕获数据包,参数同上:

1
2
3
4
5
6
PCAP_API int pcap_dispatch(
pcap_t *p,
int cnt,
pcap_handler,
u_char *user
); //成功返回读取到的字节数 读取到EOF返回0 错误-1

出现错误返回-1时可用pcap_errorpcap_geterr获取错误信息。

pcap_loop在没有数据流到达时将阻塞,pcap_dispatch可不被阻塞。

MAC冗余校验码一般不出现,因为大多数网卡直接对调冗余码出错的数据包,所以NPcap一般不能捕获这些出错的数据包。

pcap_next_ex

捕获数据包:

1
2
3
4
5
PCAP_API int pcap_next_ex(
pcap_t *p, //打开的网卡
struct pcap_pkthdr **pkt_header, //报文头 包括存储时间、包长度等
const u_char **pkt_data //数据包内容
); //成功1 超时0 错误-1

出现错误时用pcap_geterr获取。参数pkt_data一般系统分配500KB左右内存,不能将大量数据内容放在这,数据占满后会从开始位置覆盖原有数据,所以应写入本地文件或另开辟内存空间来存储。

实战

枚举本机网卡

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <pcap.h>
int main() {
pcap_if_t* alldevs;
pcap_if_t* d;
int i = 0;
char errbuf[PCAP_ERRBUF_SIZE];
if (pcap_findalldevs(&alldevs, errbuf) == -1) {
fprintf(stderr, "Error in pcap_findalldevs_ex: %s\n", errbuf);
return -1;
};
for (d = alldevs; d != NULL; d = d->next) {
printf("%d. %s", ++i, d->name);
if (d->description)
printf(" (%s)\n", d->description);
else
printf(" (No description available)\n");
};
if (i == 0) {
printf("\nNo interfaces found! Make sure WinPcap is installed.\n");
return -1;
};
pcap_freealldevs(alldevs);
return 0;
};

捕获访问Web站点的数据包

pheader.h:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
// define struct of ethernet header , ip address , ip header and tcp header
#ifndef PHEADER_H_INCLUDED
#define PHEADER_H_INCLUDED
#define ETHER_ADDR_LEN 6 /* ethernet address */
#define ETHERTYPE_IP 0x0800 /* ip protocol */
#define TCP_PROTOCAL 0x0600 /* tcp protocol */
#define BUFFER_MAX_LENGTH 65536 /* buffer max length */
#define true 1 /* define true */
#define false 0 /* define false */
// define struct of ethernet header , ip address , ip header and tcp header
/* ethernet header */
typedef struct ether_header {
u_char ether_shost[ETHER_ADDR_LEN]; /* source ethernet address, 8 bytes */
u_char ether_dhost[ETHER_ADDR_LEN]; /* destination ethernet addresss, 8 bytes */
u_short ether_type; /* ethernet type, 16 bytes */
}ether_header;
/* four bytes ip address */
typedef struct ip_address {
u_char byte1;
u_char byte2;
u_char byte3;
u_char byte4;
}ip_address;
/* ipv4 header */
typedef struct ip_header {
u_char ver_ihl; /* version and ip header length */
u_char tos; /* type of service */
u_short tlen; /* total length */
u_short identification; /* identification */
u_short flags_fo; // flags and fragment offset
u_char ttl; /* time to live */
u_char proto; /* protocol */
u_short crc; /* header checksum */
ip_address saddr; /* source address */
ip_address daddr; /* destination address */
u_int op_pad; /* option and padding */
}ip_header;
/* tcp header */
typedef struct tcp_header {
u_short th_sport; /* source port */
u_short th_dport; /* destination port */
u_int th_seq; /* sequence number */
u_int th_ack; /* acknowledgement number */
u_short th_len_resv_code; /* datagram length and reserved code */
u_short th_window; /* window */
u_short th_sum; /* checksum */
u_short th_urp; /* urgent pointer */
}tcp_header;
#endif // PHEADER_H_INCLUDED

test.cpp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
#include <stdio.h>
#include <stdlib.h>
#define HAVE_REMOTE
#include <pcap.h>
#include "pheader.h"
#define BUFFER_MAX_LENGTH 1024
int main() {
pcap_if_t* alldevs; // list of all devices
pcap_if_t* d; // device you chose
pcap_t* adhandle;
char errbuf[PCAP_ERRBUF_SIZE]; //error buffer
int i = 0;
int inum;
struct pcap_pkthdr* pheader; /* packet header */
const u_char* pkt_data; /* packet data */
int res;
/* pcap_findalldevs_ex got something wrong */
if (pcap_findalldevs_ex(PCAP_SRC_IF_STRING, NULL /* auth is not needed*/, &alldevs, errbuf) == -1) {
fprintf(stderr, "Error in pcap_findalldevs_ex: %s\n", errbuf);
exit(1);
}
/* print the list of all devices */
for (d = alldevs; d != NULL; d = d->next) {
printf("%d. %s", ++i, d->name); // print device name , which starts with "rpcap://"
if (d->description)
printf(" (%s)\n", d->description); // print device description
else
printf(" (No description available)\n");
}
/* no interface found */
if (i == 0) {
printf("\nNo interface found! Make sure Winpcap is installed.\n");
return -1;
}
printf("Enter the interface number (1-%d):", i);
scanf("%d", &inum);
if (inum < 1 || inum > i) {
printf("\nInterface number out of range.\n");
pcap_freealldevs(alldevs);
return -1;
}
for (d = alldevs, i = 0; i < inum - 1; d = d->next, i++); /* jump to the selected interface */
/* open the selected interface*/
if ((adhandle = pcap_open(d->name, /* the interface name */65536, /* length of packet that has to be retained */PCAP_OPENFLAG_PROMISCUOUS, /* promiscuous mode */1000, /* read time out */NULL, /* auth */errbuf /* error buffer */)) == NULL) {
fprintf(stderr, "\nUnable to open the adapter. %s is not supported by Winpcap\n", d->description);
return -1;
}
printf("\nListening on %s...\n", d->description);
pcap_freealldevs(alldevs); // release device list
/* capture packet */
while ((res = pcap_next_ex(adhandle, &pheader, &pkt_data)) >= 0) {
if (res == 0)
continue; /* read time out*/
ether_header* eheader = (ether_header*)pkt_data; /* transform packet data to ethernet header */
if (eheader->ether_type == htons(ETHERTYPE_IP)) { /* ip packet only */
ip_header* ih = (ip_header*)(pkt_data + 14); /* get ip header */
if (ih->proto == htons(TCP_PROTOCAL)) { /* tcp packet only */
int ip_len = ntohs(ih->tlen); /* get ip length, it contains header and body */
int find_http = false;
char* ip_pkt_data = (char*)ih;
int n = 0;
char buffer[BUFFER_MAX_LENGTH];
int bufsize = 0;
for (; n < ip_len; n++) {
/* http get or post request */
if (!find_http && ((n + 3 < ip_len && strncmp(ip_pkt_data + n, "GET", strlen("GET")) == 0) || (n + 4 < ip_len && strncmp(ip_pkt_data + n, "POST", strlen("POST")) == 0)))
find_http = true;
/* http response */
if (!find_http && n + 8 < ip_len && strncmp(ip_pkt_data + n, "HTTP/1.1", strlen("HTTP/1.1")) == 0)
find_http = true;
/* if http is found */
if (find_http) {
buffer[bufsize] = ip_pkt_data[n]; /* copy http data to buffer */
bufsize++;
}
}
/* print http content */
if (find_http) {
buffer[bufsize] = '\0';
printf("%s\n", buffer);
printf("\n**********************************************\n\n");
}
}
}
}
return 0;
}