本文介绍Windows XP下PspCidTable的组织结构和基于该结构的一种进程检测方法。
PspCidrfahle组织结构简介
在Windows NT下,所有的资源都是以对象(object)的方式进行管理。我们平时最常见的进程(process).文件(file).设备(device)等都是对象。当我们要访问一个对象时,比如说打开一个文件.系统就会创建一个对象句柄.通过这个句柄来完成对文件的打开、关闭、删除等操作。句柄和对象之间的联系是通过句柄表来完成的。准确地说,一个句柄是它所对应的对象在句柄表中的索引。通过句柄.可以在句柄表找到对象的指针,通过指针对对象进行操作。PspCidTable是Windows系统中一种特殊的句柄表,它不链接在系统句柄表上,也不属于任何一个进程。通过它可以访问系统中所有的对象。在
Windbg下面可以查看PspCidTable的地址
看得出来,这个系统里PspCidTable的地址为Ox8055a360(不同的系统中.这个值是不同的)。这个地址中是一个指向_HANDLE_TABLE结构的指针:
这里的TableCode中记录着句柄表的地址。在windows 2000中PspCidTabla句柄表采用的是三层表结构.TableCode中记录着三层表——上层表、中层表
和下层表的地址。但是在VVindows XP中,为了节省系统空间,采用了动态扩展的表结构,当句柄数目少的时候仅仅采用下层表,当句柄数目较大的时
候系统才会启用中层表.直至上层表。那么怎么判断系统使用了几层表呢?TableCocie的后两位是判断的依据.后两位是00则是一层表结构,后两位是0
1则是两层表结构,后两位是1 0则是三层表结构。如果系统采用了两层或者三层表的话.TableCode中就不是句柄表地址了.把这个值最后两位置0之后,则是一个指向多层表地址的指针。下面看看另外一个Windows XP系统下的对应的_HANDLE_TABLE。
看得出来,这个系统采用了两层表结构。下层表地址为Oxe1003000.中层表地址为Oxe27f5000。这其中有一个差别,一般采用一层表结构时,偏Ox038处的NextHandleNeedingPool是OxBOO.采用两层表结构时为Oxl000 (增加了Ox800),相应的,采用三层表时为Ox1800。
获得了三层句柄表之后,我们就可以通过句柄来访问对象了。具体方法是如果句柄值小于Ox800则将句柄值乘以2作为下层表中的偏移与下层表基地址相加就可以获得一个类型为_HANDLE_TABLE_ENTRY的地址了,该结构中偏移00处为Object.将这个值最高位置1,最低三位置0+就是一个对象(体)指针了,如果句柄值大小大于Oxl000.则将句柄值减去Ox800再乘以2作为中层表中的偏移与中层表地址相加,同样可以获得一个类型为HANDLE_TABLE_ENTHIY的地址,依次类推。
下面举一个例子来说明怎么通过句柄来访问对象。Windows XP下,System.exe进程的PID始终为04,实际上就是句柄值。当前系统的句柄表为两层表结构,地址分别为下层表Oxe1003000.中层表地址为Oxe2715000,由于0 4小于Ox800,所以_HAlxiDLE_TABLE_ENTRY的地址为Oxe1003000+Ox04*2-Oxe1003008。即我们把Object处的指针转换为对象(体)指针,即Ox8234a660.看看对不对。
看得出来,的确是系统中的System进程(Image为进程名)。前面"Ikd> lobject Ox8234a660"中显示.对象的Type内容为Process。系统中类型相同的对象,这个地方的值是一样的。进程对象同样如此。同时,在对象(体)指针之前有一个类型为_OBJECT_HEADER的对象(头)指针,记录着对象的一些基本信息,比如类型。上面显示的OblectHeader为Ox8234a648(在对象(体)之前偏移Ox18处,Ox8234a660Ox8234a648=Ox18J.即偏移08处是类型为
_OBJECT_TYPE的Type指针,是一个Process对象。
基于PspCidTable结构的进程检测方法利用PspCidTable来访问对象,我们已经简单地了解了。接下来介绍一下基于PspCidTable结构的进程检测如何具体实现。
基本原理是这样的系统内所有进程对象的对象类型(即Type)是一样的,先取得任一进程的类型,然后访问所有可能的句柄值,如果句柄的类型是进程的话,就记录下来.这样的话.系统内所有的进程对象就都被记录下来了。
要解决的第一个问题是获取PspCidTable的地址。前面已经知道了,在不同的系统、不同的系统环境下.PspCidTable的地址是不同的。因为系统函数PsLookupPr。cessByProcessld中引用了该地址,因而在程序中我们可以通过搜索PsLookupProcessByProcessld的内存空间来获得该地址。我们先看看PsLookuoProcessByProcessld的部分反汇编代码。
从上面的代码可知,在PsLookupProcessByProcessld函数的内存空间中搜索Ox35ff和Oxe8就可以确定PspCidTable的地址了。
要解决的第二个问题是由对象(体)指针确定对象(头)指针。大部分系统中.对象(头)指针在对象(体)指针之前偏移Ox18处,但是也有特殊的,所以我们这里使用OBJECT_TO_OBJECT_HEADER宏来实现这个功能。它接受一个参数——对象体指针返回对象头指针。
接着要获取进程对象的类型指针,我们可以取得任一进程对象的地址(其实就是进程对象(体)指针值)。然后访问其对象(头)指针.获得进程对象类型的指针。
最后。取得系统的甸柄表层数,对所有可能的句柄值分别进行索引.取得对象(体)指针和对象(头)指针,把类型指针与前面所获取的类型指针相同的
对象存储下来,这样我们就获得了所有的进程对象了。判断句柄表结构的代码如下。
遍历所有句柄的部分代码如下(句柄范围Ox0Ox4elc参考自(FUTo》)。
具体细节的补充说明
1、我所给出的基本结构和原理是WindowsnT架构下所有系统都采用的,因而在其他系统下这个方法依然可以实现进程检测。但是由于在winodws20CO/XP/2003下句柄表或者对象的数据结构都有一定的差别,所以在其他系统下需要对相应句柄表访问方式、系统数据偏移量等值进行修改。
我所给出的驱动程序在Windows XP SP2下由Windows DDK编译,可以使用rootkit.com提供的InsiDrv进行加载.使用Dbgview查看运行结果,在VVindows XP SP2下测试通过,可以成功检测FU_rootkit和hxdefl00(黑客守护者)隐藏的进程。
2、这种检测方法针对的是系统句柄,因而对于不存在句柄的System Idle Process无法检测。
3、因为进程的退出也是基于句柄的.所以可能存在进程已经退出,但由于系统句柄并没有完全删除.而进程对象仍然存在的情况。对于这种情况.可以通过进程对象EPROCESS结构中ProcessExitin.