分析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的前置知识,我们能更好的分析接下来的汇编代码。