x86下的_HANDLE_TABLE_ENTRY长度为8B,其低4字节Object指针直接指向对象头(_OBJECT_HEADER)。
在全局句柄表PspCidTable中,其PVOID指针直接指向对象体(_EPROCESS/_ETHREAD)。
在x64的Windows 10中,_HANDLE_TABLE_ENTRY结构长度拓展为16字节,其结构体中指向对象头的PVOID指针也消失了。
通过对PsLookupProcessByProcessId函数进行分析,其函数栈如下:
PsLookupProcessByProcessId -> PspReferenceCidTableEntry -> ExpLookupHandleTableEntry
句柄与对象指针的关键转换代码如下:
----------------------------------------
nt!ExpLookupHandleTableEntry:
fffff805`78ca15d0 8b01 mov eax,dword ptr [rcx] ; rcx=*PspCidTable
fffff805`78ca15d2 4883e2fc and rdx,0FFFFFFFFFFFFFFFCh ; rdx=pid/handle
fffff805`78ca15d6 483bd0 cmp rdx,rax
fffff805`78ca15d9 7357 jae nt!ExpLookupHandleTableEntry+0x62 (fffff805`78ca1632)
fffff805`78ca15db 4c8b4108 mov r8,qword ptr [rcx+8] ; r8将存储PspCidTable->TableCode
fffff805`78ca15df 418bc0 mov eax,r8d
fffff805`78ca15e2 83e003 and eax,3
fffff805`78ca15e5 83f801 cmp eax,1 ; 判断是否为二级索引
fffff805`78ca15e8 7517 jne nt!ExpLookupHandleTableEntry+0x31 (fffff805`78ca1601)
fffff805`78ca15ea 488bc2 mov rax,rdx
fffff805`78ca15ed 48c1e80a shr rax,0Ah
fffff805`78ca15f1 81e2ff030000 and edx,3FFh
fffff805`78ca15f7 498b44c0ff mov rax,qword ptr [r8+rax*8-1]
fffff805`78ca15fc 488d0490 lea rax,[rax+rdx*4]
fffff805`78ca1600 c3 ret
nt!PspReferenceCidTableEntry+0x5d:
fffff805`78ccd0ed 488b5c2420 mov rbx,qword ptr [rsp+20h] ; rbx=句柄表项存储的低8字节值
fffff805`78ccd0f2 48c1fb10 sar rbx,10h ; 右移16bit
fffff805`78ccd0f6 4883e3f0 and rbx,0FFFFFFFFFFFFFFF0h ; rbx指向对象头/对象体
fffff805`78ccd0fa 0fb603 movzx eax,byte ptr [rbx]
fffff805`78ccd0fd 247f and al,7Fh
----------------------------------------
每个PAGE_SIZE大小为4KB,可存储句柄表项数为2^8个,且由于句柄号是4的整数倍,因此计算句柄项的中间索引时需要右移10bit获得Mid.index,计算句柄项的Low.index时需要与上0x3ff。中间索引表的表项应该存储的是PHANDLE_TABLE_ENTRY,长度为8字节;在计算Low.offset时,由于HANDLE_TABLE_ENTRY长度为16B,其句柄号是4的整数倍(本身已经乘4),只需要句柄号再乘4即可。句柄表项中并不直接映射对象头,而是间接进行映射。
其转换方式为(*(__int64*)handle_table_entry >> 0x10) && 0xfffffffffffffff0
.
如果是进程句柄表,则在句柄较少的情况下采用直接索引(取决于TableCode的低2bit),即在句柄表中直接进行索引。此时其句柄表项可直接通过 `句柄表基址+4*handle` 取低8字节获取,之后的转换与上边的一样。
如被测试进程LoadPE.exe的pid为0x700,通过测试程序调用OpenProcess获取目标进程的句柄,如下:
LoadPE.exe进程的内核对象地址为0xFFFFCE0EAA636080(指向对象体),句柄号为0xac.
通过windbg获取LoadPE.exe进程的句柄表地址为 0xffffb98041457800,其 TableCode=0xffffb9803c9ff000.
由于TableCode&2 = 0,即采用直接索引,其句柄表项为 ce0eaa63`60500001 00000000`001fffff,其低8B可转换为对象头指针。
(0xce0eaa6360500001 >> 0x10) & 0xfffffffffffffff0 = 0xFFFFCE0EAA636050 (指向对象头)
以同样的方式解析全局句柄表PspCidTable,如下:
PspCidTable=ffffb980`37006e00,其TableCode=0xffffb9803a978001,即采用2级索引。
由于被测试程序LoadPE.exe的pid=0x700,(0x700 >> a)& 0x3ff = 1,即 Mid.index=1, Mid.offset=1*8.
Low.index=0x700 & 0x3ff = 0x300, Low.offset=4*0x300.
将取出的值做如上的处理便可得到对象体指针。
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)