首页
社区
课程
招聘
[原创]Windows中动态禁用启用设备碰到的问题
发表于: 2025-10-28 00:52 443

[原创]Windows中动态禁用启用设备碰到的问题

2025-10-28 00:52
443

使用WDF驱动框架,设置为类过滤机制后,可以拦截到某类特定类型的设备。

碰到一个在DeviceAdd回调函数中,判断如果设备是黑名单设备,调用WdfDeviceSetDeviceState设置设备状态的问题,调用这个函数后,本驱动创建的过滤设备后续会被移除,并且这个设备也确实被禁用了,但是在用户层如果想restart这个函数就会出现问题,如果在用户层调用如下代码:

bool ResetDevice(HDEVINFO devs, PSP_DEVINFO_DATA devInfo)
{
    SP_PROPCHANGE_PARAMS pcParams;
    pcParams.ClassInstallHeader.cbSize = sizeof(SP_CLASSINSTALL_HEADER);
    pcParams.ClassInstallHeader.InstallFunction = DIF_PROPERTYCHANGE;
    pcParams.StateChange = DICS_PROPCHANGE;
    pcParams.Scope = DICS_FLAG_CONFIGSPECIFIC;
    pcParams.HwProfile = 0;
    
    if (!SetupDiSetClassInstallParams(devs, devInfo, &pcParams.ClassInstallHeader, sizeof(pcParams)) ||
        !SetupDiCallClassInstaller(DIF_PROPERTYCHANGE, devs, devInfo))
    {
        DWORD error = GetLastError();
        printf("error:%d\n", error);
        return false;
    }
    else
    {
        SP_DEVINSTALL_PARAMS devInstallParams;
        devInstallParams.cbSize = sizeof(devInstallParams);
        if (SetupDiGetDeviceInstallParams(devs, devInfo, &devInstallParams) && (devInstallParams.Flags & (DI_NEEDRESTART | DI_NEEDREBOOT)))
        {
            DWORD error = GetLastError();
            printf("error:%d\n", error);
            return false;
        }
    }

    return true;
}

使用这段代码时不会报错,但是并不会触发设备的软插拔,然后在UsbTreeView软件中看到对应的usb设备节点,右键restart此设备也会报提示CR_INVALID_DATA 错误码。

应该是 在错误的时间节点禁用设备导致设备的pnp状态冲突,设备后续只能硬插拔。


后续经过尝试,判断设备的context中记录的标志,在PrepareHardware事件函数中如果返回错误,也会导致设备状态错误无法restart,然后测试不处理PrepareHardware,在DoEntry事件中,判断标志,如果需要禁用,那么就返回错误码STATUS_DEVICE_NOT_READY,之后也会触发WDF框架设备对象的清除操作。


这种情况下,再在用户层去reset或者restart设备可以触发到软插拔,如果此时黑白名单策略更改,应用新的策略就可以动态修改设备的启用禁用状态。



驱动中获取到的deviceid和instanceid,其中instanceid是serialid,是相对总线的编号,而在用户层中调用CM_Get_Device_IDW获取到deviceid 是包括了一部分的harewareid以及instanceid,可能这个串中还包含了其他的数据。

动态启用禁用的机制,需要通过用户层枚举到设备id(部分的harewareid)、 instanceid、以及兼容id等属性,然后发送给驱动,通过驱动判断是否是黑白名单,根据deviceid和instanceid进行匹配可以 找到内存中维护的一些设备,比如一些usb的hub就不需要拦截不需要处理。


下面这部分代码是在用户层获取 各种id以及从deviceid中提取 前面的deviceid(部分harewardid)以及serialid

