自Windows 10 1809起,通过USB HID设备创建文件失败,访问被拒绝(5)

时间:2018-12-13 12:01:34

标签: c windows winapi windows-10 usb

自从Windows 10 1809最新更新以来,我们无法再使用CreateFile打开我们类似USB HID键盘的设备。我们将此问题简化为以下最小示例:

#include <windows.h>
#include <setupapi.h>
#include <stdio.h>
#include <hidsdi.h>

void bad(const char *msg) {
    DWORD w = GetLastError();
    fprintf(stderr, "bad: %s, GetLastError() == 0x%08x\n", msg, (unsigned)w);
}

int main(void) {
    int i;
    GUID hidGuid;
    HDEVINFO deviceInfoList;
    const size_t DEVICE_DETAILS_SIZE = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA) + MAX_PATH;
    SP_DEVICE_INTERFACE_DETAIL_DATA *deviceDetails = alloca(DEVICE_DETAILS_SIZE);
    deviceDetails->cbSize = sizeof(*deviceDetails);

    HidD_GetHidGuid(&hidGuid);
    deviceInfoList = SetupDiGetClassDevs(&hidGuid, NULL, NULL,
                                         DIGCF_PRESENT | DIGCF_INTERFACEDEVICE);
    if(deviceInfoList == INVALID_HANDLE_VALUE) {
        bad("SetupDiGetClassDevs");
        return 1;
    }

    for (i = 0; ; ++i) {
        SP_DEVICE_INTERFACE_DATA deviceInfo;
        DWORD size = DEVICE_DETAILS_SIZE;
        HIDD_ATTRIBUTES deviceAttributes;
        HANDLE hDev = INVALID_HANDLE_VALUE;

        fprintf(stderr, "Trying device %d\n", i);
        deviceInfo.cbSize = sizeof(deviceInfo);
        if (!SetupDiEnumDeviceInterfaces(deviceInfoList, 0, &hidGuid, i,
                                         &deviceInfo)) {
            if (GetLastError() == ERROR_NO_MORE_ITEMS) {
                break;
            } else {
                bad("SetupDiEnumDeviceInterfaces");
                continue;
            }
        }

        if(!SetupDiGetDeviceInterfaceDetail(deviceInfoList, &deviceInfo,
                                        deviceDetails, size, &size, NULL)) {
            bad("SetupDiGetDeviceInterfaceDetail");
            continue;
        }

        fprintf(stderr, "Opening device %s\n", deviceDetails->DevicePath);
        hDev = CreateFile(deviceDetails->DevicePath, 0,
                          FILE_SHARE_READ | FILE_SHARE_WRITE, NULL,
                          OPEN_EXISTING, 0, NULL);
        if(hDev == INVALID_HANDLE_VALUE) {
            bad("CreateFile");
            continue;
        }

        deviceAttributes.Size = sizeof(deviceAttributes);
        if(HidD_GetAttributes(hDev, &deviceAttributes)) {
            fprintf(stderr, "VID = %04x PID = %04x\n", (unsigned)deviceAttributes.VendorID, (unsigned)deviceAttributes.ProductID);
        } else {
            bad("HidD_GetAttributes");
        }
        CloseHandle(hDev);
    }

    SetupDiDestroyDeviceInfoList(deviceInfoList);
    return 0;
}

它枚举了所有HID设备,试图在CreateFile提供的路径上使用SetupDiGetDeviceInterfaceDetail,然后调用HidD_GetAttributes,以获得每个设备的供应商ID /产品ID。

此代码可以在以前的Windows版本上运行(在Windows 7,Windows 10 1709和1803上进行了测试,并且从中提取原始代码,因为始终从XP开始运行,所以没有问题),但是都具有最新更新(1809) 键盘设备(包括我们的设备)无法打开,因为CreateFile失败,访问被拒绝(GetLastError() == 5)。以管理员身份运行该程序没有任何效果。

比较更新前后的输出,我注意到现在无法打开的设备在设备路径中获得了尾随\kbd,即以前的状态

\\?\hid#vid_24d6&pid_8000&mi_00#7&294a3305&0&0000#{4d1e55b2-f16f-11cf-88cb-001111000030}

现在是

\\?\hid#vid_24d6&pid_8000&mi_00#7&294a3305&0&0000#{4d1e55b2-f16f-11cf-88cb-001111000030}\kbd

最新的Windows 10版本是否存在错误/新的安全限制?这段代码是否总是错误并且以前是偶然的?这个可以解决吗?


