Win10 下基于 Npcap 的抓包程序详解

1. 背景

wireshark 是最常用的是网络抓包程序 ,不过,wireshark 也有无法满足我的时候。试想下,我们发送的 UDP 数据包的负载(payload)是自定义内容,此时的 wireshark 只能看到 payload 的原始数据。虽然可以使用 wireshark dissastor 将协议可视化(参考:Wireshark C 插件开发之插件框架),但是工作量大且繁琐。那有没有一种相对方便的手段实现监控数据包并解析 payload 的功能呢?有!我们可以自己实现抓包程序。

2. wireshark 抓包原理

Document:

wireshark 调用 libpcap 实现监控网络数据包的功能。Win10 环境下,libpcap 的功能由 Npcap 提供。

3. Npcap 简介

Document:

Npcap 是专为 Windows 开发的一款网络抓包 SDK,该 SDK 提供了被应用程序调用的库文件和系统驱动程序。通过 Npcap,我们可以得到原始(raw)网络数据,即未经过 TCP/IP 协议栈的数据,也就是网卡收到的数据。同时呢,我们也可以通过 Npcap 设置接收过滤器,这样收到的数据就是我们感兴趣的数据,比如某个端口的数据。而且,Npcap 还提供发送原始(raw)网络数据的功能。

4. Npcap 原理

Document:

Npcap 实现了 Win10 驱动程序,叫做 NPF(Netgroup Packet Filter),该驱动从 Win10 miniport 驱动获取网卡数据实现监控网络数据包的功能(Win10 使用 miniport 驱动控制网卡)。

5. Npcap 使用

Npcap SDK 使用起来很简单,一共分为三步。

5.1 安装 visual studio

我使用的是 visual studio 2019。

5.2 安装 npcap 到 win10

安装 Npcap 1.71 installer 到 win10 系统中,主要是安装了 NPF 驱动和 dll 文件(Packet.dll 和 Wpcap.dll)。

下载地址:

5.3 下载 Npcap SDK

Npcap SDK 中提供了 lib 和头文件,我们编写抓包程序时需要用到这些。

5.4 例程

这里,我以 Npcap SDK 中的 npcap-sdk-1.13\Examples-pcap\UDPdump 为例进行说明,UDPdump 用于监控收到的 UDP 数据包。

5.4.1 udpdump.c

在原有文件的基础上我添加了 #pragma comment(lib,"ws2_32.lib") 语句,否则 ntohs() 会导致编译失败。

/*
 * Copyright (c) 1999 - 2005 NetGroup, Politecnico di Torino (Italy)
 * Copyright (c) 2005 - 2006 CACE Technologies, Davis (California)
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 * notice, this list of conditions and the following disclaimer in the
 * documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the Politecnico di Torino, CACE Technologies
 * nor the names of its contributors may be used to endorse or promote
 * products derived from this software without specific prior written
 * permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 */

#ifdef _MSC_VER
 /*
  * we do not want the warnings about the old deprecated and unsecure CRT functions
  * since these examples can be compiled under *nix as well
  */
#define _CRT_SECURE_NO_WARNINGS
#endif

#include <pcap.h>
#include <time.h>
#include <winsock.h>

#pragma comment(lib,"ws2_32.lib")

#ifdef _WIN32
#include <tchar.h>
BOOL LoadNpcapDlls()
{
    _TCHAR npcap_dir[512];
    UINT len;
    len = GetSystemDirectory(npcap_dir, 480);
    if (!len) {
        fprintf(stderr, "Error in GetSystemDirectory: %x", GetLastError());
        return FALSE;
    }
    _tcscat_s(npcap_dir, 512, _T("\\Npcap"));
    if (SetDllDirectory(npcap_dir) == 0) {
        fprintf(stderr, "Error in SetDllDirectory: %x", GetLastError());
        return FALSE;
    }
    return TRUE;
}
#endif

/* 4 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 (4 bits) + Internet header length (4 bits)
    u_char  tos;            // Type of service 
    u_short tlen;           // Total length 
    u_short identification; // Identification
    u_short flags_fo;       // Flags (3 bits) + Fragment offset (13 bits)
    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 + Padding
}ip_header;

/* UDP header*/
typedef struct udp_header
{
    u_short sport;          // Source port
    u_short dport;          // Destination port
    u_short len;            // Datagram length
    u_short crc;            // Checksum
}udp_header;

/* prototype of the packet handler */
void packet_handler(u_char* param, const struct pcap_pkthdr* header, const u_char* pkt_data);


