天天看点

Windows进程中的句柄表

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 进程的句柄表结构

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。