☰
windows 驱动实例分析系列: wireguard-nt驱动分析-api篇(三)
2026/9/25 3:48:49 网站建设 项目流程

WireGuard-NT API 模块分析 - 第三部分:注册表操作、资源提取与跨架构支持

1. 注册表操作模块 (registry.c / registry.h)

注册表操作模块提供了对 Windows 注册表的封装读写功能,专门用于处理 WireGuard 适配器的配置信息。这些操作主要集中在HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Class\{GUID_DEVCLASS_NET}下的适配器子键。

1.1 注册表路径管理

#defineMAX_REG_PATH256

这是 Windows 注册表路径的最大长度限制(参考 Microsoft 文档 KB256986)。所有路径操作都基于此限制,避免缓冲区溢出。

LoggerGetRegistryKeyPath函数将 HKEY 句柄转换为可读的注册表路径字符串,用于日志输出:

  1. 使用NtQueryKey查询键名信息(KeyNameInformation,信息类 3)
  2. 如果查询失败或键名为空,则使用十六进制地址表示
  3. 结果写入Path缓冲区(大小MAX_REG_PATH)

1.2 字符串值读取 (RegistryQueryString)

LPWSTRRegistryQueryString(HKEY Key,LPCWSTR Name,BOOL Log)
功能

读取注册表字符串值,支持三种数据类型:

  • REG_SZ:普通字符串
  • REG_EXPAND_SZ:包含环境变量的字符串(如%SystemRoot%),自动展开
  • REG_MULTI_SZ:多字符串,仅返回第一个字符串
内部流程
  1. 分配初始缓冲区(256 个宽字符)
  2. 循环调用RegQueryValueExW,如果返回ERROR_MORE_DATA则调整缓冲区大小
  3. 检查值类型是否为REG_SZ、REG_EXPAND_SZ或REG_MULTI_SZ
  4. 调用RegistryGetString处理字符串展开和零终止符验证
RegistryGetString 详细处理
BOOLRegistryGetString(LPWSTR*Buf,DWORD Len,DWORD ValueType)
  • 零终止符检查:验证字符串是否以零终止,若没有则重新分配并追加零
  • 环境变量展开(仅REG_EXPAND_SZ):
    • 调用ExpandEnvironmentStringsW尝试展开
    • 如果返回长度大于缓冲区,重新分配并重试
    • 如果展开失败,记录错误并返回 FALSE
  • 对于REG_MULTI_SZ,只保留第一个字符串(遇到第一个零终止符即截断)

1.3 DWORD 值读取 (RegistryQueryDWORD)

BOOLRegistryQueryDWORD(HKEY Key,LPCWSTR Name,DWORD*Value,BOOL Log)
严格验证
  • 值类型必须为REG_DWORD
  • 数据大小必须为sizeof(DWORD)(4 字节)
  • 任何不符条件都视为错误,记录日志并返回 FALSE
错误处理
  • 如果Log参数为TRUE,所有失败都会记录详细日志,包含完整的注册表路径
  • 路径构造通过LoggerGetRegistryKeyPath实现,方便调试

2. 资源提取模块 (resource.c / resource.h)

该模块负责从 DLL 资源中提取驱动程序文件,支持安装和 WOW64 辅助场景。

2.1 资源定位 (ResourceGetAddress)

constVOID*ResourceGetAddress(LPCWSTR ResourceName,DWORD*Size)
流程
  1. 调用FindResourceW查找 RT_RCDATA 类型的资源
  2. 调用SizeofResource获取资源大小
  3. 调用LoadResource加载资源
  4. 调用LockResource获取内存地址(资源在进程中固定不变)
  5. 返回地址和大小

注意:LockResource返回的指针在资源卸载前有效(进程生命周期内),无需解锁。

2.2 资源写入文件 (ResourceCopyToFile)

BOOLResourceCopyToFile(LPCWSTR DestinationPath,LPCWSTR ResourceName)
流程
  1. 调用ResourceGetAddress获取资源地址和大小
  2. 使用CreateFileW创建目标文件:
    • 标志:CREATE_NEW(避免覆盖现有文件)
    • 属性:FILE_ATTRIBUTE_NORMAL | FILE_ATTRIBUTE_TEMPORARY
    • 安全属性:使用全局SecurityAttributes(继承自DllMain初始化)
  3. 使用WriteFile写入资源数据
  4. 验证写入字节数是否等于资源大小
  5. 关闭文件句柄

2.3 临时目录创建 (ResourceCreateTemporaryDirectory)

BOOLResourceCreateTemporaryDirectory(LPWSTR RandomTempSubDirectory)
流程
  1. 获取 Windows 目录(GetWindowsDirectoryW)
  2. 组合成临时目录路径(Windows\Temp)
  3. 使用RtlGenRandom生成 32 字节随机数
  4. 将随机数格式化为十六进制字符串(64 字符)
  5. 创建随机子目录(Windows\Temp\<hex>)
  6. 返回完整路径

特点:

  • 使用加密安全的随机数生成器(RtlGenRandom)
  • 目录名长度为 64 个十六进制字符(256 位熵)
  • 安全属性继承自全局设置(允许系统和管理员访问)

3. WOW64 跨架构支持 (rundll32.c / rundll32.h)

WOW64(Windows-on-Windows 64-bit)支持使得 32 位应用程序可以在 64 位 Windows 上运行。当 32 位进程调用 SetupAPI 操作 64 位驱动程序时,需要特殊的辅助机制。

3.1 问题背景

  • 驱动安装限制:32 位进程无法直接安装 64 位驱动程序,因为 SetupAPI 在 WOW64 下会搜索 32 位驱动目录(%SystemRoot%\System32\DriverStore\FileRepository的重定向路径)
  • 设备操作限制:某些 SetupAPI 操作(如CM_Uninstall_DevNode)在 WOW64 下行为不同,可能导致设备移除失败

3.2 解决方案架构

使用rundll32.exe启动 64 位辅助 DLL(setupapihost.dll),在原生 64 位上下文中执行 SetupAPI 操作。

┌─────────────────────────────────────────────────────────────┐ │ 32 位进程 (wireguard.dll) │ ├─────────────────────────────────────────────────────────────┤ │ 1. 创建临时目录 │ │ 2. 提取 setupapihost-<arch>.dll 到临时目录 │ │ 3. 构造命令行: rundll32 <dll>,<Function> <Arguments> │ │ 4. 创建管道(stdout / stderr) │ │ 5. 启动 rundll32.exe(从 Sysnative 路径) │ │ 6. 读取管道获取返回码 │ │ 7. 清理临时文件 │ └─────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────┐ │ 64 位辅助进程 (rundll32.exe) │ ├─────────────────────────────────────────────────────────────┤ │ 1. 加载 setupapihost.dll │ │ 2. 调用指定导出函数 │ │ 3. 执行 SetupAPI 操作(无 WOW64 重定向) │ │ 4. 返回结果码(通过 stdout) │ └─────────────────────────────────────────────────────────────┘

3.3 命令行参数构造 (ArgvToCommandLineW)

这是一个健壮的参数转命令行函数,正确处理参数中的引号和反斜杠:

转义规则

根据 Windows 命令行解析规则:

  • 参数用双引号包围
  • 参数内的反斜杠需要加倍
  • 参数内的双引号用\"转义,但反斜杠需特殊处理
// 示例:"C:\Program Files\App\" 参数// 输入参数: C:\Program Files\App\ // 转义后: "C:\\Program Files\\App\\"
算法逻辑

对每个字符遍历,统计连续反斜杠数量:

  • 如果遇到双引号:输出2*n+1个反斜杠 +\"
  • 如果到达字符串结尾:输出2*n个反斜杠
  • 其他情况:输出n个反斜杠 + 当前字符

3.4 进程通信机制

管道创建
  1. 创建两个管道对(stdout 和 stderr):
    CreatePipe(&StreamRStdout,&StreamWStdout,&SecurityAttributes,0);CreatePipe(&StreamRStderr,&StreamWStderr,&SecurityAttributes,0);
  2. 设置写端句柄为可继承(SetHandleInformation)
  3. 在STARTUPINFOW中设置hStdOutput和hStdError
