寫在前面
此系列是本人一個字一個字碼出來的,包括示例和實驗截圖。由於系統內核的復雜性,故可能有錯誤或者不全面的地方,如有錯誤,歡迎批評指正,本教程將會長期更新。 如有好的建議,歡迎反饋。碼字不易,如果本篇文章有幫助你的,如有閑錢,可以打賞支持我的創作。如想轉載,請把我的轉載信息附在文章后面,並聲明我的個人信息和本人博客地址即可,但必須事先通知我。
你如果是從中間插過來看的,請仔細閱讀 羽夏看Win系統內核——簡述 ,方便學習本教程。
看此教程之前,問一個問題,你明確學系統調用的目的了嗎? 沒有的話就不要繼續了,請重新學習 羽夏看Win系統內核——系統調用篇 里面的內容。
🔒 華麗的分割線 🔒
分析 KiSystemService
這個函數所有的分析如下,如果自己單獨分析完畢后可以查看下面的折疊:
🔒 點擊查看分析 🔒
_KiSystemService proc near ; CODE XREF: ZwAcceptConnectPort(x,x,x,x,x,x)+C↑p
; ZwAccessCheck(x,x,x,x,x,x,x,x)+C↑p ...
arg_0 = dword ptr 4
push 0 ; errorcode
push ebp
push ebx
push esi
push edi
push fs
mov ebx, 30h ; '0' ; 以上代碼填充 Trap_Frame 結構體數據
mov fs, bx ; 加載0環的fs,指向KPCR,基址:0FFDFF000h
assume fs:nothing
push dword ptr ds:0FFDFF000h ; 壓入 ExceptionList,是KPCR的第一個成員的TIB的第一個成員
mov dword ptr ds:0FFDFF000h, 0FFFFFFFFh ; 將 ExceptionList 置為 -1(EXCEPTION_CHAIN_END)
mov esi, ds:0FFDFF124h ; KPCR + 0x124:CurrentThread
push dword ptr [esi+140h] ; ETHREAD + 0x140:PreviousMode
sub esp, 48h ; 提棧,目前指向 DbgEbp 的位置
mov ebx, [esp+68h+arg_0] ; ebx = SegCs
and ebx, 1 ; 判斷調用者是不是 0環 權限
mov [esi+140h], bl ; 將計算結果賦給 PreciousMode
mov ebp, esp ; ebp = esp,目前指向 TrapFrame 的首地址
mov ebx, [esi+134h] ; 將 CurrentThread 的 TrapFrame 賦給 ebx
mov [ebp+3Ch], ebx ; TrapFrame 的 Edx = ebx 存的 TrapFrame
mov [esi+134h], ebp ; 將 CurrentThread 的 TrapFrame 替換為新構建的 TrapFrame
cld
mov ebx, [ebp+60h] ; 3環的 ebp
mov edi, [ebp+68h] ; 3環的 EIP
mov [ebp+0Ch], edx ; 將 3環 的參數列表存入到 DbgArgPointer
; 這個參數是在調用 int 2Eh 前傳入的
mov dword ptr [ebp+8], 0BADB0D00h ; DbgArgMark 賦值,細節未知
mov [ebp+0], ebx ; DbgEbp = ebx 的值,即3環的ebp
mov [ebp+4], edi ; DbgEip = edi 的值,即3環的eip
test byte ptr [esi+2Ch], 0FFh ; 判斷 DebugActive 是否有值
jnz Dr_kss_a ; 如果是0,則說明未被調試,不跳
KiSystemServiceCallEnd: ; CODE XREF: Dr_kss_a+10↑j
; Dr_kss_a+7C↑j
sti ; 啟用中斷
jmp APIService ; 跳到這個地址(這個名字是我自己起的)
_KiSystemService endp
如果你發現自己分析的和我的差不多,恭喜你,你基本掌握了這個函數的處理流程,本小節的下面分析的你就可以跳過了,下面開始對開頭部分的代碼進行分析,畢竟比較難理解的就在這里:
push 0 ; errorcode
push ebp
push ebx
push esi
push edi
push fs
mov ebx, 30h ; '0' ; 以上代碼填充 Trap_Frame 結構體數據
mov fs, bx ; 加載0環的fs,指向KPCR,基址:0FFDFF000h
assume fs:nothing
push dword ptr ds:0FFDFF000h ; 壓入 ExceptionList,是KPCR的第一個成員的TIB的第一個成員
講解前問一個問題:上來第一個push
,壓棧到哪里去了?到0環的堆棧去了。因為自從int 2Eh
我們就進去了0環,堆棧也被換掉了。既然是Windows
寫的代碼,我們之前講過它在0環會維護一個結構體,名為棧幀。那么在這里就是在維護這個結構體。為了方便講解我們把這個結構體搬來了:
kd> dt _KTrap_Frame
nt!_KTRAP_FRAME
+0x000 DbgEbp : Uint4B
+0x004 DbgEip : Uint4B
+0x008 DbgArgMark : Uint4B
+0x00c DbgArgPointer : Uint4B
+0x010 TempSegCs : Uint4B
+0x014 TempEsp : Uint4B
+0x018 Dr0 : Uint4B
+0x01c Dr1 : Uint4B
+0x020 Dr2 : Uint4B
+0x024 Dr3 : Uint4B
+0x028 Dr6 : Uint4B
+0x02c Dr7 : Uint4B
+0x030 SegGs : Uint4B
+0x034 SegEs : Uint4B
+0x038 SegDs : Uint4B
+0x03c Edx : Uint4B
+0x040 Ecx : Uint4B
+0x044 Eax : Uint4B
+0x048 PreviousMode : Uint4B
+0x04c ExceptionList : Ptr32 _EXCEPTION_REGISTRATION_RECORD
+0x050 SegFs : Uint4B
+0x054 Edi : Uint4B
+0x058 Esi : Uint4B
+0x05c Ebx : Uint4B
+0x060 Ebp : Uint4B
+0x064 ErrCode : Uint4B
+0x068 Eip : Uint4B
+0x06c SegCs : Uint4B
+0x070 EFlags : Uint4B
+0x074 HardwareEsp : Uint4B
+0x078 HardwareSegSs : Uint4B
+0x07c V86Es : Uint4B
+0x080 V86Ds : Uint4B
+0x084 V86Fs : Uint4B
+0x088 V86Gs : Uint4B
一旦切換到0環堆棧,所謂的esp0
就會指向我們Trap_Frame
結構體偏移+0x07c
的位置,即指向V86Es
的位置,然后根據CPU
的使用中斷門的約定,依次壓入3環的ss
、esp
、eflag
、cs
和eip
,是不是和結構體里面的定義一模一樣?
你可能問道ErrCode
是什么鬼東西?我在上一篇教程提了一嘴:ErrCode
有時由操作系統壓入,有時由CPU
壓入。我們先看一張你熟悉的一張圖:
這張圖在保護模式篇的總結與提升部分講解過。看沒看到Error Code
這一列?如果為Yes
,發生中斷的時候,CPU
就會再把這個值壓入堆棧當中。Windows
系統設計時為了保持對齊,這里的push 0
就是這么來的。
其他的部分就不難了,自己看折疊的分析就能看懂了,自行分析即可。
分析 KiFastCallEntry
現在的CPU
都支持sysenter
/sysexit
指令,KiFastCallEntry
這個函數分析必不可少,這個函數所有的分析如下,如果自己單獨分析完畢后可以查看下面的折疊:
🔒 點擊查看分析 🔒
_KiFastCallEntry proc near ; DATA XREF: KiLoadFastSyscallMachineSpecificRegisters(x)+24↑o
; _KiTrap01+72↓o
var_B = byte ptr -0Bh
anonymous_0 = dword ptr -8
anonymous_1 = dword ptr -4
; FUNCTION CHUNK AT .text:00466519 SIZE 00000025 BYTES
; FUNCTION CHUNK AT .text:004667DC SIZE 00000014 BYTES
mov ecx, 23h ; '#'
push 30h ; '0'
pop fs ; 這兩行代碼是加載 fs 為0環的
mov ds, ecx
mov es, ecx ; 加載 ds 和 es
mov ecx, ds:0FFDFF040h ; 取出 KPCR 的 TSS
mov esp, [ecx+4] ; 取出 TSS 的 ESP0,並賦給 esp
push 23h ; '#' ; 開始填充 TrapFrame 數據
push edx
pushf
loc_46655A: ; CODE XREF: _KiFastCallEntry2+22↑j
push 2
add edx, 8 ; edx 就是 3環 傳來的參數列表地址,加個8
popf ; eflag = 2,即清空Eflag
or [esp+0Ch+var_B], 2 ; 設置存入的EFLAG的第二個位
push 1Bh ; SegCs
push dword ptr ds:0FFDF0304h ; SystemCallReturn
push 0 ; errorcode
push ebp
push ebx
push esi
push edi
mov ebx, ds:0FFDFF01Ch ; ebx = KPCR
push 3Bh ; ';' ; SegFs
mov esi, [ebx+124h] ; esi = KPCR.CurrentThread
push dword ptr [ebx] ; ExceptionList
mov dword ptr [ebx], 0FFFFFFFFh ; 將 ExceptionList 置 -1(EXCEPTION_CHAIN_END)
mov ebp, [esi+18h] ; InitialStack,指向 TrapFrame 基址
push 1 ; PreviousMode
sub esp, 48h ; 提棧,此時 esp 指向 TrapFrame 的結構體首地址
sub ebp, 29Ch ; 0x29C = NPX_FRAME_LENGTH + KTRAP_FRAME_LENGTH
; = sizeof(_FX_SAVE_AREA) + sizeof(_Trap_Frame)
; = 0x210 + 0x8c
mov byte ptr [esi+140h], 1 ; ApcNeeded
cmp ebp, esp
jnz short loc_46653C ; 如果不相等說明這個是 VX86 線程,拒絕訪問,跳走
and dword ptr [ebp+2Ch], 0 ; Dr7
test byte ptr [esi+2Ch], 0FFh ; DebugActive
mov [esi+134h], ebp ; 替換 TrapFrame
jnz Dr_FastCallDrSave
loc_4665B6: ; CODE XREF: Dr_FastCallDrSave+10↑j
; Dr_FastCallDrSave+7C↑j
mov ebx, [ebp+60h] ; ebx = TrapFrame.Ebp
mov edi, [ebp+68h] ; edi = TrapFrame.Eip
mov [ebp+0Ch], edx ; DbgArgPointer = 3環傳來的參數列表
mov dword ptr [ebp+8], 0BADB0D00h ; DbgArgMark
mov [ebp+0], ebx ; DbgEbp
mov [ebp+4], edi ; DbgEip
sti ; 啟用中斷
APIService: ; CODE XREF: _KiBBTUnexpectedRange+18↑j
; _KiSystemService+6F↑j
同樣,接下來我對比較難理解難分析的部分逐個講述,簡單的部分自行分析:
push dword ptr [ebx] ; ExceptionList
mov dword ptr [ebx], 0FFFFFFFFh ; 將 ExceptionList 置 -1(EXCEPTION_CHAIN_END)
mov ebp, [esi+18h] ; InitialStack,指向 TrapFrame 基址
push 1 ; PreviousMode
sub esp, 48h ; 提棧,此時 esp 指向 TrapFrame 的結構體首地址
sub ebp, 29Ch ; 0x29C = NPX_FRAME_LENGTH + KTRAP_FRAME_LENGTH
; = sizeof(_FX_SAVE_AREA) + sizeof(_Trap_Frame)
; = 0x210 + 0x8c
mov byte ptr [esi+140h], 1
cmp ebp, esp
jnz short loc_46653C ; 如果不相等說明這個是 VX86 線程,拒絕訪問,跳走
既然有了分析KiSystemService
的基礎,前面的應該就不太難了,同樣是填充結構體,只是多做中斷門做了,而sysenter
沒做的事情,也就上面的部分難以理解。我也沒講過,百度也沒了作用。然而為何不看看WRK
呢?經過函數定位,我們定位到了這幾句:
;
; Save the old exception list in trap frame and initialize a new empty
; exception list.
;
push [ebx].PcExceptionList ; save old exception list
mov [ebx].PcExceptionList, EXCEPTION_CHAIN_END ; set new empty list
mov ebp, [esi].ThInitialStack
;
; Save the old previous mode in trap frame, allocate remainder of trap frame,
; and set the new previous mode.
;
push MODE_MASK ; Save previous mode as user
sub esp,TsPreviousPreviousMode ; allocate remainder of trap frame
sub ebp, NPX_FRAME_LENGTH + KTRAP_FRAME_LENGTH
mov byte ptr [esi].ThPreviousMode,MODE_MASK ; set new previous mode of user
;
; Now the full trap frame is build.
; Calculate initial stack pointer from thread initial stack to contain NPX and trap.
; If this isn't the same as esp then we are a VX86 thread and we are rejected
;
cmp ebp, esp
jne short Kfsc91
里面有幾個偽指令定義,我們把它給找出來:
EXCEPTION_CHAIN_END equ 0FFFFFFFFH
TsPreviousPreviousMode equ 00048H
NPX_FRAME_LENGTH equ 00210H
KTRAP_FRAME_LENGTH equ 0008CH
MODE_MASK equ 00001H
通過上面的注釋,我們明白了ExceptionList
賦值為-1
的含義,還有下面的代碼是怎么來的:
sub esp, 48h ; 提棧,此時 esp 指向 TrapFrame 的結構體首地址
sub ebp, 29Ch ; 0x29C = NPX_FRAME_LENGTH + KTRAP_FRAME_LENGTH
; = sizeof(_FX_SAVE_AREA) + sizeof(_Trap_Frame)
; = 0x210 + 0x8c
mov byte ptr [esi+140h], 1
cmp ebp, esp
jnz short loc_46653C ; 如果不相等說明這個是 VX86 線程,拒絕訪問,跳走
但我還是不明白NPX_FRAME_LENGTH
是啥,NPX
到底是啥,搜啊。在WRK
搜一下有沒有,嘗試失敗:
放心,這個是百度搜不到的。通過谷歌搜索找到了一個線索,來源於 此鏈接 ,我把這位同志畫的整理一下,如下所示:
通過它畫的內核線程堆棧圖可知,所謂的NPX
不過是一個FX_SAVE_AREA
結構體,我們在WRK
搜一下,結果搜到了,整理一下:
// Union for FLOATING_SAVE_AREA and MMX_FLOATING_SAVE_AREA
typedef struct _FX_SAVE_AREA {
union {
FNSAVE_FORMAT FnArea;
FXSAVE_FORMAT FxArea;
} U;
ULONG NpxSavedCpu; // Cpu that last did fxsave for this thread
ULONG Cr0NpxState; // Has to be the last field because of the
// Boot thread
} FX_SAVE_AREA, *PFX_SAVE_AREA;
// Define the size of the 80387 save area, which is in the context frame.
#define SIZE_OF_80387_REGISTERS 80
// Format of data for fnsave/frstor instruction
typedef struct _FNSAVE_FORMAT {
ULONG ControlWord;
ULONG StatusWord;
ULONG TagWord;
ULONG ErrorOffset;
ULONG ErrorSelector;
ULONG DataOffset;
ULONG DataSelector;
UCHAR RegisterArea[SIZE_OF_80387_REGISTERS];
} FNSAVE_FORMAT, *PFNSAVE_FORMAT;
這個結構體根據注釋來看和浮點運算有關,這個不在我們研究的范圍,僅作了解。然后計算一下,果然大小和我們上面的NPX_FRAME_LENGTH
的值是一模一樣的,這個問題也就迎刃而解了。
下面的部分是本篇文章將要介紹的部分:系統服務表。
SystemServiceTable
之前我們講到進0環后,3環的各種寄存器的值都會保留到_Trap_Frame
結構體中,接下來我將會講解:如何根據系統服務號(eax中存儲)找到要執行的內核函數?調用時參數是存儲到3環的堆棧,如何傳遞給內核函數?
結構
首先我們得知道一個結構體,用來描述內核函數信息的表:SystemServiceTable
,即系統服務表,它不是SSDT
,至於SSDT
的詳細內容將會在下一篇講解。現在我們看一看它的結構:
可以看出這個表由4部分組成,ServiceTable
指向的是函數地址數組,每個成員四個字節;Count
表示調用次數,沒啥意義;ServiceLimit
表示這張表有幾個函數;ArgumentTable
指向對應函數有幾個字節參數,每個成員一個字節。
從圖中可以看出,Windows
提供了兩張表:上面的表是用來處理一般內核函數的,下面這張表是用來處理與GUI
相關的內核函數。
位置
既然知道了表的結構,我們得知道它在哪里,否則怎么調用它?它在EThread
結構體里面,我再把前面放的拿來:
kd> dt _KTHREAD
nt!_KTHREAD
+0x000 Header : _DISPATCHER_HEADER
+0x010 MutantListHead : _LIST_ENTRY
+0x018 InitialStack : Ptr32 Void
+0x01c StackLimit : Ptr32 Void
+0x020 Teb : Ptr32 Void
+0x024 TlsArray : Ptr32 Void
+0x028 KernelStack : Ptr32 Void
+0x02c DebugActive : UChar
+0x02d State : UChar
+0x02e Alerted : [2] UChar
+0x030 Iopl : UChar
+0x031 NpxState : UChar
+0x032 Saturation : Char
+0x033 Priority : Char
+0x034 ApcState : _KAPC_STATE
+0x04c ContextSwitches : Uint4B
+0x050 IdleSwapBlock : UChar
+0x051 Spare0 : [3] UChar
+0x054 WaitStatus : Int4B
+0x058 WaitIrql : UChar
+0x059 WaitMode : Char
+0x05a WaitNext : UChar
+0x05b WaitReason : UChar
+0x05c WaitBlockList : Ptr32 _KWAIT_BLOCK
+0x060 WaitListEntry : _LIST_ENTRY
+0x060 SwapListEntry : _SINGLE_LIST_ENTRY
+0x068 WaitTime : Uint4B
+0x06c BasePriority : Char
+0x06d DecrementCount : UChar
+0x06e PriorityDecrement : Char
+0x06f Quantum : Char
+0x070 WaitBlock : [4] _KWAIT_BLOCK
+0x0d0 LegoData : Ptr32 Void
+0x0d4 KernelApcDisable : Uint4B
+0x0d8 UserAffinity : Uint4B
+0x0dc SystemAffinityActive : UChar
+0x0dd PowerState : UChar
+0x0de NpxIrql : UChar
+0x0df InitialNode : UChar
+0x0e0 ServiceTable : Ptr32 Void
+0x0e4 Queue : Ptr32 _KQUEUE
+0x0e8 ApcQueueLock : Uint4B
+0x0f0 Timer : _KTIMER
+0x118 QueueListEntry : _LIST_ENTRY
+0x120 SoftAffinity : Uint4B
+0x124 Affinity : Uint4B
+0x128 Preempted : UChar
+0x129 ProcessReadyQueue : UChar
+0x12a KernelStackResident : UChar
+0x12b NextProcessor : UChar
+0x12c CallbackStack : Ptr32 Void
+0x130 Win32Thread : Ptr32 Void
+0x134 TrapFrame : Ptr32 _KTRAP_FRAME
+0x138 ApcStatePointer : [2] Ptr32 _KAPC_STATE
+0x140 PreviousMode : Char
+0x141 EnableStackSwap : UChar
+0x142 LargeStack : UChar
+0x143 ResourceIndex : UChar
+0x144 KernelTime : Uint4B
+0x148 UserTime : Uint4B
+0x14c SavedApcState : _KAPC_STATE
+0x164 Alertable : UChar
+0x165 ApcStateIndex : UChar
+0x166 ApcQueueable : UChar
+0x167 AutoAlignment : UChar
+0x168 StackBase : Ptr32 Void
+0x16c SuspendApc : _KAPC
+0x19c SuspendSemaphore : _KSEMAPHORE
+0x1b0 ThreadListEntry : _LIST_ENTRY
+0x1b8 FreezeCount : Char
+0x1b9 SuspendCount : Char
+0x1ba IdealProcessor : UChar
+0x1bb DisableBoost : UChar
由於EThread
結構體挺大的,而我們的服務表在它的第一個成員里面,我就只放這個結構體。明顯它在+0x0e0
偏移位置。
如何調用
拿到了服務號,如何找到真正的函數呢?我們先看一張示意圖:
我們根據服務表的索引12這個位可以判斷是哪張服務表,既然找到了哪張服務表,后12位就是在服務表的索引。通過這個索引,找到函數地址,有幾個參數,我們就可以調用它了,接下來我們開始分析調用真正函數地址的細節。
分析 APIService
APIService
是我自己起的名字,你應該是默認的名字,自己隨便起。APIService
這個地址很有意思,無論是通過中斷門進行系統調用,還是sysenter
,最終都會走這里。好,我們下來可以繼續分析,你可以以不再閱讀,停下來自行分析,分析不動了再回來看看:
APIService: ; CODE XREF: _KiBBTUnexpectedRange+18↑j
; _KiSystemService+6F↑j
mov edi, eax ; eax = 3環傳來的服務號
shr edi, 8
and edi, 30h
mov ecx, edi ; 正好一個表的大小就是0x10,如果是GUI相關,就加;反之不加。
add edi, [esi+0E0h] ; edi = ServiceTable
mov ebx, eax ; eax = 3環傳來的服務號
and eax, 0FFFh ; 去掉索引為12的位
cmp eax, [edi+8] ; 得到的結果與ServiceLimit比較
jnb _KiBBTUnexpectedRange ; 如果超出,說明越界,跳走
cmp ecx, 10h
jnz short loc_46660C ; 判斷是否是調用 win32k.sys 的,不是的話跳走
mov ecx, ds:0FFDFF018h ; _DWORD
xor ebx, ebx
loc_4665FA: ; DATA XREF: _KiTrap0E+113↓o
or ebx, [ecx+0F70h]
jz short loc_46660C
push edx
push eax
call ds:_KeGdiFlushUserBatch
pop eax
pop edx
loc_46660C: ; CODE XREF: _KiFastCallEntry+B0↑j
; _KiFastCallEntry+C0↑j
inc dword ptr ds:0FFDFF638h ; KeSystemCalls 自增 1
mov esi, edx ; 3環來的參數列表
mov ebx, [edi+0Ch] ; SSDT參數表地址
xor ecx, ecx
mov cl, [eax+ebx] ; 獲得調用指定函數的參數表的參數長度
mov edi, [edi] ; 函數地址表
mov ebx, [edi+eax*4] ; 獲取調用的指定函數的地址
sub esp, ecx ; 提棧,供給參數拷貝空間
shr ecx, 2 ; number of argument DWORDs
mov edi, esp ; 設置拷貝地址
cmp esi, ds:_MmUserProbeAddress ; 判斷拷貝的源地址是否超出用戶態能讀取的寬度
jnb loc_4667DC ; 如果超出,跳走,報錯
loc_466634: ; CODE XREF: _KiFastCallEntry+2A0↓j
; DATA XREF: _KiTrap0E+109↓o
rep movsd ; 從3環拷貝參數到0環
call ebx ; 真正調用函數
loc_466638: ; CODE XREF: _KiFastCallEntry+2AB↓j
; DATA XREF: _KiTrap0E+129↓o ...
mov esp, ebp
loc_46663A: ; CODE XREF: _KiBBTUnexpectedRange+38↑j
; _KiBBTUnexpectedRange+43↑j
mov ecx, ds:0FFDFF124h
mov edx, [ebp+3Ch]
mov [ecx+134h], edx
_KiFastCallEntry endp
這次分析應該沒啥難度了。可以看出如果通過服務號發現是與GUI
相關的函數,它會調用一個函數。如果找到正確的函數地址和參數,它會提棧把3環的數據拷貝過來,然后正式調用。我們來看看WRK
是怎么寫的:
; (eax) = Service number
; (edx) = Callers stack pointer
; (esi) = Current thread address
;
; All other registers have been saved and are free.
;
; Check if the service number within valid range
;
_KiSystemServiceRepeat:
mov edi, eax ; copy system service number
shr edi, SERVICE_TABLE_SHIFT ; isolate service table number
and edi, SERVICE_TABLE_MASK ;
mov ecx, edi ; save service table number
add edi, [esi]+ThServiceTable ; compute service descriptor address
mov ebx, eax ; save system service number
and eax, SERVICE_NUMBER_MASK ; isolate service table offset
;
; If the specified system service number is not within range, then attempt
; to convert the thread to a GUI thread and retry the service dispatch.
;
cmp eax, [edi]+SdLimit ; check if valid service
jae Kss_ErrorHandler ; if ae, try to convert to GUI thread
;
; If the service is a GUI service and the GDI user batch queue is not empty,
; then call the appropriate service to flush the user batch.
;
cmp ecx, SERVICE_TABLE_TEST ; test if GUI service
jne short Kss40 ; if ne, not GUI service
mov ecx, PCR[PcTeb] ; get current thread TEB address
xor ebx, ebx ; get number of batched GDI calls
KiSystemServiceAccessTeb:
or ebx, [ecx]+TbGdiBatchCount ; may cause an inpage exception
jz short Kss40 ; if z, no batched calls
push edx ; save address of user arguments
push eax ; save service number
call [_KeGdiFlushUserBatch] ; flush GDI user batch
pop eax ; restore service number
pop edx ; restore address of user arguments
;
; The arguments are passed on the stack. Therefore they always need to get
; copied since additional space has been allocated on the stack for the
; machine state frame. Note that we don't check for the zero argument case -
; copy is always done regardless of the number of arguments because the
; zero argument case is very rare.
;
Kss40: inc dword ptr PCR[PcPrcbData+PbSystemCalls] ; system calls
FPOFRAME ?FpoValue, 0
mov esi, edx ; (esi)->User arguments
mov ebx, [edi]+SdNumber ; get argument table address
xor ecx, ecx
mov cl, byte ptr [ebx+eax] ; (ecx) = argument size
mov edi, [edi]+SdBase ; get service table address
mov ebx, [edi+eax*4] ; (ebx)-> service routine
sub esp, ecx ; allocate space for arguments
shr ecx, 2 ; (ecx) = number of argument DWORDs
mov edi, esp ; (edi)->location to receive 1st arg
cmp esi, _MmUserProbeAddress ; check if user address
jae kss80 ; if ae, then not user address
KiSystemServiceCopyArguments:
rep movsd ; copy the arguments to top of stack.
; Since we usually copy more than 3
; arguments. rep movsd is faster than
; mov instructions.
;
; Make actual call to system service
;
kssdoit:
call ebx ; call system service
kss61:
;
; Upon return, (eax)= status code. This code may also be entered from a failed
; KiCallbackReturn call.
;
mov esp, ebp ; deallocate stack space for arguments
;
; Restore old trap frame address from the current trap frame.
;
kss70: mov ecx, PCR[PcPrcbData+PbCurrentThread] ; get current thread address
mov edx, [ebp].TsEdx ; restore previous trap frame address
mov [ecx].ThTrapFrame, edx ;
回到3環
分析到現在,函數其實沒有結束完,它還得通過KiServiceExit
退到3環,完成系統調用。但是不幸的是,這得需要APC
的知識,可能相當的一段時間才能講解到。學會APC
后,我們將重新分析該部分內容,下面是WRK
的有關該函數的注釋說明:
;
; System service's private version of KiExceptionExit
; (Also used by KiDebugService)
;
; Check for pending APC interrupts, if found, dispatch to them
; (saving eax in frame first).
;