内核句柄表与创建句柄.docx
《内核句柄表与创建句柄.docx》由会员分享,可在线阅读,更多相关《内核句柄表与创建句柄.docx(13页珍藏版)》请在冰豆网上搜索。
内核句柄表与创建句柄
进程句柄表与创建句柄表
我们编写Windows程序中经常使用到内核对象,特别是句柄这个概念,通过句柄可以对内核对象进行访问,那句柄到底是什么?
本文将会从内核来说明这个概念。
Windows采取了面向对象设计,内核中有一个的模块来管理内核对象,有很多资料都是说是“对象管理器”,本文也采用这个概念。
对象管理器用来管理内核对象信息和记录内核对象的使用情况,包括引用计数。
每个进程都要创建一个句柄列表,这些句柄指向各种系统资源,比如信号量,线程,和文件等,进程中的所有线程都可以访问这些资源性),如下图所示,进程和资源:
1.进程与句柄表数据关系
在用户模式下如果调用CloseHanele()表示不再使用这个对象,在内核中进程便会删除句柄(释放对象引用);对象管理器也会将内核对象的引用计数也会减一,当对象的句柄引用为0时,对象管理器便会释放这个对象。
句柄表最基本作用就是句柄与目标对象之间的映射表,下图是进程与句柄的简化模型图(有些数据域要经过处理):
_HANDLE_TABLE是句柄表的信息的结构体,在内核中句柄是句柄表中表项的索引,在这里可以简单的理解,由索引(句柄)在句柄表中查找到进程引用的内核对象.
在Windbg中查看_HANDLE_TABLE(这里例出部分有意义的项)
kd>dt_HANDLE_TABLE
nt!
_HANDLE_TABLE
+0x000TableCode:
Uint4B//指向第一层局部表,并记录层数
+0x004QuotaProcess:
Ptr32_EPROCESS//指向进程_EPROCESS块
+0x008UniqueProcessId:
Ptr32Void//进程ID
+0x03cHandleCount:
Int4B//句柄计数,当前使用句柄个数
kd>dt_EPROCESS//进程_EPROCESS块信息
nt!
_EPROCESS
+0x084UniqueProcessId:
Ptr32Void//进程ID
+0x0c4ObjectTable:
Ptr32_HANDLE_TABLE//指向_HANDLE_TABLE结构
2.句柄的数据结构
内核与SDK中定义句柄都为:
typedefvoid*HANDLE;表明句柄是一个无符号整数,实际上有效句柄的值时有范围的,大家想想如果采用数组来存储句柄需要耗费很大的内存,Windows句柄表使用了稀疏数组.
2.1XP/2003句柄表项:
先看下句柄表中存放的是什么?
句柄表主要是存放的是对象的地址与属性信息,当然还要存放句柄表相关一些信息(审计,空闲项),每个句柄表项是由_HANDLE_TABLE_ENTRY描述的,_HANDLE_TABLE_ENTRY占8字节,定义如下:
kd>dt_HANDLE_TABLE_ENTRY
nt!
_HANDLE_TABLE_ENTRY
+0x000Object:
Ptr32Void//对象指针
+0x000ObAttributes:
Uint4B
+0x000InfoTable:
Ptr32_HANDLE_TABLE_ENTRY_INFO
+0x000Value:
Uint4B
+0x004GrantedAccess:
Uint4B
+0x004GrantedAccessIndex:
Uint2B
+0x006CreatorBackTraceIndex:
Uint2B
+0x004NextFreeTableEntry:
Int4B
由于_HANDLE_TABLE_ENTRY有些联合体,不好理解,源码定义如下:
typedefstruct_HANDLE_TABLE_ENTRY{
union{
PVOIDObject;//对象指针
ULONGObAttributes;//对象属性
PHANDLE_TABLE_ENTRY_INFOInfoTable;
ULONG_PTRValue;//值
};
union{
union{
ACCESS_MASKGrantedAccess;//访问掩码
struct{
USHORTGrantedAccessIndex;
USHORTCreatorBackTraceIndex;
};
};
LONGNextFreeTableEntry;//下一个空闲的句柄表项,空闲链表索引
};
}HANDLE_TABLE_ENTRY,*PHANDLE_TABLE_ENTRY;
表示的意义:
1.对象指针Object有效则第二个域为访问掩码GrantedAccess
2.第一个域为0,第二个域可能是NextFreeTableEntry,也可能为审计,后面会有相关算法用到这个域,要根据上下文来判断。
这里的Object并不是“真正”的对象指针,而是包括了对象的指针域对象的属性域,由于在内核中对象总是8字节对齐的,那么指向对象的指针最低3位总是0,微软把这3位也利用上,Object的最低3位做为对象的属性,看下面的一组宏定义:
#defineOBJ_HANDLE_ATTRIBUTES(OBJ_PROTECT_CLOSE|OBJ_INHERIT|OBJ_AUDIT_OBJECT_CLOSE)
第0位OBJ_PROTECT_CLOSE:
句柄表项是否被锁定,1锁定,0未锁定
第1位OBJ_INHERIT:
指向该进程所创建的子进程是否可以继承该句柄,既是否将该句柄项拷贝到它的句柄表中
第2位OBJ_AUDIT_OBJECT_CLOSE:
关闭该对象时是否产生一个审计事件
2.2XP/2003句柄表项:
Windows为了节省空间采用动态扩展结构,类似于页表结构,最大可扩展3层表._HANDLE_TABLE.TableCode存放了第一层局部表的基址指针和层数,微软在这里设计很精妙,由于效率32位地址都以4对齐,最低2位为0,微软把_HANDLE_TABLE.TableCode的最低两位作为句柄表层数的纪录,即00一层表,
01二层表10三层表.句柄表的结构图如下:
一层表:
_HANDLE_TABLE_ENTRY
_HANDLE_TABLE_ENTRY
两层表时:
_HANDLE_TABLE_ENTRY
_HANDLE_TABLE_ENTRY
三层表时:
_HANDLE_TABLE_ENTRY
_HANDLE_TABLE_ENTRY
有上图所示,最低层局部表都是是存放着_HANDLE_TABLE_ENTTY结构,中间层和最高层都是存放着页表指针,当句柄增加时,便会判断是否需要扩展。
2.3XP/2003句柄表表项计数:
句柄表是动态扩展,当引用资源足够多时,句柄的数目也在增加,当到一定数目时,句柄表便会扩展,扩展的标准是什么?
下面一系列宏给出了定义
(1)最低层存放句柄表项数:
每个最底层页表存放的是_HANDLE_TABLE_ENTRY结构,即4096/8=512,其中第一项做审计用,最多有511个有效项
#defineLOWLEVEL_COUNT(TABLE_PAGE_SIZE/sizeof(HANDLE_TABLE_ENTRY))
(2)中间层可以存放的项数
中间层存放的页表指针,最多有4028/4=1024
#defineMIDLEVEL_COUNT(PAGE_SIZE/sizeof(PHANDLE_TABLE_ENTRY))
(3)可分配的最大句柄值,不是我们想象的无符号整数最大值
#defineMAX_HANDLES(1<<24)//224
(4)最高层最大项数:
#defineHIGHLEVEL_COUNTMAX_HANDLES/(LOWLEVEL_COUNT*MIDLEVEL_COUNT)
即224/(1024*512)=25=32,在句柄表结构图已经说明3层表第一层表最大有32项
通过上面计算:
二级表最大可以存放511*1024=523264个对象引用,没有特殊情况一般来说已经够了,所以我们一般只能观察到一层,两层句柄表
3.nt!
PspCreateProcess中创建进程句柄表
3.1在创建进程时初始化进程对象并创建进程句柄表
创建进程时先创建进程对象,再创建进程句柄表,即nt!
PspCreateProcess->nt!
ObInitProcess->nt!
ExCreateHandleTable,创建句柄表的核心流程图如下:
分配进程句柄表例程步骤:
1.调用ExpAllocateHandleTable分配句柄表及_HANDLE_TABLE结构
2.插入到进程句柄表链表
函数描述:
;RoutineDescription:
;Thisfunctionallocateandinitializeanewnewhandletable
;这个例程分配并初始化一个新的句柄表(_HANDLE_TABLE)
;Arguments:
;Process-Suppliesanoptionalpointertotheprocessagainstwhichquota
;willbecharged.
;提供一个将要记录相关信息(对象)的进程的指针
;ReturnValue:
;Ifahandletableissuccessfullycreated,thentheaddressofthe
;handletableisreturnedasthefunctionvalue.Otherwise,avalue
;NULLisreturned.
;如果成功函数返回handletable的地址,负责返回0
_HANDLE_TABLE*__stdcallExCreateHandleTable(_EPROCESS*pProcess)
核心算法分析:
由于进程句柄表是一个双向链表结构,是系统很重要的数据结构,所以必须考虑同步问题,只有在加锁的情况下才能修改
通过ExpAllocateHandleTable分配进程句柄表:
push1;DoInit
push[ebp+pProcess];pProcess
call_ExpAllocateHandleTable@8;创建句柄表例程
movebx,eax
testebx,ebx;判断ExpAllocateHandleTable是否成功
jzshortALLOC_HANDLE_TABLE_UNSUCCESS
句柄表是进程句柄链表是内核重要结构,有同步问题存在,这里给句柄表上锁
moveax,0
;系统句柄链表的改变必须要实现同步操作,所以要使用锁
movecx,offset_HandleTableListLock
lockbts[ecx],eax;加锁
加入进程句柄表链表
movecx,_HandleTableListHead.Blink
leaeax,[ebx+_HANDLE_TABLE.HandleTableList.Flink
;取_Handle_TABLE.HandleTableList的Flink指针
mov[eax+_LIST_ENTRY.Flink],ecx
;_HANDLE_TABLE.HandleTable