输出读取线程

stdout 读取线程(ProcessStdout):

  • 读取管道数据,每次读取按宽字符对齐
  • 数据格式为十六进制错误码(如"00000000")
  • 存储在Response缓冲区,最多ResponseCapacity个字符

stderr 读取线程(ProcessStderr):

  • 读取辅助进程的日志输出
  • 日志格式:[<级别> <时间戳>] <消息>
    • +→WIREGUARD_LOG_INFO
    • -→WIREGUARD_LOG_WARN
    • !→WIREGUARD_LOG_ERR
  • 转发给全局Logger回调
超时与同步
  • 主线程等待进程结束(WaitForSingleObject(pi.hProcess, INFINITE))
  • 然后等待输出线程结束
  • 检查 stdout 线程的退出码获取操作结果

3.5 辅助 DLL 导出函数

setupapihost.dll导出以下函数(由源代码的rundll32辅助项目生成):

导出函数功能对应 SetupAPI 操作
RemoveInstance移除设备实例DIF_REMOVE
EnableInstance启用设备实例DIF_PROPERTYCHANGE(DICS_ENABLE)
DisableInstance禁用设备实例DIF_PROPERTYCHANGE(DICS_DISABLE)

每个函数接收设备实例 ID 作为参数,执行操作后返回结果码。

3.6 实际调用 (InvokeClassInstaller)

BOOLInvokeClassInstaller(LPCWSTR Action,LPCWSTR Function,HDEVINFO DevInfo,SP_DEVINFO_DATA*DevInfoData)
流程
  1. 获取实例 ID:调用SetupDiGetDeviceInstanceIdW
  2. 构造参数:ArgvToCommandLineW(1, InstanceId)
  3. 执行 rundll32:ExecuteRunDll32(Function, Arguments, Response, _countof(Response))
  4. 解析响应:
    • 使用CommandLineToArgvW解析返回的十六进制字符串
    • 转换为 DWORD 错误码(wcstoul(Argv[0], NULL, 16))
  5. 返回是否成功(错误码为ERROR_SUCCESS)
平台检测

ExecuteRunDll32中根据NativeMachine选择正确的资源:

  • IMAGE_FILE_MACHINE_AMD64→setupapihost-amd64.dll
  • IMAGE_FILE_MACHINE_ARM64→setupapihost-arm64.dll

Sysnative 路径:使用%SystemRoot%\Sysnative\rundll32.exe确保在 32 位进程中启动 64 位程序。

3.7 条件编译

#ifdefMAYBE_WOW64// WOW64 支持代码#endif

MAYBE_WOW64在以下平台定义(api.vcxproj):

  • Win32 (x86)
  • x64
  • ARM

仅在 ARM64 上未定义(因为 ARM64 原生进程不存在 WOW64 问题)。

4. 安全与权限管理

4.1 安全描述符初始化 (InitializeSecurityObjects)

在DllMain的DLL_PROCESS_ATTACH阶段初始化:

  1. 获取当前进程 SID:
    • 打开进程令牌(OpenProcessToken)
    • 查询TokenUser信息
  2. 判断是否为 LocalSystem:
    • 创建WinLocalSystemSid并与进程 SID 比较
    • 设置全局变量IsLocalSystem
  3. 创建安全描述符:
    • LocalSystem:O:SYD:P(A;;GA;;;SY)(A;;GA;;;BA)S:(ML;;NWNRNX;;;HI)
    • 非 LocalSystem:O:BAD:P(A;;GA;;;SY)(A;;GA;;;BA)S:(ML;;NWNRNX;;;HI)
    • 使用ConvertStringSecurityDescriptorToSecurityDescriptorW转换
    • 存储到SecurityAttributes.lpSecurityDescriptor
SDDL 解析
组件含义
O:SY/O:BA所有者:SYSTEM / Built-in Administrators
D:PDACL(自由访问控制列表)已保护
(A;;GA;;;SY)允许 SYSTEM 完全访问
(A;;GA;;;BA)允许 Administrators 完全访问
S:(ML;;NWNRNX;;;HI)强制完整性标签:高完整性级别,拒绝读写执行

