句柄表

2K:

2K下采用三层表结构,上层表256个项,每个项占用4字节 对应一个中层表的地址,每个中层表有256项,每个项占用4字节对应一个下层表地址,每个下层表256项,每个项占用8字节(每个项就是HANDLE_TABLE_ENTRY)。

因此 每个进程支持256*256*256个句柄

上层表大小:256 * 4字节 = 1KB

中层表大小:256 *4字节 = 1KB

下层表大小:256 *sizeof(HANDLE_TABLE_ENTRY) = 2KB

当进程打开的对象不超过大约256个时,系统只为句柄表分配一个上层表,大小为1KB。一个中层表,大小为1KB。一个下层表,大小为2KB。正好可以放在同一个物理页中(4KB)。

当进程打开的对 象超过大约256个时,系统会再分配2个下层表,放在同一个物理页,并把这2个下层表的地址,填入中层表的相应项,这样就可以存放大约256*3=768 个对象的对象头指针。

更多的句柄以此类推。

所以绝大多数情况下,进程的句柄表消耗的物理内存为4K一个页(打开的对象不超过大约256个时)或者8K 两个页(打开的对象不超过大约768个时)。

如何通过句柄定位内核对象:

一个句柄被分为三部分,分别做这三个表中的索引:

bits2-9这 8bit,为下层表索引,乘以8得到在下层表中的偏移。

bits10-17这 8bit,为中层表的索引,乘以4得到在中层表中的偏移。

bits18-25这8bit,为高层表的索引,乘以4得到在高层表中的偏移。

如何定位句柄表:

通过 EPROCESS 的 struct HANDLE_TABLE *ObjectTable 我们可以找到一个进程的 HANDLE_TABLE 结构,通过 HANDLE_TABLE 结构的 struct _HANDLE_TABLE_ENTRY ***Table 我们可以找到这个进程的句柄表。

如何通过HANDLE_TABLE_ENTRY获得内核对象的地址:

HANDLE_TABLE_ENTRY 大小为 8 个字节,由2个32bit组成。如果第一个32bit值不为0,那么第一个32bit就可以转换成一个指向对象头的指针。由于对象头总是32bit对齐的,所以一个对象头的指针的低3bit总是0。所以 HANDLE_TABLE_ENTRY 第一个32bit 的低3bit,被用作标志。由于所有的对象都在系统地址空间(0x80000000-0xFFFFFFFF)中,所以一个对象头的指针的最高位总是1。所以 HANDLE_TABLE_ENTRY 第一个32bit 的最高位也被用作标志。当我们把一个HANDLE_TABLE_ENTRY 第一个32bit 转换成对象头的指针时,需要把低3bit设为0(&0xFFFFFFF8),最高位设为1(OR 0x80000000)。对象指针总是指的对象体的指针,由于对象头在对象体之前,大小为0x18字节,所以对象指针等于对象头指针加0x18。

 

XP/2003:

通过EPROCESS的struct HANDLE_TABLE *ObjectTable我们可以找到这个进程的HANDLE_TABLE结构,这点和2K没啥不同,通过HANDLE_TABLE结构的第一个成员TableCode来定位句柄表。由于地址要按照4字节对齐,SO,句柄表地址的低2位就是0,TableCode的高30位用来表示句柄表地址,而低2位则表示这是几级表。

image

 

原因是什么呢?其实就是因为XP/2K3不再像2K那样一下子就分配3层表,而是从下往上逐渐分配 。一个页面4KB ,每个HANDLE_TABLE_ENTRY占8个字节,那么一个页面就可以保存4KB/8字节= 512项,因为需要一项用来处理审计,那么实际上可用的为511项,上面两级的句柄表可以存储的指针项为4096/4=1024个,因此最多可以保存511 * 1024 *1024个句柄。(这一块的详细内容参看windows internals的137页)

当大于511 个句柄的时候才会分配1级表(中层表)

大于511 ×1023个句柄的时候才会分配2级表(上层表)

如何根据句柄来定位内核对象:

我们首先要看进程的句柄表有几层,通过HANDLE_TABLE里的TableCode成员的低2位,如果低2位是0 则说明只有一层表 也就是0级表(低层表),TableCode里保存的就是0级表(低层表)的地址;如果是1则说明有两层表,0级表(低层表)和1级表(中层表),TableCode里的地址就是1级表(中层表)的地址;如果是2则说明有三层表,0级表(低层表),1级表(中层表)和2层表(高层表),TableCode里的地址就是2级表(高层表)的地址。

然后句柄仍然当索引用:

bits2-10 这   9bit,为下层表的索引,乘以8得到在下层表中的偏移。

bits11-20这 10bit,为中层表的索引,乘以4得到在中层表中的偏移。

bits21-30这 10bit,为高层表的索引,乘以4得到在高层表中的偏移。

最高位一定是0

关于HANDLE_TABLE_ENTRY的其他都和2K一样。

其他:

PspCidTable是系统特殊的HANDLE_TALBLE ,保存着所有进程和线程对象的指针。PID(进程ID)和 ThreadID(线程ID) 就是在这个句柄表中的索引。这个HANDLE_TABLE不与任何HANDLE_TABLE相连接,而且特殊的是,一般的句柄表里的HANDLE_TABLE_ENTRY的第一个DWORD保存的是对象头的地址 需要加上对象头的大小才能定位到对象体的地址,而PspCidTable里的HANDLE_TABLE_ENTRY里保存的就是对象体的指针(当然上段文字讲的变换还是要做的),有些老的ARK程序在枚举进程的时候用到了PspCidTable。

所有进程的HANDLE_TABLE通过其成员HandleTableList链接起来,这个成员是个LIST_ENTRY结构 链表的首指针是HandleTableListHead,所以要遍历所有进程的句柄表可以通过这个符号。

 

参考资料:

1.《JIURL玩玩Win2k进程线程篇 HANDLE_TABLE 》 JIURL

2.《Windows句柄表格式(1) - 2000句柄表格式》 ftofficer

3.《Windows句柄表格式(2) - XP句柄表格式》 ftofficer

4.《windows internals 》第五版

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值