(转)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。因为要做的只是计算便宜,因此效率也是很高的。

posted @ 2012-12-20 09:58  himessage  阅读(916)  评论(0编辑  收藏  举报