谈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系统中,主要分为两种句柄表:
1、单个进程的句柄表
2、系统全局句柄表pspCidTable
前者主要用于进程打开的各种对象,而后者用于分配全局进程PID。以任务管理器关闭某个进程为例,如果其要关闭一个进程,首先根据进程PID打开其进程并获取访问这个进程的句柄,这时,PID对应在PspHandleTable中的索引,而获得的句柄对应任务管理器的句柄表中的索引,仅仅在任务管理器的进程空间中有效,一个全局、一个局部。而解析句柄和PID的过程完全一致。
主要区别在于全局句柄表的表项指向的是对象体而不是对象头。