(转)WINDBG查看SHADOW SSDT(方法二)
http://blog.csdn.net/debugge/article/details/7626698
关于SSDT即系统描述符表,《SSDT Hook的妙用-对抗ring0 inline hook》这篇文章描述的已经很清楚了。引用文章中的一段话:
“内核中有两个系统服务描述符表,一个是KeServiceDescriptorTable(由ntoskrnl.exe导出),一个是KeServieDescriptorTableShadow(没有导出)。两者的区别是,KeServiceDescriptorTable仅有ntoskrnel一项,KeServieDescriptorTableShadow包含了ntoskrnel以及win32k。一般的Native API的服务地址由KeServiceDescriptorTable分派,gdi.dll/user.dll的内核API调用服务地址由KeServieDescriptorTableShadow分派。还有要清楚一点的是win32k.sys只有在GUI线程中才加载,一般情况下是不加载的……”
现在我们看看KeServiceDescriptorTable的内容
lkd> dd KeServiceDescriptorTable
843b0b00 842c5d7c 00000000 00000191 842c63c4
843b0b10 00000000 00000000 00000000 00000000
843b0b20 845744f2 843246ab 00000000 01fa8bd2
843b0b30 00000bb8 00000011 5385d2ba d717548f
可以发现,只有第一项有内容,第二项并没有内容。现在我们要谈的就是以一个可编程实现的思路,用WinDbg获取KeServieDescriptorTableShadow。
在线程的内核对象中即KTHREAD结构里有一个成员是很特殊的,即ServiceTable,在我现在的Win 7 SP1系统下,这个成员的偏移是0x0bc。我们随便在System(4号进程)里找个线程看看这个成员的值,
lkd> dt _kthread 0x863bfd48
nt!_KTHREAD
+0×000 Header : _DISPATCHER_HEADER
+0×010 CycleTime : 0×23`5c65163e
+0×018 HighCycleTime : 0×23
+0×020 QuantumTarget : 0×23`6050f166
+0×028 InitialStack : 0x80786fd0 Void
…………
+0x0b8 ThreadFlags : 0n96
+0x0bc ServiceTable : 0x843b0b00 Void
+0x0c0 WaitBlock : [4] _KWAIT_BLOCK
+0×120 QueueListEntry : _LIST_ENTRY [ 0x0 - 0x0 ]
…………
lkd> dd 0x843b0b00
843b0b00 842c5d7c 00000000 00000191 842c63c4
843b0b10 00000000 00000000 00000000 00000000
843b0b20 845744f2 843246ab 00000000 01fa8bd2
843b0b30 00000bb8 00000011 5385d2ba d717548f
会发现恰好这个地址就是KeServieDescriptorTable的地址,KeServieDescriptorTable是被内核导出的,我们并不需要获取。System(4号进程)中0x863bfd48是非GUI线程,然而只有GUI线程的线程内核对象此域才是KeServieDescriptorTableShadow的地址,因此我们如果想要找到获取这个地址,那么必须先从系统中找到一个GUI线程,其实这很简单。
在KTHREAD中,有几个成员是专门负责记录线程的栈的信息的。如StackBase内核态栈的栈基址,StackLimit内核态栈栈边界,LargeStack是否是大内核栈等,其中我们需要关注的是LargeStack。当线程刚被创建时,所有线程都不是GUI线程,只有当线程使用Windows子系统内核服务(win32k.sys)时,Windows才将线程转换为GUI线程。在x86架构的系统中,内核态栈的初始大小是12K(StackBase-StackLimit)。在Windows将一个线程转换成GUI线程时,系统会重新为线程分配一个栈,新的栈是可以改变大小的,称之为大内核栈。同时KTHREAD的LargeStack域由非1变为1,Win32Thread域由0变为非0,这是判断一个线程是不是GUI线程的依据。
下面我们用Windbg来看看这一过程。现在的一个关键问题是如何找一个GUI线程,仔细想想便可知道explorer.exe是一个再好不过的切入点了。explorer.exe是Windows的图形壳,负责管理开始菜单、任务栏等资源,里面必然充斥着大量GUI线程。
首先我们在Windbg里输入!process 0 0,列出当前的所有进程,找出当前的explorer.exe的信息
PROCESS 865d2538 SessionId: 1 Cid: 0f28 Peb: 7ffd3000 ParentCid: 0f00
DirBase: 7a0f15a0 ObjectTable: a19f1458 HandleCount: 959.
Image: explorer.exe
可知EPROCESS的地址是865d2538,KPROCESS作为EPROCESS的第一个成员,因此地址也是865d2538。在KPROCESS中有一个双链表ThreadListHead将当前进程中的所有线程连接起来
lkd> dt _KPROCESS 865d2538
nt!_KPROCESS
+0×000 Header : _DISPATCHER_HEADER
+0×010 ProfileListHead : _LIST_ENTRY [ 0x865d2548 - 0x865d2548 ]
+0×018 DirectoryTableBase : 0x7a0f15a0
+0x01c LdtDescriptor : _KGDTENTRY
+0×024 Int21Descriptor : _KIDTENTRY
+0x02c ThreadListHead : _LIST_ENTRY [ 0x8655b210 - 0xc2b0f210 ]
+0×034 ProcessLock : 0
+0×038 Affinity : _KAFFINITY_EX
+0×044 ReadyListHead : _LIST_ENTRY [ 0x865d257c - 0x865d257c ]
+0x04c SwapListEntry : _SINGLE_LIST_ENTRY
+0×050 ActiveProcessors : _KAFFINITY_EX
………………
同时在KTHREAD中,这个链表体现在ThreadListEntry
lkd> dt _kthread
nt!_KTHREAD
…………
+0x1bc LegoData : Ptr32 Void
+0×194 SuspendApcFill5 : [47] UChar
+0x1c3 LargeStack : UChar
+0x1c4 UserTime : Uint4B
+0x1c8 SuspendSemaphore : _KSEMAPHORE
+0x1c8 SuspendSemaphorefill : [20] UChar
+0x1dc SListFaultCount : Uint4B
+0x1e0 ThreadListEntry : _LIST_ENTRY
+0x1e8 MutantListHead : _LIST_ENTRY
+0x1f0 SListFaultAddress : Ptr32 Void
+0x1f4 ThreadCounters : Ptr32 _KTHREAD_COUNTERS
+0x1f8 XStateSave : Ptr32 _XSTATE_SAVE
因此,我们查找一个线程的KTHREAD地址就可以是KPROCESS中链表中的一项(0x8655b210)减去KTHREAD中ThreadListEntry的便宜(0x1e0),我们来瞅下:
lkd> dt _kthread 0x8655b210-0x1e0
nt!_KTHREAD
+0×000 Header : _DISPATCHER_HEADER
+0×010 CycleTime : 0xe`e8408198
+0×018 HighCycleTime : 0xe
+0×020 QuantumTarget : 0xe`ead812a6
+0×028 InitialStack : 0x9ba53fd0 Void
+0x02c StackLimit : 0x9ba51000 Void
+0×030 KernelStack : 0x9ba53ba0 Void
+0×034 ThreadLock : 0
…………
+0x0b8 UmsDispatched : 0y0
+0x0b8 ReservedFlags : 0y000000000000000000000 (0)
+0x0b8 ThreadFlags : 0n96
+0x0bc ServiceTable : 0x843b0b40 Void
+0x0c0 WaitBlock : [4] _KWAIT_BLOCK
+0×120 QueueListEntry : _LIST_ENTRY [ 0x0 - 0x0 ]
…………
+0x18a OtherPlatformFill : 0 ”
+0x18c Win32Thread : 0xff3bc710 Void
+0×190 StackBase : 0x9ba54000 Void
+0×194 SuspendApc : _KAPC
+0×194 SuspendApcFill0 : [1] "??????"
+0×195 ResourceIndex : 0×1 ”
+0×194 SuspendApcFill1 : [3] "???"
+0×197 QuantumReset : 0×6 ”
+0×194 SuspendApcFill2 : [4] "???"
+0×198 KernelTime : 0x37b
+0×194 SuspendApcFill3 : [36] "???"
+0x1b8 WaitPrcb : 0x84371d20 _KPRCB
+0×194 SuspendApcFill4 : [40] "???"
+0x1bc LegoData : (null)
+0×194 SuspendApcFill5 : [47] "???"
+0x1c3 LargeStack : 0 ”
…………
此时ServiceTable的值是0x843b0b40,看看里面的内容。
lkd> dd 0x843b0b40
843b0b40 842c5d7c 00000000 00000191 842c63c4
843b0b50 9982a000 00000000 00000339 9982b02c
843b0b60 00000100 00000000 00000000 843b0b68
843b0b70 00000210 00000200 00000000 00000000
上述思路如果用编程实现的话,要做的只是PsGetCurrentProcess获取当前线程的所属进程,然后遍历进程链表找到explorer.exe,然后遍历explorer的线程链表,定位到某个线程的KTHREAD,然后比较LargeStack和Win32Thread偏移,判断是不是GUI线程,一遍获取Shadow SSDT。因为要做的只是计算便宜,因此效率也是很高的。