系統呼叫篇——0環層面呼叫過程(下)

寂靜的羽夏發表於2021-11-16

寫在前面

  此係列是本人一個字一個字碼出來的,包括示例和實驗截圖。由於系統核心的複雜性,故可能有錯誤或者不全面的地方,如有錯誤,歡迎批評指正,本教程將會長期更新。 如有好的建議,歡迎反饋。碼字不易,如果本篇文章有幫助你的,如有閒錢,可以打賞支援我的創作。如想轉載,請把我的轉載資訊附在文章後面,並宣告我的個人資訊和本人部落格地址即可,但必須事先通知我

你如果是從中間插過來看的,請仔細閱讀 羽夏看Win系統核心——簡述 ,方便學習本教程。

  看此教程之前,問一個問題,你明確學系統呼叫的目的了嗎? 沒有的話就不要繼續了,請重新學習 羽夏看Win系統核心——系統呼叫篇 裡面的內容。


? 華麗的分割線 ?


分析 KiSystemService

  這個函式所有的分析如下,如果自己單獨分析完畢後可以檢視下面的摺疊:

? 點選檢視分析 ?
.text:00466481 _KiSystemService proc near              ; CODE XREF: ZwAcceptConnectPort(x,x,x,x,x,x)+C↑p
.text:00466481                                         ; ZwAccessCheck(x,x,x,x,x,x,x,x)+C↑p ...
.text:00466481
.text:00466481 arg_0           = dword ptr  4
.text:00466481
.text:00466481                 push    0               ; errorcode
.text:00466483                 push    ebp
.text:00466484                 push    ebx
.text:00466485                 push    esi
.text:00466486                 push    edi
.text:00466487                 push    fs
.text:00466489                 mov     ebx, 30h ; '0'  ; 以上程式碼填充 Trap_Frame 結構體資料
.text:0046648E                 mov     fs, bx          ; 載入0環的fs,指向KPCR,基址:0FFDFF000h
.text:00466491                 assume fs:nothing
.text:00466491                 push    dword ptr ds:0FFDFF000h ; 壓入 ExceptionList,是KPCR的第一個成員的TIB的第一個成員
.text:00466497                 mov     dword ptr ds:0FFDFF000h, 0FFFFFFFFh ; 將 ExceptionList 置為 -1(EXCEPTION_CHAIN_END)
.text:004664A1                 mov     esi, ds:0FFDFF124h ; KPCR + 0x124:CurrentThread
.text:004664A7                 push    dword ptr [esi+140h] ; ETHREAD + 0x140:PreviousMode
.text:004664AD                 sub     esp, 48h        ; 提棧,目前指向 DbgEbp 的位置
.text:004664B0                 mov     ebx, [esp+68h+arg_0] ; ebx = SegCs
.text:004664B4                 and     ebx, 1          ; 判斷呼叫者是不是 0環 許可權
.text:004664B7                 mov     [esi+140h], bl  ; 將計算結果賦給 PreciousMode
.text:004664BD                 mov     ebp, esp        ; ebp = esp,目前指向 TrapFrame 的首地址
.text:004664BF                 mov     ebx, [esi+134h] ; 將 CurrentThread 的 TrapFrame 賦給 ebx
.text:004664C5                 mov     [ebp+3Ch], ebx  ; TrapFrame 的 Edx = ebx 存的 TrapFrame
.text:004664C8                 mov     [esi+134h], ebp ; 將 CurrentThread 的 TrapFrame 替換為新構建的 TrapFrame
.text:004664CE                 cld
.text:004664CF                 mov     ebx, [ebp+60h]  ; 3環的 ebp
.text:004664D2                 mov     edi, [ebp+68h]  ; 3環的 EIP
.text:004664D5                 mov     [ebp+0Ch], edx  ; 將 3環 的引數列表存入到 DbgArgPointer
.text:004664D5                                         ; 這個引數是在呼叫 int 2Eh 前傳入的
.text:004664D8                 mov     dword ptr [ebp+8], 0BADB0D00h ; DbgArgMark 賦值,細節未知
.text:004664DF                 mov     [ebp+0], ebx    ; DbgEbp = ebx 的值,即3環的ebp
.text:004664E2                 mov     [ebp+4], edi    ; DbgEip = edi 的值,即3環的eip
.text:004664E5                 test    byte ptr [esi+2Ch], 0FFh ; 判斷 DebugActive 是否有值
.text:004664E9                 jnz     Dr_kss_a        ; 如果是0,則說明未被除錯,不跳
.text:004664EF
.text:004664EF KiSystemServiceCallEnd:                 ; CODE XREF: Dr_kss_a+10↑j
.text:004664EF                                         ; Dr_kss_a+7C↑j
.text:004664EF                 sti                     ; 啟用中斷
.text:004664F0                 jmp     APIService      ; 跳到這個地址(這個名字是我自己起的)
.text:004664F0 _KiSystemService endp

  如果你發現自己分析的和我的差不多,恭喜你,你基本掌握了這個函式的處理流程,本小節的下面分析的你就可以跳過了,下面開始對開頭部分的程式碼進行分析,畢竟比較難理解的就在這裡:

