☰
Windows 驱动实例分析系列:injdrv 驱动分析 - src篇(二)
2026/10/10 6:00:27 网站建设 项目流程

子文档 2:核心状态机与回调分发(src/injlib/injlib.c 上半部)

injlib.c是驱动最核心的库文件,约 600 行代码,实现了状态跟踪、DLL 加载判定以及 APC 队列。它不依赖injdrv/main.c中的逻辑,而是独立提供 API 供其调用。

1. 全局数据结构与链表管理

  • InjInfoListHead:一个双向链表头,用于挂载所有待注入进程的INJ_INJECTION_INFO。
  • InjDllPath[InjArchitectureMax]:全局存储从Settings复制过来的完整 DLL 路径(通过RtlDuplicateUnicodeString深拷贝)。
  • InjThunk[InjArchitectureMax]:指向硬编码的 shellcode 数组(InjpThunkX86、InjpThunkX64等)。

InjCreateInjectionInfo分配并初始化INJ_INJECTION_INFO。注意,它允许传入一个已存在的指针(用于某些调试场景),但通常传入NULL使其内部申请。成功后将节点插入链表尾。

2. 进程创建/退出回调:InjCreateProcessNotifyRoutineEx

该回调原型符合PSCREATEPROCESSNOTIFYROUTINEEX。当CreateInfo非空时,表示进程正在创建,此时调用InjCreateInjectionInfo(NULL, ProcessId)创建跟踪节点。当CreateInfo为空时,表示进程退出,调用InjRemoveInjectionInfoByProcessId(ProcessId, TRUE)移除并释放节点。

这里隐含了一个关键假设:新进程的ntdll.dll尚未加载时,进程回调已经触发。实际上,Windows 在创建进程对象后、映射ntdll.dll前会调用此回调,因此创建节点是安全的。

3. 映像加载回调:InjLoadImageNotifyRoutine

这是最复杂的逻辑所在(约 150 行)。每次系统映射 DLL 或 EXE 时都会调用。函数首先通过InjFindInjectionInfo(ProcessId)查找节点。若节点不存在或已被标记IsInjected,则直接返回。

随后检查PsIsProtectedProcess,若为受保护进程(PPL),则输出调试信息并移除节点(放弃注入)。

接下来进入状态机核心:

步骤一:若未满足注入条件(!InjCanInject),则遍历InjpSystemDlls数组,通过RtlxSuffixUnicodeString比对当前加载的FullImageName是否以这些系统 DLL 的路径结尾(如\System32\ntdll.dll)。若匹配,则设置LoadedDlls对应标志位。特别地,当加载的是ntdll.dll时,通过RtlxFindExportedRoutineByName捕获其LdrLoadDll的地址存入节点,供后续注入使用。

步骤二:若已满足注入条件(InjCanInject返回 TRUE),则调用InjpQueueApc(KernelMode, ...)将InjpInjectApcNormalRoutine作为 APC 的NormalRoutine排队,并以InjectionInfo作为参数。紧接着,将IsInjected置为 TRUE,避免重复注入。

4. Windows 7 的特殊补丁

在InjCanInject返回 TRUE 后,若检测到InjIsWindows7且为 Wow64 进程且使用 Thunk 方法,则会额外判断当前加载的 DLL 是否为kernel32.dll或user32.dll(原生和 Wow64 双版本)。只有这些 DLL 也加载完成,才真正执行注入。这是对 Windows 7 下 Wow64 初始化顺序差异的精准适配。

5. RtlxSuffixUnicodeString 与 RtlxFindExportedRoutineByName

由于这些 Rtl 函数在旧版 Windows 上可能不导出,项目自己实现了等效函数。RtlxSuffixUnicodeString使用RtlCompareUnicodeStrings进行字符串后缀匹配,忽略大小写。RtlxFindExportedRoutineByName则通过解析 PE 导出表进行二分查找(仅查找函数名,不查找序号),确保了兼容性。


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

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

立即咨询