谈windows中的句柄

谈windows中的句柄windows

 
每当一个进程打开一个对象,系统就返回一个句柄做为凭证,由此能够想到,句柄是依赖于具体的进程的,换句话说,句柄必定属于某个进程,之后在访问这个对象时就要使用这个凭证!
    因此句柄还能够认为是一个进程和一个对象之间创建的一种链接,一座桥梁,一个上下文,就像一个门同样,已经打开。这种链接就持续存在,直到关闭。
一个进程可打开对个对象,就会拥有多个句柄,因此每一个进程都拥有一个句柄表,在进程控制块EPROCESS中有个指针ObjectTable是 _HANDLE_TABLE类型,指向本进程的句柄表!
看下 _HANDLE_TABLE结构:
 1 kd> dt _handle_table
 2 nt!_HANDLE_TABLE
 3 +0x000 TableCode : Uint4B
 4 +0x004 QuotaProcess : Ptr32 _EPROCESS
 5 +0x008 UniqueProcessId : Ptr32 Void
 6 +0x00c HandleLock : _EX_PUSH_LOCK
 7 +0x010 HandleTableList : _LIST_ENTRY //句柄表双向链表
 8 +0x018 HandleContentionEvent : _EX_PUSH_LOCK
 9 +0x01c DebugInfo : Ptr32 _HANDLE_TRACE_DEBUG_INFO
10 +0x020 ExtraInfoPages : Int4B
11 +0x024 Flags : Uint4B
12 +0x024 StrictFIFO : Pos 0, 1 Bit
13 +0x028 FirstFreeHandle : Uint4B
14 +0x02c LastFreeHandleEntry : Ptr32 _HANDLE_TABLE_ENTRY
15 +0x030 HandleCount : Uint4B
16 +0x034 NextHandleNeedingPool : Uint4B
17 +0x038 HandleCountHighWatermark : Uint4B

 

TableCode的低两位被用做标志位,用于表示当前句柄表的级数,0,1,2分别表示一级表,二级表,三级表。
一级表其实是一个_HANDLE_TABLE_ENTRY 数组,每一个_HANDLE_TABLE_ENTRY 8个字节,而一级表是一个page的大小,因此一级表能够容纳2^9个_HANDLE_TABLE_ENTRY 
 1 lkd> dt _HANDLE_TABLE_ENTRY
 2 nt!_HANDLE_TABLE_ENTRY
 3 +0x000 Object : Ptr32 Void//指向对象的一个指针
 4 +0x000 ObAttributes : Uint4B
 5 +0x000 InfoTable : Ptr32 _HANDLE_TABLE_ENTRY_INFO
 6 +0x000 Value : Uint4B
 7 +0x004 GrantedAccess : Uint4B
 8 +0x004 GrantedAccessIndex : Uint2B
 9 +0x006 CreatorBackTraceIndex : Uint2B
10 +0x004 NextFreeTableEntry : Uint4B

 

而句柄以4为步进(注0),所以最大句柄为0x200*4=0x800.其中可存放的最大句柄不超过0x800(最大为0x800-4),而每一个一级表的第一个HANDLE_TABLE_ENTRY的Object老是为0,由于咱们都知道0是一个无效的句柄,它不指向一个有效的对象。所以,每一个一级表实际存放的句柄数为511个!
 
_HANDLE_TABLE结构中有个TableCode字段,该字段有32位,低2位做为标记是几级表,0为一级,1为2级,2为3级。当为0时,TableCode值就指向一个Page,此页就是句柄表,每一个表项8个字节为一个entry,低4个字节是对象地址,注意第一个entry不表明任何对象。
若TableCode前两位为1或者2,那么TableCode&0xfffffffd就指向二级或者三级表,每一个表项四个字节,指向一个一级或者二级表,以此类推!!
注意:因为对象体都是8字节对齐的,因此对象地址的低3位老是0,可用于标记对象的某些属性,因此在一级句柄表中的对象地址要&0xfffffff8才表示对象头的地址,加上0x18后获得对象体(这里涉及到windows内核中对象的管理,参见另外一篇关于对象的文章)!
句柄表架构如图所示:

 

至于为何句柄表的号都是4的倍数呢?


一个进程的句柄表包含了全部已被该进程打开的那些对象的指针。对象句柄是用来检索句柄表的一个“伪索引”。对于句柄表机制,achillis <<Windows句柄表>>系列文章已经分析得很透彻了,只是对“句柄以4为步进”来源不明。经查,根源以下:

typedef struct _EXHANDLE
{
 union
 {
  struct
  {
   ULONG TagBits:2;
   ULONG Index:30;
  }
  HANDLE GenericHandleOverlay;
  #define HANLE_VALUE_INC 4
  ULONG_PTR Value;
 }

}EXHANDLE,*PEXHANDLE;
此结构正是用来定义句柄类型。低2位TagBits为标志位Windows用于其它用途,故句柄值低2位对其做为句柄表索引自己无心义,因此等于4的倍数。有了以上分析,天然,在用句柄值为索引取句柄表项时,句柄值必须/4。所以程序中用到的句柄值并不能直接用来索引句柄表,也就有了“伪索引”说法。
数组

 
在windows系统中,主要分为两种句柄表:
一、单个进程的句柄表
二、系统全局句柄表pspCidTable
前者主要用于进程打开的各类对象,然后者用于分配全局进程PID。以任务管理器关闭某个进程为例,若是其要关闭一个进程,首先根据进程PID打开其进程并获取访问这个进程的句柄,这时,PID对应在PspHandleTable中的索引,而得到的句柄对应任务管理器的句柄表中的索引,仅仅在任务管理器的进程空间中有效,一个全局、一个局部。而解析句柄和PID的过程彻底一致。
主要区别在于全局句柄表的表项指向的是对象体而不是对象头。
相关文章
相关标签/搜索