更新

作为一个绝望的尝试,我们尝试从返回的字符串中删除\kbd ...,现在CreateFile可以工作了!因此,现在我们有了一个解决方法,但是了解这是否是SetupDiGetDeviceInterfaceDetail中的错误,是否有意为之以及该解决方法是否实际上是正确的做法,将会很有趣。

3 个答案:

答案 0 :(得分:12)

我认为这是最新Windows 10版本中的新安全限制。

我正在寻找字符串KBD(采用UTF-16格式)-它仅存在于1809版本的两个驱动程序中,分别是 hidclass.sys kbdhid.sys ,并且在1709版本中不存在。

hidclass.sys 中,他们更改了HidpRegisterDeviceInterface函数。在此版本之前,它使用IoRegisterDeviceInterface调用了GUID_DEVINTERFACE_HID,并且 ReferenceString 指针设置为0。但是在新版本中,取决于GetHidClassCollection的结果,它将通过KBD作为 ReferenceString 指针。

kbdhid.sys 内部,他们更改了KbdHid_Create,这是对KBD字符串的检查,以返回错误(访问被拒绝或共享冲突)。

要更确切地了解原因,需要进行更多研究。一些分歧:

enter image description here enter image description here enter image description here


供参考,建于1709年的HidpRegisterDeviceInterface

enter image description here

此处 ReferenceString ==始终为0( xor r8d,r8d ),并且对类集合数据没有检查cmp word [rbp + a],6


但是,1809年的KbdHid_Create包含一个错误。代码是:

NTSTATUS KbdHid_Create(PDEVICE_OBJECT DeviceObject, PIRP Irp)
{
  //...

    PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp);

    if (PFILE_OBJECT FileObject = IrpSp->FileObject)
    {
        PCUNICODE_STRING FileName = &FileObject->FileName;

        if (FileName->Length)
        {
        #if ver == 1809
            UNICODE_STRING KBD = RTL_CONSTANT_STRING(L"KBD"); // !! bug !!

            NTSTATUS status = RtlEqualUnicodeString(FileName, &KBD, FALSE)
                ? STATUS_SHARING_VIOLATION : STATUS_ACCESS_DENIED;
        #else
            NTSTATUS status = STATUS_ACCESS_DENIED;
        #endif

            // log

            Irp->IoStatus.Status = status;
            IofCompleteRequest(Irp, IO_NO_INCREMENT);
            return status;
        }
    }
    // ...
}

此功能在这里试图做什么?它从 Irp 当前堆栈位置查找传递的PFILE_OBJECT FileObject。没有提供FileObject或名称为空,允许打开;否则,打开失败。

在1809年之前,它总是失败,并显示错误STATUS_ACCESS_DENIED0xc0000022),但是从1809年开始,将检查名称,如果名称等于KBD(区分大小写),则会出现另一个错误-返回STATUS_SHARING_VIOLATION。但是,名称始终以\符号开头,因此它永远不会与KBD匹配。它可以是\KBD,因此,要修复此检查,需要将以下行更改为:

UNICODE_STRING KBD = RTL_CONSTANT_STRING(L"\\KBD");

并使用此字符串执行比较。因此,通过设计,当尝试通过STATUS_SHARING_VIOLATION名称打开键盘设备时,应该出现*\KBD错误,但是由于实现错误,我们实际上在这里出现了STATUS_ACCESS_DENIED

enter image description here

另一项更改是HidpRegisterDeviceInterface-在设备上调用IoRegisterDeviceInterface之前,它会查询GetHidClassCollection结果,以及是否在WORD(2字节)字段中结构等于6,加上后缀KBD ReferenceString )。我猜(但我不确定)6可以是Usage ID for keyboard,并且此前缀的基本原理是设置互斥访问模式


实际上,如果我们使用通过\打开的相对设备,则可以使FileName以OBJECT_ATTRIBUTES开头。因此,仅出于测试目的,我们可以执行以下操作:如果接口名称以\KBD结尾,则首先打开不带后缀的文件(因此,相对设备名称为空),并且此打开必须可以;然后,我们可以尝试使用名称为KBD的相对打开文件-我们必须在1809年获得STATUS_SHARING_VIOLATION,并在以前的版本中获得STATUS_ACCESS_DENIED(但是在这里我们将没有\KBD后缀) :

