x86系统调用(中)

 

分析KiSystemService保存现场代码(INT 2E)

当3环代码通过sysenter或者int 2E的方式进入0换后,总归是要重新回到三环的,那么必然进入0环前就必须要保存原来三环的寄存器,这些寄存器保存在哪?

打开ntoskrnl.exe文件,Alt+T搜索_IDT。

再IDT表中找到int 2E,IDA已经帮我们解析出了各个中断号要去的EIP。

此时已经进入0环代码,push 0压的是在ESP/0的堆栈。

但是这里push 0是什么意思,在后面会有解释

学习中断门的时候,我们都知道,当进入0环的时候,会往0环的堆栈压5个值:eip,cs,eflag,esp,ss。

Trap_Frame

windows将0环堆栈定义成一个结构体,叫_Trap_Frame。这是操作系统定义的。

用windbg查看这个结构体,在结构体最前面加上_K

kd> dt _KTrap_Frame

最后四个成员在8086模式下使用,保护模式下并没有使用。

进入0环的一瞬间,ESP0(EIP)指向+0x07c的位置,也就是ss的下一个位置,当push SS的时候,ss的值正好存储在+0x78的位置,然后以此类推。五个值存储在这五个成员中:

这五个值细说一下,想一下这个问题:这五个值是谁push的?

我们在操作系统代码中并没有看到有这段代码,学过中断门的同学应该知道,这是cpu的规定,所以操作系统的代码并没有push ss ,push esp。。。理清楚的关键在于要弄清楚cpu和操作系统分别做了那些事情,以及cpu的设计思想和操作系统的设计思想。

所以当再回头看int 2E的代码

上来的几个push,实际上就是压的_Trap_Frame结构体中的几个成员

+0x050 SegFs            : Uint4B
+0x054 Edi              : Uint4B
+0x058 Esi              : Uint4B
+0x05c Ebx              : Uint4B
+0x060 Ebp              : Uint4B
+0x064 ErrCode          : Uint4B

每一个线程都有一个自己的ESP0,而TSS只有一个,当线程切换时,一定能保证当前TSS中存储的ESP0是当前线程的ESP0。

并不是所有的中断代码上来就push 0。以缺页异常举例:

缺页异常是e号中断,我们查看int e的代码

可以看到上来之后并没有push 0,很好解释,errorcode已经有了,谁填充的呢?cpu。

说明如果是int e,那么除了压5个值到堆栈中,还要将errorcode写入。

KPCR

KPCR是个结构体。叫CPU控制区(Processor Control Region)。

CPU也有自己的控制块,每一个CPU有一个,叫KPCR。

kd> dt _KPCR

查看cpu数量,这里是单核。

kd> dd KeNumberProcessors

在三环fs指向了TEB,在0环fs指向KPCR

KPCR结构的最后一个成员,也是一个结构体_KPRCB

