小Win,點一份APC(Apc機制詳解)(一)

翻開

翻開小Win的菜單握巢,APC赫然在目...

做工講究晕鹊,味道不錯,是小Win的熱門菜暴浦,我們點一來嘗嘗溅话!

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

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

細節(jié)來自于ReactOS源碼分析。

如果對這個發(fā)神經(jīng)的文風(fēng)有任何不適肉渴,請諒解公荧,因為我確實神經(jīng)了

來一份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內(nèi)部是NtQueueApcThread做的同规,兩者區(qū)別不大,當然,使用后者可以字節(jié)加點調(diào)料(不使用IntCallUserApc券勺、換成自己的函數(shù)绪钥,函數(shù)參數(shù)也可以有三個了,而PARCFUNC只有一個參數(shù))关炼。

小Win默認是通過統(tǒng)一的接口IntCallUserApc來調(diào)用的顧客指定的Apc函數(shù)程腹。

static void CALLBACK 
IntCallUserApc(PVOID Function, PVOID dwData, PVOID Arg3)
{
    ((PAPCFUNC)Function)(dwData);
}

ring0這么做的

NtQueueApcThread經(jīng)過系統(tǒng)調(diào)用進入到ring0,一般人是看不到了...儒拂,我也是一般人來著寸潦,下面努力變成二班的...。

1. 創(chuàng)建APC對象

進了NtQueueApcThread社痛,先通過KeInitializeApc初始化一個Apc對象


    /* Initialize the APC */
    KeInitializeApc(Apc,
                    &Thread->Tcb, //KTHREAD
                    OriginalApcEnvironment,
                    PspQueueApcSpecialApc,
                    NULL,
                    ApcRoutine,
                    UserMode,
                    NormalContext);

APC對象結(jié)構(gòu)定義如下:

typedef struct _KAPC {
  UCHAR Type; //類型ApcObject
  UCHAR SpareByte0;
  UCHAR Size; //APC結(jié)構(gòu)體大小
  UCHAR SpareByte1;
  ULONG SpareLong0;
  struct _KTHREAD *Thread; //當前線程的KTHREAD
  LIST_ENTRY ApcListEntry; //當前線程的APC鏈表
  PKKERNEL_ROUTINE KernelRoutine; //
  PKRUNDOWN_ROUTINE RundownRoutine; //
  PKNORMAL_ROUTINE NormalRoutine; //
  PVOID NormalContext; //用戶定義的Apc函數(shù)
  PVOID SystemArgument1; //用戶Apc函數(shù)的參數(shù)
  PVOID SystemArgument2;//
  CCHAR ApcStateIndex; //Apc狀態(tài)
  KPROCESSOR_MODE ApcMode; //Apc所處的Mode见转,UserMode/KernelMode
  BOOLEAN Inserted;     //是否已經(jīng)被插入隊列
} KAPC, *PKAPC, *RESTRICTED_POINTER PRKAPC;

根據(jù)KeInitializeApc傳入?yún)?shù),Apc被賦值如下:

Apc->KernelRoutine = PspQueueApcSpecialApc;
Apc->RundownRoutine = NULL;
Apc->NormalRoutine = ApcRoutine;//如果使用QueueUserApc蒜哀,其實就是IntCallUserApc
Apc->NormalContext = NormalContext;//pfnApc;//用戶指定的Apc函數(shù)
Apc->Type = ApcObject;

//如果參數(shù)指定的是CurrentApcEnvironment斩箫,直接賦值Thread->ApcStateIndex
Apc->ApcStateIndex = Thread->ApcStateIndex;
//不是則
Apc->ApcStateIndex = OriginalApcEnvironment;//

//如果參數(shù)ApcRoutine不是NULL
Apc->ApcMode = Mode;
Apc->NormalContext = Context;
//是NULL
Apc->ApcMode = KernelMode;
Apc->NormalContext = NULL;

Apc->Inserted = False;

其中關(guān)于ApcStateIndex有4中值,如下:

// APC Environment Types
//
typedef enum _KAPC_ENVIRONMENT
{
    OriginalApcEnvironment,//0
    AttachedApcEnvironment,//1
    CurrentApcEnvironment,//2
    InsertApcEnvironment
} KAPC_ENVIRONMENT;

