美文网首页Windows原理
小Win,点一份APC(Apc机制详解)(一)

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

作者: anhkgg | 来源:发表于2017-05-22 22:27 被阅读0次

    翻开

    翻开小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/

    相关文章

      网友评论

        本文标题:小Win,点一份APC(Apc机制详解)(一)

        本文链接:https://www.haomeiwen.com/subject/sawixxtx.html