int main()
{
    pcap_if_t* alldevs;
    pcap_if_t* d;
    int inum;
    int i = 0;
    pcap_t* adhandle;
    char errbuf[PCAP_ERRBUF_SIZE];
    u_int netmask;
    char packet_filter[] = "ip and udp";
    struct bpf_program fcode;

#ifdef _WIN32
    /* Load Npcap and its functions. */
    if (!LoadNpcapDlls())
    {
        fprintf(stderr, "Couldn't load Npcap\n");
        exit(1);
    }
#endif

    /* Retrieve the device list */
    if (pcap_findalldevs(&alldevs, errbuf) == -1)
    {
        fprintf(stderr, "Error in pcap_findalldevs: %s\n", errbuf);
        exit(1);
    }

    /* Print the list */
    for (d = alldevs; d; 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 Npcap is installed.\n");
        return -1;
    }

    printf("Enter the interface number (1-%d):", i);
    scanf("%d", &inum);

    /* Check if the user specified a valid adapter */
    if (inum < 1 || inum > i)
    {
        printf("\nAdapter number out of range.\n");

        /* Free the device list */
        pcap_freealldevs(alldevs);
        return -1;
    }

    /* Jump to the selected adapter */
    for (d = alldevs, i = 0; i < inum - 1; d = d->next, i++);

    /* Open the adapter */
    if ((adhandle = pcap_open_live(d->name, // name of the device
65536,          // portion of the packet to capture. 
// 65536 grants that the whole packet will be captured on all the MACs.
1,              // promiscuous mode (nonzero means promiscuous)
1000,           // read timeout
errbuf          // error buffer
)) == NULL)
    {
        fprintf(stderr, "\nUnable to open the adapter: %s\n", errbuf);
        /* Free the device list */
        pcap_freealldevs(alldevs);
        return -1;
    }

    /* Check the link layer. We support only Ethernet for simplicity. */
    if (pcap_datalink(adhandle) != DLT_EN10MB)
    {
        fprintf(stderr, "\nThis program works only on Ethernet networks.\n");
        /* Free the device list */
        pcap_freealldevs(alldevs);
        return -1;
    }

    if (d->addresses != NULL)
        /* Retrieve the mask of the first address of the interface */
        netmask = ((struct sockaddr_in*)(d->addresses->netmask))->sin_addr.S_un.S_addr;
    else
        /* If the interface is without addresses we suppose to be in a C class network */
        netmask = 0xffffff;


    //compile the filter
if (pcap_compile(adhandle, &fcode, packet_filter, 1, netmask) < 0)
    {
        fprintf(stderr, "\nUnable to compile the packet filter. Check the syntax.\n");
        /* Free the device list */
        pcap_freealldevs(alldevs);
        return -1;
    }

    //set the filter
if (pcap_setfilter(adhandle, &fcode) < 0)
    {
        fprintf(stderr, "\nError setting the filter.\n");
        /* Free the device list */
        pcap_freealldevs(alldevs);
        return -1;
    }

    printf("\nlistening on %s...\n", d->description);

    /* At this point, we don't need any more the device list. Free it */
    pcap_freealldevs(alldevs);

    /* start the capture */
    pcap_loop(adhandle, 0, packet_handler, NULL);

    return 0;
}

/* Callback function invoked by libpcap for every incoming packet */
void packet_handler(u_char* param, const struct pcap_pkthdr* header, const u_char* pkt_data)
{
    struct tm* ltime;
    char timestr[16];
    ip_header* ih;
    udp_header* uh;
    u_int ip_len;
    u_short sport, dport;
    time_t local_tv_sec;

    /*
     * unused parameter
     */
    (VOID)(param);

    /* convert the timestamp to readable format */
    local_tv_sec = header->ts.tv_sec;
    ltime = localtime(&local_tv_sec);
    strftime(timestr, sizeof timestr, "%H:%M:%S", ltime);

    /* print timestamp and length of the packet */
    printf("%s.%.6d len:%d ", timestr, header->ts.tv_usec, header->len);

    /* retireve the position of the ip header */
    ih = (ip_header*)(pkt_data +
        14); //length of ethernet header
/* retireve the position of the udp header */
    ip_len = (ih->ver_ihl & 0xf) * 4;
    uh = (udp_header*)((u_char*)ih + ip_len);

    /* convert from network byte order to host byte order */
    sport = ntohs(uh->sport);
    dport = ntohs(uh->dport);

    /* print ip addresses and udp ports */
    printf("%d.%d.%d.%d.%d -> %d.%d.%d.%d.%d\n",
        ih->saddr.byte1,
        ih->saddr.byte2,
        ih->saddr.byte3,
        ih->saddr.byte4,
        sport,
        ih->daddr.byte1,
        ih->daddr.byte2,
        ih->daddr.byte3,
        ih->daddr.byte4,
        dport);
}

5.4.2 visual studio 2019 工程设置

在 visual studio 工程中需要指定 Npcap SDK 中头文件和库文件的目录,以及需要链接的库文件。

5.4.2.1 指定头文件位置

5.4.2.2 指定库文件(lib)位置

5.4.2.2 指定链接的库

注意图中的“延迟加载的 DLL wpcap.dll”。如果系统中同时存在 WinPcap 和 Npcap 的情况,设置该选项,同时在代码中使用 SetDllDirectory() 设置 Npcap 的 dll 路径后(C:\Windows\System32\Npcap\),程序执行时会优先使用 Npcap 提供的 dll 文件。

5.5 执行

比如监控无线网卡收到的数据。

6. 总结

Npcap SDK 提供了多个例程,不只有在线监控,也可以读取离线文件。更多例程请查看 SDK 中的 Examples-pcap/ 目录。

转:https://zhuanlan.zhihu.com/p/573366847

posted @ 2023-03-16 09:31  rmticocean  阅读(1168)  评论(0编辑  收藏  举报