Apc->KernelRoutine總是有值的撵儿,被賦值為PspQueueApcSpecialApc乘客,用于Apc結(jié)束時候釋放Apc對象內(nèi)存

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插入隊列易核,可能通過軟中斷或者喚醒線程得到執(zhí)行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取出對應(yīng)的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)
                    {
                        //中斷線程當前執(zhí)行六?浪默?
                        /* 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是怎么得到執(zhí)行的牡直,來看看KAPC_STATE

typedef struct _KAPC_STATE
{
    LIST_ENTRY ApcListHead[2];//UserMode/KernelMode的兩個鏈表
    struct _KPROCESS *Process;
    BOOLEAN KernelApcInProgress;
    BOOLEAN KernelApcPending; //等待執(zhí)行
    BOOLEAN UserApcPending; //等待執(zhí)行
} KAPC_STATE, *PKAPC_STATE, *RESTRICTED_POINTER PRKAPC_STATE;

其中ApcListHead保存了線程的兩個Apc鏈表,分別對應(yīng)UserMode和KernelMode浴鸿。

Thread->ApcState表示當前需要執(zhí)行的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
    }

來一個結(jié)構(gòu)圖

apc.png

上菜吃飯

Apc已經(jīng)點了岳链,什么時候才能端上來呢花竞?我們接著看...

Apc投遞

線程wait、線程切換到應(yīng)用層掸哑、線程被掛起等约急,一旦線程有空隙了,windows就會把apc隊列順便執(zhí)行一遍

搜索NormalRoutineKernelRoutine字段苗分,找到KiDeliverApc厌蔽,這個函數(shù)是具體分發(fā)Apc的函數(shù)

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.             

那在哪里調(diào)用的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

根據(jù)《windows內(nèi)核情景分析》介紹, 執(zhí)行用戶APC的時機在從內(nèi)核返回用戶空間的途中(可能是系統(tǒng)調(diào)用奴饮、中斷纬向、異常處理之后需要返回用戶空間)

也就是肯定會經(jīng)過_KiServiceExit,那就跟著來看看吧戴卜。

  1. CHECK_FOR_APC_DELIVER宏 檢查是不是需要投遞Apc逾条,具體檢查trapframe是不是指向返回用戶模式的,是則繼續(xù)檢查用戶模式Apc是否需要投遞投剥。
    參數(shù):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
  • 調(diào)用KiDeliverApc(UserMode, 0, trap_frame);
  • 恢復(fù)irql
  • 如果PreserveEax=1债蓝,恢復(fù)eax
  1. TRAP_EPILOG是自陷處理壳鹤,參數(shù):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恢復(fù)一堆寄存器、堆棧信息饰迹,然后sysexit回到用戶態(tài)空間

繼續(xù)看一下調(diào)用KiDeliverApc內(nèi)部究竟是怎么處理的

KiDeliverApc(IN KPROCESSOR_MODE DeliveryMode,
             IN PKEXCEPTION_FRAME ExceptionFrame,
             IN PKTRAP_FRAME TrapFrame) //系統(tǒng)空間堆棧的“自陷框架”
{
//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. 先投遞內(nèi)核Apc芳誓,循環(huán)投遞隊列中所有的內(nèi)核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啊鸭,特指內(nèi)核Apc锹淌,但是Apc的NormalRoutine是空的
    if (!NormalRoutine) {
        //將Apc出隊列,然通過KernelRoutine調(diào)用內(nèi)核Apc響應(yīng)函數(shù)
        KernelRoutine(Apc,
                          &NormalRoutine,
                          &NormalContext,
                          &SystemArgument1,
                          &SystemArgument2);
    } else {
        //普通的內(nèi)核Apc
        if ((Thread->ApcState.KernelApcInProgress) ||
                (Thread->KernelApcDisable))
            { //退出赠制,必須安全才會投遞
            }
        ////將Apc出隊列赂摆,然通過KernelRoutine調(diào)用內(nèi)核Apc響應(yīng)函數(shù)
        KernelRoutine(Apc,
                          &NormalRoutine, //內(nèi)部可能修改NormalRoutine
                          &NormalContext,
                          &SystemArgument1,
                          &SystemArgument2);
        
        //如果NormalRoutine依然不為空,在調(diào)用NormalRoutine
        if (NormalRoutine)
        {
            /* At Passive Level, an APC can be prempted by a Special APC */
            Thread->ApcState.KernelApcInProgress = TRUE;
            KeLowerIrql(PASSIVE_LEVEL); //將到PASSIVE_LEVEL執(zhí)行

            /* Call and Raise IRQ back to APC_LEVEL */
            NormalRoutine(NormalContext, SystemArgument1, SystemArgument2);
            KeRaiseIrql(APC_LEVEL, &ApcLock.OldIrql);
        }
        Thread->ApcState.KernelApcInProgress = FALSE;
        //繼續(xù)循環(huán)
    }
}

