好友
阅读权限 20
听众
最后登录 1970-1-1
古月不傲
发表于 2020-3-5 04:21
本帖最后由 古月不傲 于 2020-3-6 04:26 编辑
链接:https://bbs.pediy.com/thread-217298.htm 看雪直接扒过来的 作者:Angelxf
资料:
https://www.bilibili.com/video/av68700135?p=69
windows内核情景分析 第五章
翻开 翻开小Win的菜单,APC赫然在目...做工讲究,味道不错,是小Win的热门菜,我们点一来尝尝!吃了可以做很多事情... APC注入 APC注入
APC注入
...
细节来自于ReactOS源码分析。
如果对这个发神经的文风有任何不适,请谅解,因为我确实神经了 来一份APC ring3这么做的 点APC的正确姿势是使用QueueUserApc,不走寻常路的也可以使用NtQueueApcThread [C] 纯文本查看 复制代码
NTSTATUS NTAPI NtQueueApcThread(IN HANDLE ThreadHandle,
IN PKNORMAL_ROUTINUE ApcRoutine,
IN PVOID NormalContext, //pfnApc
IN PVOID SystemArgument1, //dwData
IN PVOID SystemArgument2
); 也就是QueueUserApc内部是NtQueueApcThread做的,两者区别不大,当然,使用后者可以字节加点调料(不使用IntCallUserApc、换成自己的函数,函数参数也可以有三个了,而PARCFUNC只有一个参数)。 小Win默认是通过统一的接口IntCallUserApc来调用顾客指定的Apc函数。 [C] 纯文本查看 复制代码
static void CALLBACK
IntCallUserApc(PVOID Function, PVOID dwData, PVOID Arg3){
((PAPCFUNC)Function)(dwData);
} ring0这么做的
NtQueueApcThread经过系统调用进入到ring0,一般人是看不到了...,我也是一般人来着,下面努力变成二班的...。 1. 创建APC对象
[C] 纯文本查看 复制代码
/* Initialize the APC */
KeInitializeApc(Apc,
&Thread->Tcb, //KTHREAD
OriginalApcEnvironment,
PspQueueApcSpecialApc,
NULL,
ApcRoutine,
UserMode,
NormalContext); APC对象结构定义如下:
[C] 纯文本查看 复制代码
typedef struct _KAPC
{
UCHAR Type; //类型ApcObject
UCHAR SpareByte0; UCHAR Size; //APC结构体大小
UCHAR SpareByte1; ULONG SpareLong0;
struct _KTHREAD *Thread; //当前线程的KTHREAD
LIST_ENTRY ApcListEntry; //当前线程的APC链表
PKKERNEL_ROUTINE KernelRoutine; //
PKRUNDOWN_ROUTINE RundownRoutine; //
PKNORMAL_ROUTINE NormalRoutine; //
PVOID NormalContext; //用户定义的Apc函数
PVOID SystemArgument1; //用户Apc函数的参数
PVOID SystemArgument2;//
CCHAR ApcStateIndex; //Apc状态
KPROCESSOR_MODE ApcMode; //Apc所处的Mode,UserMode/KernelMode
BOOLEAN Inserted; //是否已经被插入队列} KAPC, *PKAPC, *RESTRICTED_POINTER PRKAPC;
}KAPC, *PKAPC;
根据KeInitializeApc传入参数,Apc被赋值如下:
[C] 纯文本查看 复制代码
Apc->KernelRoutine = PspQueueApcSpecialApc;
Apc->RundownRoutine = NULL;
Apc->NormalRoutine = ApcRoutine;//如果使用QueueUserApc,其实就是IntCallUserApcApc->NormalContext = NormalContext;//pfnApc;//用户指定的Apc函数Apc->Type = ApcObject;//如果参数指定的是CurrentApcEnvironment,直接赋值Thread->ApcStateIndexApc->ApcStateIndex = Thread->ApcStateIndex;//不是则Apc->ApcStateIndex = OriginalApcEnvironment;////如果参数ApcRoutine不是NULLApc->ApcMode = Mode;
Apc->NormalContext = Context;//是NULLApc->ApcMode = KernelMode;
Apc->NormalContext = NULL;
Apc->Inserted = False;
其中关于ApcStateIndex有4中值,如下:
[C] 纯文本查看 复制代码
// APC Environment Types
typedef enum _KAPC_ENVIRONMENT{
OriginalApcEnvironment,//0
AttachedApcEnvironment,//1
CurrentApcEnvironment,//2
InsertApcEnvironment
} KAPC_ENVIRONMENT;
Apc->KernelRoutine总是有值的,被赋值为PspQueueApcSpecialApc,用于Apc结束时候释放Apc对象内存
[C] 纯文本查看 复制代码
VOID
NTAPI
PspQueueApcSpecialApc(IN PKAPC Apc,
IN OUT PKNORMAL_ROUTINE* NormalRoutine,
IN OUT PVOID* NormalContext,
IN OUT PVOID* SystemArgument1,
IN OUT PVOID* SystemArgument2)
{ /* Free the APC and do nothing else */
ExFreePool(Apc);
} 2. 插入APC队列
通过 KeInsertQueueApc插入队列,在队列中等待被上菜...
[C] 纯文本查看 复制代码
VOID
FASTCALL
KiInsertQueueApc(IN PKAPC Apc,
IN KPRIORITY PriorityBoost)
{
if (Apc->ApcStateIndex == InsertApcEnvironment)
{
Apc->ApcStateIndex = Thread->ApcStateIndex;
}
//PKAPC_STATE ApcStatePointer[2];//说明ApcStateIndex只能是
//OriginalApcEnvironment,//0
//AttachedApcEnvironment,//1
//从Thread的ApcStatePointer取出对应的ApcState
ApcState = Thread->ApcStatePointer[(UCHAR)Apc->ApcStateIndex];
ApcMode = Apc->ApcMode;
ASSERT(Apc->Inserted == TRUE);
/* 插入队列的三种方式:
* 1) Kernel APC with Normal Routine or User APC = Put it at the end of the List
* 2) User APC which is PsExitSpecialApc = Put it at the front of the List
* 3) Kernel APC without Normal Routine = Put it at the end of the No-Normal Routine Kernel APC list
*/
//PsExitSpecialApc
if (Thread->ApcStateIndex == Apc->ApcStateIndex)
{
if (当前线程) {
if (KernelMode) {
Thread->ApcState.KernelApcPending = TRUE;
if (!Thread->SpecialApcDisable)
{ //中断线程当前执行六??
/* They're not, so request the interrupt */
HalRequestSoftwareInterrupt(APC_LEVEL);
}
}
}
else {
if (KernelMode) {
Thread->ApcState.KernelApcPending = TRUE;
if (Thread->State == Running) HalRequestSoftwareInterrupt(APC_LEVEL);
else if (一堆条件) {
KiUnwaitThread(Thread, Status, PriorityBoost);//唤醒线程
}
}
else {
if ((Thread->State == Waiting) &&
(Thread->WaitMode == UserMode) &&
((Thread->Alertable) || //
(Thread->ApcState.UserApcPending)))
{ /* Set user-mode APC pending */
Thread->ApcState.UserApcPending = TRUE;
Status = STATUS_USER_APC;
KiUnwaitThread(Thread, Status, PriorityBoost);//唤醒线程
}
}
}
}
} 先不管Apc是怎么得到执行的,来看看KAPC_STATE
[C] 纯文本查看 复制代码
typedef struct _KAPC_STATE
{
LIST_ENTRY ApcListHead[2];//UserMode/KernelMode的两个链表
struct _KPROCESS *Process;
BOOLEAN KernelApcInProgress;
BOOLEAN KernelApcPending; //等待执行
BOOLEAN UserApcPending; //等待执行
} KAPC_STATE, *PKAPC_STATE, *RESTRICTED_POINTER PRKAPC_STATE; 其中ApcListHead保存了线程的两个Apc链表,分别对应UserMode和KernelMode。Thread->ApcState表示当前需要执行的ApcState,可能是挂靠进程的Thread->SavedApcState表示挂靠后保存的当前线程的ApcState,KTHREAD的ApcStatePointer[2]字段保存了两个ApcState的指针具体看下面的代码 [C] 纯文本查看 复制代码
KeAttachProcess->
VOID
NTAPI
KiAttachProcess(IN PKTHREAD Thread,
IN PKPROCESS Process,
IN PKLOCK_QUEUE_HANDLE ApcLock,
IN PRKAPC_STATE SavedApcState //&Thread->SavedApcThread
)
{/* Swap the APC Environment */
KiMoveApcState(&Thread->ApcState, SavedApcState); //把当前ApcState保存到SavedApcState
/* Reinitialize Apc State */
InitializeListHead(&Thread->ApcState.ApcListHead[KernelMode]);
InitializeListHead(&Thread->ApcState.ApcListHead[UserMode]);
Thread->ApcState.Process = Process;
Thread->ApcState.KernelApcInProgress = FALSE;
Thread->ApcState.KernelApcPending = FALSE;
Thread->ApcState.UserApcPending = FALSE; /* Update Environment Pointers if needed*/
if (SavedApcState == &Thread->SavedApcState)
{
Thread->ApcStatePointer[OriginalApcEnvironment] = &Thread->
SavedApcState;//
Thread->ApcStatePointer[AttachedApcEnvironment] = &Thread->ApcState;
Thread->ApcStateIndex = AttachedApcEnvironment; //index变成了AttachedApcEnvironment
}
} 来一个结构图 上菜吃饭
Apc已经点了,什么时候才能端上来呢?我们接着看...Apc投递
[C] 纯文本查看 复制代码
线程wait、线程切换到应用层、线程被挂起等,一旦线程有空隙了,windows就会把apc队列顺便执行一遍
搜索 NormalRoutine和 KernelRoutine字段,找到 KiDeliverApc,这个函数是具体分发Apc的函数
[C] 纯文本查看 复制代码
VOID
NTAPI
KiDeliverApc(IN KPROCESSOR_MODE DeliveryMode, IN PKEXCEPTION_FRAME ExceptionFrame, IN PKTRAP_FRAME TrapFrame)
* @remarks First, Special APCs are delivered, followed by Kernel-Mode APCs and
* User-Mode APCs. Note that the TrapFrame is only valid if the
* delivery mode is User-Mode.
* Upon entry, this routine executes at APC_LEVEL.
那在哪里调用的KiDeliverApc的呢,找到多处
[C] 纯文本查看 复制代码
//hal\halx86\generic\irq.S.globl _HalpApcInterrupt2ndEntry.func HalpApcInterrupt2ndEntry]//hal\halx86\generic\irql.cVOID HalpLowerIrql(KIRQL NewIrql);//暂时忽略上面两个了//ke\i386\trap.s.func KiServiceExit
_KiServiceExit: /* Disable interrupts */
cli /* Check for, and deliver, User-Mode APCs if needed */
CHECK_FOR_APC_DELIVER 1 //
/* Exit and cleanup */
TRAP_EPILOG FromSystemCall, DoRestorePreviousMode, DoNotRestoreSegments, DoNotRestoreVolatiles, DoRestoreEverything
.endfunc
根据《windows内核情景分析》介绍, 执行用户APC的时机在从内核返回用户空间的途中(可能是系统调用、中断、异常处理之后需要返回用户空间) 也就是肯定会经过_KiServiceExit,那就跟着来看看吧。CHECK_FOR_APC_DELIVER宏 检查是不是需要投递Apc,具体检查trapframe是不是指向返回用户模式的,是则继续检查用户模式Apc是否需要投递。 参数:ebp = PKTRAP_FRAME,PreserveEax
trap_frame.Eflags == EFLAGS_V86_MASK,运行在V86模式,不检查是否是用户模式的trap_frame trap_frame.Segcs != 1(KernelMode),表示是用户模式 kthread = PCR[KPCR_CURRENT_THREAD],kthread.alerted = 0,置为不可唤醒 kthread->ApcState.UserApcPending 是FALSE,啥也不做,TRUE才进行投递 如果PreserveEax=1,保存eax,保存一些IRQL提升会清除的信息到trap_frame,fs,ds,es,gs 提示irql到APC_LEVEL 调用KiDeliverApc(UserMode, 0, trap_frame); 恢复irql 如果PreserveEax=1,恢复eax
TRAP_EPILOG是自陷处理,参数:ebp = PKTRAP_FRAME
[C] 纯文本查看 复制代码
// This macro creates an epilogue for leaving any system trap. // It is used for exiting system calls, exceptions, interrupts and generic // traps.
通过TrapFrame恢复一堆寄存器、堆栈信息,然后sysexit回到用户态空间
继续看一下调用KiDeliverApc内部究竟是怎么处理的[C] 纯文本查看 复制代码
KiDeliverApc(IN KPROCESSOR_MODE DeliveryMode,
IN PKEXCEPTION_FRAME ExceptionFrame,
IN PKTRAP_FRAME TrapFrame) //系统空间堆栈的“自陷框架”{//1. 保存原来的trap_frameOldTrapFrame = Thread->TrapFrame;
Thread->TrapFrame = TrapFrame;/* Clear Kernel APC Pending */Thread->ApcState.KernelApcPending = FALSE;/* Check if Special APCs are disabled */if (Thread->SpecialApcDisable) goto Quickie;//2. 先投递内核Apc,循环投递队列中所有的内核apc,不涉及切换到用户空间while (!IsListEmpty(&Thread->ApcState.ApcListHead[KernelMode]))
{ //Thread->ApcQueueLock加锁访问
//取出一个Apc
ApcListEntry = Thread->ApcState.ApcListHead[KernelMode].Flink;
Apc = CONTAINING_RECORD(ApcListEntry, KAPC, ApcListEntry);
NormalRoutine = Apc->NormalRoutine;
KernelRoutine = Apc->KernelRoutine;
NormalContext = Apc->NormalContext;
SystemArgument1 = Apc->SystemArgument1;
SystemArgument2 = Apc->SystemArgument2;
//特殊Apc,特指内核Apc,但是Apc的NormalRoutine是空的
if (!NormalRoutine) { //将Apc出队列,然通过KernelRoutine调用内核Apc响应函数
KernelRoutine(Apc,
&NormalRoutine,
&NormalContext,
&SystemArgument1,
&SystemArgument2);
} else { //普通的内核Apc
if ((Thread->ApcState.KernelApcInProgress) ||
(Thread->KernelApcDisable))
{ //退出,必须安全才会投递
} ////将Apc出队列,然通过KernelRoutine调用内核Apc响应函数
KernelRoutine(Apc,
&NormalRoutine, //内部可能修改NormalRoutine
&NormalContext,
&SystemArgument1,
&SystemArgument2);
//如果NormalRoutine依然不为空,在调用NormalRoutine
if (NormalRoutine)
{ /* At Passive Level, an APC can be prempted by a Special APC */
Thread->ApcState.KernelApcInProgress = TRUE;
KeLowerIrql(PASSIVE_LEVEL); //将到PASSIVE_LEVEL执行
/* Call and Raise IRQ back to APC_LEVEL */
NormalRoutine(NormalContext, SystemArgument1, SystemArgument2);
KeRaiseIrql(APC_LEVEL, &ApcLock.OldIrql);
}
Thread->ApcState.KernelApcInProgress = FALSE; //继续循环
}
}//3. 投递完内核apc,如果KiDeliverApc目标是用户apc,那么继续投递用户apc//每次值投递一个User mode Apcif ((DeliveryMode == UserMode) &&
!(IsListEmpty(&Thread->ApcState.ApcListHead[UserMode])) &&
(Thread->ApcState.UserApcPending)) //TRUE {
Thread->ApcState.UserApcPending = FALSE; //取出第一个Apc
//先调用他的KernelRoutine
KernelRoutine(Apc,
&NormalRoutine,
&NormalContext,
&SystemArgument1,
&SystemArgument2); /* Check if there's no normal routine */
if (!NormalRoutine)
{ /* Check if more User APCs are Pending */
KeTestAlertThread(UserMode);
} else
{ /* Set up the Trap Frame and prepare for Execution in NTDLL.DLL */
//不是直接调用NormalRoutine,因为他是用户太的函数,需要切换到用户空间才能执行
KiInitializeUserApc(ExceptionFrame,
TrapFrame,
NormalRoutine,
NormalContext,
SystemArgument1,
SystemArgument2);
}
} 根据注释应该很清楚deliver的逻辑了,还是在看张图 CHECK_FOR_APC_DELIVER用户态Apc的delvier有个重点,Thread->ApcState.UserApcPending必须是TRUE,那什么时候才会是TRUE,我蛮来看看 在KiInsertQueueApc,如果线程等待,且Alertable是TRUE
[C] 纯文本查看 复制代码
else if ((Thread->State == Waiting) &&
(Thread->WaitMode == UserMode) &&
((Thread->Alertable) || //
(Thread->ApcState.UserApcPending)))
{ /* Set user-mode APC pending */
Thread->ApcState.UserApcPending = TRUE;
Status = STATUS_USER_APC;
goto Unwait;
}
KiCheckAlertability中(wrk中是TestForAlertPending)
[C] 纯文本查看 复制代码
FORCEINLINE
NTSTATUS
KiCheckAlertability(IN PKTHREAD Thread,
IN BOOLEAN Alertable,
IN KPROCESSOR_MODE WaitMode)
{ /* Check if the wait is alertable */
if (Alertable)
{ /* It is, first check if the thread is alerted in this mode */
if (Thread->Alerted[WaitMode])
{ /* It is, so bail out of the wait */
Thread->Alerted[WaitMode] = FALSE; return STATUS_ALERTED;
} else if ((WaitMode != KernelMode) &&
(!IsListEmpty(&Thread->ApcState.ApcListHead[UserMode])))
{ /* It's isn't, but this is a user wait with queued user APCs */
Thread->ApcState.UserApcPending = TRUE; return STATUS_USER_APC;
两种情况都需要Alertable = TRUE,这个字段表示线程是唤醒的,也就是说只有可唤醒的线程,才能拿投递他的用态APC,否则不会
[C] 纯文本查看 复制代码
SleepEx, WaitForSingleObject,WaitForMultipleObjects都可以设置线程为Alertable
接着继续看看 KiInitializeUserApc是怎么切换到用户空间执行的用户态函数
[C] 纯文本查看 复制代码
VOIDNTAPI
KiInitializeUserApc(IN PKEXCEPTION_FRAME ExceptionFrame, IN PKTRAP_FRAME TrapFrame, IN PKNORMAL_ROUTINE NormalRoutine, IN PVOID NormalContext, IN PVOID SystemArgument1, IN PVOID SystemArgument2)
{ //V86模式下,不投递
/* Save the full context */
Context.ContextFlags = CONTEXT_FULL | CONTEXT_DEBUG_REGISTERS;
KeTrapFrameToContext(TrapFrame, ExceptionFrame, &Context);
//检查不是KernleMode
ASSERT((TrapFrame->SegCs & MODE_MASK) != KernelMode);
...
/* Get the aligned size */
AlignedEsp = Context.Esp & ~3;//来自于TrapFrame.HardwareEsp或TempEsp
//Context和4个参数的长度
ContextLength = CONTEXT_ALIGNED_SIZE + (4 * sizeof(ULONG_PTR)); //将原始堆栈扩展ContextLength,用来保存Context和参数
Stack = ((AlignedEsp - 8) & ~3) - ContextLength; /* Probe the stack */
ProbeForWrite((PVOID)Stack, AlignedEsp - Stack, 1);
ASSERT(!(Stack & 3)); /* Copy data into it */
//(4 * sizeof(ULONG_PTR)))是后面4个参数的位置,然后接着拷贝Context,将老的TrapFrame内容拷贝到用户太堆栈中
RtlCopyMemory((PVOID)(Stack + (4 * sizeof(ULONG_PTR))), &Context,
sizeof(CONTEXT)); /* Run at APC dispatcher */
TrapFrame->Eip = (ULONG)KeUserApcDispatcher; //KeUserApcDispatcher保存的其实就是KiUserApcDispatcher,是用户空间函数
TrapFrame->HardwareEsp = Stack;//栈顶
/* Setup Ring 3 state */
TrapFrame->SegCs = Ke386SanitizeSeg(KGDT_R3_CODE, UserMode);
TrapFrame->HardwareSegSs = Ke386SanitizeSeg(KGDT_R3_DATA, UserMode);
TrapFrame->SegDs = Ke386SanitizeSeg(KGDT_R3_DATA, UserMode);
TrapFrame->SegEs = Ke386SanitizeSeg(KGDT_R3_DATA, UserMode);
TrapFrame->SegFs = Ke386SanitizeSeg(KGDT_R3_TEB, UserMode);
TrapFrame->SegGs = 0;
TrapFrame->ErrCode = 0; /* Sanitize EFLAGS */
TrapFrame->EFlags = Ke386SanitizeFlags(Context.EFlags, UserMode); /* Check if thread has IOPL and force it enabled if so */
if (KeGetCurrentThread()->Iopl) TrapFrame->EFlags |= 0x3000; /* Setup the stack */
*(PULONG_PTR)(Stack + 0 * sizeof(ULONG_PTR)) = (ULONG_PTR)NormalRoutine;
*(PULONG_PTR)(Stack + 1 * sizeof(ULONG_PTR)) = (ULONG_PTR)NormalContext;
*(PULONG_PTR)(Stack + 2 * sizeof(ULONG_PTR)) = (ULONG_PTR)SystemArgument1;
*(PULONG_PTR)(Stack + 3 * sizeof(ULONG_PTR)) = (ULONG_PTR)SystemArgument2; ...
}
执行流程根据注释应该很清楚了,这里要解释一下TrapFrame。 CPU进入内核之后,内核堆栈就会有个TrapFrame,保存的是用户空间的线程(因进入内核原因不同,可能是自陷、中断、异常框架,都是一样的结构)。 CPU返回用户空间时会使用这个TrapFrame,才能正确返回原来的断点,并回复寄存器的状态 这里为了让Apc返回到用户空间执行,就会修改这个TrapFrame, 原来的TrapFrame就需要保存,这里保存在了用户空间堆栈中(CONTEXT) 执行完Apc函数之后,执行一个NtContinue,将这个CONTEXT作为参数,这样保存的TrapFrame就会还原到原来的状态, 然后CPU又能正常回之前的用户空间了。 KiDeliverApc完了之后,回到_KiServiceExit,会使用被修改过的TrapFrame回到用户空间,执行指定的 KiUserApcDispatcher(ntdll提供)
[C] 纯文本查看 复制代码
//根据这个执行KiUserApcDispatcher
TrapFrame->Eip = (ULONG)KeUserApcDispatcher; //其实就是KiUserApcDispatcher,是用户空间函数
TrapFrame->HardwareEsp = Stack;//栈顶
.func [url=mailto:KiUserApcDispatcher@16.globl]KiUserApcDispatcher@16.globl[/url] _KiUserApcDispatcher@16_KiUserApcDispatcher@16:
/* Setup SEH stack */ lea eax, [esp+CONTEXT_ALIGNED_SIZE+16];原始堆栈的位置,SEH
mov ecx, fs:[TEB_EXCEPTION_LIST] mov edx, offset _KiUserApcExceptionHandler mov [eax], ecx
mov [eax+4], edx
/* Enable SEH */ mov fs:[TEB_EXCEPTION_LIST], eax
/* Put the Context in EDI */ pop eax;弹出第一个参数
lea edi, [esp+12];context的位置
/* Call the APC Routine */ call eax //调用IntCallUserApc
/* Restore exception list */ mov ecx, [edi+CONTEXT_ALIGNED_SIZE] mov fs:[TEB_EXCEPTION_LIST], ecx
/* Switch back to the context */ push 1
push edi;Context
call _ZwContinue@8 //正常是不会返回的
/* Save callback return value */ mov esi, eax
/* Raise status */StatusRaiseApc:
push esi
call _RtlRaiseStatus@4 //如果ZwContinue失败了,这里处理 jmp StatusRaiseApc ret 16.endfunc
KiUserApcDispatcher其实挺简单的,通过esp弹出APc函数,然后调用,就进入了IntCallUserApc,
恢复TrapFrame
执行完成后,调用_ZwContinue(Context, 1),回到内核回复之前修改TrapFrame,也会重新检查是否有Apc需要投递,有则继续投递, 重复上面的步骤,直到没有了则可以回到之前被中断的用户态的断点处。
[C] 纯文本查看 复制代码
.func NtContinue@8_NtContinue@8:
/* NOTE: We -must- be called by Zw* to have the right frame! */
/* Push the stack frame */ push ebp ; 指向本次调用的自陷框架,记为T1
/* Get the current thread and restore its trap frame */ mov ebx, PCR[KPCR_CURRENT_THREAD] mov edx, [ebp+KTRAP_FRAME_EDX] mov [ebx+KTHREAD_TRAP_FRAME], edx;thread->TrapFrame = edx
/* Set up stack frame */ mov ebp, esp ; ESP指向新的框架(函数调用框架)
/* Save the parameters */ mov eax, [ebp+0] ; 原来的EBP,就是自陷框架指针,就是T1
mov ecx, [ebp+8] ; Context
/* Call KiContinue */ push eax ;TrapFrame
push 0 ;ExceptionFrame
push ecx ;Context
call _KiContinue@12 ; 将Context恢复到T1中
/* Check if we failed (bad context record) */ or eax, eax
jnz Error
/* Check if test alert was requested */ cmp dword ptr [ebp+12], 0
je DontTest
/* Test alert for the thread */ mov al, [ebx+KTHREAD_PREVIOUS_MODE] push eax
call _KeTestAlertThread@4 ; 检查用户模式APC队列是否为空,不空将UserApcPending置为TRUEDontTest:
/* Return to previous context */ pop ebp
mov esp, ebp
jmp _KiServiceExit2 ; 本质和_KiServiceExit相同,如果还有用户APC,会继续投递,直到投递完,才会回到用户被中断的点Error:
pop ebp
mov esp, ebp
jmp _KiServiceExit.endfunc
下面将_KiServiceExit到IntCallUserApc的流程总结一下:
到这里,终于执行到了用户的Apc函数。
结账走人 到这,APC流程基本弄清楚了。下一篇将结合APC机制分析一下最近比较新的AtomBombing注入技术的详细实现和各个细节。参考 Reactos内核情景源码分析 线程的Alertable与User APC
如果大家觉得还不错,欢迎关注我的博客: http://anhkgg.github.io/win-apc-analyze1/
免费评分
查看全部评分