使用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实现的动态禁用启用功能,禁用后设备管理器中也会显示设备但是有黄色感叹号表示状态异常,之所以通过用户层询问驱动是否拦截,是因为驱动之前读取了配置信息,缓存了下来,并且会及时更新策略,因此为了使用一套判断机制就通过用户层跟驱动通信的方式。
传播安全知识、拓宽行业人脉——看雪讲师团队等你加入!