0%

2015-12-19-句柄与句柄表结构简介

    这里说的句柄指内核对象的句柄,窗口句柄hWnd等是另外的管理结构,不牵扯内核对象,系统的句柄表主要有三种,第一种是系统全局PspCidTable句柄表,保存所有进程和线程的句柄,第二种是进程内部句柄表,保存该进程所打开的内核对象句柄,最后一种是系统进程system的全局句柄表,这几种句柄表对应的格式都是一样的。

句柄表结构及PspCidTable

    以PspCidTable句柄表为例查看句柄表结构,该句柄表存放所有进程、线程的句柄:

    TableCode为0x8f122001,后三位值1代表是二级表(0为一级表、2为三级表)。WindowsXP及以上系统都是采用的动态3层句柄表,句柄数超过一级表的容量时,就会扩展为二级表,此时二级表中放的是指向一级表的指针,这个TableCode的值去掉后三位的值0x8f122000指向一级表基址,如图0x8f122000地址存放了两个一级表的指针:

    在一级表0x8a204000中存放的每个_HANDLE_TABLE_ENTRY结构占8字节,前四个字节0x865ccab1去掉后三位标志位之后的0x865ccab0即指向一个_EPROCESS结构(其实是system进程对象):

    为嘛句柄表中第一个结构对应的是System进程?在我Win7系统上System进程的PID值为4。因为句柄值的低两位用来用来存储该索引对应的句柄表的层次,所以实际的句柄值都是4的倍数,除以4才能得到在句柄表中的实际索引,同样如果已知一个句柄值,也可以据此定位到该句柄所对应句柄表中的位置,每个句柄表最多能存储0x800=2048个_HANDLE_TABLE_ENTRY结构,如果给定Handle值为0x804,可以计算得到该句柄对应第0x804/0x800=2个句柄表项的第(0x804-0x800)/4=1个索引,选择刚才看到的两个一级表指针数组的第二个0x8f102000,一个_HANDLE_TABLE_ENTRY占8个字节,从而定位到句柄0x804对应的_HANDLE_TABLE_ENTRY,查看该对象属性可以确认句柄0x804对应的是一个线程对象:


TIPS: 实际的句柄值都是4的倍数,如果给OpenProcess等函数构造PID值5/6/7,实际上打开的进程还是PID=4的system进程,这样有时可以绕过一些限制打开PID的检查。

进程内部句柄表

    进程内部句柄表指针存储在进程_EPROCESS结构的ObjectTable成员中:

    可以使用API函数ZwQuerySystemInformation获取所有进程打开的内核对象句柄,参数需要设置成SystemHandleInformation,注意得到的是全部进程的全部内核对象句柄,得到存放句柄信息的缓冲区之后需要根据PID值确定句柄值属于哪个进程。

    在使用ZwQuerySystemInformation时还有个小问题,在获取句柄信息时无法事先获取全部内核对象的大小,所以现在一般都采用上图中循环申请内存,如果缓冲区大小不匹配则返回值是STATUS_INFO_LENGTH_MISMATCH,最后在该错误不再出现时即为缓冲区大小符合要求,函数执行成功。(俺之前好奇Process Explorer的搜索句柄功能是咋实现的,后来看网上有分析说Process Explorer也是用了该函数获取句柄值的部分,Win8及以上系统上还不一定支持该函数,暂未确认了~~)