📄
字号:
}
/* Dequeue the APC */
RemoveEntryList(ApcListEntry);
Apc->Inserted = FALSE;
/* Go back to APC_LEVEL */
KeReleaseSpinLock(&Thread->ApcQueueLock, OldIrql);
/* Call the Kernel APC */
DPRINT("Delivering a Normal APC: %x\n", Apc);
KernelRoutine(Apc,
&NormalRoutine,
&NormalContext,
&SystemArgument1,
&SystemArgument2);
/* If There still is a Normal Routine, then we need to call this at PASSIVE_LEVEL */
if (NormalRoutine != NULL) {
/* At Passive Level, this APC can be prempted by a Special APC */
Thread->ApcState.KernelApcInProgress = TRUE;
KeLowerIrql(PASSIVE_LEVEL);
/* Call and Raise IRQ back to APC_LEVEL */
DPRINT("Calling the Normal Routine for a Normal APC: %x\n", Apc);
NormalRoutine(&NormalContext, &SystemArgument1, &SystemArgument2);
KeRaiseIrql(APC_LEVEL, &OldIrql);
}
/* Raise IRQL and Lock again */
KeAcquireSpinLock(&Thread->ApcQueueLock, &OldIrql);
Thread->ApcState.KernelApcInProgress = FALSE;
}
} //end while[/code]
参数DeliveryMode表示需要“投递”哪一种APC,可以是UserMode,也可以是KernelMode。不过,KernelMode确实表示只要求执行内核APC,而UserMode却表示在执行内核APC之外再执行用户APC。这里所谓“执行内核APC”是执行内核APC队列中的所有请求,而“执行用户APC”却只是执行用户APC队列中的一项。
所以首先检查内核模式APC队列,只要非空就通过一个while循环处理其所有的APC请求。队列中的每一项(如果队列非空的话)、即每一个APC请求都是KAPC结构,结构中有三个函数指针,但是这里只涉及其中的两个。一个是NormalRoutine,若为非0就是指向一个实质性的内核APC函数。另一个是KernelRoutine,指向一个辅助性的内核APC函数,这个指针不会是0,否则这个KAPC结构就不会在队列中了(注意KernelRoutine与内核模式NormalRoutine的区别)。NormalRoutine为0是一种特殊的情况,在这种情况下KernelRoutine所指的内核函数无条件地得到调用。但是,如果NormalRoutine非0,那么首先得到调用的是KernelRoutine,而指针NormalRoutine的地址是作为参数传下去的。KernelRoutine的执行有可能改变这个指针的值。这样,如果执行KernelRoutine以后NormalRoutine仍为非0,那就说明需要加以执行,所以通过这个函数指针予以调用。不过,内核APC函数的执行是在PASSIVE_LEVEL级别上执行的,所以对NormalRoutine的调用前有KeLowerIrql()、后有KeRaiseIrql(),前者将CPU的运行级别调整为PASSIVE_LEVEL,后者则将其恢复为APC_LEVEL。
执行完内核APC队列中的所有请求以后,如果调用参数DeliveryMode为UserMode的话,就轮到用户APC了。我们继续往下看:
[code][KiDeliverApc()]
/* Now we do the User APCs */
if ((!IsListEmpty(&Thread->ApcState.ApcListHead[UserMode])) &&
(DeliveryMode == UserMode) && (Thread->ApcState.UserApcPending == TRUE)) {
/* It's not pending anymore */
Thread->ApcState.UserApcPending = FALSE;
/* Get the APC Object */
ApcListEntry = Thread->ApcState.ApcListHead[UserMode].Flink;
Apc = CONTAINING_RECORD(ApcListEntry, KAPC, ApcListEntry);
/* Save Parameters so that it's safe to free the Object in Kernel Routine*/
NormalRoutine = Apc->NormalRoutine;
KernelRoutine = Apc->KernelRoutine;
NormalContext = Apc->NormalContext;
SystemArgument1 = Apc->SystemArgument1;
SystemArgument2 = Apc->SystemArgument2;
/* Remove the APC from Queue, restore IRQL and call the APC */
RemoveEntryList(ApcListEntry);
Apc->Inserted = FALSE;
KeReleaseSpinLock(&Thread->ApcQueueLock, OldIrql);
DPRINT("Calling the Kernel Routine for for a User APC: %x\n", Apc);
KernelRoutine(Apc,
&NormalRoutine,
&NormalContext,
&SystemArgument1,
&SystemArgument2);
if (NormalRoutine == NULL) {
/* Check if more User APCs are Pending */
KeTestAlertThread(UserMode);
}else {
/* Set up the Trap Frame and prepare for Execution in NTDLL.DLL */
DPRINT("Delivering a User APC: %x\n", Apc);
KiInitializeUserApc(Reserved,
TrapFrame,
NormalRoutine,
NormalContext,
SystemArgument1,
SystemArgument2);
}
} else {
/* Go back to APC_LEVEL */
KeReleaseSpinLock(&Thread->ApcQueueLock, OldIrql);
}
}[/code]
当然,执行用户APC是有条件的。首先自然是用户APC队列非空,同时调用参数DeliveryMode必须是UserMode;并且ApcState中的UserApcPending为TRUE,表示队列中的请求确实是要求尽快加以执行的。
读者也许已经注意到,比之内核APC队列,对用户APC队列的处理有个显著的不同,那就是对用户APC队列并不是通过一个while循环处理队列中的所有请求,而是每次进入KiDeliverApc()只处理用户APC队列中的第一个请求。同样,这里也是只涉及两个函数指针,即NormalRoutine和KernelRoutine,也是先执行KernelRoutine,并且KernelRoutine可以对指针NormalRoutine作出修正。但是再往下就不同了。
首先,如果执行完KernelRoutine(所指的函数)以后指针NormalRoutine为0,这里要执行KeTestAlertThread()。这又是跟设备驱动有关的事(Windows术语中的Alert相当于Linux术语中的“唤醒”),我们在这里暂不关心。
反之,如果指针NormalRoutine仍为非0,那么这里执行的是KiInitializeUserApc(),而不是直接调用NormalRoutine所指的函数,因为NormalRoutine所指的函数是在用户空间,要等CPU回到用户空间才能执行,这里只是为其作好安排和准备。
[code][KiDeliverApc() > KiInitializeUserApc()]
VOID
STDCALL
KiInitializeUserApc(IN PVOID Reserved,
IN PKTRAP_FRAME TrapFrame,
IN PKNORMAL_ROUTINE NormalRoutine,
IN PVOID NormalContext,
IN PVOID SystemArgument1,
IN PVOID SystemArgument2)
{
PCONTEXT Context;
PULONG Esp;
. . . . . .
/*
* Save the thread's current context (in other words the registers
* that will be restored when it returns to user mode) so the
* APC dispatcher can restore them later
*/
Context = (PCONTEXT)(((PUCHAR)TrapFrame->Esp) - sizeof(CONTEXT));
RtlZeroMemory(Context, sizeof(CONTEXT));
Context->ContextFlags = CONTEXT_FULL;
Context->SegGs = TrapFrame->Gs;
Context->SegFs = TrapFrame->Fs;
Context->SegEs = TrapFrame->Es;
Context->SegDs = TrapFrame->Ds;
Context->Edi = TrapFrame->Edi;
Context->Esi = TrapFrame->Esi;
Context->Ebx = TrapFrame->Ebx;
Context->Edx = TrapFrame->Edx;
Context->Ecx = TrapFrame->Ecx;
Context->Eax = TrapFrame->Eax;
Context->Ebp = TrapFrame->Ebp;
Context->Eip = TrapFrame->Eip;
Context->SegCs = TrapFrame->Cs;
Context->EFlags = TrapFrame->Eflags;
Context->Esp = TrapFrame->Esp;
Context->SegSs = TrapFrame->Ss;
/*
* Setup the trap frame so the thread will start executing at the
* APC Dispatcher when it returns to user-mode
*/
Esp = (PULONG)(((PUCHAR)TrapFrame->Esp) -
(sizeof(CONTEXT) + (6 * sizeof(ULONG))));
Esp[0] = 0xdeadbeef;
Esp[1] = (ULONG)NormalRoutine;
Esp[2] = (ULONG)NormalContext;
Esp[3] = (ULONG)SystemArgument1;
Esp[4] = (ULONG)SystemArgument2;
Esp[5] = (ULONG)Context;
TrapFrame->Eip = (ULONG)LdrpGetSystemDllApcDispatcher();
TrapFrame->Esp = (ULONG)Esp;
}[/code]
这个函数的名字取得不好,很容易让人把它跟前面的KeInitializeApc()相连系,实际上却完全是两码事。参数TrapFrame是由KiDeliverApc()传下来的一个指针,指向用户空间堆栈上的“中断现场”。这里要做的事情就是在原有现场的基础上“注水”,伪造出一个新的现场,使得CPU返回用户空间时误认为中断(或系统调用)发生于进入APC函数的前夕,从而转向APC函数。
怎么伪造呢?首先使用户空间的堆栈指针Esp下移一个CONTEXT数据结构的大小,外加6个32位整数的位置(注意堆栈是由上向下伸展的)。换言之就是在用户空间堆栈上扩充出一个CONTEXT数据结构和6个32位整数。注意,TrapFrame是在系统空间堆栈上,而TrapFrame->Esp的值是用户空间的堆栈指针,所指向的是用户空间堆栈。所以这里扩充的是用户空间堆栈。这样,原先的用户堆栈下方是CONTEXT数据结构Context,再往下就是那6个32位整数。然后把TrapFrame的内容保存在这个CONTEXT数据结构中,并设置好6个32位整数,那是要作为调用参数传递的。接着就把保存在TrapFrame中的Eip映像改成指向用户空间的一个特殊函数,具体的地址通过LdrpGetSystemDllApcDispatcher()获取。这样,当CPU返回到用户空间时,就会从这个特殊函数“继续”执行。当然,也要调整TrapFrame中的用户空间堆栈指针Esp。
LdrpGetSystemDllApcDispatcher()只是返回一个(内核)全局量SystemDllApcDispatcher的值,这个值是个函数指针,指向ntdll.dll中的一个函数,是在映射ntdll.dll映像时设置好的。
[code]PVOID LdrpGetSystemDllApcDispatcher(VOID)
{
return(SystemDllApcDispatcher);
}[/code]
与全局变量SystemDllApcDispatcher相似的函数指针有:
● SystemDllEntryPoint,指向LdrInitializeThunk()。
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -