Windows 驱动实例分析系列:injdrv 驱动分析 - src篇(三)

子文档 3:Thunk 方法与 APC 队列(src/injlib/injlib.c 下半部 + shellcode)

这部分实现了 APC 的初始化、内存区段的创建以及 shellcode 的部署。

1. InjpQueueApc:通用 APC 入队函数

该函数负责分配 KAPC 结构(从非分页池 NonPagedPoolNx 分配,带 INJ_MEMORY_TAG 标签)。调用 KeInitializeApc 时指定 Environment = OriginalApcEnvironment,KernelRoutine = &InjpInjectApcKernelRoutine,NormalRoutine 则由调用者传入。KeInsertQueueApc 将 APC 插入目标线程队列(此处为 PsGetCurrentThread())。

注意:为什么 APCs 总是排入当前线程而非目标进程的主线程?因为在映像加载回调的上下文中,当前线程正是执行加载操作的线程(通常是进程的主线程或加载器线程)。注入 APC 到当前线程是安全的,且能保证在返回用户态时触发。

2. 内核 APC 解决死锁(InjpInjectApcKernelRoutine)

在 InjInject 函数中(由 InjpInjectApcNormalRoutine 调用),如果直接在映像加载回调里调用 ZwMapViewOfSection,很可能重入 NtMapViewOfSection 内部已持有的 AddressCreationLock。为规避此问题,InjInject 被设计为由内核 APC 间接调用。代码中并未显式调用 InjpQueueApc(KernelMode, ...)?实际上,InjInject 本身是由 InjpInjectApcNormalRoutine 调用的,而 InjpInjectApcNormalRoutine 是作为用户态 APC 的 NormalRoutine 传入的。但在 InjLoadImageNotifyRoutine 中,调用的是:

c 复制代码
InjpQueueApc(KernelMode, &InjpInjectApcNormalRoutine, InjectionInfo, NULL, NULL);

这里将 NormalRoutine 设为 InjpInjectApcNormalRoutine,但 ApcMode 却是 KernelMode。这有些反直觉。实际上,对于内核 APC,NormalRoutine 并非由 KiDeliverApc 在用户态执行,而是由内核 APC 分发器在 IRQL 1 执行。InjpInjectApcNormalRoutine 内部调用了 InjInject,而 InjInject 执行区段映射时,此时已不在映像加载的原始调用栈上,因此 AddressCreationLock 已被释放。

3. 区段创建与双映射技巧(InjInject)

InjInject 首先创建 ZwCreateSection,指定 PAGE_EXECUTE_READWRITE 作为区段的最大权限。随后调用 ZwMapViewOfSection 映射到当前进程(即目标进程,因为 APC 执行时已附加到目标进程上下文)的 PAGE_READWRITE 视图。

接着,将 shellcode(InjThunk[Architecture].Buffer)复制到视图起始位置,紧随其后复制 DLL 路径(InjDllPath[Architecture].Buffer)。注意,shellcode 中硬编码了 LdrLoadDll 的参数布局,因此路径必须紧跟在 shellcode 后面。

写完后,调用 ZwUnmapViewOfSection 解除映射,再以 PAGE_EXECUTE_READ 重新映射同一区段。这时,用户态内存变为可执行但不可写。

4. 架构相关的 Thunk Shellcode 分析

以 x64 的 InjpThunkX64 为例(反汇编):

asm 复制代码
sub rsp, 38h          ; 分配影子空间
mov rax, rcx          ; NormalContext (LdrLoadDll 地址)
mov [rsp+20h], r8w    ; 保存路径长度
mov [rsp+22h], r8w    ; 再次保存
lea r9, [rsp+40h]     ; 指向栈上的 BaseAddress 输出位置
mov [rsp+28h], rdx    ; 保存路径指针
lea r8, [rsp+20h]     ; 指向 UNICODE_STRING 结构
xor edx, edx          ; DllCharacteristics = 0
xor ecx, ecx          ; SearchPath = NULL
call rax              ; 调用 LdrLoadDll
add rsp, 38h
ret

该 shellcode 完全符合 x64 调用约定,将 4 个参数正确填入寄存器,并确保 RSP 对齐。ARM64 版本则使用 blr x9 等指令,逻辑一致。


相关推荐
海盗12341 小时前
微软技术日报 2026-10-08:Windows 给智能体立沙箱,Surface 换上 NVIDIA 芯
人工智能·windows·microsoft·机器人·aigc
2401_862880821 小时前
i.MX6ULL 嵌入式 Linux 系统移植
驱动开发
遇印记1 小时前
数据通信初步
运维·服务器·网络·windows·学习
weixin_448119941 小时前
Datawhale Easy Data × AI:构建知识与记忆驱动的 Agent笔记3
人工智能·windows·笔记
Nebula_g4 小时前
JavaSE加强:IO流
java·开发语言·windows·stream·javase
Nebula_g16 小时前
JavaSE加强:Stream流
java·开发语言·windows·stream·javase·流
在角落发呆20 小时前
软件配置备份方法:从手动到自动化的实践指南
windows·安全
sunoo-22920 小时前
【嵌入式Linux驱动学习】Day1-Day2 全流程梳理:环境搭建→U-Boot→内核→根文件系统→驱动基础
linux·运维·驱动开发·笔记·学习·阿里云·恩智浦
lingchen190620 小时前
Win 11 系统语言栏悬浮于桌面上影响操作感受,怎么取消?
windows