【问题标题】:How to open a handle to a device using its Device Instance ID?如何使用设备实例 ID 打开设备句柄?
【发布时间】:2020-01-14 21:18:54
【问题描述】:

我正在更新我的问题,以更好地反映我的实际目标。为了快速说明我最初的困惑,说“设备接口类 GUID”和Device Instance ID 之间存在一对一的关系是不正确的。一个设备可以有很多设备接口。正如 Ben Voigt 在 cmets 中所述,see this 了解更多信息。


如何在调用CM_Get_Child (...)函数后打开子设备的句柄?

以下面的代码片段为例:

#pragma comment (lib, "Setupapi.lib")
#pragma comment (lib, "Cfgmgr32.lib")

#include <iostream>
#include <Windows.h>
#include <Setupapi.h>
#include <Cfgmgr32.h> 

#define GUID_STRING_SIZE 40

int main ()
{
    CONFIGRET CMResult = CR_SUCCESS;
    WCHAR DeviceInstanceID[] = L"USB\\VID_2109&PID_0813\\8&216C1825&0&4\0"; // Parent Device Instance ID.

    DEVNODE ParentDeviceNode = (DWORD) 0; // A device instance handle. This handle is bounded to the local machine.
    CMResult = CM_Locate_DevNode ((PDEVINST) &ParentDeviceNode, DeviceInstanceID, CM_LOCATE_DEVNODE_NORMAL);

    if (CMResult != CR_SUCCESS)
    {
        std::cout << "No parent device node found." << std::endl;
        return -1;
    }
    else
    {
        DEVINST NextChildDeviceNode = (DWORD) 0;
        CMResult = CM_Get_Child ((PDEVINST) &NextChildDeviceNode, ParentDeviceNode, 0x0);    // Gets the first child of the parent node. If this returns "CR_NO_SUCH_DEVNODE," then there is no child attached.

        if (CMResult != CR_SUCCESS)
        {
            std::cout << "No child device node found." << std::endl;
            return -2;
        }
        else
        {
            ULONG ChildInstanceIDBuffLength = 0;
            CMResult = CM_Get_Device_ID_Size (&ChildInstanceIDBuffLength, NextChildDeviceNode, 0x0);

            if (CMResult != CR_SUCCESS)
            {
                std::cout << "Could not get the size of the device instance ID of child device." << std::endl;
                return -3;
            }
            else
            {
                WCHAR * ChildInstanceIDBuff = (WCHAR *) malloc (ChildInstanceIDBuffLength);
                CMResult = CM_Get_Device_IDW (NextChildDeviceNode, ChildInstanceIDBuff, ChildInstanceIDBuffLength, 0x0);

                if (CMResult != CR_SUCCESS)
                {
                    std::cout << "Could not actual device instance ID string of child device" << std::endl;
                    return -4;
                }
                else
                {
                    std::cout << "Found child device instance ID: ";
                    std::wcout << ChildInstanceIDBuff << std::endl;

                    /*
                     *  Open handle to the child device node now!
                     */
                }

                free (ChildInstanceIDBuff);
            }
        }
    }

    return 0;
}

如何使用新获得的子Device Instance ID 打开设备句柄? CreateFile (...) 需要完整的设备路径,其中包括缺少的“设备接口类 GUID”。

更具体地说,设备路径具有以下格式:
\\?\usb#vid_2109&amp;pid_0813#7&amp;3981C8D6&amp;0&amp;2#{[DEVICE_INTERFACE_GUID]},其中:

  1. [DEVICE_INTERFACE_GUID] - 这是“设备接口类 GUID”。这与“设备设置类 GUID相同。

如果没有某种程度的蛮力(例如CM_Enumerate_Classes (...) 使用 CM_ENUMERATE_CLASSES_INTERFACE 标志),似乎没有一种简单的方法来获得这个“设备接口类 GUID”。 我是否可以调用一个函数来仅使用设备的“设备实例 ID”来获取设备句柄,这样我就可以调用 DeviceIoControl (...) 并查询有关设备的信息?

【问题讨论】:

  • 一个设备可以实现多个设备接口...
  • @BenVoigt 那么,有没有办法获得这些接口的列表?我想知道的是,当我调用 CM_Get_Child (...) 时,我得到了子设备的“Device Instance Handle”(即可用于在设备树)。怎样才能得到完整的设备路径,才能真正在设备上打开一个文件句柄来查询信息?
  • 有一种方法,我找到过一次,但是比较隐蔽,不知道有没有保留代码。同时我建议你写你的问题不要假设实例 ID 到设备接口路径的 1:1 映射。
  • 你需要什么设备路径? DEVPKEY_Device_PDOName 适合你吗?我被问到 - 你是如何/从哪里获得 pDeviceID 的?那么你一开始有什么,最终目标是什么?

标签: windows setupapi


【解决方案1】:

如果我们需要Device Instance ID打开设备句柄 -

  • 首先调用CM_Locate_DevNodeW函数获取设备 与关联的设备节点的实例句柄 本地机器上指定的设备实例 ID。
  • 然后我们需要调用CM_Get_DevNode_PropertyW函数 DEVPKEY_Device_PDOName - 此返回代表设备的物理名称对象 (PDO) 的名称,我们可以在调用 NtOpenFile 中使用它。当然,如果非常想要 - 可以使用并打电话给CreateFileW,如果在名称中添加L"\\\\?\\Global\\GLOBALROOT",但这样做没有任何意义。

volatile UCHAR guz = 0;