kd> dt _KPRCB
nt!_KPRCB
   +0x000 MinorVersion     : Uint2B
   +0x002 MajorVersion     : Uint2B
   +0x004 CurrentThread    : Ptr32 _KTHREAD
   +0x008 NextThread       : Ptr32 _KTHREAD
   +0x00c IdleThread       : Ptr32 _KTHREAD
   +0x010 Number           : Char
   +0x011 Reserved         : Char
   +0x012 BuildType        : Uint2B
   +0x014 SetMember        : Uint4B
   +0x018 CpuType          : Char
   +0x019 CpuID            : Char
   +0x01a CpuStep          : Uint2B
   +0x01c ProcessorState   : _KPROCESSOR_STATE
   +0x33c KernelReserved   : [16] Uint4B
   +0x37c HalReserved      : [16] Uint4B
   +0x3bc PrcbPad0         : [92] UChar
   +0x418 LockQueue        : [16] _KSPIN_LOCK_QUEUE
   +0x498 PrcbPad1         : [8] UChar
   +0x4a0 NpxThread        : Ptr32 _KTHREAD
   +0x4a4 InterruptCount   : Uint4B
   +0x4a8 KernelTime       : Uint4B
   +0x4ac UserTime         : Uint4B
   +0x4b0 DpcTime          : Uint4B
   +0x4b4 DebugDpcTime     : Uint4B
   +0x4b8 InterruptTime    : Uint4B
   +0x4bc AdjustDpcThreshold : Uint4B
   +0x4c0 PageColor        : Uint4B
   +0x4c4 SkipTick         : Uint4B
   +0x4c8 MultiThreadSetBusy : UChar
   +0x4c9 Spare2           : [3] UChar
   +0x4cc ParentNode       : Ptr32 _KNODE
   +0x4d0 MultiThreadProcessorSet : Uint4B
   +0x4d4 MultiThreadSetMaster : Ptr32 _KPRCB
   +0x4d8 ThreadStartCount : [2] Uint4B
   +0x4e0 CcFastReadNoWait : Uint4B
   +0x4e4 CcFastReadWait   : Uint4B
   +0x4e8 CcFastReadNotPossible : Uint4B
   +0x4ec CcCopyReadNoWait : Uint4B
   +0x4f0 CcCopyReadWait   : Uint4B
   +0x4f4 CcCopyReadNoWaitMiss : Uint4B
   +0x4f8 KeAlignmentFixupCount : Uint4B
   +0x4fc KeContextSwitches : Uint4B
   +0x500 KeDcacheFlushCount : Uint4B
   +0x504 KeExceptionDispatchCount : Uint4B
   +0x508 KeFirstLevelTbFills : Uint4B
   +0x50c KeFloatingEmulationCount : Uint4B
   +0x510 KeIcacheFlushCount : Uint4B
   +0x514 KeSecondLevelTbFills : Uint4B
   +0x518 KeSystemCalls    : Uint4B
   +0x51c SpareCounter0    : [1] Uint4B
   +0x520 PPLookasideList  : [16] _PP_LOOKASIDE_LIST
   +0x5a0 PPNPagedLookasideList : [32] _PP_LOOKASIDE_LIST
   +0x6a0 PPPagedLookasideList : [32] _PP_LOOKASIDE_LIST
   +0x7a0 PacketBarrier    : Uint4B
   +0x7a4 ReverseStall     : Uint4B
   +0x7a8 IpiFrame         : Ptr32 Void
   +0x7ac PrcbPad2         : [52] UChar
   +0x7e0 CurrentPacket    : [3] Ptr32 Void
   +0x7ec TargetSet        : Uint4B
   +0x7f0 WorkerRoutine    : Ptr32     void 
   +0x7f4 IpiFrozen        : Uint4B
   +0x7f8 PrcbPad3         : [40] UChar
   +0x820 RequestSummary   : Uint4B
   +0x824 SignalDone       : Ptr32 _KPRCB
   +0x828 PrcbPad4         : [56] UChar
   +0x860 DpcListHead      : _LIST_ENTRY
   +0x868 DpcStack         : Ptr32 Void
   +0x86c DpcCount         : Uint4B
   +0x870 DpcQueueDepth    : Uint4B
   +0x874 DpcRoutineActive : Uint4B
   +0x878 DpcInterruptRequested : Uint4B
   +0x87c DpcLastCount     : Uint4B
   +0x880 DpcRequestRate   : Uint4B
   +0x884 MaximumDpcQueueDepth : Uint4B
   +0x888 MinimumDpcRate   : Uint4B
   +0x88c QuantumEnd       : Uint4B
   +0x890 PrcbPad5         : [16] UChar
   +0x8a0 DpcLock          : Uint4B
   +0x8a4 PrcbPad6         : [28] UChar
   +0x8c0 CallDpc          : _KDPC
   +0x8e0 ChainedInterruptList : Ptr32 Void
   +0x8e4 LookasideIrpFloat : Int4B
   +0x8e8 SpareFields0     : [6] Uint4B
   +0x900 VendorString     : [13] UChar
   +0x90d InitialApicId    : UChar
   +0x90e LogicalProcessorsPerPhysicalProcessor : UChar
   +0x910 MHz              : Uint4B
   +0x914 FeatureBits      : Uint4B
   +0x918 UpdateSignature  : _LARGE_INTEGER
   +0x920 NpxSaveArea      : _FX_SAVE_AREA
   +0xb30 PowerState       : _PROCESSOR_POWER_STATE

查看KPRCB地址,这里是单核,所以只有一个值。

