子文档 1:驱动骨架与初始化(src/injdrv/main.c)
src/injdrv/main.c是整个内核驱动的入口点,其代码量虽然不大(约 150 行),却承担着承上启下的关键作用:解析配置、准备DLL路径、选择注入方法并启动回调监听。
1. 驱动入口 DriverEntry 的实现细节
DriverEntry接收两个参数:PDRIVER_OBJECT DriverObject和PUNICODE_STRING RegistryPath。后者指向注册表中该服务的配置键(通常为\Registry\Machine\System\CurrentControlSet\Services\injdrv)。函数首先设置DriverObject->DriverUnload = &DriverDestroy,确保卸载时能清理资源。
紧接着,函数在栈上声明INJ_SETTINGS Settings结构体,并为其内部的DllPath数组分配栈上的缓冲区(每个 128 个 WCHAR)。这里有一个重要的安全编码习惯:由于驱动栈大小有限(通常是 12KB 或 24KB),将BufferDllPathX86等大数组放在栈上需谨慎,但此处共 4 个架构,每个 128 WCHAR(256 字节),总计约 1KB,尚在安全范围内。
2. InjCreateSettings:逆向读取自身路径
这是初始化的核心难点。驱动需要知道injdllx86.dll等载荷文件位于何处。由于驱动文件通常与injldr.exe放在同一目录,它利用RegistryPath参数(即服务项路径)配合ImagePath值来定位。
函数调用ZwOpenKey打开自身服务注册表项,然后调用ZwQueryValueKey读取ImagePath(类型为REG_EXPAND_SZ)。这个路径通常为\??\C:\Path\to\injdrv.sys。代码检测到\??\前缀后将其跳过(通过比较ObpDosDevicesShortNamePrefix的 8 字节魔数),然后使用wcsrchr找到最后一个\并将其截断为NUL,从而提取出目录部分。
最后,通过InjpJoinPath将目录分别与injdllx86.dll、injdllx64.dll、injdllARM.dll、injdllARM64.dll拼接,存入Settings.DllPath对应槽位。若任一拼接失败(如缓冲区溢出),则返回STATUS_DATA_ERROR。
3. 注入方法的选择策略
在DriverEntry尾部,通过条件编译注入默认方法:
- x86 平台选
InjMethodThunk。 - x64 平台选
InjMethodThunkless(这是最有特色的技法)。 - ARM64 平台选
InjMethodWow64LogReparse。
这个默认选择体现了设计者的意图:优先注入原生(x64)DLL 以实现对所有进程(含 Wow64)的最大覆盖。用户若想改变,可在源码中修改Settings.Method后重新编译。
4. 驱动卸载与回调反注册
DriverDestroy调用PsRemoveLoadImageNotifyRoutine和PsSetCreateProcessNotifyRoutineEx(..., TRUE)移除回调,并调用InjDestroy释放全局链表中的INJ_INJECTION_INFO节点,防止内存泄漏。
5. 为什么不在 DriverEntry 中直接注入?
驱动加载时,系统尚未有任何用户态进程启动(除 System 进程外),因此此时注入无目标。必须等待进程创建回调触发后,才能捕获新进程的EPROCESS对象并开始跟踪。