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 等指令,逻辑一致。


相关推荐
上海云盾-小余16 分钟前
七层 CC 攻击检测难点:如何区分真实用户与攻击请求
网络·人工智能·windows·tcp/ip·udp
2501_9151063234 分钟前
Windows 上开发 Unity iOS,使用 KXBuild 进行真机调试、性能采样与打包 ipa
windows·vscode·unity·ios·个人开发·swift·敏捷流程
西柚不烦恼1 小时前
Windows 本地部署私有 AI 全攻略:纯 CPU 环境(Ollama + Open WebUI)
人工智能·windows
少年的范儿1 小时前
Windows 安装 Node.js 教程:nvm 版本管理 + pnpm 安装配置
windows·npm·node.js
bu_shuo4 小时前
删除Windows资源管理器左侧【此电脑】的Linux企鹅图标
linux·windows
清水白石0085 小时前
Python 安装与环境配置:Windows、macOS、Linux 完整指南(第2集)
windows·python·macos
木卫二号Coding5 小时前
关闭-Windows10文件资源管理器-预览窗格-有效
windows
Moshow郑锴5 小时前
GCP MIG 重建后 Ansible 初始化实战:PowerShell + NSSM 挂载 Windows 服务
windows·ansible
硅基手札5 小时前
CAN 技术系列 04 · CAN 帧格式
驱动开发·嵌入式开发·通信协议·can总线
染指11105 小时前
160.Agent-LangGraph-自定义主管架构智能体
人工智能·windows·microsoft·langchain·1024程序员节·agents