前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >18.3 NPCAP自定义数据包过滤

18.3 NPCAP自定义数据包过滤

作者头像
微软技术分享
发布2023-10-27 15:17:03
2410
发布2023-10-27 15:17:03

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

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

该函数的函数原型是:

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

该函数的函数原型为:

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

函数参数解释如下:

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

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

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

该函数的函数原型为:

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

函数参数解释如下:

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

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

代码语言:javascript
复制
#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网址的数据包,如下图所示;

本文作者: 王瑞 本文链接: https://www.lyshark.com/post/526b8a6.html 版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2023-10-26,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档