動(dòng)實(shí)例分析系列: HidHide驅(qū)動(dòng)分析-HidHideCLI 篇(三))
HidHide 驅(qū)動(dòng)分析 - HidHideCLI 篇三設(shè)備枚舉與路徑轉(zhuǎn)換一、HID 設(shè)備枚舉的核心實(shí)現(xiàn)HID.cpp模塊實(shí)現(xiàn)了完整的設(shè)備枚舉與信息采集這些功能與 GUI 版本共享相同的核心邏輯但在輸出格式上存在差異——CLI 版本輸出 JSON而 GUI 版本填充樹(shù)形控件。1.1 設(shè)備枚舉入口HidDevices 函數(shù)FriendlyNamesAndHidDeviceInformationHidDevices(boolgamingDevicesOnly){BaseContainerDeviceInstancePathAndHidDeviceInformation baseContainerMap;IterateAllHidDevicesPresentOrNotPresent([](DeviceInstancePathconstpath,std::filesystem::pathconstsymlink){autoconstinfoHidModelInfo(path,symlink);baseContainerMap.emplace(std::make_pair(info.baseContainerDeviceInstancePath,info));returntrue;});// 按容器聚合生成友好名稱// ...}流程分為兩步枚舉所有 HID 設(shè)備包括已斷開(kāi)但殘留的調(diào)用HidModelInfo獲取詳細(xì)信息。按baseContainerDeviceInstancePath基容器設(shè)備路徑聚合同一物理設(shè)備的多個(gè) HID 子設(shè)備歸為一組。1.2 設(shè)備存在性檢測(cè)與符號(hào)鏈接獲取獲取 HID 接口 GUIDGUID hidDeviceInterfaceGuid;HidD_GetHidGuid(hidDeviceInterfaceGuid);枚舉設(shè)備實(shí)例DeviceInstancePathsDeviceInstancePathsPresentOrNot(GUIDconstclassGuid){CM_Get_Device_ID_List_SizeW(needed,GuidToString(classGuid).c_str(),CM_GETIDLIST_FILTER_CLASS);std::vectorWCHARbuffer(needed);CM_Get_Device_ID_ListW(GuidToString(classGuid).c_str(),buffer.data(),needed,CM_GETIDLIST_FILTER_CLASS);returnStringListToStringSet(MultiStringToStringList(buffer));}CM_GETIDLIST_FILTER_CLASS標(biāo)志確保只返回 HID 類設(shè)備而CM_GETIDLIST_FILTER_PRESENT未被使用因此同時(shí)包含已連接和已斷開(kāi)設(shè)備。設(shè)備存在性判斷boolDevicePresent(DeviceInstancePathconstpath){DEVINST devInst;autoresultCM_Locate_DevNodeW(devInst,path.c_str(),CM_LOCATE_DEVNODE_NORMAL);return(CR_SUCCESSresult);}CM_LOCATE_DEVNODE_NORMAL標(biāo)志要求設(shè)備當(dāng)前必須存在不能為幻影節(jié)點(diǎn)。1.3 設(shè)備詳細(xì)信息的采集HidModelInfo函數(shù)是信息采集的核心它打開(kāi)設(shè)備的符號(hào)鏈接通過(guò) HID API 獲取詳細(xì)信息打開(kāi)設(shè)備autodeviceObjectCloseHandlePtr(CreateFileW(symbolicLink.c_str(),GENERIC_READ,(FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE),nullptr,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,nullptr),CloseHandle);當(dāng)設(shè)備被其他應(yīng)用獨(dú)占打開(kāi)ERROR_ACCESS_DENIED或被 HidHide 自身隱藏ERROR_SHARING_VIOLATION時(shí)返回特殊的 “denied” 狀態(tài)標(biāo)記。獲取 HID 屬性HIDD_ATTRIBUTES attributes;HidD_GetAttributes(deviceObject.get(),attributes);// attributes.VendorID, attributes.ProductID, attributes.VersionNumber獲取設(shè)備字符串HidD_GetProductString(deviceObject.get(),buffer.data(),buffer.size());HidD_GetManufacturerString(deviceObject.get(),buffer.data(),buffer.size());HidD_GetSerialNumberString(deviceObject.get(),buffer.data(),buffer.size());獲取 HID 能力Usage Page / UsagePHIDP_PREPARSED_DATA preParsedData;HidD_GetPreparsedData(deviceObject.get(),preParsedData);HIDP_CAPS capabilities;HidP_GetCaps(preParsedData,capabilities);Usage Page 和 Usage 用于判斷設(shè)備類型和生成可讀的用法名稱通過(guò)內(nèi)置的字符串表資源。1.4 游戲設(shè)備判定邏輯boolGamingDevice(HIDD_ATTRIBUTESconstattributes,HIDP_CAPSconstcapabilities){// Valve Steam 控制器特殊處理if((attributes.VendorID0x28DE)(attributes.ProductID0x1205))returntrue;if((attributes.VendorID0x28DE)(attributes.ProductID0x1142))returntrue;// Usage Page 0x05游戲控制器或 Usage Page 0x01 下的 Usage 0x04搖桿/ 0x05游戲手柄return(0x05capabilities.UsagePage)||(0x01capabilities.UsagePage(0x04capabilities.Usage||0x05capabilities.Usage));}此判定邏輯覆蓋了絕大多數(shù)主流游戲外設(shè)包括標(biāo)準(zhǔn) DirectInput 設(shè)備和特定廠商設(shè)備。1.5 容器聚合Base Container獲取容器 IDGUIDBaseContainerId(DeviceInstancePathconstpath){DEVINST devInst;CM_Locate_DevNodeW(devInst,path.c_str(),CM_LOCATE_DEVNODE_PHANTOM);CM_Get_DevNode_PropertyW(devInst,DEVPKEY_Device_ContainerId,devPropType,...);}容器 ID 是 PnP 管理器用于將屬于同一物理設(shè)備的所有子設(shè)備歸組的標(biāo)識(shí)符。獲取基容器路徑DeviceInstancePathBaseContainerDeviceInstancePath(DeviceInstancePathconstpath){autoconstbaseContainerIdBaseContainerId(path);for(autoitpath;;){autoparentDeviceInstancePathParent(it);if(baseContainerIdBaseContainerId(parent))itparent;elsereturnit;}}沿設(shè)備樹(shù)向上追溯直到找到容器 ID 與當(dāng)前設(shè)備不同的父節(jié)點(diǎn)該父節(jié)點(diǎn)即為基容器設(shè)備。獲取容器設(shè)備計(jì)數(shù)size_tBaseContainerDeviceCount(DeviceInstancePathconstpath){CM_Get_Child(devInstChild,devInst,0);for(size_t count1;;count){CM_Get_Sibling(devInstChild,devInstChild,0);// 到達(dá)末尾返回 count}}用于判斷基容器下的所有子設(shè)備是否均為 HID 設(shè)備從而決定是否可以整體隱藏該容器。二、路徑轉(zhuǎn)換子系統(tǒng)Volume 模塊Volume.cpp實(shí)現(xiàn)了邏輯盤符與 NT 設(shè)備路徑之間的雙向轉(zhuǎn)換這是白名單管理的核心依賴。2.1 完整路徑與全鏡像名的概念概念示例說(shuō)明邏輯路徑C:\Program Files\app.exe用戶界面使用的盤符路徑全鏡像名Full Image Name\Device\HarddiskVolume1\Program Files\app.exe驅(qū)動(dòng)白名單使用的 NT 設(shè)備路徑驅(qū)動(dòng)使用全鏡像名是因?yàn)楸P符是邏輯概念在系統(tǒng)啟動(dòng)早期或不同用戶會(huì)話中可能變化而設(shè)備路徑是穩(wěn)定的。2.2 邏輯路徑 → 全鏡像名FileNameToFullImageNameFullImageNameFileNameToFullImageName(std::filesystem::pathconstfullyQualifiedFileName){autoconstvolumeMountPointFindVolumeMountPointForFullyQualifiedFileName(fullyQualifiedFileName);if(volumeMountPoint.empty())return{};autoconstdosDeviceNameDosDeviceNameForVolumeName(VolumeNameForVolumeMountPoint(volumeMountPoint));autoconstfileNameWithoutMountPointfullyQualifiedFileName.native().substr(volumeMountPoint.native().size());returndosDeviceName/fileNameWithoutMountPoint;}實(shí)現(xiàn)步驟FindVolumeMountPointForFullyQualifiedFileName遍歷所有卷掛載點(diǎn)找到最長(zhǎng)匹配的邏輯路徑前綴如C:\。VolumeNameForVolumeMountPoint將掛載點(diǎn)如C:\轉(zhuǎn)換為卷名稱如\\?\Volume{...}。DosDeviceNameForVolumeName將卷名稱轉(zhuǎn)換為 NT 設(shè)備名稱如\Device\HarddiskVolume1。拼接設(shè)備名稱和文件相對(duì)路徑得到完整鏡像名。2.3 全鏡像名 → 邏輯路徑FullImageNameToFileNamestd::filesystem::pathFullImageNameToFileName(FullImageNameconstfullImageName){autoconst[volumeMountPoint,dosDeviceName]FindVolumeMountPointAndDosDeviceNameForFullImageName(fullImageName);if(volumeMountPoint.empty())return{};autoconstfullImageNameWithoutDosDeviceNamefullImageName.native().substr(dosDeviceName.native().size()1);returnvolumeMountPoint/fullImageNameWithoutDosDeviceName;}實(shí)現(xiàn)步驟FindVolumeMountPointAndDosDeviceNameForFullImageName遍歷所有卷找到與全鏡像名前綴匹配的卷。剝離設(shè)備名稱前綴得到相對(duì)路徑。拼接掛載點(diǎn)邏輯盤符與相對(duì)路徑。2.4 遍歷所有卷的機(jī)制voidIterateAllVolumeMountPointsForFileStorage(IterateAllVolumeMountPointsForFileStorageCallback cb){std::vectorWCHARvolumeName(UNICODE_STRING_MAX_CHARS);autofindVolumeClosePtrFindVolumeClosePtr(FindFirstVolumeW(volumeName.data(),...),FindVolumeClose);while(true){for(autoconstvolumeMountPoint:VolumeMountPoints(volumeName.data())){if(!cb(volumeName.data(),volumeMountPoint))return;}if(!FindNextVolumeW(...))break;}}FindFirstVolumeW和FindNextVolumeW枚舉系統(tǒng)中的所有卷GetVolumePathNamesForVolumeNameW獲取每個(gè)卷的所有掛載點(diǎn)包括盤符和掛載文件夾。2.5 盤符變化的魯棒性由于轉(zhuǎn)換過(guò)程不依賴盤符的靜態(tài)映射即使系統(tǒng)盤符發(fā)生變化如 D: 變?yōu)?E:只要 NT 設(shè)備名稱不變白名單依然能正確匹配。這一設(shè)計(jì)使得 HidHide 的配置在系統(tǒng)盤符變更后依然有效。