//3. 投遞完內(nèi)核apc钟些,如果KiDeliverApc目標是用戶apc烟号,那么繼續(xù)投遞用戶apc
//每次值投遞一個User mode Apc
if ((DeliveryMode == UserMode) &&
        !(IsListEmpty(&Thread->ApcState.ApcListHead[UserMode])) &&
         (Thread->ApcState.UserApcPending)) //TRUE  
{
    Thread->ApcState.UserApcPending = FALSE;
    //取出第一個Apc
    //先調(diào)用他的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 */
        //不是直接調(diào)用NormalRoutine,因為他是用戶太的函數(shù)政恍,需要切換到用戶空間才能執(zhí)行
        KiInitializeUserApc(ExceptionFrame,
                            TrapFrame,
                            NormalRoutine,
                            NormalContext,
                            SystemArgument1,
                            SystemArgument2);
    }                  
}
    

根據(jù)注釋應(yīng)該很清楚deliver的邏輯了汪拥,還是在看張圖

KiDeliverApc.png

CHECK_FOR_APC_DELIVER用戶態(tài)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铣焊,這個字段表示線程是喚醒的逊朽,也就是說只有可喚醒的線程罕伯,才能拿投遞他的用態(tài)APC曲伊,否則不會

SleepEx, WaitForSingleObject,WaitForMultipleObjects都可以設(shè)置線程為Alertable

接著繼續(xù)看看KiInitializeUserApc是怎么切換到用戶空間執(zhí)行的用戶態(tài)函數(shù)

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個參數(shù)的長度
    ContextLength = CONTEXT_ALIGNED_SIZE + (4 * sizeof(ULONG_PTR));
    //將原始堆棧擴展ContextLength坟募,用來保存Context和參數(shù)
    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個參數(shù)的位置,然后接著拷貝Context邑狸,將老的TrapFrame內(nèi)容拷貝到用戶太堆棧中
    RtlCopyMemory((PVOID)(Stack + (4 * sizeof(ULONG_PTR))),
                  &Context,
                  sizeof(CONTEXT));

    /* Run at APC dispatcher */
    TrapFrame->Eip = (ULONG)KeUserApcDispatcher; //KeUserApcDispatcher保存的其實就是KiUserApcDispatcher懈糯,是用戶空間函數(shù)
    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;
    ...
    
}

執(zhí)行流程根據(jù)注釋應(yīng)該很清楚了,這里要解釋一下TrapFrame单雾。

CPU進入嚙合之后赚哗,內(nèi)核堆棧就會有個TrapFrame,保存的是用戶空間的線程(因進入內(nèi)核原因不同硅堆,可能是自陷屿储、中斷、異辰ヌ樱框架够掠,都是一樣的結(jié)構(gòu))。CPU返回用戶空間時會使用這個TrapFrame茄菊,才能正確返回原理啊的斷點疯潭,并回復(fù)寄存器的狀態(tài)
這里為了讓Apc返回到用戶空間執(zhí)行,就會修改這個TrapFrame面殖,原來的TrapFrame就需要保存竖哩,這里保存在了用戶空間堆棧中(CONTEXT)
執(zhí)行完Apc函數(shù)之后,執(zhí)行一個NtContinue脊僚,將這個CONTEXT作為參數(shù)相叁,這樣保存的TrapFrame就會還原到原來的狀態(tài),然后CPU又能正吵蕴簦回之前的用戶空間了钝荡。

KiDeliverApc完了之后,回到_KiServiceExit舶衬,會使用被修改過的TrapFrame回到用戶空間埠通,執(zhí)行指定的KiUserApcDispatcher(ntdll提供)

//更具這個執(zhí)行KiUserApcDispatcher
TrapFrame->Eip = (ULONG)KeUserApcDispatcher; //其實就是KiUserApcDispatcher,是用戶空間函數(shù)
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;彈出第一個參數(shù)
    lea edi, [esp+12];context的位置

    /* Call the APC Routine */
    call eax //調(diào)用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函數(shù),然后調(diào)用舞蔽,就進入了IntCallUserApc荣病,

恢復(fù)TrapFrame

