一、初始化 APC:KeInitializeApc 函数到底做了什么
在理解 APC 的执行、投递之前,必须先搞清楚一个最基础的问题:
KeInitializeApc 初始化的,究竟是什么?
二、KeInitializeApc 的函数原型
bash
VOID
KeInitializeApc(
PKAPC Apc,
PKTHREAD Thread,
KAPC_ENVIRONMENT Environment,
PKKERNEL_ROUTINE KernelRoutine,
PKRUNDOWN_ROUTINE RundownRoutine,
PKNORMAL_ROUTINE NormalRoutine,
KPROCESSOR_MODE ApcMode,
PVOID NormalContext
);
从参数就能看出,这个函数完全不涉及"什么时候执行",
它只描述三件事:
-
APC 属于谁(Thread / Environment)
-
APC 要执行什么(Routine)
-
执行时需要什么上下文(Mode / Context)
三、分析KeInitializeApc函数的反汇编
bash
.text:004B0DF3
.text:004B0DF3 ; int __stdcall KeInitializeApc(int Apc, int Thread, int Environment, int KernelRoutine, int RundownRoutine, int NormalRoutine, char ApcMode, int NormalContext)
.text:004B0DF3 public _KeInitializeApc@32
.text:004B0DF3 _KeInitializeApc@32 proc near ; CODE XREF: IopfCompleteRequest(x,x)+2B2↑p
.text:004B0DF3 ; IopfCompleteRequest(x,x)+3DD↑p ...
.text:004B0DF3
.text:004B0DF3 Apc = dword ptr 8
.text:004B0DF3 Thread = dword ptr 0Ch
.text:004B0DF3 Environment = dword ptr 10h
.text:004B0DF3 KernelRoutine = dword ptr 14h
.text:004B0DF3 RundownRoutine = dword ptr 18h
.text:004B0DF3 NormalRoutine = dword ptr 1Ch
.text:004B0DF3 ApcMode = byte ptr 20h
.text:004B0DF3 NormalContext = dword ptr 24h
.text:004B0DF3
.text:004B0DF3 mov edi, edi
.text:004B0DF5 push ebp
.text:004B0DF6 mov ebp, esp
.text:004B0DF8 mov eax, [ebp+Apc]
.text:004B0DFB mov edx, [ebp+Environment]
.text:004B0DFE mov ecx, [ebp+Thread]
.text:004B0E01 mov [eax+_KAPC.Type], 12h
.text:004B0E04 mov [eax+_KAPC.Size], 30h ; '0'
.text:004B0E08 cmp edx, 2
.text:004B0E0B jnz short loc_4B0E13 ; 如果Environment!=2,跳转
.text:004B0E0D mov dl, [ecx+_KTHREAD.ApcStateIndex] ; 如果Environment=2(CurrentApcEnvironment)
.text:004B0E0D ; dl=_KTHREAD.ApcStateIndex
.text:004B0E13
.text:004B0E13 loc_4B0E13: ; CODE XREF: KeInitializeApc(x,x,x,x,x,x,x,x)+18↑j
.text:004B0E13 mov [eax+_KAPC.Thread], ecx ; _KAPC.Thread=Thread
.text:004B0E16 mov ecx, [ebp+KernelRoutine]
.text:004B0E19 mov [eax+_KAPC.KernelRoutine], ecx ; _KAPC.KernelRoutine=KernelRoutine
.text:004B0E1C mov ecx, [ebp+RundownRoutine]
.text:004B0E1F mov [eax+_KAPC.ApcStateIndex], dl ; _KAPC.ApcStateIndex=_KTHREAD.ApcStateIndex
.text:004B0E22 mov [eax+_KAPC.RundownRoutine], ecx ; KAPC.RundownRoutine=RundownRoutine
.text:004B0E25 mov ecx, [ebp+NormalRoutine]
.text:004B0E28 xor edx, edx ; edx=0
.text:004B0E2A mov [eax+_KAPC.NormalRoutine], ecx ; KAPC.NormalRoutine=NormalRoutine
.text:004B0E2D cmp ecx, edx
.text:004B0E2F jz short loc_4B0E3F ; 如果NormalRoutine==0跳转
.text:004B0E31 mov cl, [ebp+ApcMode]
.text:004B0E34 mov [eax+_KAPC.ApcMode], cl ; 如果NormalRoutine!=0
.text:004B0E34 ; _KAPC.ApcMode=ApcMode
.text:004B0E37 mov ecx, [ebp+NormalContext]
.text:004B0E3A mov [eax+_KAPC.NormalContext], ecx ; KAPC.NormalContext=NormalContext
.text:004B0E3D jmp short loc_4B0E45
.text:004B0E3F ; ---------------------------------------------------------------------------
.text:004B0E3F
.text:004B0E3F loc_4B0E3F: ; CODE XREF: KeInitializeApc(x,x,x,x,x,x,x,x)+3C↑j
.text:004B0E3F mov [eax+_KAPC.ApcMode], dl ; _KAPC.ApcMode=0
.text:004B0E42 mov [eax+_KAPC.NormalContext], edx ; _KAPC.NormalContext=0
.text:004B0E45
.text:004B0E45 loc_4B0E45: ; CODE XREF: KeInitializeApc(x,x,x,x,x,x,x,x)+4A↑j
.text:004B0E45 mov [eax+_KAPC.Inserted], dl ; _KAPC.Inserted=0
.text:004B0E48 pop ebp
.text:004B0E49 retn 20h ; ' '
把反汇编大概翻译成c语言如下:
c
VOID KeInitializeApc(
PKAPC Apc,
PKTHREAD Thread,
KAPC_ENVIRONMENT Environment,
PKKERNEL_ROUTINE KernelRoutine,
PKRUNDOWN_ROUTINE RundownRoutine,
PKNORMAL_ROUTINE NormalRoutine,
KPROCESSOR_MODE ApcMode,
PVOID NormalContext
)
{
UCHAR ApcStateIndex;
//_KAPC 固定初始化
Apc->Type = 0x12; // mov [eax+Type], 12h
Apc->Size = 0x30; // mov [eax+Size], 30h
//0 OriginalApcEnvironment 原始 APC 环境(线程最初所在的进程)
//1 AttachedApcEnvironment 靠环境(KeStackAttachProcess 后的 APC 环境)
//2 CurrentApcEnvironment 初始化APC时环境
//3 InsertApcEnvironment 插入APC时环境
//如果Environment==CurrentApcEnvironment
if (Environment == CurrentApcEnvironment)
{
//则使用 Thread->ApcStateIndex(此字段用于描述此线程是否为挂靠状态)
ApcStateIndex = Thread->ApcStateIndex;
}
else
{
//否则直接使用传入的 Environment
ApcStateIndex = (UCHAR)Environment;
}
//绑定 APC 所属线程
Apc->Thread = Thread;
//设置 APC 回调函数
Apc->KernelRoutine = KernelRoutine;
Apc->RundownRoutine = RundownRoutine;
Apc->NormalRoutine = NormalRoutine;
//APC 所属 APC_STATE
Apc->ApcStateIndex = ApcStateIndex;
//存在 NormalRoutine,表示该 APC 支持 Normal APC 阶段(执行模式由 ApcMode 决定)
if (NormalRoutine != NULL)
{
Apc->ApcMode = ApcMode;
Apc->NormalContext = NormalContext;
}
else
{
`//没有 NormalRoutine:
//强制 ApcMode = 0
//NormalContext = NULL
Apc->ApcMode = 0;
Apc->NormalContext = NULL;
}
//设置APC插入状态:还未插入队列
Apc->Inserted = FALSE;
}
四、KeInitializeApc 实际只做了 5 件事
1. 初始化 _KAPC 的固定头部
- 设置对象类型与大小
- 让内核知道:"这是一块 APC 结构"
bash
Apc->Type = 0x12;
Apc->Size = sizeof(KAPC);
本质:对象合法性标记
2. 绑定 APC 的"归属线程"
- 指定 APC 将来在哪个线程上下文中执行
bash
Apc->Thread = Thread;
本质:APC 是线程私有的,不是进程私有的
3. 决定 APC 属于哪个 APC_STATE(环境)
-
如果是
CurrentApcEnvironment(2)→ 自动使用 Thread->ApcStateIndex
-
否则
→ 使用调用者指定的环境
bash
Apc->ApcStateIndex =
(Environment == CurrentApcEnvironment)
? Thread->ApcStateIndex
: Environment;
本质:APC 要挂到"原始进程"还是"挂靠进程"的 APC 队列
4.填充 APC 的回调函数与执行上下文
-
KernelRoutine(必有)
-
RundownRoutine(可选)
-
NormalRoutine(决定是否存在 Normal APC 阶段)
bash
Apc->KernelRoutine = KernelRoutine;
Apc->RundownRoutine = RundownRoutine;
Apc->NormalRoutine = NormalRoutine;
并且:
只有在存在 NormalRoutine 的情况下,APC 才具备进入 Normal APC 阶段的可能。
此时:
-
ApcMode 用于指定 NormalRoutine 的执行模式(Kernel / User)
-
NormalContext 作为传递给 NormalRoutine 的上下文参数
如果 NormalRoutine == NULL:
-
该 APC 必定是一个 纯内核 APC
-
ApcMode 与 NormalContext 对执行流程不再具有语义意义,因此被内核强制清零
5. 标记 APC 当前"尚未被插入"
bash
Apc->Inserted = FALSE;
本质:告诉内核:这个 APC 只是准备好了,但还没投递
五、结论
KeInitializeApc 的唯一目的:
把一块内存,初始化成一个"合法、完整、可被 KeInsertQueueApc 投递的 APC 描述对象"。