如何在 NDIS 6 过滤器驱动程序中启用 802.11 监控模式 (DOT11_OPERATION_MODE_NETWORK_MONITOR)?

How to enable 802.11 monitor mode (DOT11_OPERATION_MODE_NETWORK_MONITOR) in a NDIS 6 filter driver?

我已将 WinPcap 移植到 NDIS 6 过滤器驱动程序https://github.com/nmap/npcap。但它仍然不支持捕获所有 802.11 本机数据包(如未捕获控制和管理帧)。

我注意到有一种方法可以使用 WlanSetInterface 函数为无线适配器设置 DOT11_OPERATION_MODE_NETWORK_MONITOR。但是这个调用成功了(return 值是可以的,并且我的 wi-fi 网络在这个调用之后断开连接)。但问题是 我无法使用 Wireshark 在 Wi-Fi 接口上看到任何数据包,甚至连假以太网形式的 802.11 数据也看不到。所以一定是有问题。

我知道从 NDIS 6 和 vista 开始,启用此功能是 可能的(至少微软自己的 Network Monitor 3.4 支持此功能) .

所以我想知道如何为 NDIS 6 版本的 WinPcap 启用监控模式?谢谢

我的代码如下:

// WlanTest.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"

#include <conio.h>
#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
#include <wlanapi.h>

#define WLAN_CLIENT_VERSION_VISTA 2

void SetInterface(WLAN_INTF_OPCODE opcode, PVOID* pData, GUID* InterfaceGuid)
{
    DWORD dwResult = 0;
    HANDLE hClient = NULL;
    DWORD dwCurVersion = 0;
    DWORD outsize = 0;

    // Open Handle for the set operation
    dwResult = WlanOpenHandle(WLAN_CLIENT_VERSION_VISTA, NULL, &dwCurVersion, &hClient);
    dwResult = WlanSetInterface(hClient, InterfaceGuid, opcode, sizeof(ULONG), pData, NULL);
    WlanCloseHandle(hClient, NULL);

}

// enumerate wireless interfaces
UINT EnumInterface(HANDLE hClient, WLAN_INTERFACE_INFO sInfo[64])
{
    DWORD dwError = ERROR_SUCCESS;
    PWLAN_INTERFACE_INFO_LIST pIntfList = NULL;
    UINT i = 0;

    __try
    {
        // enumerate wireless interfaces
        if ((dwError = WlanEnumInterfaces(
            hClient,
            NULL,               // reserved
            &pIntfList
            )) != ERROR_SUCCESS)
        {
            __leave;
        }

        // print out interface information
        for (i = 0; i < pIntfList->dwNumberOfItems; i++)
        {
            memcpy(&sInfo[i], &pIntfList->InterfaceInfo[i], sizeof(WLAN_INTERFACE_INFO));
        }

        return pIntfList->dwNumberOfItems;
    }
    __finally
    {
        // clean up
        if (pIntfList != NULL)
        {
            WlanFreeMemory(pIntfList);
        }
    }
    return 0;
}

// open a WLAN client handle and check version
DWORD
OpenHandleAndCheckVersion(
    PHANDLE phClient
    )
{
    DWORD dwError = ERROR_SUCCESS;
    DWORD dwServiceVersion;
    HANDLE hClient = NULL;

    __try
    {
        *phClient = NULL;

        // open a handle to the service
        if ((dwError = WlanOpenHandle(
            WLAN_API_VERSION,
            NULL,               // reserved
            &dwServiceVersion,
            &hClient
            )) != ERROR_SUCCESS)
        {
            __leave;
        }

        // check service version
        if (WLAN_API_VERSION_MAJOR(dwServiceVersion) < WLAN_API_VERSION_MAJOR(WLAN_API_VERSION_2_0))
        {
            // No-op, because the version check is for demonstration purpose only.
            // You can add your own logic here.
        }

        *phClient = hClient;

        // set hClient to NULL so it will not be closed
        hClient = NULL;
    }
    __finally
    {
        if (hClient != NULL)
        {
            // clean up
            WlanCloseHandle(
                hClient,
                NULL            // reserved
                );
        }
    }

    return dwError;
}

// get interface state string
LPWSTR
GetInterfaceStateString(__in WLAN_INTERFACE_STATE wlanInterfaceState)
{
    LPWSTR strRetCode;

    switch (wlanInterfaceState)
    {
    case wlan_interface_state_not_ready:
        strRetCode = L"\"not ready\"";
        break;
    case wlan_interface_state_connected:
        strRetCode = L"\"connected\"";
        break;
    case wlan_interface_state_ad_hoc_network_formed:
        strRetCode = L"\"ad hoc network formed\"";
        break;
    case wlan_interface_state_disconnecting:
        strRetCode = L"\"disconnecting\"";
        break;
    case wlan_interface_state_disconnected:
        strRetCode = L"\"disconnected\"";
        break;
    case wlan_interface_state_associating:
        strRetCode = L"\"associating\"";
        break;
    case wlan_interface_state_discovering:
        strRetCode = L"\"discovering\"";
        break;
    case wlan_interface_state_authenticating:
        strRetCode = L"\"authenticating\"";
        break;
    default:
        strRetCode = L"\"invalid interface state\"";
    }

    return strRetCode;
}