.text:00466481                 push    0               ; errorcode
.text:00466483                 push    ebp
.text:00466484                 push    ebx
.text:00466485                 push    esi
.text:00466486                 push    edi
.text:00466487                 push    fs
.text:00466489                 mov     ebx, 30h ; '0'  ; 以上程式碼填充 Trap_Frame 結構體資料
.text:0046648E                 mov     fs, bx          ; 載入0環的fs,指向KPCR,基址:0FFDFF000h
.text:00466491                 assume fs:nothing
.text:00466491                 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環的ssespeflagcseip,是不是和結構體裡面的定義一模一樣?
  你可能問道ErrCode是什麼鬼東西?我在上一篇教程提了一嘴:ErrCode有時由作業系統壓入,有時由CPU壓入。我們先看一張你熟悉的一張圖:

系統呼叫篇——0環層面呼叫過程(下)

  這張圖在保護模式篇的總結與提升部分講解過。看沒看到Error Code這一列?如果為Yes,發生中斷的時候,CPU就會再把這個值壓入堆疊當中。Windows系統設計時為了保持對齊,這裡的push 0就是這麼來的。
  其他的部分就不難了,自己看摺疊的分析就能看懂了,自行分析即可。

分析 KiFastCallEntry

  現在的CPU都支援sysenter/sysexit指令,KiFastCallEntry這個函式分析必不可少,這個函式所有的分析如下,如果自己單獨分析完畢後可以檢視下面的摺疊:

? 點選檢視分析 ?
.text:00466540 _KiFastCallEntry proc near              ; DATA XREF: KiLoadFastSyscallMachineSpecificRegisters(x)+24↑o
.text:00466540                                         ; _KiTrap01+72↓o
.text:00466540
.text:00466540 var_B           = byte ptr -0Bh
.text:00466540 anonymous_0     = dword ptr -8
.text:00466540 anonymous_1     = dword ptr -4
.text:00466540
.text:00466540 ; FUNCTION CHUNK AT .text:00466519 SIZE 00000025 BYTES
.text:00466540 ; FUNCTION CHUNK AT .text:004667DC SIZE 00000014 BYTES
.text:00466540
.text:00466540                 mov     ecx, 23h ; '#'
.text:00466545                 push    30h ; '0'
.text:00466547                 pop     fs              ; 這兩行程式碼是載入 fs 為0環的
.text:00466549                 mov     ds, ecx
.text:0046654B                 mov     es, ecx         ; 載入 ds 和 es
.text:0046654D                 mov     ecx, ds:0FFDFF040h ; 取出 KPCR 的 TSS
.text:00466553                 mov     esp, [ecx+4]    ; 取出 TSS 的 ESP0,並賦給 esp
.text:00466556                 push    23h ; '#'       ; 開始填充 TrapFrame 資料
.text:00466558                 push    edx
.text:00466559                 pushf
.text:0046655A
.text:0046655A loc_46655A:                             ; CODE XREF: _KiFastCallEntry2+22↑j
.text:0046655A                 push    2
.text:0046655C                 add     edx, 8          ; edx 就是 3環 傳來的引數列表地址,加個8
.text:0046655F                 popf                    ; eflag = 2,即清空Eflag
.text:00466560                 or      [esp+0Ch+var_B], 2 ; 設定存入的EFLAG的第二個位
.text:00466565                 push    1Bh             ; SegCs
.text:00466567                 push    dword ptr ds:0FFDF0304h ; SystemCallReturn
.text:0046656D                 push    0               ; errorcode
.text:0046656F                 push    ebp
.text:00466570                 push    ebx
.text:00466571                 push    esi
.text:00466572                 push    edi
.text:00466573                 mov     ebx, ds:0FFDFF01Ch ; ebx = KPCR
.text:00466579                 push    3Bh ; ';'       ; SegFs
.text:0046657B                 mov     esi, [ebx+124h] ; esi = KPCR.CurrentThread
.text:00466581                 push    dword ptr [ebx] ; ExceptionList
.text:00466583                 mov     dword ptr [ebx], 0FFFFFFFFh ; 將 ExceptionList 置 -1(EXCEPTION_CHAIN_END)
.text:00466589                 mov     ebp, [esi+18h]  ; InitialStack,指向 TrapFrame 基址
.text:0046658C                 push    1               ; PreviousMode
.text:0046658E                 sub     esp, 48h        ; 提棧,此時 esp 指向 TrapFrame 的結構體首地址
.text:00466591                 sub     ebp, 29Ch       ; 0x29C = NPX_FRAME_LENGTH + KTRAP_FRAME_LENGTH
.text:00466591                                         ; = sizeof(_FX_SAVE_AREA) + sizeof(_Trap_Frame)
.text:00466591                                         ; = 0x210 + 0x8c
.text:00466597                 mov     byte ptr [esi+140h], 1 ; ApcNeeded
.text:0046659E                 cmp     ebp, esp
.text:004665A0                 jnz     short loc_46653C ; 如果不相等說明這個是 VX86 執行緒,拒絕訪問,跳走
.text:004665A2                 and     dword ptr [ebp+2Ch], 0 ; Dr7
.text:004665A6                 test    byte ptr [esi+2Ch], 0FFh ; DebugActive
.text:004665AA                 mov     [esi+134h], ebp ; 替換 TrapFrame
.text:004665B0                 jnz     Dr_FastCallDrSave
.text:004665B6
.text:004665B6 loc_4665B6:                             ; CODE XREF: Dr_FastCallDrSave+10↑j
.text:004665B6                                         ; Dr_FastCallDrSave+7C↑j
.text:004665B6                 mov     ebx, [ebp+60h]  ; ebx = TrapFrame.Ebp
.text:004665B9                 mov     edi, [ebp+68h]  ; edi = TrapFrame.Eip
.text:004665BC                 mov     [ebp+0Ch], edx  ; DbgArgPointer = 3環傳來的引數列表
.text:004665BF                 mov     dword ptr [ebp+8], 0BADB0D00h ; DbgArgMark
.text:004665C6                 mov     [ebp+0], ebx    ; DbgEbp
.text:004665C9                 mov     [ebp+4], edi    ; DbgEip
.text:004665CC                 sti                     ; 啟用中斷
.text:004665CD
.text:004665CD APIService:                             ; CODE XREF: _KiBBTUnexpectedRange+18↑j
.text:004665CD                                         ; _KiSystemService+6F↑j

  同樣,接下來我對比較難理解難分析的部分逐個講述,簡單的部分自行分析:

