前言
之前我們詳細瞭解了API從三環進到零環的過程,API會通過兩種方式進入到零環,如果通過中斷門的方式進入零環,最終會進入到KiSystemService這個函數。接下來就來分析KiSystemService這個函數的內部實現細節。
用IDA打開ntkrnlpa.exe,找到KiSystemService函數
程序想要運行必須要有兩樣東西,分別是EIP和ESP。一旦進入零環,就需要把寄存器保存到一個結構體,這個結構體就是_KTRAP_FRAME
,也就是零環的堆棧。
保存現場
_KTRAP_FRAME
_KTRAP_FRAME這個結構體由操作系統維護 數據如下所示:
kd> dt _KTRAP_FRAME
nt!_KTRAP_FRAME
+0x000 DbgEbp : Uint4B
+0x004 DbgEip : Uint4B
+0x008 DbgArgMark : Uint4B
+0x00c DbgArgPointer : Uint4B
+0x010 TempSegCs : Uint2B
+0x012 Logging : UChar
+0x013 Reserved : UChar
+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 PreviousPreviousMode : 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
這個結構體的最後四個成員只有在虛擬8086模式下才會用到,保護模式下不用。
當API通過中斷門進入到零環之前,ESP0指向+0x078 HardwareSegSs
的這個位置。
接着,中斷門提權後會將SS ESP EFlags CS和EIP壓入堆棧。此時,ESP0指向+0x064 ErrCode
的位置。
到這裏,我們就知道了KiSystemService函數前幾行彙編代碼的含義:
.text:00465651 push 0 ; 保存ErrCode到esp0
.text:00465653 push ebp ; 保存ebp到esp0
.text:00465654 push ebx ; 保存ebx到esp0
.text:00465655 push esi ; 保存esi到esp0
.text:00465656 push edi ; 保存edi到esp0
.text:00465657 push fs ; 保存fs到esp0
KRPC
接下來繼續分析後面的代碼
.text:00465659 mov ebx, 30h
.text:0046565E mov fs, bx
這裏將0x30賦值給FS寄存器,通過拆分0x30段選擇子可以得到GDT表下標爲6的段描述符:FFC093DF`F0000001。這個段描述符的基址爲FFDFF0000,指向當前的KPCR結構體。
KPCR結構體如下所示:
kd> dt _KPCR
nt!_KPCR
+0x000 NtTib : _NT_TIB
+0x000 Used_ExceptionList : Ptr32 _EXCEPTION_REGISTRATION_RECORD
+0x004 Used_StackBase : Ptr32 Void
+0x008 Spare2 : Ptr32 Void
+0x00c TssCopy : Ptr32 Void
+0x010 ContextSwitches : Uint4B
+0x014 SetMemberCopy : Uint4B
+0x018 Used_Self : Ptr32 Void
+0x01c SelfPcr : Ptr32 _KPCR
+0x020 Prcb : Ptr32 _KPRCB
+0x024 Irql : UChar
+0x028 IRR : Uint4B
+0x02c IrrActive : Uint4B
+0x030 IDR : Uint4B
+0x034 KdVersionBlock : Ptr32 Void
+0x038 IDT : Ptr32 _KIDTENTRY
+0x03c GDT : Ptr32 _KGDTENTRY
+0x040 TSS : Ptr32 _KTSS
+0x044 MajorVersion : Uint2B
+0x046 MinorVersion : Uint2B
+0x048 SetMember : Uint4B
+0x04c StallScaleFactor : Uint4B
+0x050 SpareUnused : UChar
+0x051 Number : UChar
+0x052 Spare0 : UChar
+0x053 SecondLevelCacheAssociativity : UChar
+0x054 VdmAlert : Uint4B
+0x058 KernelReserved : [14] Uint4B
+0x090 SecondLevelCacheSize : Uint4B
+0x094 HalReserved : [16] Uint4B
+0x0d4 InterruptMode : Uint4B
+0x0d8 Spare1 : UChar
+0x0dc KernelReserved2 : [17] Uint4B
+0x120 PrcbData : _KPRCB
KPCR叫CPU控制區(Kernel Processor Control Region),每個CPU有一個。如果想查看當前的CPU數量,可以用下面這條指令。
kd> dd KeNumberProcessors
83fb096c 00000001 83f3cf33 00000001 00000001
83fb097c 00000000 00000000 00000020 1fc10000
83fb098c 00110006 00003c03 776c7058 776c6f58
83fb099c 776c6fc0 776c7008 776b5a8f 776b5a8d
83fb09ac 776b5a64 00000000 00ce6126 842095b0
83fb09bc 841734f2 83ec4d9c 00000000 00000191
83fb09cc 83ec53e4 00000000 00000000 00000000
83fb09dc 00000000 83f236af 00000000 026b2372
我當前的虛擬機只有一個核,所以數量是1。用下面這條指令可以查看每個覈對應的KPCR分別是什麼
kd> dd KiProcessorBlock l2
8055a320 ffdff120 00000000
這個地址顯示的是ffdff120,也就是KPCR偏移0x120的位置。KPCR偏移0x120的位置是 _KPRCB,可以理解爲擴展的KPCR。
所以下面這兩句代碼執行完之後,FS就指向KPCR
.text:00465659 mov ebx, 30h
.text:0046565E mov fs, bx ; 使FS指向KPCR
ExceptionList
接下來繼續向下分析
.text:00465661 push dword ptr ds:0FFDFF000h
.text:00465667 mov dword ptr ds:0FFDFF000h, 0FFFFFFFFh
這裏將0FFDFF000h壓入堆棧,也就是KPCR的結構體的第一個成員
+0x000 NtTib : _NT_TIB
這個NtTib是個子結構體,這個子結構體很大
kd> dt _NT_TIB
nt!_NT_TIB
+0x000 ExceptionList : Ptr32 _EXCEPTION_REGISTRATION_RECORD
+0x004 StackBase : Ptr32 Void
+0x008 StackLimit : Ptr32 Void
+0x00c SubSystemTib : Ptr32 Void
+0x010 FiberData : Ptr32 Void
+0x010 Version : Uint4B
+0x014 ArbitraryUserPointer : Ptr32 Void
+0x018 Self : Ptr32 _NT_TIB
這個結構體的第一個成員是ExceptionList
.text:00465661 push dword ptr ds:0FFDFF000h ; 保存老的ExceptionList到esp0
.text:00465667 mov dword ptr ds:0FFDFF000h, 0FFFFFFFFh ; 將新的ExceptionList賦值爲-1
這兩行代碼首先保存老的ExceptionList,並將新的ExceptionList賦值爲-1,。繼續往下分析
.text:00465671 mov esi, ds:0FFDFF124h
_KTHREAD結構體
這裏將0FFDFF124h保存到esi。KPCR偏移0x120的位置是_KPRCB
+0x120 PrcbData : _KPRCB
繼續查看一下_KPRCB結構體0x4的位置
kd> dt _KPRCB
nt!_KPRCB
+0x000 MinorVersion : Uint2B
+0x002 MajorVersion : Uint2B
+0x004 CurrentThread : Ptr32 _KTHREAD
CurrentThread是當前CPU所執行線程的_ETHREAD
結構體,繼續查看一下_ETHREAD
結構體。
kd> dt _ETHREAD
nt!_ETHREAD
+0x000 Tcb : _KTHREAD
_ETHREAD
結構體的第一個成員是_KTHREAD
,所以下面這行代碼的具體含義是將當前線程的_KTHREAD
結構體保存到esi
.text:00465671 mov esi, ds:0FFDFF124h ; 將當前線程的_KTHREAD結構體保存到esi
繼續往下分析
.text:00465677 push dword ptr [esi+140h]
這裏將esi+140壓入堆棧,esi指向_KTHREAD
結構體,查看一下_KTHREAD
0x140的位置
kd> dt _KTHREAD
nt!_KTHREAD
+0x140 PreviousMode
先前模式
0x140的位置保存的是先前模式,先前模式的作用就是記錄當前調用的這段代碼之前是被零環調用還是被三環調用
.text:00465677 push dword ptr [esi+140h] ; 保存老的先前模式到esp0
擡高堆棧
繼續往下分析
.text:0046567D sub esp, 48h
回顧一下當前的堆棧情況
kd> dt _KTRAP_FRAME
nt!_KTRAP_FRAME
+0x000 DbgEbp : Uint4B
+0x004 DbgEip : Uint4B
+0x008 DbgArgMark : Uint4B
+0x00c DbgArgPointer : Uint4B
+0x010 TempSegCs : Uint2B
+0x012 Logging : UChar
+0x013 Reserved : UChar
+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 PreviousPreviousMode : 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
通過前面的代碼分析我們可以知道當前零環的堆棧已經壓入了下面這些值
+0x048 PreviousPreviousMode : 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
而除去已經壓入堆棧的值,這個結構體正好剩下0x48個字節,sub,esp 0x48將當前的esp0指向_KTRAP_FRAME
0x00的位置
.text:0046567D sub esp, 48h ; 將esp0指向_KTRAP_FRAME頭
判斷當前權限
繼續往下分析
.text:00465680 mov ebx, [esp+68h+arg_0]
.text:00465684 and ebx, 1
這裏將esp+0x68+4的值保存到ebx,當前的esp指向``_KTRAP_FRAME`頭部,esp+6C的位置就是CS
+0x06c SegCs : Uint4B
取出CS之後,和1做與運算。
如果是3環的CS段選擇子,那麼最後兩個二進制位是11。11和1進行與運算結果還是1。
如果是0環的CS段選擇子,那麼最後兩個二進制位是00。00和1進行與運算結果還是00。
這裏實際上是通過與運算的結果來判斷當前代碼是三環還是零環。
.text:00465687 mov [esi+140h], bl ; 賦值新的先前模式
接下來將bl賦值給esi+140h的位置,也就是之前分析過的先前模式
.text:0046568D mov ebp, esp ; ebp=esp=_KTRAP_FRAME
接下來上面這行代碼執行完成之後ebp和esp都指向_KTRAP_FRAME
結構體,繼續往下分析
更新_KTRAP_FRAME
.text:0046568F mov ebx, [esi+134h]
這裏將esi+0x134的位置保存到ebx,esi指向的是_ETHREAD
,查看一下 +0x134的位置保存的內容
+0x134 TrapFrame : Ptr32 _KTRAP_FRAME
+0x134的位置保存的_KTRAP_FRAME
結構體指針,_KTRAP_FRAME
這個結構體以線程爲單位保存在_ETHREAD
結構體裏面,每個線程都有一份。
.text:0046568F mov ebx, [esi+134h] ; 保存_KTRAP_FRAME結構體到ebx
.text:00465695 mov [ebp+3Ch], ebx ; 將KTHREAD中的_KTRAP_FRAME保存到[ebp+0x3C]
所以這一句的含義實際上是先將_KTRAP_FRAME結構體保存到ebx,然後再存到[ebp+0x3C]的位置。繼續往下分析
.text:00465698 mov [esi+134h], ebp ; 更新當前線程的_KTRAP_FRAME
這裏將ebp保存到[esi+134h],此時ebp指向_KTRAP_FRAME
頭部位置,而[esi+134h]也是_KTRAP_FRAME
頭,所以這行代碼執行完,就會更新當前線程的_KTRAP_FRAME
結構體。繼續往下分析
保存三環的寄存器環境
.text:0046569F mov ebx, [ebp+60h]
.text:004656A2 mov edi, [ebp+68h]
當前的ebp=esp指向了_KTRAP_FRAME
頭的位置,
kd> dt _KTRAP_FRAME
nt!_KTRAP_FRAME
+0x060 Ebp : Uint4B
+0x064 ErrCode : Uint4B
+0x068 Eip : Uint4B
[ebp+0x60]的位置是三環的ebp,[ebp+0x68]的位置是三環的eip,
.text:0046569F mov ebx, [ebp+60h] ; 取出三環的ebp放到ebx
.text:004656A2 mov edi, [ebp+68h] ; 取出三環的eip放到edi
.text:004656A5 mov [ebp+0Ch], edx
.text:004656A8 mov dword ptr [ebp+8], 0BADB0D00h
.text:004656AF mov [ebp+0], ebx ; 將三環的ebp備份到_KTRAP_FRAME DbgEbp的位置
.text:004656B2 mov [ebp+4], edi ; 將三環的eip備份到_KTRAP_FRAME DbgEip的位置
.text:004656B5 test byte ptr [esi+2Ch], 0FFh
這裏先將三環的ebp和eip保存到寄存器,然後再備份到_KTRAP_FRAME
結構體DbgEbp和DbgEip的位置。然後繼續看中間兩行代碼
.text:004656A5 mov [ebp+0Ch], edx
.text:004656A8 mov dword ptr [ebp+8], 0BADB0D00h
這裏將edx存到ebp+0xC的位置
kd> dt _KTRAP_FRAME
nt!_KTRAP_FRAME
+0x000 DbgEbp : Uint4B
+0x004 DbgEip : Uint4B
+0x008 DbgArgMark : Uint4B
+0x00c DbgArgPointer : Uint4B
ebp+0xC的位置是DbgArgPointer,那麼這個edx是什麼呢?這裏要回顧之前學習的內容。三環進零環的兩種方式,不管是哪一種,都會用到eax和edx兩個寄存器,其中eax保存的是服務號,而edx存儲的是三環的參數開始的位置。
.text:004656A5 mov [ebp+0Ch], edx ; 將三環的參數指針存到DbgArgPointer
.text:004656A8 mov dword ptr [ebp+8], 0BADB0D00h ; 將DbgArgMark賦值爲0BADB0D00
判斷調試狀態
那麼這行彙編代碼的作用就是將三環的參數指針存到DbgArgPointer,繼續往下分析
.text:004656B5 test byte ptr [esi+2Ch], 0FFh
.text:004656B9 jnz Dr_kss_a
當前的esi指向KTHRAD結構體, [esi+2Ch]是位置是DebugActive,這個字段是調試狀態,如果當前的線程處於調試狀態,那麼這裏面的值不爲零
+0x02C DebugActive
這裏將DebugActive和FF做與運算,根據運算的結果決定是否跳轉,那麼這兩句彙編的含義就很明顯了
.text:004656B5 test byte ptr [esi+2Ch], 0FFh ; 判斷KTHREAD結構體的DebugActive是否爲零
.text:004656B9 jnz Dr_kss_a ; 如果處於調試狀態 跳轉
到此,整個KiSystemService函數保存現場的部分就已經完成了。
FFh
.text:004656B9 jnz Dr_kss_a
當前的esi指向KTHRAD結構體, [esi+2Ch]是位置是DebugActive,這個字段是調試狀態,如果當前的線程處於調試狀態,那麼這裏面的值不爲零
```assembly
+0x02C DebugActive
這裏將DebugActive和FF做與運算,根據運算的結果決定是否跳轉,那麼這兩句彙編的含義就很明顯了
.text:004656B5 test byte ptr [esi+2Ch], 0FFh ; 判斷KTHREAD結構體的DebugActive是否爲零
.text:004656B9 jnz Dr_kss_a ; 如果處於調試狀態 跳轉
到此,整個KiSystemService函數保存現場的部分就已經完成了。