ARTICLE DETAIL

资讯详情

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

Windows 驱动实例分析系列:injdrv 驱动分析 - 设计篇

Windows 驱动实例分析系列:injdrv 驱动分析 - 设计篇 Windows 驱动实例分析系列: injdrv驱动分析 - 设计篇1. 项目概述injdrv是一个开源的 Windows 内核驱动KMD示例项目旨在通过 APCAsynchronous Procedure Call异步过程调用机制在进程初始化的早期阶段向用户态进程注入 DLL。该项目由 wbenny 开发支持 Windows 7 至 Windows 10覆盖 x86、x64、ARM32 和 ARM64 架构并特别处理了 Wow64 进程的注入场景。本文将从架构设计、注入方法、同步与内存管理等多个维度剖析 injdrv 的核心设计原理。2. 整体架构injdrv 由三个主要模块组成模块路径功能驱动核心injdrv.sys由src/injdrv/和src/injlib/编译内核驱动负责注册回调、管理注入状态、执行 APC 注入用户态加载器injldr.exesrc/injldr/安装/卸载驱动并启动 ETW 会话以接收日志被注入的 DLLinjdll*.dllsrc/injdll/纯 ntdll 依赖的 DLL演示函数钩子与 ETW 日志驱动加载后会注册两个关键回调进程创建/销毁通知PsSetCreateProcessNotifyRoutineEx映像加载通知PsSetLoadImageNotifyRoutine当新进程创建时驱动分配一个INJ_INJECTION_INFO结构体记录进程 ID、已加载的系统 DLL 标志、LdrLoadDll地址等信息。该结构体挂载在全局链表InjInfoListHead中供后续回调查询。进程启动后系统会按顺序加载ntdll.dll、wow64相关模块仅 Wow64 进程及导入表依赖的 DLL。每一次映像加载都会触发InjLoadImageNotifyRoutine该回调会检查当前进程是否满足注入条件若满足则通过 APC 触发注入。3. 注入时序与状态机injdrv 的核心设计原则是在正确的时机注入避免因目标 DLL如kernel32.dll尚未完全初始化而导致LdrLoadDll调用失败。3.1 状态追踪每个进程的INJ_INJECTION_INFO中有一个LoadedDlls位掩码记录已加载的关键系统 DLL。定义如下typedefenum_INJ_SYSTEM_DLL{INJ_SYSTEM32_NTDLL_LOADED0x0008,INJ_SYSTEM32_WOW64_LOADED0x0010,INJ_SYSTEM32_WOW64WIN_LOADED0x0020,INJ_SYSTEM32_WOW64CPU_LOADED0x0040,INJ_SYSWOW64_NTDLL_LOADED0x0004,// ... 其他架构相关标志}INJ_SYSTEM_DLL;每当映像加载回调触发若加载的 DLL 路径匹配上述系统 DLL通过后缀匹配则设置对应标志位并捕获ntdll.dll中LdrLoadDll的地址。3.2 注入条件判定InjCanInject函数根据当前进程是否为 Wow64计算所需的最小加载集合。例如原生 x64 进程只需INJ_SYSTEM32_NTDLL_LOADEDWow64 x86 进程x64 系统需要ntdll.dll原生、wow64.dll、wow64win.dll、wow64cpu.dll以及SysWOW64\ntdll.dll32位只有当LoadedDlls包含所有必需标志时才认为注入窗口已打开。3.3 Windows 7 的特殊处理在 Windows 7 下Wow64 初始化过程中会加载kernel32.dll和user32.dll原生与 Wow64 版本但这些加载发生在wow64!ProcessInit中此时 Wow64 子系统尚未完全就绪。因此源码中额外推迟注入直到这些 DLL 也被加载。4. 三种注入方法injdrv 实现了三种不同的注入策略分别应对不同的架构和场景。4.1 Thunk 方法通用适用所有架构。注入与目标进程同架构的 DLL。原理在驱动中通过ZwCreateSection创建一个大小为PAGE_SIZE的内存区段。首次映射为PAGE_READWRITE将一段 shellcodethunk和目标 DLL 路径写入该区段。解除映射再以PAGE_EXECUTE_READ重新映射使内存变为可执行但不可写避免 RWX 权限。队列一个用户态 APC其NormalRoutine指向这段 shellcodeNormalContext为LdrLoadDll地址SystemArgument1/2分别为路径指针和长度。shellcode 执行时构造UNICODE_STRING并调用LdrLoadDll。关键设计为避免在映像加载回调中直接调用ZwMapViewOfSection导致递归死锁因为映像加载本身可能发生在NtMapViewOfSection调用栈内驱动先队列一个内核态 APC该 APC 的KernelRoutine中执行实际的区段映射和用户态 APC 队列。内核态 APC 会在即将返回用户态时被调度此时原始的MapViewOfSection已返回AddressCreationLock已释放从而避免死锁。4.2 Thunkless 方法仅 x64适用Windows x64 系统。注入x64 DLL到原生 x64 进程和 Wow64 x86 进程。背景挑战在 Wow64 进程中x86 CFGControl Flow Guard会阻止执行低于 4GB 地址的 shellcode而 x64 的KiUserApcDispatcher位于高于 4GB 的地址CFG 检查会失败。因此不能使用普通的 thunk 分配。巧妙设计不分配可执行 shellcode而是直接将NormalRoutine设置为 x64ntdll.dll中LdrLoadDll的函数地址。APC 参数设置NormalContext NULL→ 对应SearchPathSystemArgument1 NULL→ 对应DllCharacteristicsSystemArgument2 DllName指向一个在进程内存中分配的UNICODE_STRING依赖 x64 的 APC 分发机制KiUserApcDispatcher会传递第 4 个参数即R9指向CONTEXT结构给NormalRoutine正好作为LdrLoadDll的BaseAddress输出参数。LdrLoadDll会覆写CONTEXT.P1Home字段该字段之后不再被使用因此不会引发问题。这种方法无需分配可执行内存完美绕过 CFG 限制。4.3 Wow64Log Reparse 方法通用适用所有架构。注入原生架构 DLL与 OS 同架构到所有进程包括 Wow64。原理Wow64 子系统在启动时会尝试加载%SystemRoot%\System32\wow64log.dll该 DLL 正常情况下不存在加载失败被忽略。injdrv 实现了一个微过滤器基于 WDK 的 SimRep 示例在IRP_MJ_CREATE预处理回调中检测到对wow64log.dll的打开请求时使用IoReplaceFileObjectName将路径替换为 injdll 原生 DLL 的路径并返回STATUS_REPARSE。这样系统会重新解析路径实际加载 injdll而 injdll 内部导出了Wow64LogInitialize、Wow64LogMessageArgList等函数满足 Wow64 的依赖检查从而成功加载。对于原生进程不加载wow64.dll则回退使用 Thunk 方法。5. APC 强制交付机制默认情况下injdrv 在队列 APC 后调用KeTestAlertThread(UserMode)此函数会检查当前线程是否有用户态 APC 挂起若有则设置Thread-ApcState.UserApcPending TRUE确保在下次内核→用户态切换时立即交付 APC。如果禁用此强制ForceUserApc FALSEAPC 将延迟到线程进入可警告状态例如调用NtAlertThread时才会被交付通常发生在进程初始化结束、即将进入main之前。但那时 TLS 回调可能已经执行完毕因此注入点会稍晚。6. 内存保护与安全Thunk 方法中为了规避ZwProtectVirtualMemory在 Windows 8.1 以下版本不可用的问题采用区段重映射技巧创建区段时指定PAGE_EXECUTE_READWRITE权限仅用于区段对象本身。首次映射为PAGE_READWRITE写入数据。解除映射再次映射为PAGE_EXECUTE_READ。由于区段对象本身的权限是EXECUTE_READWRITE只要映射时的保护不超过对象权限系统允许。最终用户态内存只有EXECUTE_READ杜绝了 RWX 内存段提升安全性。7. 跨架构与 Wow64 支持injdrv 通过编译时宏和运行时检测实现多架构支持架构枚举INJ_ARCHITECTURE定义 x86/x64/ARM32/ARM64。系统 DLL 列表包含不同架构下的 ntdll 路径如\SysWOW64\ntdll.dll、\SysArm32\ntdll.dll等通过后缀匹配识别。Wow64 检测使用PsGetProcessWow64Process判断是否为 Wow64 进程使用PsWow64GetProcessMachine在 ARM64 上区分模拟的 x86 或 ARM32 环境。APC 包装对于 Wow64 进程调用PsWrapApcWow64Thread修改 APC 参数使原生ntdll.dll的KiUserApcDispatcher能够正确地将 APC 路由到 Wow64 的 32 位分发器。8. ETW 日志与钩子演示被注入的injdll.dll仅依赖ntdll.dll它通过 Detours 库挂钩NtQuerySystemInformation和NtCreateThreadEx。为了在不引入advapi32.dll的情况下使用 ETW代码通过宏将EventWriteString等 API 映射到ntdll.dll中的EtwEventWriteString等原生函数从而直接调用内核 ETW 服务。用户态工具injldr.exe在启动时创建 ETW 会话并注册回调实时打印被挂钩函数的调用信息。9. 受保护进程的处理驱动会调用PsIsProtectedProcess检查目标进程是否受保护如 PPL。若是则直接跳过注入并释放INJ_INJECTION_INFO避免触发代码完整性错误。项目文档提及可通过操纵未公开结构临时解除保护但此举风险极高不在本设计范围内。10. 总结injdrv 的设计体现了对 Windows 内核机制APC、进程回调、内存区段、过滤器、CFG的深刻理解通过分层决策和多种注入策略在兼容性、安全性和可靠性之间取得了平衡。其核心思想可归纳为时机精确追踪系统 DLL 加载状态确保ntdll.dll和 Wow64 组件准备就绪。内存安全利用区段重映射避免 RWX 内存利用内核 APC 规避死锁。架构自适针对不同 CPU 架构和 Wow64 环境选择最优注入路径。利用系统特性如 Thunkless 方法巧妙借用 APC 调用约定Reparse 方法利用文件重解析注入原生 DLL。该项目不仅是学习 Windows 驱动开发的优秀范例也为安全研究人员深入理解进程注入技术和系统内部行为提供了宝贵的参考。后续我们将继续分析其代码实现细节敬请期待下一篇实现篇。
返回列表