Windows APC機制(二)

上文中講到投遞User Mode APCs是很特殊的,道理很簡單,因爲User Mode APCring3下的回調函數,顯然ring0中的KiDeliverAPC()不能像Kernel Mode APC那樣直接call,必須要先回到ring3環境下。當然不能像普通情況那樣返回(否則就回到ring3系統調用的地方了),只有一個辦法,那就是修改TrapFrame ,欺騙系統返回“APC回調函數”!KiInitializeUserApc就是這麼做的!

該函數首先把TrapFrame轉化爲ContextFrame,然後移動用戶態ESP指針,分配大約sizeofCONTEXT+sizeofKAPC_RECORD)個字節:

UserStack->   ……

              KAPC_RECORD

              ……

……

CONTEXT

TopOfStack->  ……

KAPC_RECORD就是KiInitializeUserApc的最後四個參數

nt!_KAPC_RECORD

   +0x000 NormalRoutine    : Ptr32     void

   +0x004 NormalContext    : Ptr32 Void

   +0x008 SystemArgument1  : Ptr32 Void

   +0x00c SystemArgument2  : Ptr32 Void

CONTEXT結構主要來存放被修改前的TrapFrame,之所以用CONTEXT結構是跟APC函數返回有關!

 

TrapFrame->HardwareEsp = UserStack;

TrapFrame->Eip = (ULONG)KeUserApcDispatcher;

TrapFrame->ErrCode = 0;

從上面的代碼看到確實修改了TrapFrame,並且返回到的是ring3下的KeUserApcDispatcher,剛纔說的_KAPC_RECORD其實也是它的參數!真正我們的User APC回調函數是由KeUserApcDispatcher調用的!

 

.func KiUserApcDispatcher@16

.globl _KiUserApcDispatcher@16

_KiUserApcDispatcher@16:

 

    /* Setup SEH stack */

    lea eax, [esp+CONTEXT_ALIGNED_SIZE+16]

    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]

 

    /* Call the APC Routine */

    call eax

 

    /* Restore exception list */

    mov ecx, [edi+CONTEXT_ALIGNED_SIZE]

    mov fs:[TEB_EXCEPTION_LIST], ecx

 

    /* Switch back to the context */

    push 1    ; TestAlert

    push edi  ;edi->CONTEXT結構

call _ZwContinue@8

;;不會返回到這裏的

 

上面的代碼並不難理解,我們的User APC回調函數返回後,立即調用了ZwContinue,這是個ntdll中的導出函數,這個函數又通過系統調用進入kernel中的NtContinue

NTSTATUS

; NtContinue (

;    IN PCONTEXT ContextRecord,

;    IN BOOLEAN TestAlert

;    )

;

; Routine Description:

;

;    This routine is called as a system service to continue execution after

;    an exception has occurred. Its function is to transfer information from

;    the specified context record into the trap frame that was built when the

;    system service was executed, and then exit the system as if an exception

;    had occurred.

;

;   WARNING - Do not call this routine directly, always call it as

;             ZwContinue!!!  This is required because it needs the

;             trapframe built by KiSystemService.

;

; Arguments:

;

;    KTrapFrame (ebp+0: after setup) -> base of KTrapFrame

;

;    ContextRecord (ebp+8: after setup) = Supplies a pointer to a context rec.

;

;    TestAlert (esp+12: after setup) = Supplies a boolean value that specifies

;       whether alert should be tested for the previous processor mode.

;

; Return Value:

;

;    Normally there is no return from this routine. However, if the specified

;    context record is misaligned or is not accessible, then the appropriate

;    status code is returned.

;

;--

 

NcTrapFrame             equ     [ebp + 0]

NcContextRecord         equ     [ebp + 8]

NcTestAlert             equ     [ebp + 12]

 

align dword

cPublicProc _NtContinue     ,2

 

        push    ebp         ;ebp->TrapFrame

 

;

; Restore old trap frame address since this service exits directly rather

; than returning.

;

 

        mov     ebx, PCR[PcPrcbData+PbCurrentThread] ; get current thread address

        mov     edx, [ebp].TsEdx        ; restore old trap frame address

        mov     [ebx].ThTrapFrame, edx  ;

 

;

; Call KiContinue to load ContextRecord into TrapFrame.  On x86 TrapFrame

; is an atomic entity, so we don't need to allocate any other space here.

;

; KiContinue(NcContextRecord, 0, NcTrapFrame)

;

 

        mov     ebp,esp

        mov     eax, NcTrapFrame

        mov     ecx, NcContextRecord

        stdCall  _KiContinue, <ecx, 0, eax>

        or      eax,eax                 ; return value 0?

        jnz     short Nc20              ; KiContinue failed, go report error

 

;

; Check to determine if alert should be tested for the previous processor mode.

;

 

        cmp     byte ptr NcTestAlert,0  ; Check test alert flag

        je      short Nc10              ; if z, don't test alert, go Nc10

        mov     al,byte ptr [ebx]+ThPreviousMode ; No need to xor eax, eax.

        stdCall _KeTestAlertThread, <eax> ; test alert for current thread

;如果User APCs不爲空,它會設置UserApcPending,

;Alertable無關

Nc10:   pop     ebp                     ; (ebp) -> TrapFrame

        mov     esp,ebp                 ; (esp) = (ebp) -> trapframe

        jmp     _KiServiceExit2         ; common exit

 

Nc20:   pop     ebp                     ; (ebp) -> TrapFrame

        mov     esp,ebp                 ; (esp) = (ebp) -> trapframe

        jmp     _KiServiceExit          ; common exit

 

stdENDP _NtContinue

 

NtContinueCONTEXT結構轉化成TrapFrame(回覆原來的陷阱幀),然後就從KiServiceExit2處退出系統調用!

;++

;

;   _KiServiceExit2 - same as _KiServiceExit BUT the full trap_frame

;       context is restored

;

;--

        public  _KiServiceExit2

_KiServiceExit2:

 

        cli                             ; disable interrupts

        DISPATCH_USER_APC   ebp

 

;

; Exit from SystemService

;

 

        EXIT_ALL                            ; RestoreAll

 

KiServiceExit2KiServiceExit差不多,只是宏參數的不同!同樣如果還有User APC又會進入上文描述的情形,直到沒有User APC,至此纔會返回真正發起原始系統調用的地方!

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章