windows 驱动实例分析系列: HidHide驱动分析-HidHideCLI 篇(三)
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 的配置在系统盘符变更后依然有效。

相关新闻

ActivePieces源码审阅:开源工作流引擎的工程架构与二次开发实践

ActivePieces源码审阅:开源工作流引擎的工程架构与二次开发实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/21 3:45:20 阅读更多 →
敏捷工具选型的五大核心指标与六大陷阱

敏捷工具选型的五大核心指标与六大陷阱

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/21 3:46:18 阅读更多 →
Spring Boot构建智慧宠物医疗系统架构与实践

Spring Boot构建智慧宠物医疗系统架构与实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/21 3:45:20 阅读更多 →

最新新闻

n8n深度拆解:从执行引擎到企业级部署的实战指南

n8n深度拆解:从执行引擎到企业级部署的实战指南

1. 从20万Star说起:n8n到底解决了谁的痛点第一次认真审视n8n,是因为一个做跨境电商的朋友找我帮忙。他手头有七八个店铺,每天要手动从各个后台导出订单、汇总到表格、再分发到仓库系统,光这一套流程就要耗掉两个运营大半天。他问我…

2026/9/23 2:51:20 阅读更多 →
贾子科学定理:公理驱动与结构化推导的科学新范式

贾子科学定理:公理驱动与结构化推导的科学新范式

1. 项目背景与核心价值在科学方法论发展的漫长历程中,我们正见证着一个可能改变研究范式的理论诞生。贾子科学定理(Kucius Science Theorem)的提出,标志着科学哲学领域出现了一种全新的结构化认知框架。这个理论最引人注目的特点在…

2026/9/23 2:51:20 阅读更多 →
App分析平台选型指南:七大维度全解析与避坑实践

App分析平台选型指南:七大维度全解析与避坑实践

"App分析平台到底该怎么选?"这问题我几乎每周都会听到一次。问的人有的是刚拿到投资的创业团队CTO,有的是负责用户增长的产品经理,还有的是被Excel透视表折磨到崩溃的运营负责人。大家背景不同,但困惑高度一致&#xff…

2026/9/23 2:51:20 阅读更多 →
mac字体大小设置一文搞懂:面试高频考点与手写实现

mac字体大小设置一文搞懂:面试高频考点与手写实现

mac字体大小设置一文搞懂:面试高频考点与手写实现 复制来的代码跑不通不知道怎么调?这是不少开发者在 macOS 开发或前端适配时的真实困境。很多人对着 Apple 的文档发呆,或者在网上抄了一堆 SystemFont…

2026/9/23 2:51:20 阅读更多 →
摩比数学一文搞懂:面试被问原理答不上来?这份选型指南救你

摩比数学一文搞懂:面试被问原理答不上来?这份选型指南救你

摩比数学一文搞懂:面试被问原理答不上来?这份选型指南救你 面试时,面试官轻飘飘一句“讲讲摩比数学的核心逻辑”,你脑子一片空白,只能支支吾吾说“就是算数”。这不仅是丢分,更是直接挂票。很多开发者以为这只是个小学数学APP,其实背后藏着大量工程…

2026/9/23 2:51:20 阅读更多 →
电商AI全链路素材生产流水线:从原型图到上线交付

电商AI全链路素材生产流水线:从原型图到上线交付

1. 这不是“AI画图教程”,而是一套能跑通真实电商上线流程的素材生产流水线“从原型图到全套电商素材:AI全链路提效实战指南”——这个标题里藏着三个被多数人忽略的关键词:“原型图”、“全套”、“全链路”。它不讲怎么用AI生成一张好看的主…

2026/9/23 2:50:20 阅读更多 →

日新闻

3招搞定手机怎么下载微信面试难题实战项目解析

3招搞定手机怎么下载微信面试难题实战项目解析

3招搞定手机怎么下载微信面试难题实战项目解析 面试被问“手机怎么下载微信”背后的原理,90%的人答不上来。别笑,这看似弱智的问题,实则是考察你对移动应用分发机制、安全校验及网络协议理解的试金石。我带过不少校招新人,他们背了八股文,却连一个A…

2026/9/23 0:00:23 阅读更多 →
2k显示屏性能优化踩坑:版本升级后API全变了,这份源码解析救了我

2k显示屏性能优化踩坑:版本升级后API全变了,这份源码解析救了我

2k显示屏性能优化踩坑:版本升级后API全变了,这份源码解析救了我 刚把开发环境的显示器从1080P换到2K,跑老项目直接报错,版本升级后 API…

2026/9/23 0:01:25 阅读更多 →
3步搞定美眉图实战项目,告别官方文档抓不住重点

3步搞定美眉图实战项目,告别官方文档抓不住重点

3步搞定美眉图实战项目,告别官方文档抓不住重点 官方文档翻了三遍还是云里雾里?别急,美眉图在实战项目中常被用来做数据可视化,但它的原理比你想的简单。今天咱们直接上手,用一个完整的小项目把美眉图跑通,不再死磕那些冗长的理论说明。…

2026/9/23 0:01:25 阅读更多 →

周新闻

Flutter for OpenHarmony游戏卡片渐变背景实战:从原理到性能优化

Flutter for OpenHarmony游戏卡片渐变背景实战:从原理到性能优化

直接铺开项目本身吧。这几个月我一直在折腾一件事:用Flutter给OpenHarmony做一款游戏集合类的App,说白了就是把若干小游戏塞进一个壳里,用统一入口分发。这个方向本身不算新鲜,真正让我花了不少心思的,是首页那堆游戏卡…

2026/9/22 4:32:41 阅读更多 →
Word表格编号全攻略:从列表编号到题注交叉引用

Word表格编号全攻略:从列表编号到题注交叉引用

写Word文档,最让人头疼的往往是那些“看起来不起眼”的小问题。比如表格编号这事:今天在表后面多加了两个空白行,明天给客户交稿前发现整个章节的编号全部错位,光是挨个改序号就能耗掉大半个下午。我前阵子帮人整理一份上百页的技…

2026/9/22 4:38:57 阅读更多 →
从第一个站到第二个站:独立开发者的静态网站选型与落地实践

从第一个站到第二个站:独立开发者的静态网站选型与落地实践

1. 项目概述1.1 核心需求解析做独立开发者这几年,说实话,第一个网站上线的那天晚上我兴奋得没睡着。但等它跑了半年,流量惨淡、功能臃肿、代码自己都懒得看第二遍之后,我才慢慢琢磨明白一个道理:第一个网站是练手&…

2026/9/22 8:51:04 阅读更多 →

月新闻

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能分类:[AI/大模型]细分主题:AI 增强型 CI/CD 流水线自动化与 GitOps 实践:Agent 工作流、工具调用与任务拆解:从原型到生产的验收清单很多团队在尝试用大…

2026/9/21 15:36:51 阅读更多 →
容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场

容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场

容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场分类:[工程技术]细分主题:Kubernetes 生产环境运维与排障实战:可复制的项目复盘模板与决策记录大部分团队的事故复盘报告,最后都变成了躺在 Confluence 或钉…

2026/9/21 15:36:51 阅读更多 →
容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步

容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步

容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步分类:[工程技术]细分主题:Docker 容器化技术与镜像安全管理:核心链路的逐步实现与关键代码取舍面对一个积累了五六年历史包袱的单体架构应用(包含 Web 接口、后台…

2026/9/22 2:43:42 阅读更多 →