bool GetDeviceMultiStringProperty(SP_DEVINFO_DATA& deviceInfoData, DWORD property, const wchar_t* propertyName, std::wstring& value) {
        WCHAR buffer[4096] = { 0 };
        DWORD requiredSize = 0;
        DWORD dataType = 0;

        if (SetupDiGetDeviceRegistryPropertyW(
            m_deviceInfoSet,
            &deviceInfoData,
            property,
            &dataType,
            (PBYTE)buffer,
            sizeof(buffer),
            &requiredSize)) {

            WCHAR* currentString = buffer;
            int count = 0;
            while (*currentString != L'\0') {
                std::wcout << L"  [" << count << L"] " << currentString << std::endl;
                currentString += wcslen(currentString) + 1;
                count++;
            }
            if (0 != count)
            {
                value = std::wstring(buffer, requiredSize / sizeof(WCHAR));
                return true;
            }            

            if (count == 0) {
                std::wcout << L"  <Empty>" << std::endl;
            }
        }
        else {
            DWORD error = GetLastError();
            if (error != ERROR_INVALID_DATA) {
                std::wcout << std::setw(25) << std::left << propertyName << L": <Not available>";
                if (error == ERROR_INVALID_PARAMETER) {
                    std::wcout << L" (Property not supported)";
                }
                std::wcout << std::endl;
            }
            return false;
        }
        return false;
    }
    

    bool GetDeviceInfo(SP_DEVINFO_DATA& deviceInfoData, std::wstring& instanceid, std::wstring& hardwareids, std::wstring& compatibleids, std::wstring& device)
    {
        WCHAR deviceid[MAX_DEVICE_ID_LEN*2] = { 0 };        
        DWORD require_size = 0;
        if (SetupDiGetDeviceInstanceIdW(m_deviceInfoSet, &deviceInfoData, deviceid, ARRAYSIZE(deviceid), &require_size))
        {
            WCHAR* backslash = wcsrchr(deviceid, L'\\');
            if (NULL != backslash && GetDeviceMultiStringProperty(deviceInfoData, SPDRP_HARDWAREID, L"Hardware IDs", hardwareids))
            {
                const WCHAR* hardwareid = hardwareids.c_str();
                UINT64 deviceid_len = backslash - deviceid;                
                bool found = false;
                while (*hardwareid != L'\0') {
                    UINT64 hardwareid_len = wcslen(hardwareid);
                    if (deviceid_len == hardwareid_len && 0 == _wcsnicmp(deviceid, hardwareid, hardwareid_len))
                    {
                        found = true;
                        break;
                    }
                    hardwareid += hardwareid_len + 1;
                }
                if (found && GetDeviceMultiStringProperty(deviceInfoData, SPDRP_COMPATIBLEIDS, L"Compatible IDs", compatibleids))
                {
                    device = hardwareid;
                    const wchar_t* lastSep = backslash + 1;
                    const wchar_t separators[] = L"&#.-_";                    
                    for (UINT64 i = 0; i < wcslen(separators); i++)
                    {
                        lastSep = max(lastSep, wcsrchr(lastSep, separators[i]) + 1);
                    }
                    instanceid = lastSep;
                    return true;
                }
            }
        }
        return false;
    }

用户层发给驱动询问设备是否需要拦截,然后用户层再通过CM_Get_DevNode_Status获取设备状态,如果需要拦截且设备当前状态正常那么就reset,如果不需要拦截设备且当前设备状态不正常那么也会reset。


总结:

主要通过在设备启动阶段D0Entry中返回错误实现了禁用设备的功能,并且不会影响后续reset实现的动态禁用启用功能,禁用后设备管理器中也会显示设备但是有黄色感叹号表示状态异常,之所以通过用户层询问驱动是否拦截,是因为驱动之前读取了配置信息,缓存了下来,并且会及时更新策略,因此为了使用一套判断机制就通过用户层跟驱动通信的方式。


传播安全知识、拓宽行业人脉——看雪讲师团队等你加入!

收藏
免费 0
支持
分享
最新回复 (1)
雪    币: 2816
活跃值: (12072)
能力值: (RANK:385 )
在线值:
发帖
回帖
粉丝
2
谢谢分享思路.
2025-10-28 16:16
0
游客
登录 | 注册 方可回帖
返回