kd> dd KiProcessorBlock  L2

这个地址减去0x120就是KPCR的地址。

继续看int 2e

0FFDFF000h这个地址正好就是KPCR的首地址。

第一个成员叫 _NT_TIB

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

这里是DWORD,实际上就是_EXCEPTION_REGISTRATION_RECORD。一个异常链表,存储的是异常的处理函数。

push的作用就是保存老的异常链表,然后将新的ExceptionList设为空白。

再看0FFDFF124h这个位置,实际上是KPCRB的+0x4这个位置。当前CPU所执行线程的_ETHREAD。

而_ETHREAD第一个成员是 _KTHREAD

kd> dt _ETHREAD
nt!_ETHREAD
   +0x000 Tcb              : _KTHREAD
   +0x1c0 CreateTime       : _LARGE_INTEGER
   +0x1c0 NestedFaultCount : Pos 0, 2 Bits
   +0x1c0 ApcNeeded        : Pos 2, 1 Bit
   +0x1c8 ExitTime         : _LARGE_INTEGER
   +0x1c8 LpcReplyChain    : _LIST_ENTRY
   +0x1c8 KeyedWaitChain   : _LIST_ENTRY
   +0x1d0 ExitStatus       : Int4B
   +0x1d0 OfsChain         : Ptr32 Void
   +0x1d4 PostBlockList    : _LIST_ENTRY
   +0x1dc TerminationPort  : Ptr32 _TERMINATION_PORT
   +0x1dc ReaperLink       : Ptr32 _ETHREAD
   +0x1dc KeyedWaitValue   : Ptr32 Void
   +0x1e0 ActiveTimerListLock : Uint4B
   +0x1e4 ActiveTimerListHead : _LIST_ENTRY
   +0x1ec Cid              : _CLIENT_ID
   +0x1f4 LpcReplySemaphore : _KSEMAPHORE
   +0x1f4 KeyedWaitSemaphore : _KSEMAPHORE
   +0x208 LpcReplyMessage  : Ptr32 Void
   +0x208 LpcWaitingOnPort : Ptr32 Void
   +0x20c ImpersonationInfo : Ptr32 _PS_IMPERSONATION_INFORMATION
   +0x210 IrpList          : _LIST_ENTRY
   +0x218 TopLevelIrp      : Uint4B
   +0x21c DeviceToVerify   : Ptr32 _DEVICE_OBJECT
   +0x220 ThreadsProcess   : Ptr32 _EPROCESS
   +0x224 StartAddress     : Ptr32 Void
   +0x228 Win32StartAddress : Ptr32 Void
   +0x228 LpcReceivedMessageId : Uint4B
   +0x22c ThreadListEntry  : _LIST_ENTRY
   +0x234 RundownProtect   : _EX_RUNDOWN_REF
   +0x238 ThreadLock       : _EX_PUSH_LOCK
   +0x23c LpcReplyMessageId : Uint4B
   +0x240 ReadClusterSize  : Uint4B
   +0x244 GrantedAccess    : Uint4B
   +0x248 CrossThreadFlags : Uint4B
   +0x248 Terminated       : Pos 0, 1 Bit
   +0x248 DeadThread       : Pos 1, 1 Bit
   +0x248 HideFromDebugger : Pos 2, 1 Bit
   +0x248 ActiveImpersonationInfo : Pos 3, 1 Bit
   +0x248 SystemThread     : Pos 4, 1 Bit
   +0x248 HardErrorsAreDisabled : Pos 5, 1 Bit
   +0x248 BreakOnTermination : Pos 6, 1 Bit
   +0x248 SkipCreationMsg  : Pos 7, 1 Bit
   +0x248 SkipTerminationMsg : Pos 8, 1 Bit
   +0x24c SameThreadPassiveFlags : Uint4B
   +0x24c ActiveExWorker   : Pos 0, 1 Bit
   +0x24c ExWorkerCanWaitUser : Pos 1, 1 Bit
   +0x24c MemoryMaker      : Pos 2, 1 Bit
   +0x250 SameThreadApcFlags : Uint4B
   +0x250 LpcReceivedMsgIdValid : Pos 0, 1 Bit
   +0x250 LpcExitThreadCalled : Pos 1, 1 Bit
   +0x250 AddressSpaceOwner : Pos 2, 1 Bit
   +0x254 ForwardClusterOnly : UChar
   +0x255 DisablePageFaultClustering : UChar

