NetRoc's Blog

N-Tech

 

关于Windows系统调用实现的笔记

NetRoc

http://www.DbgTech.net/

从Win XP开始,Windows的系统调用都是通过sysenter指令进入KiFastCallEntry或者_KiFastCallEntry2。最近稍微看了一下这部分的wrk代码实现,作了些笔记。

  1. KiFastCallEntry中如何引用SSDT和SSDT Shadow

从sysenter进入KiFastCallEntry后,系统取得SSDT和SSDT Shadow的地址并不是直接通过内核导出的KeServiceDescriptorTable和未导出的KeServiceDescriptorTableShadow变量来引用的。而是从PCR取得_KTHREAD地址,通过KTHREAD中的ServiceTable来获得服务表地址的。I386处理器的KiFastCallEntry相关代码片段如下:

mov ebx, PCR[PcSelfPcr] ; 取得PRCB地址

push KGDT_R3_TEB OR RPL_MASK ; Push user mode FS

mov esi, [ebx].PcPrcbData+PbCurrentThread ; 获得当前线程地址

……

add edi, [esi]+ThServiceTable ; 计算出服务表的地址

通过这里,我们可以知道系统调用所使用的的服务表来自_KTHREAD这种"动态"数据,而非KeServiceDescriptorTable这种相对"静态"的数据。这一点在很多情况下都有较强的利用价值。比如XXX……

  1. nt!_KTHREAD中ServiceTable字段的设置

NtCreateThread内部是通过PspCreateThread创建线程的。进行了线程的相关结构内存分配后,会调用KeInitThread初始化这些结构。KeInitThread中就会设置nt!_KTHREAD的ServiceTable字段。如下:

#if defined(_AMD64_)

Thread->ServiceTable = KeServiceDescriptorTable[SYSTEM_SERVICE_INDEX].Base;

Thread->KernelLimit = KeServiceDescriptorTable[SYSTEM_SERVICE_INDEX].Limit;

#else

Thread->ServiceTable = (PVOID)&KeServiceDescriptorTable[0];

#endif

I386下,线程的初始服务表就是KeServiceDescriptorTable。这可能会被改变。后面如果这个线程调用了ShadowTable中的服务,ServiceTable会被转变成ShadowTable。KiFastCallEntry会判断系统调用的服务号,如果发现是GUI服务,则调用PsConvertToGuiThread将线程转换成GUI线程。

PsConvertToGuiThread中如下处理:

#if defined(_AMD64_)

Thread->Tcb.Win32kTable = KeServiceDescriptorTableShadow[WIN32K_SERVICE_INDEX].Base;

Thread->Tcb.Win32kLimit = KeServiceDescriptorTableShadow[WIN32K_SERVICE_INDEX].Limit;

#else

Thread->Tcb.ServiceTable = (PVOID)&KeServiceDescriptorTableShadow[0];

#endif

  1. SSDT和SSDT Shadow的初始化

两个SDT的初始化在KiInitSystem函数中:

KeServiceDescriptorTable[0].Base = &KiServiceTable[0];

KeServiceDescriptorTable[0].Count = NULL;

KeServiceDescriptorTable[0].Limit = KiServiceLimit;

KeServiceDescriptorTable[0].Number = KiArgumentTable;

for (Index = 1; Index < NUMBER_SERVICE_TABLES; Index += 1) {

KeServiceDescriptorTable[Index].Limit = 0;

……

RtlCopyMemory(KeServiceDescriptorTableShadow,

KeServiceDescriptorTable,

sizeof(KeServiceDescriptorTable));

ShadowTable的第一项被在这个时候初始化成和KeServiceDescriptorTable一样。而第二项会在后面的初始化过程中通过调用KeAddSystemServiceTable被设置。

  1. ShadowTable的设置时机

上面提到了,由于KiInitSystem的初始化,KeServiceDescriptorTableShadow[0]和KeServiceDescriptorTable[0]总是一样的,但是KeServiceDescriptorTableShadow[1]会被设置成win32k的服务。这应该是win32k加载之后调用KeAddSystemServiceTable添加的。

  1. AMD64和i386中的区别

AMD64下的实现和i386不同,AMD64下的ShadowTable在_KTHREAD中专门有一个字段,ServiceTable保存KeServiceDescriptorTable,Win32kTable保存KeServiceDescriptorTableShadow[WIN32K_SERVICE_INDEX].Base。详见PsConvertToGuiThread等函数。

  1. 有什么意义?

从上面记录的这些实现来看,很直接导致的结果就是,系统服务表可以比较容易的替换掉。这种替换有下面这些特点:

  • 只会影响指定的线程,对整个系统来说影响小。
  • 由于和传统SSDT Hook一样只需要改掉一个指针,比inline hook更加稳定。
  • 传统的SSDT Hook被用得太滥,大家都在搞,某些人还敢卸载掉并且恢复Hook之前的指针,很容易造成崩溃。这种方法因为只针对单独线程,被使用得也少,相对来说比传统SSDT Hook和Hook KiFastCallEntry都要安全。
  • 对可能转变成GUI线程的线程进行替换就需要特别的处理了。比如构造自己的ShadowTable并Hook掉PsConvertToGuiThread之类的方法。

没有什么隐蔽性,呵呵。干坏事的同学慎用。当然,用的时候再改,用过之后马上改回去,或者配合内存隐藏之类的技术还是不错滴。干好事的同学也可以用,但是不适合对所有系统线程都用上去,因为要枚举所有线程,或者Hook线程创建函数之类比较麻烦,到不如直接去干KiFastCallEntry。用来保护自己线程的服务表安全倒是不错。

posted on 2009-04-08 00:18 NetRoc 阅读(2785) 评论(0)  编辑 收藏 引用

只有注册用户登录后才能发表评论。

导航

统计

常用链接

留言簿(7)

随笔档案(99)

文章分类(35)

文章档案(32)

Friends

Mirror

搜索

最新评论

阅读排行榜

评论排行榜