上文中講到投遞User Mode APCs是很特殊的,道理很簡單,因爲User Mode APC是ring3下的回調函數,顯然ring0中的KiDeliverAPC()不能像Kernel Mode APC那樣直接call,必須要先回到ring3環境下。當然不能像普通情況那樣返回(否則就回到ring3系統調用的地方了),只有一個辦法,那就是修改TrapFrame ,欺騙系統返回“APC回調函數”!KiInitializeUserApc就是這麼做的!
該函數首先把TrapFrame轉化爲ContextFrame,然後移動用戶態ESP指針,分配大約sizeof(CONTEXT)+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,至此纔會返回真正發起原始系統調用的地方!