1 Windows进程中的句柄表
句柄是一个对象引用,同一个对象在不同的环境下可能有不同的引用(句柄)值。句柄仅在一个进程范围内有效。
HANDLE_TABLE 结构的定义:
typedef struct _HANDLE_TABLE {
ULONG_PTR TableCode;//指向句柄表的存储结构
struct _EPROCESS *QuotaProcess;//句柄表的内存资源记录在此进程中
HANDLE UniqueProcessId;//创建进程的ID,用于回调函数
#define HANDLE_TABLE_LOCKS 4
EX_PUSH_LOCK HandleTableLock[HANDLE_TABLE_LOCKS];//句柄表锁,仅在句柄表扩展时使用
LIST_ENTRY HandleTableList;//所有的句柄表形成一个链表
EX_PUSH_LOCK HandleContentionEvent;//链表投为全局变量HandleTableListHead
PHANDLE_TRACE_DEBUG_INFO DebugInfo;//调试信息,仅当调试句柄时才有意义
LONG ExtraInfoPages;//审计信息所占用的页面数量
ULONG FirstFree;//空闲链表表头的句柄索引
ULONG LastFree;//最近被释放的句柄索引,用于FIFO类型空闲链表
ULONG NextHandleNeedingPool;//下一次句柄表扩展的起始句柄索引
LONG HandleCount;//正在使用的句柄表项的数量
union {
ULONG Flags;//标志域/
// For optimization we reuse handle values quickly. This can be a problem for
// some usages of handles and makes debugging a little harder. If this
// bit is set then we always use FIFO handle allocation.
BOOLEAN StrictFIFO : 1;//是否使用FIFO风格的重用,即先释放先重用
};
} HANDLE_TABLE, *PHANDLE_TABLE;
TableCode : 指向句柄表的最高层表项页面,低2位的值表示当前句柄表的层数。
如果最低2位为0,说明句柄表只有1层,最多能容纳512个句柄。
如果最低2位为1,说明句柄表有2层,最多能容纳512*1024个句柄。
如果最低2位为2,说明句柄表有3层,最多能容纳512*1024*1024个句柄。
Windows 进程的句柄表结构

执行体在创建进程时,首先为新进程分配一个单层句柄表。由ExCreateHandleTable 函数来完成,该函数调用ExpAllocateHandleTable 来构造初始的句柄表。由函数
ExpAllocateHandleTableEntryShow 来扩展句柄表。代码见base\ntos\ex\handle.c.
FirstFree : 记录了当前句柄表中的空闲句柄链。通过句柄索引值来链接。
HANDLE_TABLE_ENTRY 结构定义如下:
typedef struct _HANDLE_TABLE_ENTRY {
union {
PVOID Object;
ULONG ObAttributes;
PHANDLE_TABLE_ENTRY_INFO InfoTable;
ULONG_PTR Value;
};
union {
union {
ACCESS_MASK GrantedAccess;
struct {
USHORT GrantedAccessIndex;
USHORT CreatorBackTraceIndex;
};
};
LONG NextFreeTableEntry;
};
} HANDLE_TABLE_ENTRY, *PHANDLE_TABLE_ENTRY;
Object : 指向句柄所代表的内核对象,最低3位含义:第0 位OBJ_PROTECT_CLOSE,表示调用者是否允许关闭该句柄;第1位OBJ_INHERIT,指示该进程所创建的子进程是否可以继承该句柄,即是否将该句柄项拷贝到它们的句柄表中;第2位OBJ_AUDIT_OBJECT_CLOSE,指示关闭该对象时是否产生一个审计事件
第二个union中,如果句柄表项指向一个有效的对象,则GrantedAccess 记录了该句柄的访问掩码;如果这是一个空闲的句柄表项,则NextFreeTableEntry 将加入到句柄表的空闲单链表中。
一个有效的句柄有4中可能:-1,表示当前进程;-2表示当前线程;负值,其绝对值为内核句柄表中的索引;不超过226的正值,当前进程的句柄表中的索引。
内核句柄表即系统的全局句柄表,wrk 中即变量ObpKernelHandleTable,也是System 进程的句柄表。解析句柄函数ObReferenceObjectByHandle,见base\ntos\ob\obref.c 。
将一个对象插入到句柄表中的函数是ObInsertObject,见base\ntos\ob\obinsert.c 。
对象的引用有两种,一种在内核中之间通过对象地址来引用,通过ObReferenceObjectByPoint记录一次新的引用,第二种,通过句柄来引用对象,通过ObpIncrementHandleCount检查并记录一次句柄引用。
在一个句柄上调用了ObReferenceObjectByHandle后,若该对象不在使用,必须调用ObDereferenceObject 函数。
进程唯一ID,即UniqueProcessId;线程有一个CLIENT_ID成员Cid,包含了所属进程的唯一ID和线程的唯一ID。