APC计算机编程,[原创]小Win,点一份APC(Apc机制详解)(一)

本文深入解析Windows操作系统中的APC机制,包括APC对象的创建、队列插入过程、投递逻辑及用户态APC的执行流程。通过详细的代码分析帮助读者理解APC的工作原理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

翻开

翻开小Win的菜单,APC赫然在目...

做工讲究,味道不错,是小Win的热门菜,我们点一来尝尝!

吃了可以做很多事情...APC注入

APC注入

APC注入

...细节来自于ReactOS源码分析。如果对这个发神经的文风有任何不适,请谅解,因为我确实神经了

来一份APC

ring3这么做的

点APC的正确姿势是使用QueueUserApc,不走寻常路的也可以使用NtQueueApcThreadDWORD WINAPI QueueUserApc(PARCFUNC pfnApc, HANDLE hThread, ULONG_PTR dwData);

{

NtQueueApcThread(hThread, IntCallUserApc, pfnApc, dwData, NULL);

}

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函数。static void CALLBACK

IntCallUserApc(PVOID Function, PVOID dwData, PVOID Arg3){

((PAPCFUNC)Function)(dwData);

}

ring0这么做的

NtQueueApcThread经过系统调用进入到ring0,一般人是看不到了...,我也是一般人来着,下面努力变成二班的...。

1. 创建APC对象

进了NtQueueApcThread,先通过KeInitializeApc初始化一个Apc对象/* Initialize the APC */

KeInitializeApc(Apc,                    &Thread->Tcb, //KTHREAD

OriginalApcEnvironment,

PspQueueApcSpecialApc,                    NULL,

ApcRoutine,

UserMode,

NormalContext);

APC对象结构定义如下: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;

根据KeInitializeApc传入参数,Apc被赋值如下: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中值,如下:// APC Environment Types//typedef enum _KAPC_ENVIRONMENT{

OriginalApcEnvironment,//0

AttachedApcEnvironment,//1

CurrentApcEnvironment,//2

InsertApcEnvironment

} KAPC_ENVIRONMENT;

Apc->KernelRoutine总是有值的,被赋值为PspQueueApcSpecialApc,用于Apc结束时候释放Apc对象内存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插入队列,在队列中等待被上菜...KeInsertQueueApc(Apc,

SystemArgument1,

SystemArgument2,

IO_NO_INCREMENT))确认Apc未被插入,Thread->ApcQueueable为真

Apc->Inserted = True

然后通过KiInsertQueueApc插入队列,可能通过软中断或者唤醒线程得到执行Apc的机会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_STATEtypedef 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的指针

具体看下面的代码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

}

来一个结构图

6c02b68f9e0a7deeec70ea8c99c6ad93.png

thread-217298.htm

上菜吃饭

Apc已经点了,什么时候才能端上来呢?我们接着看...

Apc投递线程wait、线程切换到应用层、线程被挂起等,一旦线程有空隙了,windows就会把apc队列顺便执行一遍

搜索NormalRoutine和KernelRoutine字段,找到KiDeliverApc,这个函数是具体分发Apc的函数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的呢,找到多处//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,PreserveEaxtrap_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,恢复eaxTRAP_EPILOG是自陷处理,参数:ebp = PKTRAP_FRAME// 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内部究竟是怎么处理的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的逻辑了,还是在看张图

thread-217298.htm

b9c7e8aa3f3d1e907185d86bd673cf4e.png

CHECK_FOR_APC_DELIVER用户态Apc的delvier有个重点,Thread->ApcState.UserApcPending必须是TRUE,那什么时候才会是TRUE,我蛮来看看在KiInsertQueueApc,如果线程等待,且Alertable是TRUEelse 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)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,否则不会SleepEx, WaitForSingleObject,WaitForMultipleObjects都可以设置线程为Alertable

接着继续看看KiInitializeUserApc是怎么切换到用户空间执行的用户态函数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提供)//根据这个执行KiUserApcDispatcher

TrapFrame->Eip = (ULONG)KeUserApcDispatcher; //其实就是KiUserApcDispatcher,是用户空间函数

TrapFrame->HardwareEsp = Stack;//栈顶

.func KiUserApcDispatcher@16.globl _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需要投递,有则继续投递, 重复上面的步骤,直到没有了则可以回到之前被中断的用户态的断点处。.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的流程总结一下:

thread-217298.htm

2cb0c0f3947204940fa543d5c386e493.png

到这里,终于执行到了用户的Apc函数。

结账走人

到这,APC流程基本弄清楚了。

下一篇将结合APC机制分析一下最近比较新的AtomBombing注入技术的详细实现和各个细节。

参考

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值