系统调用篇——0环层面调用过程(下)

写在前面

  此系列是本人一个字一个字码出来的,包括示例和实验截图。由于系统内核的复杂性,故可能有错误或者不全面的地方,如有错误,欢迎批评指正,本教程将会长期更新。 如有好的建议,欢迎反馈。码字不易,如果本篇文章有帮助你的,如有闲钱,可以打赏支持我的创作。如想转载,请把我的转载信息附在文章后面,并声明我的个人信息和本人博客地址即可,但必须事先通知我

你如果是从中间插过来看的,请仔细阅读 羽夏看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环的ssespeflagcseip,是不是和结构体里面的定义一模一样?
  你可能问道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).
;

下一篇

  系统调用篇——SSDT

posted @ 2021-11-16 11:31  寂静的羽夏  阅读(886)  评论(0编辑  收藏  举报