核心模式到使用者模式的回撥函式----這篇文章是十年前國外大牛寫的
核心模式到使用者模式的回撥函式
http://www.nynaeve.net/?p=200
NTDLL 擁有一些特定的函式被核心用來代表使用者模式執行特定的功能。儘管理解這些函式對於特定的功能(比如使用者模式APC)的底層實現的理解是有用的,這些函式提供的功能非常的簡單。
下面是一些NTDLL匯出的,核心用於與使用者模式通訊的函式:
1.KiUserExceptionDispatcher
2. KiUserApcDispatcher
3. KiRaiseUserExceptionDispatcher
4. KiUserCallbackDispatcher
5. LdrInitializeThunk
6. RtlUserThreadStart
7. EtwpNotificationThread
8. LdrHotPatchRoutine
(還有其它的一些匯出函式,但是不直接用於和使用者模式交流)
儘管每個函式的特定的被呼叫原因非常的不同,這些函式通常用來通知使用者模式某一事件的發生。
KiUserExceptionDispatcher,KiUserApcDispatcher, 和KiRaiseUserExceptionDispatcher只在從使用者模式進入核心模式時使用,或者隱式地,因為一個處理器中斷 (比如說,一個頁面錯誤將最終導致一個非法訪問), 或者顯式地,用於系統呼叫(比如 NtWaitForSingleObject)。核心呼叫這些入口點的方法就是,當要從核心模式返回到使用者模式的時候,修改將要被設定的執行緒上下文。返回使用者模式的上下文資訊(一個 KTRAP_FRAME結構) 被修改,因此當從核心模式返回的時候,返回地址被轉移到上面的三個分發函式中的一個,而不是執行使用者模式轉移到核心模式的指令所在的地址。如果需要的話,將給這些分發函式提供引數。
KiUserCallbackDispatcher被核心用來顯式呼叫到使用者模式。在支援如待定IRP“反向呼叫模型”等模型的時候,這個反向操作模型並不被鼓勵使用。儘管如此,由於歷史原因, Win32子系統(win32k.sys) 在許多工上使用這個函式。(比如因為一個核心模式視窗訊息操作而呼叫一個使用者模式視窗處理函式)。使用者模式呼叫機制不能被擴充套件為“支援任意的使用者模式回撥函式目的地” 。
LdrInitializeThunk是任意的使用者模式執行緒在執行真正的執行緒入口點之前執行的第一個指令。因此,這是系統範圍內使用者模式執行緒開始執行的地址。
Windows Vista 和Windows Server 2008(和之後的版本)上,如果呼叫NtCreateThreadEx 生成執行緒,RtlUserThreadStart將被用來形成初始入口點上下文 (這與NtCreateThread所採用的方法明顯的不同,其中使用者模式呼叫者提供了初始的執行緒上下文)。
EtwpNotificationThread 和LdrHotPatchRoutine 都相當於一個標準的執行緒入口點函式。核心在特定的程序的上下文中生成一些執行緒以代表核心完成某些任務時,這些執行緒可能會引用這些入口點。之後的兩個函式極少遇見,這一系列文章不對它們進行詳細的介紹。
儘管或多或少的未兌現“在帶熱更新的Windows Server 2003系統上更少的重新引導的承諾”,我在支援熱補丁的作業系統的整個生命週期中看到了一個或者兩個熱更新。
KiUserExceptionDispatcher
在上面所列舉的一系列使用者模式回撥入口點中,儘管其中的一些回撥在操作模式上有一些相似之處,但就呼叫約定和它們執行的功能而言,它們之間還是有特別大的區別的。KiUserExceptionDispatcher 是SEH分發器的使用者模式的負責函式。當一個異常發生的時候,該異常將生成一個異常事件,核心檢查該異常是否是由於執行使用者模式程式碼導致的。如果是這樣的話,核心修改棧上的trap frame,因此當核心從中斷或者異常返回的時候,執行緒將從KiUserExceptionDispatcher 函式執行而不是導致異常的指令。核心將另外安排幾個引數(一個 PCONTEXT 和一個 PEXCEPTION_RECORD),它們描述了異常發生時機器的狀態,而且線上程返回到使用者模式之前被傳遞給KiUserExceptionDispatcher 函式。
一旦核心模式棧展開,而且指令轉移到使用者模式的KiUserExceptionDispatcher 函式,該函式通過呼叫一個本地的函式RtlDispatchException來處理異常,RtlDispatchException是使用者模式異常處理邏輯中的核心函式。如果異常被成功分發的話(也就是SHE 連結串列中有一個函式宣稱可以處理該異常), RtlDispatchException呼叫RtlRestoreContext 函式實現最終的使用者模式上下文的設定,該函式只是載入給定的上下文中的暫存器到到處理器的體系結構執行狀態中。
否則,通過呼叫 NtRaiseException 函式,異常重新被提交到核心模式,這是最後一次機會了。在核心停止該程序之前,這給了使用者模式偵錯程式(如果有的話)一個處理該異常的最後機會。 (核心內部在安排KiUserExceptionDispatcher執行之前給了使用者模式偵錯程式和核心模式偵錯程式第一次處理該異常的機會)
下面是彙編程式碼註釋以及一個假的C語言表示:
.text:7C958550 ; __stdcall KiUserExceptionDispatcher(x, x)
.text:7C958550 public _KiUserExceptionDispatcher@8
.text:7C958550 _KiUserExceptionDispatcher@8 proc near ; DATA XREF: .text:off_7C94C618o
.text:7C958550
.text:7C958550 var_C = dword ptr -0Ch
.text:7C958550 var_8 = dword ptr -8
.text:7C958550 var_4 = dword ptr -4
.text:7C958550 arg_0 = dword ptr 4
.text:7C958550
.text:7C958550 mov ecx, [esp+arg_0] ; CONTEXT
.text:7C958554 mov ebx, [esp+0] ; EXCEPTION_RECORD
.text:7C958557 push ecx
.text:7C958558 push ebx
.text:7C958559 call _RtlDispatchException@8 ; RtlDispatchException(x,x)
.text:7C95855E or al, al
.text:7C958560 jz short loc_7C95856E ;如果返回FALSE
.text:7C958562 pop ebx ; ebx = EXCEPTION_RECORD
.text:7C958563 pop ecx ; ecx = CONTEXT
.text:7C958564 push 0
.text:7C958566 push ecx ; ecx = CONTEXT
.text:7C958567 call _ZwContinue@8 ;已經處理好了,按照CONTEXT 中設定的值繼續執行就好了,此函式不返回
.text:7C95856C jmp short loc_7C958579
.text:7C95856E ; ---------------------------------------------------------------------------
.text:7C95856E
.text:7C95856E loc_7C95856E: ; 沒有找到處理函式,提交一個異常->FirstChance = FALSE
.text:7C95856E pop ebx ; ebx = EXCEPTION_RECORD
.text:7C95856F pop ecx ; ecx = CONTEXT
.text:7C958570 push 0
.text:7C958572 push ecx
.text:7C958573 push ebx
.text:7C958574 call _ZwRaiseException@12 ; ZwRaiseException(x,x,x)
.text:7C958574 _KiUserExceptionDispatcher@8 endp ; sp-analysis failed
.text:7C958574
.text:7C958579 ; ---------------------------------------------------------------------------
.text:7C958579 retn 8
.text:0000000078EA124A public KiUserExceptionDispatcher
.text:0000000078EA124A KiUserExceptionDispatcher proc near ; DATA XREF: .rdata:0000000078F54BB0o
.text:0000000078EA124A ; .rdata:off_78F56298o
.text:0000000078EA124A cld
.text:0000000078EA124B mov rax, cs:Wow64PrepareForException
.text:0000000078EA1252 test rax, rax
.text:0000000078EA1255 jz short loc_78EA1266
.text:0000000078EA1257 mov rcx, rsp
.text:0000000078EA125A add rcx, 4F0h ; rcx 為第一個引數ExceptionRecord 0x4F0 為其CONTEXT 的大小
.text:0000000078EA1261 mov rdx, rsp ; rdx 為第二個引數,指向CONTEXT 結構
.text:0000000078EA1264 call rax ; Wow64PrepareForException
.text:0000000078EA1266
.text:0000000078EA1266 loc_78EA1266:
.text:0000000078EA1266 mov rcx, rsp
.text:0000000078EA1269 add rcx, 4F0h ;ExceptionRecord
.text:0000000078EA1270 mov rdx, rsp ;ContextRecord
.text:0000000078EA1273 call RtlDispatchException ;分發該異常RtlDispatchException(ExceptionRecord,ContextRecord);
.text:0000000078EA1278 test al, al
.text:0000000078EA127A jz short loc_78EA1288
.text:0000000078EA127C mov rcx, rsp ; ContextRecord
.text:0000000078EA127F xor edx, edx ; ExceptionRecord-->0
.text:0000000078EA1281 call RtlRestoreContext
.text:0000000078EA1286 jmp short loc_78EA129D
.text:0000000078EA1288 ; ---------------------------------------------------------------------------
.text:0000000078EA1288
.text:0000000078EA1288 loc_78EA1288:
.text:0000000078EA1288 mov rcx, rsp
.text:0000000078EA128B add rcx, 4F0h
.text:0000000078EA1292 mov rdx, rsp
.text:0000000078EA1295 xor r8b, r8b
.text:0000000078EA1298 call ZwRaiseException ;ZwRaiseException(ExceptionRecord,ContextRecord,FALSE);
.text:0000000078EA129D
.text:0000000078EA129D loc_78EA129D:
.text:0000000078EA129D mov ecx, eax
.text:0000000078EA129F call RtlRaiseStatus ; RtlRaiseStatus(上面的函式的返回值);
.text:0000000078EA12A4 nop
.text:0000000078EA12A5 jmp short $+2
.text:0000000078EA12A7 ; ---------------------------------------------------------------------------
.text:0000000078EA12A7
.text:0000000078EA12A7 loc_78EA12A7:
.text:0000000078EA12A7 nop
.text:0000000078EA12A7 KiUserExceptionDispatcher endp ; sp-analysis failed
VOID
KiUserExceptionDispatcher(
__in PCONTEXT ContextRecord,
__in PEXCEPTION_RECORD ExceptionRecord,
)
{
NTSTATUS Status;
//
// (A custom calling convention is used that does not pass the parameter
// values in a C-compatible fashion.)
//
#if defined(_WIN64)
//
// 如果Wow64.dll 註冊它的幫助函式來處理異常事件,呼叫這個函式
if (Wow64PrepareForException)
Wow64PrepareForException(
ExceptionRecord,
ContextRecord
);
#endif
if (RtlDispatchException(
ExceptionRecord,
ContextRecord))
{
#if defined(_WIN64)
RtlRestoreContext( ContextRecord );
#else
NtContinue(
ContextRecord,
FALSE
);
#endif
Status = (NTSTATUS)ContextRecord->Rax;
RtlRaiseStatus( Status );
//
// No return from RtlRaiseStatus.
//
}
Status = NtRaiseException(
ContextRecord,
ExceptionRecord,
FALSE
);
RtlRaiseStatus( Status );
//
// No return from RtlRaiseStatus.
//
}
並不是所有的使用者模式異常都像這樣從核心模式開始。在很多情況下(比如 RaiseException ),異常處理過程是完全從使用者模式發起的,並且並不涉及到KiUserExceptionDispatcher 。
http://msdn2.microsoft.com/en-us/library/ms680552.aspx
KiUserApcDispatcher
和異常類似,使用者模式APC 是通過NTDLL 中匯出的一個單獨的分發器函式KiUserApcDispatcher 實現的。該函式構建異常幀並通過Kernel32的一個匯出函式來呼叫真正的使用者提供的APC 函式,之後通過NtContinue 函式返回到被使用者模式APC 打斷的“呼叫可警醒系統呼叫的指令”的下一個指令的位置。細節可以通過檢視wrk 和 核心情景分析的APC 相關的章節瞭解。下面是windows server 2003 x86 中 KiUserApcDispatcher 的反彙編,以及原文作者提供的一個C 語言版本的KiUserApcDispatcher 實現。
.text:7C9584A0 ; __stdcall KiUserApcDispatcher(x, x, x, x)
.text:7C9584A0 public _KiUserApcDispatcher@16
.text:7C9584A0 _KiUserApcDispatcher@16 proc near ; DATA XREF: .text:off_7C94C618o
.text:7C9584A0
.text:7C9584A0 arg_C = byte ptr 10h
.text:7C9584A0 arg_2D8 = byte ptr 2DCh
.text:7C9584A0
.text:7C9584A0 lea eax, [esp+arg_2D8]
.text:7C9584A7 mov ecx, large fs:0
.text:7C9584AE mov edx, offset _KiUserApcExceptionHandler@16 ; 執行APC 時候的異常處理函式
.text:7C9584B3 mov [eax], ecx
.text:7C9584B5 mov [eax+4], edx
.text:7C9584B8 mov large fs:0, eax ; 安裝異常
.text:7C9584BE pop eax ; [CONTEXT]
.text:7C9584BE ; SystemArgument2
.text:7C9584BE ; SystemArgument1
.text:7C9584BE ; NormalContext
.text:7C9584BE ; NormalRoutine
.text:7C9584BF lea edi, [esp-4+arg_C]
.text:7C9584C3 call eax ; 呼叫該APC 函式
.text:7C9584C5 mov ecx, [edi+2CCh]
.text:7C9584CB mov large fs:0, ecx
.text:7C9584D2 push 1
.text:7C9584D4 push edi
.text:7C9584D5 call _ZwContinue@8 ; ZwContinue(x,x)
.text:7C9584DA mov esi, eax
.text:7C9584DC
.text:7C9584DC loc_7C9584DC: ; CODE XREF: .text:7C9584E2j
.text:7C9584DC push esi
.text:7C9584DD call _RtlRaiseStatus@4 ; RtlRaiseStatus(x)
.text:7C9584DD _KiUserApcDispatcher@16 endp ; sp-analysis failed
.text:7C9584DD
.text:7C9584E2 ; ---------------------------------------------------------------------------
.text:7C9584E2 jmp short loc_7C9584DC
.text:7C9584E4 ; -------------------------
VOID
KiUserApcDispatcher(
__in PCONTEXT Context,
__in PVOID ApcContext,
__in PVOID Argument1,
__in PVOID Argument2,
__in PKNORMAL_ROUTINE ApcRoutine
)
{
NTSTATUS Status;
try_again:
//
// A custom calling convention is used, the actual implementation is not C.
//
//
// Call the user APC routine. Note that in Windows x64, the user APC routine
// has a hidden additional (fourth) argument that does not strictly conform to
// the standard KNORMAL_ROUTINE procedure type. This hidden argument is made
// possible by virtue of the fact that the x64 calling convention passes
// arguments in registers.
//
#if defined(_WIN64)
ApcRoutine(
ApcContext,
Argument1,
Argument2,
Context
);
#else
ApcRoutine(
ApcContext,
Argument1,
Argument2
);
#endif
//
// Continue execution. Note that the TestAlert parameter is set to TRUE.
// This will invoke the next queued user mode APC, if any exists. Otherwise,
// control is resumed at the specified machine register context. In either
// case, NtContinue should never normally "return" to its caller; control is
// hard transferred to either the desired context, or to KiUserApcDispatcher.
//
Status = NtContinue(
Context,
TRUE
);
if (Status == STATUS_SUCCESS)
goto try_again;
RtlRaiseStatus( Status );
//
// No return from RtlRaiseStatus.
//
}
上面的C 語言版本的實現僅僅是便於程式功能的理解,實際的實現還是要看彙編程式碼。另外有關X64 APC 以及WOW64 APC 的實現細節請暫時沒有深入研究。
KiRaiseUserExceptionDispatcher
當一個系統呼叫想在使用者模式引起異常,而不僅僅是返回一個NTSTATUS的時候,使用KiRaiseUserExceptionDispatcher函式是標準的慣例。它使用一個傳入的狀態碼建立一個標準的異常記錄(必須提前寫入到當前執行緒的TEB中的一個眾所周知的地方。),並將此異常記錄傳遞給RtlRaiseException (Win32 RaiseException 函式的內部實現函式就是它)。
// 系統初始化的時候進行的賦值
//PsInitSystem->PspInitPhase1->PspInitializeSystemDll->PspLookupKernelUserEntryPoints()->PspLookupKernelUserEntryPoints->
EntryName = "KiRaiseUserExceptionDispatcher";
Status = PspLookupSystemDllEntryPoint(EntryName,
(PVOID *)&KeRaiseUserExceptionDispatcher);
// NtCloseHandle 裡面使用該函式
NTSTATUS
KeRaiseUserException(
IN NTSTATUS ExceptionCode
)
/*++
函式導致在呼叫執行緒的使用者模式環境中觸發一個異常,
通過修改進入核心時候建立的跳轉幀,使其指向那個“觸發所請求的異常的跳轉程式碼”來實現
--*/
{
PKTHREAD Thread;
PKTRAP_FRAME TrapFrame;
PTEB Teb;
ULONG PreviousEsp;
Thread = KeGetCurrentThread();
TrapFrame = Thread->TrapFrame;
if (TrapFrame == NULL || ((TrapFrame->SegCs & MODE_MASK) != UserMode)) {
return ExceptionCode;
}
Teb = (PTEB)Thread->Teb;
try {
Teb->ExceptionCode = ExceptionCode;
PreviousEsp = KiEspFromTrapFrame (TrapFrame) - sizeof (ULONG);
ProbeForWriteSmallStructure ((PLONG)PreviousEsp, sizeof (LONG), sizeof (UCHAR));
*(PLONG)PreviousEsp = TrapFrame->Eip;
// 棧上手動申請了一個記憶體,然後放入之前的程式碼的EIP
// 執行完我們的程式碼之後將返回到原來的位置繼續執行。
} except(EXCEPTION_EXECUTE_HANDLER) {
return(ExceptionCode);
}
KiEspToTrapFrame (TrapFrame, PreviousEsp);// 設定新的ESP
TrapFrame->Eip = (ULONG)KeRaiseUserExceptionDispatcher;// 將EIP 設定為我們的跳板函式的地址
return ExceptionCode;
}
// 下面分別是彙編版本和C 語言版本的實現
.text:7C95859C _KiRaiseUserExceptionDispatcher@0 proc near ; DATA XREF: .text:off_7C94C618o
.text:7C95859C
.text:7C95859C var_50 = EXCEPTION_RECORD ptr -50h
.text:7C95859C
.text:7C95859C push ebp
.text:7C95859D mov ebp, esp
.text:7C95859F sub esp, 50h
.text:7C9585A2 mov [esp+50h+var_50.ExceptionAddress], eax
.text:7C9585A6 mov eax, large fs:18h
.text:7C9585AC mov eax, [eax+1A4h]
.text:7C9585B2 mov [esp+50h+var_50.ExceptionCode], eax ; STATUS_INVALID_HANDLE
.text:7C9585B5 mov [esp+50h+var_50.ExceptionFlags], 0
.text:7C9585BD mov [esp+50h+var_50.ExceptionRecord], 0
.text:7C9585C5 mov [esp+50h+var_50.NumberParameters], 0
.text:7C9585CD push esp ; a1
.text:7C9585CE call _RtlRaiseException@4 ; RtlRaiseException(x)
.text:7C9585CE _KiRaiseUserExceptionDispatcher@0 endp
VOID
KiRaiseUserExceptionDispatcher(
VOID
)
{
EXCEPTION_RECORD ExceptionRecord;
//
// 建立一個標準的異常記錄,狀態碼通過TEB 中的一個專用的成員指定
// 異常地址描述了函式將要返回的地址,這個異常是可繼續執行的
//
ExceptionRecord.ExceptionCode = NtCurrentTeb()->ExceptionCode;
ExceptionRecord.ExceptionFlags = 0; // Noncontinuable flag not set.
ExceptionRecord.Exceptionrecord = 0; // No chained exception record.
ExceptionRecord.ExceptionAddress = _ReturnAddress();//彙編程式碼是通過eax 傳遞
ExceptionRecord.NumberOfParameters = 0;
RtlRaiseException( &ExceptionRecord );
}
// 再下面是關鍵的函式的實現以及解析
.text:7C9585FF ; void __stdcall __noreturn RtlRaiseException(PEXCEPTION_RECORD a1)
.text:7C9585FF public _RtlRaiseException@4
.text:7C9585FF _RtlRaiseException@4 proc near ; CODE XREF: KiUserExceptionDispatcher(x,x)+44p
.text:7C9585FF ; KiRaiseUserExceptionDispatcher()+32p ...
.text:7C9585FF
.text:7C9585FF var_2D0 = CONTEXT ptr -2D0h
.text:7C9585FF arg_0 = dword ptr 8
.text:7C9585FF
.text:7C9585FF push ebp
.text:7C958600 mov ebp, esp
.text:7C958602 lea esp, [esp-2D0h]
.text:7C958609 push esp ; ContextRecord
.text:7C95860A call _RtlCaptureContext@4 ; RtlCaptureContext(x)
.text:7C95860F mov edx, [ebp+4]
.text:7C958612 mov eax, [ebp+arg_0]
.text:7C958615 add [esp+2D0h+var_2D0._Esp], 4 ; CONTEXT.ESP + 4
.text:7C95861D mov [eax+0Ch], edx ; 異常地址放的是此函式的返回地址
.text:7C958620 mov [esp+2D0h+var_2D0.ContextFlags], 10007h ; 1、CONTEXT_DEBUG_REGISTERS,調式 10010H
.text:7C958620 ; 2、CONTEXT_FLOATING_POINT,浮點 10008H
.text:7C958620 ; 3、CONTEXT_SEGMENTS,段 10004H
.text:7C958620 ; 4、CONTEXT_INTEGER,通用 10002H
.text:7C958620 ; 5、CONTEXT_CONTROL,控制 10001H
.text:7C958620 ; 6、CONTEXT_EXTENDED_REGISTERS,擴充套件
.text:7C958620 ; 7、CONTEXT_FULL 全部 10007H
.text:7C958627 mov eax, large fs:30h
.text:7C95862D test byte ptr [eax+2], 0FFh
.text:7C958631 jnz short loc_7C95864C
.text:7C958633 push esp ; CONTEXT
.text:7C958634 push [ebp+arg_0] ; PEXCEPTION_RECORD a1
.text:7C958637 call _RtlDispatchException@8 ; RtlDispatchException(x,x)
.text:7C95863C test al, al
.text:7C95863E jz short loc_7C95865B
.text:7C958640 mov ecx, esp
.text:7C958642 push 0
.text:7C958644 push ecx ; CONTEXT
.text:7C958645 call _ZwContinue@8 ; ZwContinue(x,x)
.text:7C95864A jmp short loc_7C958668
.text:00426D8C ; NTSTATUS __stdcall NtContinue(PCONTEXT Context, BOOLEAN TestAlert)
.text:00426D8C _NtContinue@8 proc near ; DATA XREF: .text:00427B44o
.text:00426D8C
.text:00426D8C var_s0 = dword ptr 0
.text:00426D8C Context = dword ptr 8
.text:00426D8C TestAlert = byte ptr 0Ch
.text:00426D8C arg_34 = dword ptr 3Ch
.text:00426D8C
.text:00426D8C push ebp
.text:00426D8D mov ebx, ds:0FFDFF124h
.text:00426D93 mov edx, [ebp+arg_34]
.text:00426D96 mov [ebx+110h], edx
.text:00426D9C mov ebp, esp
.text:00426D9E mov eax, [ebp+var_s0]
.text:00426DA1 mov ecx, [ebp+Context]
.text:00426DA4 push eax ; ebp
.text:00426DA5 push 0 ; 0
.text:00426DA7 push ecx ; PCONTEXT
.text:00426DA8 call _KiContinue@12 ; KiContinue(x,x,x)
.text:00426DAD or eax, eax
.text:00426DAF jnz short loc_426DCB
.text:00426DB1 cmp [ebp+TestAlert], 0
.text:00426DB5 jz short loc_426DC3
.text:00426DB7 mov al, [ebx+0D7h]
.text:00426DBD push eax
.text:00426DBE call _KeTestAlertThread@4 ; KeTestAlertThread(x)
.text:00426DC3
.text:00426DC3 loc_426DC3: ; CODE XREF: NtContinue(x,x)+29j
.text:00426DC3 pop ebp
.text:00426DC4 mov esp, ebp
.text:00426DC6 jmp _KiServiceExit2
.text:00426DCB ; ---------------------------------------------------------------------------
.text:00426DCB
.text:00426DCB loc_426DCB: ; CODE XREF: NtContinue(x,x)+23j
.text:00426DCB pop ebp
.text:00426DCC mov esp, ebp
.text:00426DCE jmp _KiServiceExit
.text:00426DCE _NtContinue@8 endp
.text:00426DCE
NTSTATUS
KiContinue (
IN PCONTEXT ContextRecord,
IN PKEXCEPTION_FRAME ExceptionFrame,
IN PKTRAP_FRAME TrapFrame
)
/*++
Routine Description:
This function is called to copy the specified context frame to the
specified exception and trap frames for the continue system service.
Arguments:
ContextRecord - 環境記錄Supplies a pointer to a context record.
ExceptionFrame - 異常幀指標。Supplies a pointer to an exception frame.
TrapFrame - 陷阱幀Supplies a pointer to a trap frame.
Return Value:
STATUS_ACCESS_VIOLATION is returned if the context record is not readable
from user mode.
STATUS_DATATYPE_MISALIGNMENT is returned if the context record is not
properly aligned.
STATUS_SUCCESS is returned if the context frame is copied successfully
to the specified exception and trap frames.
--*/
{
KIRQL OldIrql;
NTSTATUS Status;
//
// 同步其它的環境操作
//
OldIrql = KeGetCurrentIrql();
if (OldIrql < APC_LEVEL) {
KfRaiseIrql(APC_LEVEL);
}
//
// 如果之前不是核心模式,使用封裝函式將context 拷貝到核心幀
// 否則,直接拷貝context 到核心幀
//
Status = STATUS_SUCCESS;
if (KeGetPreviousMode() != KernelMode) {
try {
KiContinuePreviousModeUser(ContextRecord,
ExceptionFrame,
TrapFrame);
} except(EXCEPTION_EXECUTE_HANDLER) {
Status = GetExceptionCode();
}
} else {
KeContextToKframes(TrapFrame,
ExceptionFrame,
ContextRecord,
ContextRecord->ContextFlags,
KernelMode);
}
if (OldIrql < APC_LEVEL) {
KeLowerIrql(OldIrql);
}
return Status;
}
DECLSPEC_NOINLINE
VOID
KiContinuePreviousModeUser (
IN PCONTEXT ContextRecord,
IN PKEXCEPTION_FRAME ExceptionFrame,
IN PKTRAP_FRAME TrapFrame
)
/*++
Routine Description:
內部操作是先將使用者資料拷貝到核心棧中,然後呼叫核心函式進行核心記憶體間的拷貝。
N.B. 假設此呼叫是在一個try 中
Arguments:
ContextRecord - Supplies a pointer to a context record.
ExceptionFrame - Supplies a pointer to an exception frame.
TrapFrame - Supplies a pointer to a trap frame.
Return Value:
None.
--*/
{
CONTEXT ContextRecord2;
//
// 將內容從使用者拷貝到核心棧
//
ProbeForReadSmallStructure(ContextRecord, sizeof(CONTEXT), CONTEXT_ALIGN);
RtlCopyMemory(&ContextRecord2, ContextRecord, sizeof(CONTEXT));
//
// 然後再進行拷貝
//
KeContextToKframes(TrapFrame,
ExceptionFrame,
&ContextRecord2,
ContextRecord2.ContextFlags,
UserMode);
return;
}
VOID
KeContextToKframes (
__inout PKTRAP_FRAME TrapFrame,
__inout PKEXCEPTION_FRAME ExceptionFrame,
__in PCONTEXT ContextFrame,
__in ULONG ContextFlags,
__in KPROCESSOR_MODE PreviousMode
)
/*++
Routine Description:
This routine moves the selected contents of the specified context frame into
the specified trap and exception frames according to the specified context
flags.根據ContextFlags 標誌,將CONTEXT 幀中的內容拷貝到特定的陷阱和異常幀
++*/
其中一個使用KiRaiseUserExceptionDispatcher 函式的是 NtClose,該函式負責關閉一個控制代碼 (Win32 函式CloseHandle 的內部實現). 當一個偵錯程式負載到一個程序,它的一個受保護控制代碼 (通過使用HANDLE_FLAG_PROTECT_FROM_CLOSE標誌呼叫SetHandleInformation ) 被傳給NtClose,將通過KiRaiseUserExceptionDispatcher 函式觸發STATUS_HANDLE_NOT_CLOSABLE 異常。例如,當一個被偵錯程式負載的程序嘗試關閉被保護控制代碼的時候可能會發現下面的呼叫堆疊:
(2600.1994): Unknown exception - code c0000235 (first chance)
(2600.1994): Unknown exception - code c0000235 (second chance)
ntdll!KiRaiseUserExceptionDispatcher+0x3a:
00000000`776920e7 8b8424c0000000 mov eax,dword ptr [rsp+0C0h]
0:000> !error c0000235
Error code: (NTSTATUS) 0xc0000235 (3221226037) - NtClose was
called on a handle that was protected from close via
NtSetInformationObject.
0:000> k
RetAddr Call Site
00000000`7746dadd ntdll!KiRaiseUserExceptionDispatcher+0x3a
00000000`01001955 kernel32!CloseHandle+0x29
00000000`01001e60 TestApp!wmain+0x35
00000000`7746cdcd TestApp!__tmainCRTStartup+0x120
00000000`7768c6e1 kernel32!BaseThreadInitThunk+0xd
00000000`00000000 ntdll!RtlUserThreadStart+0x1d
在偵錯程式中,這個異常可以被手動設定為繼續執行,以允許程式在異常發生之後繼續執行,儘管這樣的糟糕的控制代碼引用通常都象徵著一些列的錯誤。
如果一個程式在應用程式檢驗器下執行的時候關閉了一個非法的控制代碼,相似的異常也會出現。在一個非法的控制代碼的關閉過程中引發異常主要是為了除錯的目的,因為大部分程式不會檢查CloseHandle 函式的返回值。
在核心中像這樣關閉一個非法的控制代碼將導致藍屏,而不像使用者模式,僅僅觸發一個異常就可以了。除非是probe一個使用者模式控制代碼,驅動在引用一個控制代碼之後沒有許可權去繼續執行。
KiUserCallbackDispatcher
之前介紹的NTDLL核心模式到使用者模式回撥函式都是“被動”的—-核心沒有主動去顯式的呼叫這些函式。目前為止,我們所介紹的所有的函式都是特定情況下,在一個系統呼叫,中斷或者異常處理之後的返回過程中被呼叫的。
與之前討論的函式相比,KiUserCallbackDispatcher 完全打破了這種被動呼叫模型。使用者模式回撥分發器,顧名思義,是一種從核心模式向用戶模式發出全面呼叫的蹦床。(它由NtCallbackReturn 補充,該函式在一個使用者模式回撥函式執行完畢之後恢復在核心模式的執行。這意味著一個使用者模式回撥函式可以進行一個輔助呼叫,該呼叫直接到達核心模式,而不用返回到原來的使用者模式呼叫者–呼叫KiUserCallbackDispatcher 函式的位置)。
在Windows 中,使用者模式到核心模式的直接呼叫是非常非傳統的,避免這樣的操作是有原因的。這樣的呼叫通常是十分危險的,實現的時候需要非常小心以避免導致任何的系統可靠性或者完整性問題。除了簡單的檢查從使用者模式返回到核心的所有資料, KiUserCallbackDispatcher所實現的直接從核心模式呼叫到使用者模式的模型有更多的考慮。例如,執行在使用者模式的執行緒可以被隨意的暫停,由於其他的高優先順序的執行緒而延遲比較長的時間,甚至終止。這些行為意味著呼叫到使用者模式的程式碼不能持有任何的鎖,還有像記憶體等需要釋放的資源。
從核心的角度看,使用KiUserCallbackDispatcher 函式實現的使用者模式回撥函式的原理就是:核心在當前的核心棧上儲存當前處理器狀態,修改當前核心棧的檢視為指向剛剛被儲存到棧上的暫存器的狀態,之後,設定當前執行緒的一個成員 (CallbackStack)指向包含了被儲存的暫存器的狀態的棧幀, (之前的CallbackStack值被儲存以支援遞迴的回撥),然後使用標準返回機制返回到使用者模式。
使用者模式返回地址被設定為—-KiUserCallbackDispatcher。使用者模式回撥函式的操作非常的簡單,首先,它通過索引PEB中儲存的一個數組來作為回撥函式分發器的引數,這個引數指示了將要被執行的程式碼。然後,位於陣列中的回撥函式被呼叫,並傳入了一個核心傳遞的指標大小的引數。(引數通常是一個結構體指標,將多個引數放到一塊連續的記憶體塊中). KiUserCallbackDispatcher 的實際實現非常的簡單,下面是一個簡單的C語言版本表示:
VOID
KiUserCallbackDispatcher(
__in PVOID CallbackArgument
__in ULONG CallbackIndex
)
{
NTSTATUS Status;
ULONG ReturnStatus;
PPEB Peb;
//
// 使用了標準的呼叫約定,引數從棧傳遞,從[rsp+20] 開始(x64上),在X64 平臺上沒有使用任何的暫存器來傳遞引數
//
//
// Make the call to the specified kernel mode to user mode callback. The set
// of callback routines is stored in an array pointed to by the
// "KernelCallbackTable" member of the PEB.
//
// Each callback takes a single argument, which is typically a structure
// pointer. Most callbacks are in fact actually sub-dispatchers for several
// different callbacks that share the same calling convention after the
// callback arguments are unpacked from the structure pointer.
//
// In the case of a Wow64 process, the Wow64 layer will have installed a set
//