一、回顾
上一篇博客介绍了如何遍历一级句柄表。一级句柄表非常简单,就是一个4KB页,最多存储512个句柄表项。如果句柄数量在 512 - 1024*512 之间,句柄表就是二级结构;如果句柄数量大于 1024 * 512,就是三级结构。
这篇博客作为上一篇的补充,介绍如何从二级、三级结构句柄表中找到内核对象。
二、ExpLookupHandleTableEntry 函数
实际上这个需求就是 ExpLookupHandleTableEntry 函数做的事情:
PHANDLE_TABLE_ENTRY
ExpLookupHandleTableEntry (
IN PHANDLE_TABLE HandleTable,
IN EXHANDLE Handle
)
参数1是句柄表的地址,即 TableCode,注意,这里 TableCode 的低位不能清零,函数里要判断句柄表结构的。
参数2是句柄值,PID 的值就是一个句柄值,调用 OpenProcess 打开一个进程得到的也是句柄值,前者用来索引全局句柄表,后者用来索引进程的句柄表。
下面给出函数源码,我给关键的代码添加了注释,我们要重点研究二级和三级结构的处理方式:
PHANDLE_TABLE_ENTRY
ExpLookupHandleTableEntry (
IN PHANDLE_TABLE HandleTable,
IN EXHANDLE Handle
)
{
ULONG_PTR i,j,k;
ULONG_PTR CapturedTable;
ULONG TableLevel;
PHANDLE_TABLE_ENTRY Entry;
typedef HANDLE_TABLE_ENTRY *L1P;
typedef volatile L1P *L2P;
typedef volatile L2P *L3P;
L1P TableLevel1;
L2P TableLevel2;
L3P TableLevel3;
ULONG_PTR RemainingIndex;
ULONG_PTR MaxHandle;
ULONG_PTR Index;
PAGED_CODE();
Handle.TagBits = 0;
Index = Handle.Index;
MaxHandle = *(volatile ULONG *) &HandleTable->NextHandleNeedingPool;
if (Handle.Value >= MaxHandle) {
return NULL;
}
CapturedTable = *(volatile ULONG_PTR *) &HandleTable->TableCode;
TableLevel = (ULONG)(CapturedTable & LEVEL_CODE_MASK);
CapturedTable = CapturedTable & ~LEVEL_CODE_MASK;
switch (TableLevel) {
case 0:
TableLevel1 = (L1P) CapturedTable;
Entry = &(TableLevel1[Index]);
break;
case 1:
TableLevel2 = (L2P) CapturedTable;
i = Index / LOWLEVEL_COUNT;
j = Index % LOWLEVEL_COUNT;
Entry = &(TableLevel2[i][j]);
break;
case 2:
TableLevel3 = (L3P) CapturedTable;
i = Index / (MIDLEVEL_THRESHOLD);
RemainingIndex = Index - i * MIDLEVEL_THRESHOLD;
j = RemainingIndex / LOWLEVEL_COUNT;
k = RemainingIndex % LOWLEVEL_COUNT;
Entry = &(TableLevel3[i][j][k]);
break;
default :
_assume (0);
}
return Entry;
}
三、手动从二级句柄表里找内核对象
首先给出测试程序,打开计算器进程 1000 次,这意味着该进程的句柄表是二级结构的,TableCode 低2位是1,TableCode 所在的页可以存储1024个4字节地址,每个地址都可以指向一个4KB的句柄表页。因为我们打开了1000次,所以应该只有前两个地址是有效的。
int _tmain(int argc, _TCHAR* argv[])
{
DWORD PID;
HANDLE hPro = NULL;
HWND hwnd = FindWindowA(NULL, "计算器");
GetWindowThreadProcessId(hwnd, &PID);
for (int i = 0; i < 1000; i++)
{
hPro = OpenProcess(PROCESS_CREATE_THREAD | PROCESS_VM_OPERATION | PROCESS_VM_WRITE | PROCESS_VM_READ, TRUE, PID);
printf("句柄:%x\n", hPro);
}
SetHandleInformation(hPro, HANDLE_FLAG_PROTECT_FROM_CLOSE, HANDLE_FLAG_PROTECT_FROM_CLOSE);
getchar();
return 0;
}
随便拿一个 fcc,首先要算出它属于第几个句柄表, 0xfcc / 0x200 = 7,因为地址是4字节,所以要除以4,意味着要到 TableCode[1] 找;
0xfcc % 0x200 = 0x1cc,所以就是 TableCode[1][0x1cc]
。下面来windbg找找看:
现在找到 TableCode:
kd> dt 0xe15bb300 _HANDLE_TABLE
ntdll!_HANDLE_TABLE
+0x000 TableCode : 0xe2280001
+0x004 QuotaProcess : 0x81c01c10 _EPROCESS
+0x008 UniqueProcessId : 0x000003dc Void
+0x00c HandleTableLock : [4] _EX_PUSH_LOCK
+0x01c HandleTableList : _LIST_ENTRY [ 0xe22b5b74 - 0xe23359d4 ]
+0x024 HandleContentionEvent : _EX_PUSH_LOCK
+0x028 DebugInfo : (null)
+0x02c ExtraInfoPages : 0n0
+0x030 FirstFree : 0xfdc
+0x034 LastFree : 0
+0x038 NextHandleNeedingPool : 0x1000
+0x03c HandleCount : 0n1013
+0x040 Flags : 0
+0x040 StrictFIFO : 0y0
dd 一下 0xe2280001:
kd> dd 0xe2280000
e2280000 e2147000 e2281000 00000000 00000000
e2280010 00000000 00000000 00000000 00000000
e2280020 00000000 00000000 00000000 00000000
e2280030 00000000 00000000 00000000 00000000
e2280040 00000000 00000000 00000000 00000000
e2280050 00000000 00000000 00000000 00000000
e2280060 00000000 00000000 00000000 00000000
e2280070 00000000 00000000 00000000 00000000
TableCode[1] 就是 e2281000 ,dq看一下:
kd> dq e2281000
e2281000 fffffffe`00000000 0000003a`81c41543
e2281010 0000003a`81c41543 0000003a`81c41543
e2281020 0000003a`81c41543 0000003a`81c41543
e2281030 0000003a`81c41543 0000003a`81c41543
e2281040 0000003a`81c41543 0000003a`81c41543
e2281050 0000003a`81c41543 0000003a`81c41543
e2281060 0000003a`81c41543 0000003a`81c41543
e2281070 0000003a`81c41543 0000003a`81c41543
找 TableCode[1][0x1cc]
:
kd> dq e2281000+1cc*8
e2281e60 0000003a`81c41543 0000003a`81c41543
e2281e70 0000003a`81c41543 0000003a`81c41543
e2281e80 0000003a`81c41543 0000003a`81c41543
e2281e90 0000003a`81c41543 0000003a`81c41543
e2281ea0 0000003a`81c41543 0000003a`81c41543
e2281eb0 0000003a`81c41543 0000003a`81c41543
e2281ec0 0000003a`81c41543 0000003a`81c41543
e2281ed0 0000003a`81c41543 0000003a`81c41543
找到对象头了,下面看看 EPROCESS,注意低3位清零,还要加上 0x18:
kd> dt _EPROCESS 81c41540+18
ntdll!_EPROCESS
+0x000 Pcb : _KPROCESS
...
+0x170 Session : 0xf8bc8000 Void
+0x174 ImageFileName : [16] "calc.exe"
+0x184 JobLinks : _LIST_ENTRY [ 0x0 - 0x0 ]
...
相信看到这,你已经知道怎么通过句柄找内核对象了,如果还是不懂,请留言说出你的问题。
三级结构我就不找了,因为原理差不多,可以照抄 ExpLookupHandleTableEntry 函数。
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)