void TestOpen(PWSTR pszDeviceInterface)
{
    HANDLE hFile;

    if (PWSTR c = wcsrchr(pszDeviceInterface, '\\'))
    {
        static const UNICODE_STRING KBD = RTL_CONSTANT_STRING(L"KBD");

        if (!wcscmp(c + 1, KBD.Buffer))
        {
            *c = 0;

            OBJECT_ATTRIBUTES oa = { sizeof(oa), 0, const_cast<PUNICODE_STRING>(&KBD) };

            oa.RootDirectory = CreateFileW(pszDeviceInterface, 0, 
                FILE_SHARE_VALID_FLAGS, 0, OPEN_EXISTING, 0, 0);

            if (oa.RootDirectory != INVALID_HANDLE_VALUE)
            {
                IO_STATUS_BLOCK iosb;

                // will be STATUS_SHARING_VIOLATION (c0000043)
                NTSTATUS status = NtOpenFile(&hFile, SYNCHRONIZE, &oa, &iosb, 
                    FILE_SHARE_VALID_FLAGS, FILE_SYNCHRONOUS_IO_NONALERT);

                CloseHandle(oa.RootDirectory);

                if (0 <= status)
                {
                    PrintAttr(hFile);
                    CloseHandle(hFile);
                }
            }

            return ;
        }
    }

    hFile = CreateFileW(pszDeviceInterface, 0, 
         FILE_SHARE_VALID_FLAGS, 0, OPEN_EXISTING, 0, 0);

    if (hFile != INVALID_HANDLE_VALUE)
    {
        PrintAttr(hFile);
        CloseHandle(hFile);
    }
}
void PrintAttr(HANDLE hFile)
{
    HIDD_ATTRIBUTES deviceAttributes = { sizeof(deviceAttributes) };

    if(HidD_GetAttributes(hFile, &deviceAttributes)) {
        printf("VID = %04x PID = %04x\r\n", 
            (ULONG)deviceAttributes.VendorID, (ULONG)deviceAttributes.ProductID);
    } else {
        bad(L"HidD_GetAttributes");
    }
}

在1809年的一次测试中,我实际上得到了STATUS_SHARING_VIOLATION,它还显示了kbdhid.KbdHid_Create中的另一个错误-如果我们检查FileName,我们需要检查RelatedFileObject-是吗还是0。


此外,与错误无关,但建议:使用CM_Get_Device_Interface_List代替SetupAPI更为有效:

volatile UCHAR guz = 0;

CONFIGRET EnumInterfaces(PGUID InterfaceClassGuid)
{
    CONFIGRET err;

    PVOID stack = alloca(guz);
    ULONG BufferLen = 0, NeedLen = 256;

    union {
        PVOID buf;
        PWSTR pszDeviceInterface;
    };

    for(;;) 
    {
        if (BufferLen < NeedLen)
        {
            BufferLen = RtlPointerToOffset(buf = alloca((NeedLen - BufferLen) * sizeof(WCHAR)), stack) / sizeof(WCHAR);
        }

        switch (err = CM_Get_Device_Interface_ListW(InterfaceClassGuid, 
            0, pszDeviceInterface, BufferLen, CM_GET_DEVICE_INTERFACE_LIST_PRESENT))
        {
        case CR_BUFFER_SMALL:
            if (err = CM_Get_Device_Interface_List_SizeW(&NeedLen, InterfaceClassGuid, 
                0, CM_GET_DEVICE_INTERFACE_LIST_PRESENT))
            {
        default:
            return err;
            }
            continue;

        case CR_SUCCESS:

            while (*pszDeviceInterface)
            {
                TestOpen(pszDeviceInterface);

                pszDeviceInterface += 1 + wcslen(pszDeviceInterface);
            }
            return 0;
        }
    }
}

EnumInterfaces(const_cast<PGUID>(&GUID_DEVINTERFACE_HID));

答案 1 :(得分:5)

此修复程序在今天(2019年3月1日)发布的Windows更新中。

https://support.microsoft.com/en-us/help/4482887/windows-10-update-kb4482887

答案 2 :(得分:0)

您可以在Delphi-Praxis in German

找到解决方法

简而言之:更改单元JvHidControllerClass

    if not HidD_GetAttributes(HidFileHandle, FAttributes) then
  raise EControllerError.CreateRes(@RsEDeviceCannotBeIdentified);

    HidD_GetAttributes(HidFileHandle, FAttributes);

并通过运行JEDI Install EXE重新编译Delhi JCL和JCVL组件。