ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

windows 驱动实例分析系列: wintun驱动分析-SetupApiHost篇(上)

windows 驱动实例分析系列: wintun驱动分析-SetupApiHost篇(上) Wintun 之 SetupApiHost 模块深度解析这个模块主要编译为window驱动的安装工具。一、模块定位为什么需要“主机代理”1.1 问题的根源在 Windows 平台上驱动相关的 Setup API如SetupDiCreateDeviceInfoListExW、SetupDiCallClassInstaller等有一个严格的限制它们只能从与操作系统架构匹配的进程中被成功调用。具体来说一个64 位进程可以正常调用这些 API 来安装/卸载/启用/禁用网络适配器。一个32 位进程运行在 64 位 Windows 上即 WOW64 环境调用这些 API 会失败即使传递完全相同的参数系统也会返回诸如ERROR_IN_WOW64之类的错误码。1.2 Wintun 的挑战Wintun 的核心交付物是wintun.dll。这个 DLL 本身是平台特定的——对于 x64 系统有 64 位的wintun.dll对于 x86 系统有 32 位的wintun.dll。然而许多应用程序是以32 位进程运行的尤其是一些老旧的游戏、企业软件、或为了兼容性而编译为 32 位的项目。如果这些 32 位程序直接链接wintun.dll并试图创建 Wintun 适配器必然失败。1.3 解决方案代理模式Wintun 采用了一种优雅的代理Proxy模式在 64 位系统上当 32 位程序加载 32 位的wintun.dll时该 DLL 并不直接执行驱动安装操作。相反它会从自身资源中提取一个预先内置的64 位代理 DLL即 setupapihost.dll将其写入磁盘并通过一个 64 位的辅助进程rundll32.exe加载该代理 DLL。这个 64 位的代理 DLL 拥有调用 Setup API 的完整权限执行真正的适配器管理操作并将结果成功/失败、实例 ID 等返回给 32 位调用方。setupapihost文件夹中的代码正是这个 64 位代理 DLL 的全部源代码。二、代码逐层深度解析2.1 host.c —— 核心代理逻辑2.1.1 延迟加载钩子Delayed Load HookstaticFARPROC WINAPIDelayedLoadLibraryHook(unsigneddliNotify,PDelayLoadInfo pdli){if(dliNotify!dliNotePreLoadLibrary)returnNULL;HMODULE LibraryLoadLibraryExA(pdli-szDll,NULL,LOAD_LIBRARY_SEARCH_SYSTEM32);if(!Library)abort();return(FARPROC)Library;}constPfnDliHook __pfnDliNotifyHook2DelayedLoadLibraryHook;技术细节setupapihost.dll在链接时声明了setupapi.dll和shell32.dll为延迟加载Delay-Load模块见.vcxproj中的DelayLoadDLLs设置。通过覆盖__pfnDliNotifyHook2这个全局钩子代码在延迟加载发生时强制从%SystemRoot%\System32目录加载目标 DLL。这样可以避免恶意 DLL 劫持DLL Hijacking确保了加载的是系统原生的、经过签名的可靠版本。如果加载失败则调用abort()直接终止进程——这是一个非常强硬但安全的措施。2.1.2 格式化输出辅助函数staticDWORDWriteFormatted(_In_ DWORD StdHandle,_In_z_ LPCWSTR Template,...){LPWSTR FormattedMessageNULL;DWORD Size;va_list Arguments;va_start(Arguments,Template);DWORD LenFormatMessageW(/* ... */);if(SUCCEEDED(DWordMult(Len,sizeof(*FormattedMessage),Size)))WriteFile(GetStdHandle(StdHandle),FormattedMessage,Size,Size,NULL);/* ... */}设计意图所有导出函数都将操作结果通常是一个 Win32 错误码通过WriteFormatted写回标准输出。使用FormatMessageW配合FORMAT_MESSAGE_FROM_STRING和%1!X!这样的占位符可以安全地将DWORD格式化为十六进制字符串。DWordMult用于检查乘法溢出确保内存分配安全。调用方wintun.dll通过管道或临时文件捕获这个标准输出解析其中的错误码和实例 ID从而获知操作结果。2.1.3 核心导出函数一RemoveInstance移除适配器VOID __stdcallRemoveInstance(HWND hwnd,HINSTANCE hinst,LPSTR lpszCmdLine,intnCmdShow){/* ... */WCHAR*InstanceIdArgv[2];HDEVINFO DevInfoSetupDiCreateDeviceInfoListExW(GUID_DEVCLASS_NET,NULL,NULL,NULL);/* ... */SetupDiOpenDeviceInfoW(DevInfo,InstanceId,NULL,DIOD_INHERIT_CLASSDRVS,DevInfoData);SP_REMOVEDEVICE_PARAMS RemoveDeviceParams{/* ... */};SetupDiSetClassInstallParamsW(/* ... */);SetupDiCallClassInstaller(DIF_REMOVE,DevInfo,DevInfoData);/* ... */}流程详解通过CommandLineToArgvW解析命令行参数获取待操作的设备实例 IDInstance ID。创建“网络设备类”GUID_DEVCLASS_NET的设备信息集。根据传入的实例 ID 打开对应的设备信息条目。构造SP_REMOVEDEVICE_PARAMS结构体设置作用域为DI_REMOVEDEVICE_GLOBAL全局移除。调用SetupDiCallClassInstaller触发DIF_REMOVE安装请求最终会调用SetupDiRemoveDevice删除该设备实例并卸载其驱动。注意如果SetupDiOpenDeviceInfoW返回ERROR_PATH_NOT_FOUND则视为“设备已不存在”直接返回成功LastError ERROR_SUCCESS。这是一种常见的幂等性设计避免重复删除时产生误导性错误。2.1.4 核心导出函数二EnableInstance / DisableInstance启用/禁用适配器SP_PROPCHANGE_PARAMS Params{.ClassInstallHeader{.InstallFunctionDIF_PROPERTYCHANGE},.StateChangeDICS_ENABLE,/* 或 DICS_DISABLE */.ScopeDICS_FLAG_GLOBAL};SetupDiSetClassInstallParamsW(/* ... */);SetupDiCallClassInstaller(DIF_PROPERTYCHANGE,DevInfo,DevInfoData);关键点DIF_PROPERTYCHANGE是 Setup API 中用于修改设备状态的安装请求。StateChange可以取DICS_ENABLE启用或DICS_DISABLE禁用对应“启用设备”和“禁用设备”两种操作。Scope DICS_FLAG_GLOBAL表示这是一个全局性的状态更改会持久化到系统配置中重启后依然生效而不是临时的用户会话级别变更。执行效果调用后Windows 网络适配器列表中该 Wintun 适配器的状态会变为“已启用”或“已禁用”相当于在设备管理器中右键点击设备并选择“启用设备”/“禁用设备”。2.2 host_win7.h —— Windows 7 专属的“无中生有”这个头文件只在NTDDI_VERSION NTDDI_WIN7时才被包含意味着它只在编译面向 Windows 7 版本时生效。2.2.1 为什么 Windows 7 需要特殊处理在 Windows 8 及更高版本中Wintun 的api模块可以通过WintunCreateAdapter直接调用SetupDiCreateDeviceInfo等 API 来创建适配器并指定自定义的 GUID。但在Windows 7上这个路径存在一个严重问题Windows 7 的 NDIS 驱动模型要求适配器的实例 IDInstance ID必须包含硬件 ID 的哈希值否则系统会拒绝加载。更关键的是Windows 7 无法通过标准 Setup API 为未签名的第三方驱动直接创建带有自定义硬件 ID 的适配器需要绕道“驱动程序预安装”流程。
返回列表