深入理解句柄表

来源:互联网 发布:移动网络电影网站 编辑:程序博客网 时间:2024/06/10 02:17

涉及到句柄表的有以下这些概念:

  • HANDLE_TABLE
  • HANDLE_TABLE结构体中的TableCode变量

实际上啊,TableCode是指向句柄表项第一个句柄表项的指针(NULL句柄表项),TableCode就是HANDLE_TABLE_ENTRY的指针。

但是,当有两级以上表时,这个时候就不是了,先来搞定最简单的。

HANDLE_TABLE_ENTRY:句柄表项

对象头_OBJECT_HANDLE

EXHANDLE:这个就是提供给用户使用的句柄值

HANDLE_TABLE_ENTRY是一个8个字节的结构体。它包括:

  • 指向对象头的指针
  • 32位的访问掩码

因为对象头_OBJECT_HANDLE的大小是0x18h,是8的倍数,因为对象头总是按照8个字节对齐。所以对象头的地址低3位肯定是0。所以HANDLE_TABLE_ENTRY的对象头的指针的低3位为0,低三位被用作一些访问标志。

句柄值:通过查看句柄值可以发现,句柄值总是4的倍数。例如:

0x0004

0x0008

0x000C

0x0010

所以,句柄值低2位总是0,所以低2位可以被用作标志位。

句柄值的结构类型是EXHANDLE,在EXHANDLE中低2位为标志位。

0x0000这一个句柄值被用来做位NULL无效句柄。提供给用户使用:

if(Handle == NULL/ *0x0000*/ )

return;

typedef struct _EXHANDLE

{

//注意啦,这里是个联合。

//实际上该结构体就占4个字节

union

{

struct

{

ULONG TagBits:2;

ULONG Index:30;

}

HANDLE GenericHandleOverlay; //呵呵,这是用来提供给用户使用的句柄。

#define HANLE_VALUE_INC 4

ULONG_PTR Value; //这个表示什么意思呢?

}

}EXHANDLE,*PEXHANDLE;

*******************************************************************************

如何知道HANDLE_TABLE和HANDLE_TABLE_ENTRY的关系,可以参考ExpAllocateHandleTable。

ExpAllocateHandleTable用来为每个进程分配句柄表,并初始化句柄表。第一次只分配0级的句柄(保存真正句柄项)。

*******************************************************************************

我不知道该怎么写才顺,我就按照我的分析流程写吧。

!Process查看当前进程

PROCESS 87ca7cf8   SessionId: 0   Cid: 0cb0     Peb: 7ffd8000   ParentCid: 03c4

     DirBase: 3f13d000   ObjectTable: e2d11b78   HandleCount:   76.

     Image: windbg.exe

其中EPROCESS的地址在87ca7cf8,ObjectTable的地址在e2d11b78

ObjectTable是HANDLE_TABLE结构变量,它保存在EPROCESS中。

dt _EPROCESS 87ca7cf8  来查看EPROCESS结构体的值。

.....

    +0x0c4 ObjectTable       :0xe2d11b78_HANDLE_TABLE

......

ObjectTable在EPROCESS偏移0x0c4处。

ObjectTable保存着关于句柄表的信息。

我们使用下面的命令来查看HANDLE_TABLE内容:

dt _HANDLE_TABLE 0xe2d11b78     

    +0x000 TableCode         : 0xe4702000

   ......

其中偏移0为TableCode,它实际上是句柄项(_HANDLE_TABLE_ENTRY)的指针。

句柄项(_HANDLE_TABLE_ENTRY)是用来保存真正的句柄信息的结构体。

(注意,当TableCode低2位为0时,TableCode才指向_HANDLE_TABLE_ENTRY,至于如何给TableCode分配值,还是需要分析一下源码。)

_HANDLE_TABLE_ENTRY是两个32位的结构体:一个指向对象头的指针;一个是32位的标志。

注意,这个32位的对象头的指针并不是全部有效。因为对象头为0x18个字节,所以windows能保证对象头的分配地址总是8的倍数。所以这32位的对象头的指针低3位肯定都为0,windows将这低3位用作其他用途。因此,当我们使用对象头指针时,一定要记得,低3位值不是我们需要的,应该置0 。即:value & 0xFFFFFFF8。

我们使用dt _HANDLE_TABLE_ENTRY 0xe4702000 来查看其值。

lkd> dt _HANDLE_TABLE_ENTRY 0xe4702000

nt!_HANDLE_TABLE_ENTRY

    +0x000 Object            : (null)

    +0x000 ObAttributes      : 0

    +0x000 InfoTable         : (null)

    +0x000 Value             : 0

    +0x004 GrantedAccess     : 0xfffffffe

    +0x004 GrantedAccessIndex : 0xfffe

    +0x006 CreatorBackTraceIndex : 0xffff

    +0x004 NextFreeTableEntry : -2

