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,

        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, ; 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,至此才会返回真正发起原始系统调用的地方!

 

你可能感兴趣的:(内核,驱动)