1.句柄表

句柄是进程访问内核对象的机制,防止应用层直接修改内核对象导致错误。每个进程有一张句柄表,记录句柄与内核对象的关系。句柄表的每个成员8字节,由不同部分组成,分别用于权限控制、属性标志和对象地址。OpenProcess和SetHandleInformation等函数会影响句柄表中的信息。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

什么是句柄(内核对象)

当一个进程创建或者打开一个内核对象时,将获得一个句柄,通过这个句柄可以访问内核对象。

如:

HANDLE g_hMutex =::CreateMutex(NULL,FALSE, "XYZ");
HANDLE g_hMutex =::OpenMutex(MUTEX ALL ACCESS, FALSE, "XYZ");
HANDLE g_hEvent =::CreateEvent(NULL, TRUE, FALSE, NULL);
HANDLE g_hThread =::CreateTpread(NULL, 0, Proc, NULL, 0, NULL);
...

我们调用一些窗口相关函数的句柄,虽然也是句柄但是和这个是不一样的,这里面的句柄它一定对应一个内核对象

这些内核对象其实也是一个结构体,要在0环才能进行读写。

为什么要有句柄?

句柄存在的目的是为了避免在应用层直接修改内核对象。

HANDLE g hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);

如果 g_hEvent 存储的就是EVENT内核对象的地址,那么就意味着我们可以在应用层修改这个地址,一旦指向了无效的内核内存地址就会蓝屏

在这里插入图片描述

每一个进程都会一张句柄表。
创建的和打开的句柄它在0环对应的内核对象的地址就会存储到这张表中。(创建和打开是不一样的)

如果再次通过OpenEvent来打开,这时系统并不会为这个事件创建一个新的结构体,它有一个计数器打开一次它就加一次。

调用的函数返回值为HANDLE的返回的就是句柄表中对应成员的索引。

句柄表的位置

dt _EPROCESS
...
 +0x0c4 ObjectTable      : Ptr32 _HANDLE_TABLE
...

kd> dt _HANDLE_TABLE
nt!_HANDLE_TABLE
   +0x000 TableCode        : //指向句柄表的存储结构
   +0x004 QuotaProcess     : _EPROCESS//句柄表记录的内存资源记录在此进程中
   +0x008 UniqueProcessId  : Void//创建进程的ID,用于回调函数
   +0x00c HandleTableLock  : [4] _EX_PUSH_LOCK//句柄表锁 (仅在句柄表拓展时使用)
   +0x01c HandleTableList  : _LIST_ENTRY //所有句柄表形成一个链表,链表头为全局变量HandleTableListHand
   +0x024 HandleContentionEvent : _EX_PUSH_LOCK//若在访问句柄表时发生竞争,则在此推锁上等待
   +0x028 DebugInfo        : //调试信息,当调试句柄时才有意义
   +0x02c ExtraInfoPages   : //审计信息所占用的页面数量
   +0x030 FirstFree        : //空闲链表表头的句柄索引
   +0x034 LastFree         : //最近被释放的句柄索引,用于FIFO类型空闲链表
   +0x038 NextHandleNeedingPool : //下一次句柄表拓展的起始句柄索引
   +0x03c HandleCount      : //正在使用的句柄表项的数量
   +0x040 Flags            : //标志域
   +0x040 StrictFIFO       : //是否使用FIFO风格的重用,既先释放先重用

测试代码:

int main()
{
	DWORD pid;
	HANDLE hProcess;

	HWND hwnd = ::FindWindow(NULL,"计算器");
	::GetWindowThreadProcessId(hwnd, &pid);

	for (int i = 0;i<100;i++)
	{
		hProcess = ::OpenProcess(PROCESS_ALL_ACCESS,TRUE, pid);
		//hProcess = ::OpenProcess(PROCESS_CREATE_THREAD, TRUE, pid);

		printf("句柄:%x\n", hProcess);

	}

	//HANDLE_FLAG_PROTECT_FROM_CLOSE 句柄不可用CloseHandle关闭
	//SetHandleInformation(hProcess, HANDLE_FLAG_PROTECT_FROM_CLOSE, HANDLE_FLAG_PROTECT_FROM_CLOSE);

	
	getchar();
	return 0;
}
!!!
这段代码,可以看到有100个句柄,但不要认为创建了100个,这里一个都没创建
进程句柄在你的程序运行起来就创建好了。(都是同一个内核对象)

句柄表的成员每个是8字节

