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_PATH)
1.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)
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 进程通信机制
管道创建
- 创建两个管道对(stdout 和 stderr):
CreatePipe(&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_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)流程
- 获取实例 ID:调用
SetupDiGetDeviceInstanceIdW - 构造参数:
ArgvToCommandLineW(1, InstanceId) - 执行 rundll32:
ExecuteRunDll32(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.dll
Sysnative 路径:使用%SystemRoot%\Sysnative\rundll32.exe确保在 32 位进程中启动 64 位程序。
3.7 条件编译
#ifdefMAYBE_WOW64// WOW64 支持代码#endifMAYBE_WOW64在以下平台定义(api.vcxproj):
- Win32 (x86)
- x64
- ARM
仅在 ARM64 上未定义(因为 ARM64 原生进程不存在 WOW64 问题)。
4. 安全与权限管理
4.1 安全描述符初始化 (InitializeSecurityObjects)
在DllMain的DLL_PROCESS_ATTACH阶段初始化:
- 获取当前进程 SID:
- 打开进程令牌(
OpenProcessToken) - 查询
TokenUser信息
- 打开进程令牌(
- 判断是否为 LocalSystem:
- 创建
WinLocalSystemSid并与进程 SID 比较 - 设置全局变量
IsLocalSystem
- 创建
- 创建安全描述符:
- 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
- LocalSystem:
SDDL 解析
| 组件 | 含义 |
|---|---|
O:SY/O:BA | 所有者:SYSTEM / Built-in Administrators |
D:P | DACL(自由访问控制列表)已保护 |
(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}检测逻辑
- 首选方法:
IsWow64Process2(Windows 10 1511+)- 返回
ProcessMachine(进程架构)和NativeMachine(系统架构) - 直接获得
NativeMachine值
- 返回
- 回退方法:
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 辅助 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、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 劫持攻击。