什么是句柄(内核对象)
当一个进程创建或者打开一个内核对象时,将获得一个句柄,通过这个句柄可以访问内核对象。
如:
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
......略
;就是当前进程的结构对象