小Win,点一份APC(Apc机制详解)(一)

翻开

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

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

吃了可以做很多事情...

  • APC注入
  • APC注入
  • APC注入
  • ...

细节来自于ReactOS源码分析。

如果对这个发神经的文风有任何不适,请谅解,因为我确实神经了

来一份APC

ring3这么做的

点APC的正确姿势是使用QueueUserApc,不走寻常路的也可以使用NtQueueApcThread

DWORD 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,其实就是IntCallUserApc
Apc->NormalContext = NormalContext;//pfnApc;//用户指定的Apc函数
Apc->Type = ApcObject;

//如果参数指定的是CurrentApcEnvironment,直接赋值Thread->ApcStateIndex
Apc->ApcStateIndex = Thread->ApcStateIndex;
//不是则
Apc->ApcStateIndex = OriginalApcEnvironment;//

//如果参数ApcRoutine不是NULL
Apc->ApcMode = Mode;
Apc->NormalContext = Context;
//是NULL
Apc->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))
  1. 确认Apc未被插入,Thread->ApcQueueable为真
  2. Apc->Inserted = True
  3. 然后通过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_STATE

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的指针

具体看下面的代码

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.png

上菜吃饭

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

Apc投递

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

搜索NormalRoutineKernelRoutine字段,找到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.c
VOID 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,那就跟着来看看吧。

  1. 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
  1. TRAP_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_frame
OldTrapFrame = 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 Apc
if ((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的逻辑了,还是在看张图

KiDeliverApc.png

CHECK_FOR_APC_DELIVER用户态Apc的delvier有个重点,Thread->ApcState.UserApcPending必须是TRUE,那什么时候才会是TRUE,我蛮来看看

  1. 在KiInsertQueueApc,如果线程等待,且Alertable是TRUE
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;
            }
  1. 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是怎么切换到用户空间执行的用户态函数

VOID
NTAPI
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置为TRUE

DontTest:
    /* Return to previous context */
    pop ebp
    mov esp, ebp
    jmp _KiServiceExit2 ; 本质和_KiServiceExit相同,如果还有用户APC,会继续投递,直到投递完,才会回到用户被中断的点

Error:
    pop ebp
    mov esp, ebp
    jmp _KiServiceExit
.endfunc

下面将_KiServiceExit到IntCallUserApc的流程总结一下:

deliver.png

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

结账走人

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

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

参考

  1. Reactos内核情景源码分析
  2. 线程的Alertable与User APC

转载请注明出处,博客原文:http://anhkgg.github.io/win-apc-analyze1/

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,012评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,628评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,653评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,485评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,574评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,590评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,596评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,340评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,794评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,102评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,276评论 1 344
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,940评论 5 339
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,583评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,201评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,441评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,173评论 2 366
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,136评论 2 352

推荐阅读更多精彩内容