18.3 NPCAP自定义数据包过滤
NPCAP 库是一种用于在Windows平台上进行网络数据包捕获和分析的库。它是WinPcap库的一个分支,由Nmap开发团队开发,并在Nmap软件中使用。与WinPcap一样,NPCAP库提供了一些API,使开发人员可以轻松地在其应用程序中捕获和处理网络数据包。NPCAP库可以通过WinPcap API进行编程,因此现有的WinPcap应用程序可以轻松地迁移到NPCAP库上。
自定义数据包过滤其核心原理是使用pcap_compile
函数,该函数用于编译一个过滤表达式并生成过滤程序。该函数可以把用户指定的过滤表达式编译成可被BPF(Berkeley Packet Filter)虚拟机处理的内部表示格式,从而可以快速过滤捕获的数据包,仅保留指定条件的数据包。
该函数的函数原型是:
int pcap_compile(pcap_t *p, struct bpf_program *fp, const char *str, int optimize, bpf_u_int32 netmask);
函数参数解释如下:
- p:指向打开的
pcap
文件或设备的指针。 - fp:指向表示过滤程序的
bpf_program
结构体的指针。 - str:指向过滤表达式字符串的指针。
- optimize:用于指定是否优化过滤表达式的执行。设为1时表示启用优化。
- netmask:用于指定网络掩码。
使用该函数,可以将一个用户指定的过滤表达式编译成可被BPF
虚拟机执行的内部表示格式,生成表示过滤程序的bpf_program
结构体。这个过滤程序可以直接用于pcap_loop()
等函数,在捕获数据包时进行过滤,函数返回值为0表示编译成功,否则返回一个非零值。
当过滤规则被编译成功后则下一步就是设置过滤器,此时读者可调用pcap_setfilter()
函数,该函数用于设置捕获数据包时的过滤条件,并将一个表示过滤程序的bpf_program
结构体所代表的过滤程序应用到指定的pcap
文件或设备上,并过滤出符合条件的数据包。只有符合过滤条件的数据包才会被传递给抓包程序进行处理。
该函数的函数原型为:
int pcap_setfilter(pcap_t *p, struct bpf_program *fp);
函数参数解释如下:
- p:指向打开的
pcap
文件或设备的指针。 - fp:指向表示过滤程序的
bpf_program
结构体的指针。
使用该函数可以将一个表示过滤程序的bpf_program
结构体所代表的过滤程序应用到指定的pcap
文件或设备上,并过滤出符合条件的数据包。使用该函数后,pcap_loop()
等函数在捕获数据包时仅会传递符合过滤条件的数据包,过滤出的数据包将会被传递给抓包程序进行处理,而不会将所有数据包进行处理,这样可以大大减少资源占用,并同时提高数据包捕获和分析的效率。函数返回值为0表示设置过滤程序成功,否则返回一个非零值。
数据包过滤最后一步是设置一个回调函数,通过调用pcap_loop()
函数可实现循环等待数据包,并设置一个回调函数,当出现数据时会将数组自动发送至回调函数上,再回点函数内读者可对数据包进行任意形式的解析处理。
该函数的函数原型为:
int pcap_loop(pcap_t *p, int cnt, pcap_handler callback, u_char *user);
函数参数解释如下:
- p:指向打开的
pcap
文件或设备的指针。 - cnt:用于指定捕获的数据包的数量,
-1
表示捕获数据包的数量没有限制。 - callback:指向用户自定义的回调函数的指针,用于处理每一个捕获到的数据包。
- user:传递给回调函数的用户指针。
使用该函数,可以在指定的pcap
文件或设备上启动一个循环,等待并捕获符合过滤条件的数据包,并通过用户自定义的回调函数对其进行处理。回调函数会在每个数据包被捕获时调用,在回调函数中可以根据需求进行特定的数据包分析和处理操作。函数返回值为-1
表示捕获数据包失败,否则返回一个非负整数,表示捕获的数据包数量,当理解了上述程序定义,那么实现自定义抓包过滤功能将变得很容易,如下则是完整的代码案例;
#include <iostream>
#include <winsock2.h>
#include <Windows.h>
#include <string>
#include <pcap.h>
#pragma comment(lib,"ws2_32.lib")
#pragma comment(lib, "packet.lib")
#pragma comment(lib, "wpcap.lib")
using namespace std;
// 打开网卡返回的指针
pcap_t* m_adhandle;
// 解码TCP数据包,需要先加14跳过数据链路层, 然后再加20跳过IP层。
void PrintTCPHeader(const unsigned char * packetData)
{
typedef struct tcp_header
{
short SourPort; // 源端口号16bit
short DestPort; // 目的端口号16bit
unsigned int SequNum; // 序列号32bit
unsigned int AcknowledgeNum; // 确认号32bit
unsigned char reserved : 4, offset : 4; // 预留偏移
unsigned char flags; // 标志
short WindowSize; // 窗口大小16bit
short CheckSum; // 检验和16bit
short surgentPointer; // 紧急数据偏移量16bit
}tcp_header;
struct tcp_header *tcp_protocol;
// +14 跳过数据链路层 +20 跳过IP层
tcp_protocol = (struct tcp_header *)(packetData + 14 + 20);
u_short sport = ntohs(tcp_protocol->SourPort);
u_short dport = ntohs(tcp_protocol->DestPort);
int window = tcp_protocol->WindowSize;
int flags = tcp_protocol->flags;
printf("源端口: %6d --> 目标端口: %6d --> 窗口大小: %7d --> 标志: (%d)",
sport, dport, window, flags);
if (flags & 0x08) printf("PSH 数据传输\n");
else if (flags & 0x10) printf("ACK 响应\n");
else if (flags & 0x02) printf("SYN 建立连接\n");
else if (flags & 0x20) printf("URG \n");
else if (flags & 0x01) printf("FIN 关闭连接\n");
else if (flags & 0x04) printf("RST 连接重置\n");
else printf("None 未知\n");
}
// 解析过滤数据包(回调函数)
void packet_handler(u_char* param, const struct pcap_pkthdr* header, const u_char* pkt_data)
{
// std::cout << pkt_data << std::endl;
PrintTCPHeader(pkt_data);
}
// 获取网卡并设置过滤器
BOOL GetAndOpenAdapter(std::string local_address, std::string filter)
{
pcap_if_t* alldevs = NULL, *d = NULL;
char errbuf[256] = { 0 };
bpf_program fcode;
u_int netmask;
// 获取网卡设备指针
if (-1 == pcap_findalldevs_ex(PCAP_SRC_IF_STRING, NULL, &alldevs, errbuf))
{
return FALSE;
}
// 选取适合网卡
int flag = 0;
for (d = alldevs; d; d = d->next)
{
pcap_addr_t* p = d->addresses;
while (p)
{
if (local_address == inet_ntoa(((sockaddr_in*)p->addr)->sin_addr))
{
flag = 1;
break;
}
p = p->next;
}
if (1 == flag)
break;
}
if (0 == flag)
{
// 请检查本机IP地址是否正确
return FALSE;
}
// 获取子网掩码
netmask = ((sockaddr_in*)d->addresses->netmask)->sin_addr.S_un.S_addr;
// 打开网卡
m_adhandle = pcap_open(d->name, 65536, PCAP_OPENFLAG_PROMISCUOUS, 1000, NULL, errbuf);
if (NULL == m_adhandle)
{
pcap_freealldevs(alldevs);
return FALSE;
}
// 检查以太网
if (DLT_EN10MB != pcap_datalink(m_adhandle))
{
pcap_freealldevs(alldevs);
return FALSE;
}
// 编译过滤器
char filter_ptr[4096] = { 0 };
sprintf(filter_ptr, filter.c_str());
if (0 > pcap_compile(m_adhandle, &fcode, filter_ptr, 1, netmask))
{
// 编译过滤器出错
pcap_freealldevs(alldevs);
return FALSE;
}
// 设置过滤器
if (0 > pcap_setfilter(m_adhandle, &fcode))
{
// 设置过滤器出错
pcap_freealldevs(alldevs);
return FALSE;
}
// 释放网卡设备列表
pcap_freealldevs(alldevs);
// 开始捕捉数据包
pcap_loop(m_adhandle, 0, packet_handler, NULL);
return TRUE;
}
int main(int argc, char* argv[])
{
BOOL flag = GetAndOpenAdapter("192.168.9.125", "dst host www.lyshark.com");
system("pause");
return 0;
}
读者可自行运行上述代码片段,当程序运行后会自动侦听192.168.9.125
地址,并使用dst host www.lyshark.com
过滤规则,该规则的含义是,如果有数据包从本网卡流出,则自动执行过滤输出lyshark.com
网址的数据包,如下图所示;
本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!