APCs (二)

 

上文中讲到投递 User Mode APCs 是很特殊的,道理很简单,因为 User Mode APC ring3 下的回调函数,显然 ring0 中的 KiDeliverAPC ()不能像 Kernel Mode APC 那样直接 call ,必须要先回到 ring3 环境下。当然不能像普通情况那样返回(否则就回到 ring3 系统调用的地方了),只有一个办法,那就是修改 TrapFrame  ,欺骗系统返回“ APC 回调函数”! KiInitializeUserApc 就是这么做的!

该函数首先把TrapFrame 转化为ContextFrame ,然后移动用户态ESP 指针,分配大约sizeofCONTEXT+sizeof KAPC_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

 

NtContinue CONTEXT 结构转化成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

 

KiServiceExit2 KiServiceExit 差不多,只是宏参数的不同! 同样如果还有User APC 又会进入上文描述的情形,直到没有User APC ,至此才会返回真正发起原始系统调用的地方!

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值