執(zhí)行完成后,調(diào)用_ZwContinue(Context, 1)渗柿,回到內(nèi)核回復(fù)之前修改TrapFrame个盆,也會重新檢查是否有Apc需要投遞,有則繼續(xù)投遞朵栖,
重復(fù)上面的步驟颊亮,直到?jīng)]有了則可以回到之前被中斷的用戶態(tài)的斷點處。


.func NtContinue@8
_NtContinue@8:

    /* NOTE: We -must- be called by Zw* to have the right frame! */
    /* Push the stack frame */
    push ebp ; 指向本次調(diào)用的自陷框架陨溅,記為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指向新的框架(函數(shù)調(diào)用框架)

    /* 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恢復(fù)到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 ; 本質(zhì)和_KiServiceExit相同雹有,如果還有用戶APC,會繼續(xù)投遞臼寄,直到投遞完霸奕,才會回到用戶被中斷的點

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

下面將_KiServiceExit到IntCallUserApc的流程總結(jié)一下:

deliver.png

到這里,終于執(zhí)行到了用戶的Apc函數(shù)脯厨。

結(jié)賬走人

到這铅祸,APC流程基本弄清楚了。

下一篇將結(jié)合APC機制分析一下最近比較新的AtomBombing注入技術(shù)的詳細實現(xiàn)和各個細節(jié)合武。

參考

  1. Reactos內(nèi)核情景源碼分析
  2. 線程的Alertable與User APC

轉(zhuǎn)載請注明出處临梗,博客原文:http://anhkgg.github.io/win-apc-analyze1/

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市稼跳,隨后出現(xiàn)的幾起案子盟庞,更是在濱河造成了極大的恐慌,老刑警劉巖汤善,帶你破解...
    沈念sama閱讀 216,843評論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件什猖,死亡現(xiàn)場離奇詭異,居然都是意外死亡红淡,警方通過查閱死者的電腦和手機不狮,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,538評論 3 392
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來在旱,“玉大人摇零,你說我怎么就攤上這事⊥靶” “怎么了驻仅?”我有些...
    開封第一講書人閱讀 163,187評論 0 353
  • 文/不壞的土叔 我叫張陵谅畅,是天一觀的道長。 經(jīng)常有香客問我噪服,道長毡泻,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,264評論 1 292
  • 正文 為了忘掉前任粘优,我火速辦了婚禮仇味,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘敬飒。我一直安慰自己邪铲,他們只是感情好,可當我...
    茶點故事閱讀 67,289評論 6 390
  • 文/花漫 我一把揭開白布无拗。 她就那樣靜靜地躺著,像睡著了一般昧碉。 火紅的嫁衣襯著肌膚如雪英染。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,231評論 1 299
  • 那天被饿,我揣著相機與錄音四康,去河邊找鬼。 笑死狭握,一個胖子當著我的面吹牛闪金,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播论颅,決...
    沈念sama閱讀 40,116評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼哎垦,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了恃疯?” 一聲冷哼從身側(cè)響起漏设,我...
    開封第一講書人閱讀 38,945評論 0 275
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎今妄,沒想到半個月后郑口,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,367評論 1 313
  • 正文 獨居荒郊野嶺守林人離奇死亡盾鳞,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,581評論 2 333
  • 正文 我和宋清朗相戀三年犬性,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片腾仅。...
    茶點故事閱讀 39,754評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡乒裆,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出攒砖,到底是詐尸還是另有隱情缸兔,我是刑警寧澤日裙,帶...
    沈念sama閱讀 35,458評論 5 344
  • 正文 年R本政府宣布,位于F島的核電站惰蜜,受9級特大地震影響昂拂,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜抛猖,卻給世界環(huán)境...
    茶點故事閱讀 41,068評論 3 327
  • 文/蒙蒙 一格侯、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧财著,春花似錦联四、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,692評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至伟姐,卻和暖如春收苏,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背愤兵。 一陣腳步聲響...
    開封第一講書人閱讀 32,842評論 1 269
  • 我被黑心中介騙來泰國打工鹿霸, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人秆乳。 一個月前我還...
    沈念sama閱讀 47,797評論 2 369
  • 正文 我出身青樓懦鼠,卻偏偏與公主長得像,于是被迫代替她去往敵國和親屹堰。 傳聞我的和親對象是個殘疾皇子肛冶,可洞房花燭夜當晚...
    茶點故事閱讀 44,654評論 2 354

推薦閱讀更多精彩內(nèi)容