int main()
{
    HANDLE hClient = NULL;
    WLAN_INTERFACE_INFO sInfo[64];
    RPC_CSTR strGuid = NULL;

    TCHAR szBuffer[256];
    DWORD dwRead;
    if (OpenHandleAndCheckVersion(&hClient) != ERROR_SUCCESS)
        return -1;

    UINT nCount = EnumInterface(hClient, sInfo);
    for (UINT i = 0; i < nCount; ++i)
    {
        if (UuidToStringA(&sInfo[i].InterfaceGuid, &strGuid) == RPC_S_OK)
        {
            printf(("%d. %s\n\tDescription: %S\n\tState: %S\n"),
                i,
                strGuid,
                sInfo[i].strInterfaceDescription,
                GetInterfaceStateString(sInfo[i].isState));

            RpcStringFreeA(&strGuid);
        }
    }

    UINT nChoice = 0;
//  printf("for choice wireless card:");
// 
//  if (ReadConsole(GetStdHandle(STD_INPUT_HANDLE), szBuffer, _countof(szBuffer), &dwRead, NULL) == FALSE)
//  {
//      puts("error input");
//      return -1;
//  }
//  szBuffer[dwRead] = 0;
//  nChoice = _ttoi(szBuffer);
// 
//  if (nChoice > nCount)
//  {
//      puts("error input.");
//      return -1;
//  }

    //ULONG targetOperationMode = DOT11_OPERATION_MODE_EXTENSIBLE_STATION;
    ULONG targetOperationMode = DOT11_OPERATION_MODE_NETWORK_MONITOR;

    SetInterface(wlan_intf_opcode_current_operation_mode, (PVOID*)&targetOperationMode, &sInfo[nChoice].InterfaceGuid);

    return 0;
}

更新:

Guy 已经让我清楚WinPcap 的高级库端应该如何处理监控模式,本质上是setting/getting OID 值。但是WinPcap驱动怎么办,需要换驱动吗?我认为 WlanSetInterface 调用实际上与使用 OID 请求设置 DOT11_OPERATION_MODE_NETWORK_MONITOR 做同样的事情?它不起作用是否意味着 npf 驱动程序也需要进行某种更改?

(针对问题更新和后续评论更新了答案。)

使用master分支libpcap版本pcap-win32.c中的pcap_oid_set_request_win32(),进行OIDsetting/getting操作。如果在 pcap_activate_win32() 中设置了 p->opt.rfmon,请将 OID OID_DOT11_CURRENT_OPERATION_MODE 设置为 DOT11_CURRENT_OPERATION_MODE structure,将 uCurrentOpMode 设置为 DOT11_OPERATION_MODE_NETWORK_MONITOR

对于 pcap_can_set_rfmon_win32(),尝试获取设备的句柄(注意这是在激活调用之前完成的),如果成功,则使用 pcap_oid_get_request_win32() 尝试 get那个OID的值;如果成功就可以设置,否则要么设置不上,要么报错。

驱动程序已经支持通用的 get/set OID 操作——这就是 PacketRequest() 使用的,并且 pcap_oid_get_request_win32()/pcap_oid_set_request_win32()PacketRequest() 之上实现,所以这就是他们使用的。

作为I indicated in messages in the thread you started on the wireshark-dev list, the code that handles receive indications from NDIS has to be able to handle "raw packet" receive indications, and you might have to add those to the NDIS packet filter as well. (And you'll have to hack dumpcap, if you're going to use Wireshark to test the changes;您将无法更改 NPcap,以便人们可以将其放入,现有版本的 Wireshark 将支持监控模式。)

我也指出了how to query a device to find out whether it supports monitor mode

至于关闭监控模式,这需要驱动程序、packet.dll 和 libpcap 工作。在驱动程序中:

  • 在 NDIS 6 驱动程序中,对于每个接口,都有一个 "monitor mode instances" 的计数和一个保存的操作模式,并且对于一个接口的每个打开的 NPF 实例,都有一个 "monitor mode" 标志;
  • 在 Windows 9x 和 NDIS 4/5 驱动程序中,添加一个 "turn on monitor mode" BIOC 调用,该调用总是失败 ERROR_NOT_SUPPORTED;
  • 在 NDIS 6 驱动程序中,添加相同的 BIOC 调用,如果未设置实例的 "monitor mode" 标志,则尝试将操作模式设置为监控模式,如果它成功,如果接口的监控模式计数为零,则保存旧的操作模式,增加接口的监控模式计数并设置实例的 "monitor mode" 标志(它也可以将适当的值添加到数据包过滤器);
  • 让关闭打开的 NPF 实例的例程检查实例的 "monitor mode" 标志,如果已设置,则减少 "monitor mode instances" 计数,如果计数达到零,则恢复旧的操作模式。

在 packet.dll 中,添加一个 PacketSetMonitorMode() 例程,它是对相关 BIOC ioctl 的包装。

pcap-win32.c 中,如果请求监控模式,则调用 PacketSetMonitorMode(),而不是直接设置操作模式。

要在驱动程序中设置 OID,请参阅 NPF_IoControl()BIOCQUERYOIDBIOCSETOID 的代码路径 - 新的 BIOC ioctl 将在 [=33= 中处理].

(当然,还要进行适当的 MP 锁定。)

如果您可以枚举接口的所有 NPF 实例,则可能不需要监控模式计数 - 计数只是设置了监控模式标志的实例数。

在驱动程序中执行此操作意味着如果执行 monitor-mode 捕获的程序突然终止,因此没有 user-mode 代码进行任何清理,模式仍然可以重置。