全局句柄表
Windows内核分析索引目录:https://www.cnblogs.com/onetrainee/p/11675224.html
全局句柄表
全局句柄表中只存两种对象,进程_EPROCESS与线程_ETHREAD,下面分析全局句柄表的结构
1. 通过ID来获取句柄
只要有过Windows编程经验的人,都会知道进程id与进程句柄的概念,其使用OpenProcess函数,传入一个进程id,其会帮你打开一个进程并返回该进程的句柄。
注意,在Windows操作系统中,进程id、线程id、句柄都是4的倍数,不信可以打开任务管理器验证我们的说法。
1)进程id在内核对象的位置
其进程id保存在 _EPROCESS+0x84 UniqueProcessId,可以通过Windbg验证这说法。
我们在内核中都使用过一个函数 PsLookupProcessByProcessId(),其可以通过进程Id来获取进程_EPROCESS。
如下图该函数简单分析,现在我们得知其通过Pid从PspClidTable这张表中获取_EPROCESS。
2)_HANDLE_TABLE
PspCidTable其是一个_HANDLE_TABLE的数据结构,我们可以通过windbg查看有关成员。
TableCode的分析
TableCode指向的就是句柄表,但又不完全指向句柄表(末位0、1、3的区别)。
注意:TableCode末尾是要分析清楚,一个句柄表以页为单位4KB,句柄表8个为一个单位,但是进程尤其在服务器中,其数量可以高过512个。
2. 通过ID来获取句柄
我们现在来实验一下通过进程id通过全局句柄表PspCidTable来找到_EPROCESS结构体。
1)查看一个进程Id,我们以csrss.exe来进行实验
csrss.exe的Pid为576,我们之前说过其都是为4的整数倍,然后576/4 = 144(0x90)来获取其索引
2)通过PspCidTable查看其全局句柄表
kd> dd PspCidTable
80562460 e10008c0
我们知道该地址e10008c0指向一个_HANDLE_TABLE的数据结构,我们遍历获取其TableCode e10008c0
kd> dt _HANDLE_TABLE e10008c0
nt!_HANDLE_TABLE
+0x000 TableCode : 0xe1003000
+0x004 QuotaProcess : (null)
+0x008 UniqueProcessId : (null)
3)全局句柄表的元素为8字节一个单位,因此计算
kd> dq 0xe1003000+0x90*8
e1003480 00000000`81ef7ab1 000003d0`00000000
e1003490 00000000`81b45021 00000000`81b453c1
e10034a0 00000000`81a88441 00000000`81db1d11
获取地址 81ef7ab1,注意其最后一位为属性位,减1即可 81ef7ab0(如果是9则变为8,不要抹零)。
4)查看其_EPROCESS结构
dt _EPROCESS 81ef7ab0
注释:如果其为两层表,则如果索引xx>512,则查找第二张表 dq (xx-512)*8 这种方式来进行进程。
3. 如何判断从全局句柄表中获得的是进程还是线程
无论进程还是线程,其都存储在全局句柄表中,现在有一个问题,如何判断其取出来的是进程还是线程呢?
我们看下面一个结构,Object_Header,任何对象上面都有这样一个,其Type指向一个_Object_Type结构,在该结构中有个name成员,
通过这个Name成员可以判断是Process还是Thread,这个技巧是要明确的。
4. 遍历全局句柄表
其存储在全局句柄表中只有线程和进程,我们现在写一个简单的demo来遍历出相关全局句柄表,当然其只支持单个句柄表遍历,只有有详细寻求在继续往上添加即可。
NTSTATUS TraverseGlobalHandleTable() { // 来遍历全局句柄表 // 1.获取 PspCidTable 地址 // 2. // 获取PspClidTable地址 FindCode findcodes2[1] = { 0 }; initFindCodeStruct(&findcodes2[0], "FF35****E8****8BD885DBC745*****74*578B3B80**75*83BF*****74*8BCFE8****84C074*8B45*8365**893853", 0, 0); PUCHAR f = (PUCHAR)FindAddressByCode(findcodes2, 1); PULONG PspCidTable = *(PULONG)(*(PULONG)((PUCHAR)f + 2)); PULONG TableCode = *PspCidTable; // 判断链表的是否是单层,目前只支持单层 if (((ULONG)TableCode & 1) != 0) { DbgPrint("句柄表存在多级\r\n"); return STATUS_UNSUCCESSFUL; } // 获取数量 ULONG HandleCount = *(PULONG)((PUCHAR)PspCidTable + 0x03c); //DbgPrint("该句柄表中的句柄个数:%x\r\n", HandleCount); // 开始遍历句柄表 PULONG p; // _指向EPROCESS或ETHREAD PULONG pObjHeader; // 指向对象头 PULONG pType; for (ULONG i = 0; i < HandleCount; i++) { p = *(PULONG)((PUCHAR)TableCode + i * 8) ; // 获取值 p = (ULONG)p & 0xFFFFFFFE; // 将最后1bit清零 if (MmIsAddressValid(p)) { // 当该地址为有效地址时 pObjHeader = (PULONG)((PUCHAR)p - 0x18); // 获取对象头 pType = *(PULONG)((PUCHAR)pObjHeader + 0x8); // 获取 TYPE_OBJECT //DbgPrint("名字:%x\r\n", (ULONG)((PUCHAR)pType + 0x40)); // TYPE_OBJECT.name DbgPrint("%wZ", (PULONG)((PUCHAR)pType + 0x40)); } } return STATUS_SUCCESS; }