.text:00466581                 push    dword ptr [ebx] ; ExceptionList
.text:00466583                 mov     dword ptr [ebx], 0FFFFFFFFh ; 將 ExceptionList 置 -1(EXCEPTION_CHAIN_END)
.text:00466589                 mov     ebp, [esi+18h]  ; InitialStack,指向 TrapFrame 基址
.text:0046658C                 push    1               ; PreviousMode
.text:0046658E                 sub     esp, 48h        ; 提棧,此時 esp 指向 TrapFrame 的結構體首地址
.text:00466591                 sub     ebp, 29Ch       ; 0x29C = NPX_FRAME_LENGTH + KTRAP_FRAME_LENGTH
.text:00466591                                         ; = sizeof(_FX_SAVE_AREA) + sizeof(_Trap_Frame)
.text:00466591                                         ; = 0x210 + 0x8c
.text:00466597                 mov     byte ptr [esi+140h], 1
.text:0046659E                 cmp     ebp, esp
.text:004665A0                 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賦值為0的含義,還有下面的程式碼是怎麼來的:

.text:0046658E                 sub     esp, 48h        ; 提棧,此時 esp 指向 TrapFrame 的結構體首地址
.text:00466591                 sub     ebp, 29Ch       ; 0x29C = NPX_FRAME_LENGTH + KTRAP_FRAME_LENGTH
.text:00466591                                         ; = sizeof(_FX_SAVE_AREA) + sizeof(_Trap_Frame)
.text:00466591                                         ; = 0x210 + 0x8c
.text:00466597                 mov     byte ptr [esi+140h], 1
.text:0046659E                 cmp     ebp, esp
.text:004665A0                 jnz     short loc_46653C ; 如果不相等說明這個是 VX86 執行緒,拒絕訪問,跳走

  但我還是不明白NPX_FRAME_LENGTH是啥,NPX到底是啥,搜啊。在WRK搜一下有沒有,嘗試失敗:

系統呼叫篇——0環層面呼叫過程(下)

  放心,這個是百度搜不到的。通過谷歌搜尋找到了一個線索,來源於 此連結 ,我把這位同志畫的整理一下,如下所示:

系統呼叫篇——0環層面呼叫過程(下)

  通過它畫的核心執行緒堆疊圖可知,所謂的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的詳細內容將會在下一篇講解。現在我們看一看它的結構:

系統呼叫篇——0環層面呼叫過程(下)

  可以看出這個表由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偏移位置。

如何呼叫

  拿到了服務號,如何找到真正的函式呢?我們先看一張示意圖:

