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 句柄转换为可读的注册表路径字符串用于日志输出使用NtQueryKey查询键名信息KeyNameInformation信息类 3如果查询失败或键名为空则使用十六进制地址表示结果写入Path缓冲区大小MAX_REG_PATH1.2 字符串值读取 (RegistryQueryString)LPWSTRRegistryQueryString(HKEY Key,LPCWSTR Name,BOOL Log)功能读取注册表字符串值支持三种数据类型REG_SZ普通字符串REG_EXPAND_SZ包含环境变量的字符串如%SystemRoot%自动展开REG_MULTI_SZ多字符串仅返回第一个字符串内部流程分配初始缓冲区256 个宽字符循环调用RegQueryValueExW如果返回ERROR_MORE_DATA则调整缓冲区大小检查值类型是否为REG_SZ、REG_EXPAND_SZ或REG_MULTI_SZ调用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)流程调用FindResourceW查找 RT_RCDATA 类型的资源调用SizeofResource获取资源大小调用LoadResource加载资源调用LockResource获取内存地址资源在进程中固定不变返回地址和大小注意LockResource返回的指针在资源卸载前有效进程生命周期内无需解锁。2.2 资源写入文件 (ResourceCopyToFile)BOOLResourceCopyToFile(LPCWSTR DestinationPath,LPCWSTR ResourceName)流程调用ResourceGetAddress获取资源地址和大小使用CreateFileW创建目标文件标志CREATE_NEW避免覆盖现有文件属性FILE_ATTRIBUTE_NORMAL | FILE_ATTRIBUTE_TEMPORARY安全属性使用全局SecurityAttributes继承自DllMain初始化使用WriteFile写入资源数据验证写入字节数是否等于资源大小关闭文件句柄2.3 临时目录创建 (ResourceCreateTemporaryDirectory)BOOLResourceCreateTemporaryDirectory(LPWSTR RandomTempSubDirectory)流程获取 Windows 目录GetWindowsDirectoryW组合成临时目录路径Windows\Temp使用RtlGenRandom生成 32 字节随机数将随机数格式化为十六进制字符串64 字符创建随机子目录Windows\Temp\hex返回完整路径特点使用加密安全的随机数生成器RtlGenRandom目录名长度为 64 个十六进制字符256 位熵安全属性继承自全局设置允许系统和管理员访问3. WOW64 跨架构支持 (rundll32.c / rundll32.h)WOW64Windows-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 位辅助 DLLsetupapihost.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*n1个反斜杠 \如果到达字符串结尾输出2*n个反斜杠其他情况输出n个反斜杠 当前字符3.4 进程通信机制管道创建创建两个管道对stdout 和 stderrCreatePipe(StreamRStdout,StreamWStdout,SecurityAttributes,0);CreatePipe(StreamRStderr,StreamWStderr,SecurityAttributes,0);设置写端句柄为可继承SetHandleInformation在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_REMOVEEnableInstance启用设备实例DIF_PROPERTYCHANGE(DICS_ENABLE)DisableInstance禁用设备实例DIF_PROPERTYCHANGE(DICS_DISABLE)每个函数接收设备实例 ID 作为参数执行操作后返回结果码。3.6 实际调用 (InvokeClassInstaller)BOOLInvokeClassInstaller(LPCWSTR Action,LPCWSTR Function,HDEVINFO DevInfo,SP_DEVINFO_DATA*DevInfoData)流程获取实例 ID调用SetupDiGetDeviceInstanceIdW构造参数ArgvToCommandLineW(1, InstanceId)执行 rundll32ExecuteRunDll32(Function, Arguments, Response, _countof(Response))解析响应使用CommandLineToArgvW解析返回的十六进制字符串转换为 DWORD 错误码wcstoul(Argv[0], NULL, 16)返回是否成功错误码为ERROR_SUCCESS平台检测ExecuteRunDll32中根据NativeMachine选择正确的资源IMAGE_FILE_MACHINE_AMD64→setupapihost-amd64.dllIMAGE_FILE_MACHINE_ARM64→setupapihost-arm64.dllSysnative 路径使用%SystemRoot%\Sysnative\rundll32.exe确保在 32 位进程中启动 64 位程序。3.7 条件编译#ifdefMAYBE_WOW64// WOW64 支持代码#endifMAYBE_WOW64在以下平台定义api.vcxprojWin32 (x86)x64ARM仅在 ARM64 上未定义因为 ARM64 原生进程不存在 WOW64 问题。4. 安全与权限管理4.1 安全描述符初始化 (InitializeSecurityObjects)在DllMain的DLL_PROCESS_ATTACH阶段初始化获取当前进程 SID打开进程令牌OpenProcessToken查询TokenUser信息判断是否为 LocalSystem创建WinLocalSystemSid并与进程 SID 比较设置全局变量IsLocalSystem创建安全描述符LocalSystemO:SYD:P(A;;GA;;;SY)(A;;GA;;;BA)S:(ML;;NWNRNX;;;HI)非 LocalSystemO:BAD:P(A;;GA;;;SY)(A;;GA;;;BA)S:(ML;;NWNRNX;;;HI)使用ConvertStringSecurityDescriptorToSecurityDescriptorW转换存储到SecurityAttributes.lpSecurityDescriptorSDDL 解析组件含义O:SY/O:BA所有者SYSTEM / Built-in AdministratorsD: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{// 回退到传统 IsWow64ProcessNativeMachineIsWoW64?IMAGE_FILE_MACHINE_AMD64:IMAGE_FILE_PROCESS;}#endif}检测逻辑首选方法IsWow64Process2Windows 10 1511返回ProcessMachine进程架构和NativeMachine系统架构直接获得NativeMachine值回退方法IsWow64Process仅返回是否 WOW64若是 WOW64默认系统架构为 AMD64传统 x64 系统否则为当前进程架构NativeMachine用于选择正确的驱动程序资源x64/ARM64。6. 资源嵌入与构建6.1 资源文件 (resources.rc)在编译时驱动程序文件作为 RT_RCDATA 资源嵌入 DLLwireguard.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 辅助 DLL6.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、ARMWANT_ARM64_WOW64当前平台需要包含 ARM64 资源x86、x64、ARM这些宏控制resources.rc中哪些资源被编译进 DLL避免不必要的体积增长。7. 延迟加载配置7.1 延迟加载 DLL 列表DelayLoadDLLsadvapi32.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 LibraryLoadLibraryExA(pdli-szDll,NULL,LOAD_LIBRARY_SEARCH_SYSTEM32);if(!Library)abort();return(FARPROC)Library;}强制系统目录搜索使用LOAD_LIBRARY_SEARCH_SYSTEM32标志确保从System32目录加载 DLL防止 DLL 劫持攻击。