KTHREAD结构为:

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

0FFDFF124h+140h就是PreviousMode,意为先前模式。

保存老的先前模式,因为一段代码如果是0环执行和3环执行是不一样的,记录调用这段代码之前是0环的还是3环的。

sub     esp, 48h

将esp指向_Trap_Frame的起始位置。

mov    ebx, [esp+68h+arg_0]
and     ebx, 1

这里arg_0是4

那么就是将esp+6c位置上的值赋给ebx,并与上1。esp+6c位置就是cs的值,主要是为了判断权限的问题,如果是3环来的,cs的值是11,如果是0环来的,cs的值是8。所以与后如果是0,那么就是0环来的,如果是1,那么就是3环来的。

mov     [esi+140h], bl

esi是KTHREAD,esi+140偏移的地方正好是先前模式,将值赋给先前模式,“新的先前模式”。

mov     ebp, esp

esp和ebp此时都指向Trap_Frame的首地址。

mov     ebx, [esi+134h]

esi+134h是KTHREAD+134h偏移又是TrapFrame,这里也可以验证TrapFrame结构体实际上是一个线程一份,因为在KTHREAD这个线程结构体中有一份,而KTHREAD是每个线程一份。

mov     [ebp+3Ch], ebx
mov     [esi+134h], ebp

将老的TrapFrame的值存在一个位置上,然后将新的TrapFrame放到esi+134h这个位置。

mov     ebx, [ebp+60h]
mov     edi, [ebp+68h]

将原来三环的ebp放到ebx里,将三环eip放到了edi中

mov     [ebp+0Ch], edx

进0环的时候,edx保存的是参数的地址,这里赋值给TrapFrame+0Ch偏移正好是DbgArgPointer

mov     [ebp+0], ebx
mov     [ebp+4], edi

TrapFrame+0h 和TrapFrame+4h分别是DbgEbp和DbgEip。

test    byte ptr [esi+2Ch], 0FFh

判断是否处于调试状态(DebugActive),如果是处于调试状态,才会把cr0-cr7存到TrapFrame结构体中,如果没处于调试状态就不存cr0-cr7的值。

 

分析KiFastCallEntry(Sysenter)中保存现场的代码

方法和分析KiSystemService差不多,这里就不一句一句分析了,凡是有push的代码,无法解释就注意和TRAP_FRAME结构体对照看。

 

SystemServiceTable(系统服务表)

eax存着一个索引号,通过SystemServiceTable可以找到对应的0环函数。

这里有一个容易混淆的地方:误以为SystemServiceTable(SST)就是SSDT表,实际上是完全不一样的两张表,SST的结构为:

而可以看到SSDT是其中一个成员。

那么又思考:当我们从三环进入0环的时候传递了两个寄存器,一个就是eax,另一个是edx,而edx仅仅是存储了参数的地址,那如何知道参数有多少个呢?并且参数都在三环堆栈中,现在堆栈已经变成esp0,又如何拿到这些参数呢?

为了解决这些问题,我们先上一张关系图。

我们先说参数个数这个问题,参数个数可以从图中看出,哪个函数有多少个参数是写死的,对应的参数个数就写在SST的最后一个成员里(SSPT),这也是张表,存储的是参数个数。如eax==0x02,那么函数地址就为函数地址表的第二个成员,对应的参数在函数参数表的第二个成员。

ServiceLimit是描述函数地址表有多大。

从图中可以看出,有两张系统服务表。第一张是用来找内核函数的,第二张是找Win32k.sys驱动函数的。但是索引号只有一份,怎么确定我到底是找哪张表呢?

这里也相当于一个规定,如图所示:

如系统服务号为0x1002,那么12位为1,则应该找第二表的第二个函数。

如果系统服务好为0x0002,那么12位为0,那应该找第一个表的第二个函数。

如何才能找到SystemServiceTable?

通过_KTHREAD+0xE0可以找到。

有了SystemServiceTable的前置知识,我们能更好的分析接下来的汇编代码。

(完)