ULONG OpenDeviceByDeviceID(_Out_ PHANDLE FileHandle, _In_ PWSTR DeviceID)
{
    DEVINST dnDevInst;

    CONFIGRET CmReturnCode = CM_Locate_DevNodeW(&dnDevInst, DeviceID, CM_LOCATE_DEVNODE_NORMAL);

    if (CmReturnCode == CR_SUCCESS)
    {
        ULONG cb = 0, rcb = 128;

        PVOID stack = alloca(guz);

        DEVPROPTYPE PropertyType;

        union {
            PVOID pv;
            PWSTR sz;
            PBYTE pb;
        };

        do 
        {
            if (cb < rcb)
            {
                rcb = cb = RtlPointerToOffset(pv = alloca(rcb - cb), stack);
            }

            CmReturnCode = CM_Get_DevNode_PropertyW(dnDevInst, 
                &DEVPKEY_Device_PDOName, &PropertyType, pb, &rcb, 0);

            if (CmReturnCode == CR_SUCCESS)
            {
                if (PropertyType == DEVPROP_TYPE_STRING)
                {
                    DbgPrint("PDOName = %S\n", sz);

#if 1
                    IO_STATUS_BLOCK iosb;
                    UNICODE_STRING ObjectName;
                    OBJECT_ATTRIBUTES oa = { sizeof(oa), 0, &ObjectName };
                    RtlInitUnicodeString(&ObjectName, sz);
                    NTSTATUS status = NtOpenFile(FileHandle, 
                        FILE_GENERIC_READ, &oa, &iosb, 0, 0);

                    return 0 > status ? RtlNtStatusToDosError(status) : NOERROR;
#else
                    static WCHAR prefix[] = L"\\\\?\\Global\\GLOBALROOT";
                    alloca(sizeof(prefix) - sizeof(WCHAR));

                    PWSTR fileName = sz - _countof(prefix) + 1;
                    memcpy(fileName, prefix, sizeof(prefix) - sizeof(WCHAR));

                    HANDLE hFile = CreateFileW(fileName, FILE_GENERIC_READ, 
                        0, 0, OPEN_EXISTING, 0, 0);
                    if (hFile == INVALID_HANDLE_VALUE)
                    {
                        return GetLastError();
                    }

                    *FileHandle = hFile;
                    return NOERROR;
#endif
                }
                else
                {
                    CmReturnCode = CR_WRONG_TYPE;
                }
            }

        } while (CmReturnCode == CR_BUFFER_SMALL);
    }

    return CM_MapCrToWin32Err(CmReturnCode, 0);
}

【讨论】:

  • 这里alloca的整个使用都非常脆弱
  • @BenVoigt - 认为这不是这里的重点 - 在堆栈或堆中分配内存。但是为什么你认为它是非常脆弱的
  • @BenVoigt - 否 - 在下一条指令 memcpy使用返回缓冲区。你的意思是它 implicit 使用,但无论如何语言不能放弃对 alloca 的调用,因为除了返回值之外它可能有其他副作用。所以我在这里看不到任何不安全的地方,但无论如何认为这不是关于 c++ 语言的问题,而是关于如何获取设备路径的问题。在这一点上你认为有什么问题吗?
  • @BenVoigt 已经没有说这是在非编译路径下。当我们已经有了 NT 路径时,我永远不会在这里使用 CreateFile 而不是 NtOpenFile。这只是为了演示如何将 NT-path 转换为 dos 路径
  • @BenVoigt - 放置金丝雀值 - 这是由/RTC (Run-Time Error Checks) 控制的运行时检查。如果我们关闭它 - 编译器将不会放置金丝雀值。此时我们可以完全控制编译器。当然,必须关闭此检查才能使用此类代码。真的当然代码好,我多年来在很多地方使用这样的代码没有问题。但这个问题与 c++ 无关。我只是为此准备了准备好的代码并复制粘贴它。 OP 可以使用另一个内存分配 - 这不是重点
【解决方案2】:

您可以使用CM_Enumerate_Classes functionCM_ENUMERATE_CLASSES_INTERFACE 标志(需要Windows 8)来获取可能的值作为SetupDiEnumDeviceInterfaces 的第三个参数传递。

从 Windows 8 和更高版本的操作系统开始,调用者可以使用 ulFlags 成员来指定应返回哪些设备类 CM_Enumerate_Classes。在 Windows 8 之前,CM_Enumerate_Classes 仅返回设备设置类。

请注意,查找设备上的所有接口对于诊断驱动程序问题和/或对从报废中挽救的随机外围设备进行逆向工程非常有用。但是在调用CreateFile 之前,您应该知道您正在处理的接口类。

【讨论】:

  • 我已经更新了我的问题,以更好地反映我想要了解的内容。看看它,让我知道你的想法。要评论带有CM_ENUMERATE_CLASSES_INTERFACE 标志的CM_Enumerate_Classes (...) 函数,是不是可能选择了错误的接口?例如,如果父 设备 A 有一个子 设备 B 使用 接口 D,但同时 设备 B 也是另一个设备的子设备,但是使用不同的接口 C,如果你只是一味地暴力破解,你会不会弄错句柄?
  • 或者这个子设备B会用两个唯一的实例ID枚举两次吗?
  • @CodeDoggo:设备只有一个父设备。您可以有两个接口,它们是同一设备的子设备,但该设备不能在层次结构中多次出现。
  • 那么,对于给定的设备,一次只能“激活”一个设备接口?
  • 我没这么说。实际上,枚举所有设备接口类对于确定设备是否支持多个接口非常有用。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多