4.2 对象创建使用安全属性

所有需要安全保护的内核对象创建都使用SecurityAttributes:

  • 命名互斥锁(CreateMutexW)
  • 私有命名空间(CreatePrivateNamespaceW)
  • 临时目录(CreateDirectoryW)
  • 管道(CreatePipe)
  • 驱动程序文件(CreateFileW)

这确保了只有 SYSTEM 和 Administrators 可以操作 WireGuard 对象。

5. 环境初始化 (EnvInit)

staticvoidEnvInit(VOID){#ifdefMAYBE_WOW64// 检测进程架构if(IsWow64Process2(GetCurrentProcess(),&ProcessMachine,&NativeMachine)){// 获取原生系统架构}else{// 回退到传统 IsWow64ProcessNativeMachine=IsWoW64?IMAGE_FILE_MACHINE_AMD64:IMAGE_FILE_PROCESS;}#endif}

检测逻辑

  1. 首选方法:IsWow64Process2(Windows 10 1511+)
    • 返回ProcessMachine(进程架构)和NativeMachine(系统架构)
    • 直接获得NativeMachine值
  2. 回退方法:IsWow64Process
    • 仅返回是否 WOW64
    • 若是 WOW64,默认系统架构为 AMD64(传统 x64 系统)
    • 否则为当前进程架构

NativeMachine用于选择正确的驱动程序资源(x64/ARM64)。

6. 资源嵌入与构建

6.1 资源文件 (resources.rc)

在编译时,驱动程序文件作为 RT_RCDATA 资源嵌入 DLL:

wireguard.sys → 当前架构驱动 wireguard.cat → 当前架构 CAT 文件 wireguard.inf → 当前架构 INF 文件 wireguard-amd64.sys → x64 驱动(用于 WOW64) wireguard-amd64.cat → x64 CAT 文件 wireguard-amd64.inf → x64 INF 文件 wireguard-arm64.sys → ARM64 驱动 wireguard-arm64.cat → ARM64 CAT 文件 wireguard-arm64.inf → ARM64 INF 文件 setupapihost-amd64.dll → x64 辅助 DLL setupapihost-arm64.dll → ARM64 辅助 DLL

6.2 条件资源包含

api.vcxproj中的ResourceCompile预处理器定义:

  • BUILT_AMD64_WOW64:如果..\$(Configuration)\amd64\driver\wireguard.sys存在
  • BUILT_ARM64_WOW64:如果..\$(Configuration)\arm64\driver\wireguard.sys存在
  • WANT_AMD64_WOW64:当前平台需要包含 x64 资源(x86、x64、ARM)
  • WANT_ARM64_WOW64:当前平台需要包含 ARM64 资源(x86、x64、ARM)

这些宏控制resources.rc中哪些资源被编译进 DLL,避免不必要的体积增长。

7. 延迟加载配置

7.1 延迟加载 DLL 列表

<DelayLoadDLLs>advapi32.dll; api-ms-win-devices-query-l1-1-0.dll; api-ms-win-devices-swdevice-l1-1-0.dll; cfgmgr32.dll; iphlpapi.dll; ole32.dll; nci.dll; setupapi.dll; shlwapi.dll; version.dll</DelayLoadDLLs>

所有系统 DLL 都配置为延迟加载,优点:

  • 减少 DLL 加载时间
  • 允许在不支持某些 API 的旧 Windows 版本上运行(需要调用前检查)
  • 降低内存占用

7.2 自定义加载钩子 (__pfnDliNotifyHook2)

staticFARPROC WINAPIDelayedLoadLibraryHook(unsigneddliNotify,PDelayLoadInfo pdli){if(dliNotify!=dliNotePreLoadLibrary)returnNULL;HMODULE Library=LoadLibraryExA(pdli->szDll,NULL,LOAD_LIBRARY_SEARCH_SYSTEM32);if(!Library)abort();return(FARPROC)Library;}

强制系统目录搜索:使用LOAD_LIBRARY_SEARCH_SYSTEM32标志确保从System32目录加载 DLL,防止 DLL 劫持攻击。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询