但是得到的值好像是无效的。

这就对了,因为句柄表的第一个值不被使用,做为一个无效值,用来提供给程序员做错误处理使用。

下面该怎么办呢?句柄表,句柄表肯定是一个连续的数组,连续的保存一些句柄表项。

我们使用dd 0x4702000来查看这块地址的一些值

lkd> dd 0xe4702000

e4702000   00000000 fffffffee1008719 000f0003

e4702010   e1858019 00000003 87d68f13 00100020

......

看,值出来了,因为一个句柄表项_HANDLE_TABLE_ENTRY占8个字节。所以,前两个值看似一个无效的值。但是红色标示的却象个有用的句柄项。

注意了,_HANDLE_TABLE_ENTRY是2个32位组成的64位结构体。前面说过了,低32位是对象头的指针。但是要得到对象头的指针,我们必须将值&0xFFFFFFF8,将低3位置0。

e1008719 & 0xFFFFFFF8 = e1008718

我们还必须加上0x18才能得到真正的内核对象。因为内核对象在对象头的后面,对象头的大小是0x18

dt _object_header

lkd> dt _object_header

nt!_OBJECT_HEADER

    +0x000 PointerCount      : Int4B

...

    +0x018 Body              : _QUAD

用刚才的 e1008718 + 0x018 =e1008730 ,然后我们使用!Object e1008730查看。

lkd> !object e1008730

Object: e1008730    Type: (89bddad0) KeyedEvent

     ObjectHeader: e1008718 (old version)

     HandleCount: 48   PointerCount: 49

     Directory Object: e1000270   Name: CritSecOutOfMemoryEvent

哈哈,挺像的 。使用!Handle列出当前句柄来测试一把。

lkd> !handle

processor number 0, process 87ca7cf8

PROCESS 87ca7cf8   SessionId: 0   Cid: 0cb0     Peb: 7ffd8000   ParentCid: 03c4

     DirBase: 3f13d000   ObjectTable: e2d11b78   HandleCount:   99.

     Image: windbg.exe

Handle table at e4702000 with 99 Entries in use

0004: Object:e1008730   GrantedAccess: 000f0003 Entry: e4702008

Object: e1008730   Type: (89bddad0) KeyedEvent

     ObjectHeader: e1008718 (old version)

         HandleCount: 48   PointerCount: 49

         Directory Object: e1000270   Name: CritSecOutOfMemoryEvent

0008: Object: e1858030   GrantedAccess: 00000003 Entry: e4702010

Object: e1858030   Type: (89c153b0) Directory

......

对比一下,成功,我们已经找到了句柄值为0004的内核对象。

OK,冒出了句柄值,那么句柄值是如何被关联起来呢?想下0004,它对应_HANDLE_TABLE_ENTRY表项的低几个啊?

句柄值为0x0000代表是NULL,刚好_HANDLE_TABLE_ENTRY的第0个表项为无效值

句柄值为0x0004有效,刚好指的是_HANDLE_TABLE_ENTRY的第1个表项。

那句柄值为0x0008了?

原来句柄值总是4的倍数。值/4就代表句柄表项数组_HANDLE_TABLE_ENTRY的索引啊。

这时,句柄值的低两位永远是0啦,为啥呢?是4的倍数,第2为不就为0?自己算算。

0x00,0x04,0x08,0x10,0x14等等的二进制

既然第2位永远为0,那么微软就利用了这两位做一个标志位,用来指示当前句柄值所代表的内核对象到那个表项数组中找到?

什么意思呢?

句柄表实际上是分级的,分3级,我们可以像理解分页一样。

分页分为:页目录、页表、物理页。

每个页目录保存1024个页表,每个页表保存着1024个物理页,每个页为4k。

句柄表可以这样分:

4K的目录,保存1K个表指针(指针为一项,占4个字节吗,总共是1024个项)。

每个4K的表,保存着1K个_HANDLE_TABLE_ENTRY数组指针。

每个4K的_HANDLE_TABLE_ENTRY数组保存着512个_HANDLE_TABLE_ENTRY,咋是512个?因为每一个_HANDLE_TABLE_ENTRY是8个字节。

每个_HANDLE_TABLE_ENTRY指向真正的内核对象前的对象头。(第1个表项除外,代表空。)

哈哈,句柄表就这么简单,就是太多繁琐的东西。记不住。

记下笔记备忘之。

这两天试着写写访问句柄表信息的小驱动。写完后配合代码和WRK再整理。

0 0
原创粉丝点击