我们来看看最后一个句柄,我这是0x640。0x640/4 =0x190。(x86的 HANDLE 为4字节)

在这里插入图片描述

应用层函数对句柄表中表项的影响

typedef struct _HANDLE_TABLE_ENTRY
{
    union
    {
        PVOID Object;//指向句柄所代表的对象 (bit31- bit0)
        ULONG_PTR ObAttributes;//最低3位有特别含义,参见OBJ_HANDLE_ATTRIBUTES结构
        PHANDLE_TABLE_ENTRY_INFO InfoTable;//各个句柄表页面的第一个句柄表项,使用此成员指向一张表
        ULONG_PTR Value;
    };
    union
    {
        ULONG GrantedAccess;//访问掩码 (②)
		
		//当NtGlobalFlag中包含 FLE_KERNEL_STACK_TRACE_DB 标记时使用
        struct
        {
            USHORT GrantedAccessIndex;
            USHORT CreatorBackTraceIndex;
        };
        LONG NextFreeTableEntry;//空闲时表示下一个句柄表索引
    };
} HANDLE_TABLE_ENTRY, *PHANDLE_TABLE_ENTRY;

在这里插入图片描述
8字节分成4部分每个部分2字节。

①这一块共计两个字节,低字节保留恒为0,高位字节是给SetHandlelnformation,这个函数用的,
比如写成SetHandleInformation(Handle, HANDLE_FLAG_PROTECT_FROM_CLOSE, HANDLE_FLAG PROTECT_FROM_CLOSE),那么这个位置将被写入0x02;
HANDLE_FLAG PROTECT_FROM_CLOSE宏的值为0x00000002,取最低字节,最终①这块是 0x02 00

②这块是访问掩码,是给OpenProcess这个函数用的,
OpenProcess(dmDesirdAccess,blnheriHande, dwProcessld)具体的存的就是这个函数的第一个参数的值

③和④这两个块共计四个字节,其中bito-bit2存的是这个句柄的属性,其中bit2 bit0 默认为0,1;
bit1表示的函数是该句柄是否可继承; OpenProcess的第二个参数与bit1有关;
bit31- bit3 则是存放的该内核对象在内核中的具体的地址

演示代码

int main()
{
	DWORD pid;
	HANDLE hProcess;

	HWND hwnd = ::FindWindow(NULL,L"计算器");
	::GetWindowThreadProcessId(hwnd, &pid);

	for (int i = 0;i<100;i++)
	{
		//hProcess = ::OpenProcess(PROCESS_ALL_ACCESS,TRUE, pid);
		hProcess = ::OpenProcess(PROCESS_CREATE_THREAD, TRUE, pid);

		printf("句柄:%x\n", hProcess);

	}

	//HANDLE_FLAG_PROTECT_FROM_CLOSE 句柄不可用CloseHandle关闭
	SetHandleInformation(hProcess, HANDLE_FLAG_PROTECT_FROM_CLOSE, HANDLE_FLAG_PROTECT_FROM_CLOSE);

	getchar();
	return 0;
}

在这里插入图片描述
只有它一个不一样,这就是OpenProcess和SetHandleInformation对它的影响。

后4字节就是对应的0环结构体的地址,后3bit要清0。

所有的内核对象看到的并不是完整的,它上面还有0x18个字节的_OBJECT_HEADER结构

kd> dt _OBJECT_HEADER
nt!_OBJECT_HEADER
   +0x000 PointerCount     : Int4B
   +0x004 HandleCount      : Int4B
   +0x004 NextToFree       : Ptr32 Void
   +0x008 Type             : Ptr32 _OBJECT_TYPE
   +0x00c NameInfoOffset   : UChar
   +0x00d HandleInfoOffset : UChar
   +0x00e QuotaInfoOffset  : UChar
   +0x00f Flags            : UChar
   +0x010 ObjectCreateInfo : Ptr32 _OBJECT_CREATE_INFORMATION
   +0x010 QuotaBlockCharged : Ptr32 Void
   +0x014 SecurityDescriptor : Ptr32 Void
   +0x018 Body             : _QUAD

句柄表中后4字节的地址后3bit清0后,得到的是一个完整的结构,我这里OpenProcess对应的是_EPROCESS。

_OBJECT_HEADER后面紧跟着就是_EPROCESS。

892e81b3后3bit清0 -> 892e81b0 
dt _EPROCESS 892e81b0 + 0x18
......;就是当前进程的结构对象
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值