系統呼叫篇——0環層面呼叫過程(下)

  我們根據服務表的索引12這個位可以判斷是哪張服務表,既然找到了哪張服務表,後12位就是在服務表的索引。通過這個索引,找到函式地址,有幾個引數,我們就可以呼叫它了,接下來我們開始分析呼叫真正函式地址的細節。

分析 APIService

  APIService是我自己起的名字,你應該是預設的名字,自己隨便起。APIService這個地址很有意思,無論是通過中斷門進行系統呼叫,還是sysenter,最終都會走這裡。好,我們下來可以繼續分析,你可以以不再閱讀,停下來自行分析,分析不動了再回來看看:

.text:004665CD APIService:                             ; CODE XREF: _KiBBTUnexpectedRange+18↑j
.text:004665CD                                         ; _KiSystemService+6F↑j
.text:004665CD                 mov     edi, eax        ; eax = 3環傳來的服務號
.text:004665CF                 shr     edi, 8
.text:004665D2                 and     edi, 30h
.text:004665D5                 mov     ecx, edi        ; 正好一個表的大小就是0x10,如果是GUI相關,就加;反之不加。
.text:004665D7                 add     edi, [esi+0E0h] ; edi = ServiceTable
.text:004665DD                 mov     ebx, eax        ; eax = 3環傳來的服務號
.text:004665DF                 and     eax, 0FFFh      ; 去掉索引為12的位
.text:004665E4                 cmp     eax, [edi+8]    ; 得到的結果與ServiceLimit比較
.text:004665E7                 jnb     _KiBBTUnexpectedRange ; 如果超出,說明越界,跳走
.text:004665ED                 cmp     ecx, 10h
.text:004665F0                 jnz     short loc_46660C ; 判斷是否是呼叫 win32k.sys 的,不是的話跳走
.text:004665F2                 mov     ecx, ds:0FFDFF018h ; _DWORD
.text:004665F8                 xor     ebx, ebx
.text:004665FA
.text:004665FA loc_4665FA:                             ; DATA XREF: _KiTrap0E+113↓o
.text:004665FA                 or      ebx, [ecx+0F70h]
.text:00466600                 jz      short loc_46660C
.text:00466602                 push    edx
.text:00466603                 push    eax
.text:00466604                 call    ds:_KeGdiFlushUserBatch
.text:0046660A                 pop     eax
.text:0046660B                 pop     edx
.text:0046660C
.text:0046660C loc_46660C:                             ; CODE XREF: _KiFastCallEntry+B0↑j
.text:0046660C                                         ; _KiFastCallEntry+C0↑j
.text:0046660C                 inc     dword ptr ds:0FFDFF638h ; KeSystemCalls 自增 1
.text:00466612                 mov     esi, edx        ; 3環來的引數列表
.text:00466614                 mov     ebx, [edi+0Ch]  ; SSDT參數列地址
.text:00466617                 xor     ecx, ecx
.text:00466619                 mov     cl, [eax+ebx]   ; 獲得呼叫指定函式的參數列的引數長度
.text:0046661C                 mov     edi, [edi]      ; 函式地址表
.text:0046661E                 mov     ebx, [edi+eax*4] ; 獲取呼叫的指定函式的地址
.text:00466621                 sub     esp, ecx        ; 提棧,供給引數拷貝空間
.text:00466623                 shr     ecx, 2          ; number of argument DWORDs
.text:00466626                 mov     edi, esp        ; 設定拷貝地址
.text:00466628                 cmp     esi, ds:_MmUserProbeAddress ; 判斷拷貝的源地址是否超出使用者態能讀取的寬度
.text:0046662E                 jnb     loc_4667DC      ; 如果超出,跳走,報錯
.text:00466634
.text:00466634 loc_466634:                             ; CODE XREF: _KiFastCallEntry+2A0↓j
.text:00466634                                         ; DATA XREF: _KiTrap0E+109↓o
.text:00466634                 rep movsd               ; 從3環拷貝引數到0環
.text:00466636                 call    ebx             ; 真正呼叫函式
.text:00466638
.text:00466638 loc_466638:                             ; CODE XREF: _KiFastCallEntry+2AB↓j
.text:00466638                                         ; DATA XREF: _KiTrap0E+129↓o ...
.text:00466638                 mov     esp, ebp
.text:0046663A
.text:0046663A loc_46663A:                             ; CODE XREF: _KiBBTUnexpectedRange+38↑j
.text:0046663A                                         ; _KiBBTUnexpectedRange+43↑j
.text:0046663A                 mov     ecx, ds:0FFDFF124h
.text:00466640                 mov     edx, [ebp+3Ch]
.text:00466643                 mov     [ecx+134h], edx
.text:00466643 _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).
;

下一篇

  系統呼叫篇——SSDT

相關文章