18.3 NPCAP自定义数据包过滤

2023-10-26 16:25:07 浏览数 (2)

NPCAP 库是一种用于在Windows平台上进行网络数据包捕获和分析的库。它是WinPcap库的一个分支,由Nmap开发团队开发,并在Nmap软件中使用。与WinPcap一样,NPCAP库提供了一些API,使开发人员可以轻松地在其应用程序中捕获和处理网络数据包。NPCAP库可以通过WinPcap API进行编程,因此现有的WinPcap应用程序可以轻松地迁移到NPCAP库上。

自定义数据包过滤其核心原理是使用pcap_compile函数,该函数用于编译一个过滤表达式并生成过滤程序。该函数可以把用户指定的过滤表达式编译成可被BPF(Berkeley Packet Filter)虚拟机处理的内部表示格式,从而可以快速过滤捕获的数据包,仅保留指定条件的数据包。

该函数的函数原型是:

代码语言:c复制
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文件或设备上,并过滤出符合条件的数据包。只有符合过滤条件的数据包才会被传递给抓包程序进行处理。

该函数的函数原型为:

代码语言:c复制
int pcap_setfilter(pcap_t *p, struct bpf_program *fp);

函数参数解释如下:

  • p:指向打开的pcap文件或设备的指针。
  • fp:指向表示过滤程序的bpf_program结构体的指针。

使用该函数可以将一个表示过滤程序的bpf_program结构体所代表的过滤程序应用到指定的pcap文件或设备上,并过滤出符合条件的数据包。使用该函数后,pcap_loop()等函数在捕获数据包时仅会传递符合过滤条件的数据包,过滤出的数据包将会被传递给抓包程序进行处理,而不会将所有数据包进行处理,这样可以大大减少资源占用,并同时提高数据包捕获和分析的效率。函数返回值为0表示设置过滤程序成功,否则返回一个非零值。

数据包过滤最后一步是设置一个回调函数,通过调用pcap_loop()函数可实现循环等待数据包,并设置一个回调函数,当出现数据时会将数组自动发送至回调函数上,再回点函数内读者可对数据包进行任意形式的解析处理。

该函数的函数原型为:

代码语言:c复制
int pcap_loop(pcap_t *p, int cnt, pcap_handler callback, u_char *user);

函数参数解释如下:

  • p:指向打开的pcap文件或设备的指针。
  • cnt:用于指定捕获的数据包的数量,-1表示捕获数据包的数量没有限制。
  • callback:指向用户自定义的回调函数的指针,用于处理每一个捕获到的数据包。
  • user:传递给回调函数的用户指针。

使用该函数,可以在指定的pcap文件或设备上启动一个循环,等待并捕获符合过滤条件的数据包,并通过用户自定义的回调函数对其进行处理。回调函数会在每个数据包被捕获时调用,在回调函数中可以根据需求进行特定的数据包分析和处理操作。函数返回值为-1表示捕获数据包失败,否则返回一个非负整数,表示捕获的数据包数量,当理解了上述程序定义,那么实现自定义抓包过滤功能将变得很容易,如下则是完整的代码案例;

代码语言:c复制
#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("源端口: m --> 目标端口: m --> 窗口大小: } --> 标志: (%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网址的数据包,如下图所示;

本文作者: 王瑞

本文链接: https://www.lyshark.com/post/526b8a6.html

版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!

0 人点赞