)
HidHide 驅動分析 - HidHideCLI 篇三設備枚舉與路徑轉換一、HID 設備枚舉的核心實現HID.cpp模塊實現了完整的設備枚舉與信息采集這些功能與 GUI 版本共享相同的核心邏輯但在輸出格式上存在差異——CLI 版本輸出 JSON而 GUI 版本填充樹形控件。1.1 設備枚舉入口HidDevices 函數FriendlyNamesAndHidDeviceInformationHidDevices(boolgamingDevicesOnly){BaseContainerDeviceInstancePathAndHidDeviceInformation baseContainerMap;IterateAllHidDevicesPresentOrNotPresent([](DeviceInstancePathconstpath,std::filesystem::pathconstsymlink){autoconstinfoHidModelInfo(path,symlink);baseContainerMap.emplace(std::make_pair(info.baseContainerDeviceInstancePath,info));returntrue;});// 按容器聚合生成友好名稱// ...}流程分為兩步枚舉所有 HID 設備包括已斷開但殘留的調用HidModelInfo獲取詳細信息。按baseContainerDeviceInstancePath基容器設備路徑聚合同一物理設備的多個 HID 子設備歸為一組。1.2 設備存在性檢測與符號鏈接獲取獲取 HID 接口 GUIDGUID hidDeviceInterfaceGuid;HidD_GetHidGuid(hidDeviceInterfaceGuid);枚舉設備實例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標志確保只返回 HID 類設備而CM_GETIDLIST_FILTER_PRESENT未被使用因此同時包含已連接和已斷開設備。設備存在性判斷boolDevicePresent(DeviceInstancePathconstpath){DEVINST devInst;autoresultCM_Locate_DevNodeW(devInst,path.c_str(),CM_LOCATE_DEVNODE_NORMAL);return(CR_SUCCESSresult);}CM_LOCATE_DEVNODE_NORMAL標志要求設備當前必須存在不能為幻影節點。1.3 設備詳細信息的采集HidModelInfo函數是信息采集的核心它打開設備的符號鏈接通過 HID API 獲取詳細信息打開設備autodeviceObjectCloseHandlePtr(CreateFileW(symbolicLink.c_str(),GENERIC_READ,(FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE),nullptr,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,nullptr),CloseHandle);當設備被其他應用獨占打開ERROR_ACCESS_DENIED或被 HidHide 自身隱藏ERROR_SHARING_VIOLATION時返回特殊的 “denied” 狀態標記。獲取 HID 屬性HIDD_ATTRIBUTES attributes;HidD_GetAttributes(deviceObject.get(),attributes);// attributes.VendorID, attributes.ProductID, attributes.VersionNumber獲取設備字符串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 用于判斷設備類型和生成可讀的用法名稱通過內置的字符串表資源。1.4 游戲設備判定邏輯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));}此判定邏輯覆蓋了絕大多數主流游戲外設包括標準 DirectInput 設備和特定廠商設備。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 管理器用于將屬于同一物理設備的所有子設備歸組的標識符。獲取基容器路徑DeviceInstancePathBaseContainerDeviceInstancePath(DeviceInstancePathconstpath){autoconstbaseContainerIdBaseContainerId(path);for(autoitpath;;){autoparentDeviceInstancePathParent(it);if(baseContainerIdBaseContainerId(parent))itparent;elsereturnit;}}沿設備樹向上追溯直到找到容器 ID 與當前設備不同的父節點該父節點即為基容器設備。獲取容器設備計數size_tBaseContainerDeviceCount(DeviceInstancePathconstpath){CM_Get_Child(devInstChild,devInst,0);for(size_t count1;;count){CM_Get_Sibling(devInstChild,devInstChild,0);// 到達末尾返回 count}}用于判斷基容器下的所有子設備是否均為 HID 設備從而決定是否可以整體隱藏該容器。二、路徑轉換子系統Volume 模塊Volume.cpp實現了邏輯盤符與 NT 設備路徑之間的雙向轉換這是白名單管理的核心依賴。2.1 完整路徑與全鏡像名的概念概念示例說明邏輯路徑C:\Program Files\app.exe用戶界面使用的盤符路徑全鏡像名Full Image Name\Device\HarddiskVolume1\Program Files\app.exe驅動白名單使用的 NT 設備路徑驅動使用全鏡像名是因為盤符是邏輯概念在系統啟動早期或不同用戶會話中可能變化而設備路徑是穩定的。2.2 邏輯路徑 → 全鏡像名FileNameToFullImageNameFullImageNameFileNameToFullImageName(std::filesystem::pathconstfullyQualifiedFileName){autoconstvolumeMountPointFindVolumeMountPointForFullyQualifiedFileName(fullyQualifiedFileName);if(volumeMountPoint.empty())return{};autoconstdosDeviceNameDosDeviceNameForVolumeName(VolumeNameForVolumeMountPoint(volumeMountPoint));autoconstfileNameWithoutMountPointfullyQualifiedFileName.native().substr(volumeMountPoint.native().size());returndosDeviceName/fileNameWithoutMountPoint;}實現步驟FindVolumeMountPointForFullyQualifiedFileName遍歷所有卷掛載點找到最長匹配的邏輯路徑前綴如C:\。VolumeNameForVolumeMountPoint將掛載點如C:\轉換為卷名稱如\\?\Volume{...}。DosDeviceNameForVolumeName將卷名稱轉換為 NT 設備名稱如\Device\HarddiskVolume1。拼接設備名稱和文件相對路徑得到完整鏡像名。2.3 全鏡像名 → 邏輯路徑FullImageNameToFileNamestd::filesystem::pathFullImageNameToFileName(FullImageNameconstfullImageName){autoconst[volumeMountPoint,dosDeviceName]FindVolumeMountPointAndDosDeviceNameForFullImageName(fullImageName);if(volumeMountPoint.empty())return{};autoconstfullImageNameWithoutDosDeviceNamefullImageName.native().substr(dosDeviceName.native().size()1);returnvolumeMountPoint/fullImageNameWithoutDosDeviceName;}實現步驟FindVolumeMountPointAndDosDeviceNameForFullImageName遍歷所有卷找到與全鏡像名前綴匹配的卷。剝離設備名稱前綴得到相對路徑。拼接掛載點邏輯盤符與相對路徑。2.4 遍歷所有卷的機制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枚舉系統中的所有卷GetVolumePathNamesForVolumeNameW獲取每個卷的所有掛載點包括盤符和掛載文件夾。2.5 盤符變化的魯棒性由于轉換過程不依賴盤符的靜態映射即使系統盤符發生變化如 D: 變為 E:只要 NT 設備名稱不變白名單依然能正確匹配。這一設計使得 HidHide 的配置在系統盤符變更后依然有效。