个人理解,记录一下,如有错误请轻喷。。。。
PspCidTable存放着一个结构为_HANDLE_TABLE的指针,_HANDLE_TABLE结构记录句柄表的相关信息,句柄表存放着所有进程和线程的对象,其索引就是PID和TID,在WinDbg看一下相关的结构。
我们遍历进程对象需要关注两个成员,第一个是TableCode,第二个是NextHandleNeedingPool。
TableCode:该成员记录表级和表地址,低2位记录着表的级数,掩掉低2位后为表地址,表级为0时为一级表,表地址直接指向_HANDLE_TABLE_ENTRY数组,表级为1时为二级表,表地址指向一级表数组,表级为2时为三级表,表地址指向二级表数组。
引用一位博主的图片https://blog.****.net/whatday/article/details/17189093
每个表的大小为1页=4K,二级表和三级表储存指针,所以分别可以储存4K/8=512个表,一级表储存_HANDLE_TABLE_ENTRY结构,在x64中该结构大小为16,所以一级表可以储存4K/16=256个表,计算PID的方式为:(三级表索引*二级表最大表数(512)*一级表最大表数(256)+二级表索引*一级表最大表数(256)+一级表索引)*4
NextHandleNeedingPool:该成员记录下一次句柄扩展时的起始值。
下面通过WinDbg来获取进程的对象,
首先访问PspCidTable获取指针地址,
通过该指针地址获取_HANDLE_TABLE相关信息,
根据TableCode低两位为0,说明是一个二级表,我们通过TableCode&(~3)得到二级表指针,
然后访问第一个一级表指针,
通过结构_HANDLE_TABLE_ENTRY访问第一项为,
由于是二级表,我们通过(二级表索引*一级表最大表数(256)+一级表索引)*4计算得出(0*256+0)*4=0,
因为0对应无效句柄,所以该项对象值为null,我们可以通过Object指向的地址是否有效来判断对象是否有效,
然后我们访问一级表的第二项,
在_HANDLE_TABLE_ENTRY中的Object成员中,低三位作为标志位,掩掉低三位就是对象地址,
(PS:在进程中的_HANDLE_TABLE_ENTRY中的Object成员记录的是ObjectHander地址,而在PspCidTable记录的是对象body地址,所以遍历进程中的对象时,需要Object成员的地址加上sizeof(ObjectHander)的大小)
因为该对象是一个进程对象,对应结构为_EPROCESS,
在_PROCESS结构中偏移0x2e0的成员为ImageFileName,访问后发现进程对象对应的是System,
我们计算PID=(二级表索引*一级表最大表数(256)+一级表索引)*4=(0*256+1)*4=4,所以刚好对应PID为4的System进程,表示计算方式没有问题。
通过遍历表可以枚举出进程,因为该表还记录着线程对象,所以我们遍历时候需要判断是否为进程对象,通过未公开的函数ObGetObjectType获取对象类型地址,然后跟导出变量PsProcessType中的值对比。
PspCidTable地址可以通过硬编码搜索PsLookupProcessByProcessId函数获取。
PULONG_PTR GetPspCidTable()
{
UNICODE_STRING uStrName;
ULONG_PTR pPsLookupProcessByProcessId;
ULONG_PTR i;
PULONG_PTR PspCidTable;
RtlInitUnicodeString(&uStrName, L"PsLookupProcessByProcessId");
pPsLookupProcessByProcessId = (ULONG_PTR)MmGetSystemRoutineAddress(&uStrName);
if (!pPsLookupProcessByProcessId)
{
return 0;
}
/*
fffff800`04147391 488bd1 mov rdx,rcx
fffff800`04147394 488b0dcdf0edff mov rcx,qword ptr [nt!PspCidTable (fffff800`04026468)]
*/
for (i = pPsLookupProcessByProcessId; i < pPsLookupProcessByProcessId + 0xff; i++)
{
if (*(PUCHAR)i == 0x48 && *(PUCHAR)(i + 1) == 0x8b && *(PUCHAR)(i + 2) == 0xd1)
{
PspCidTable = (LONG_PTR*)(*(LONG*)(i + 3 + 3) + 7 + 3 + i);
KdPrint(("PspCidTable:%p\n", PspCidTable));
return PspCidTable;
}
}
return 0;
}
遍历进程代码
VOID EnumProcess()
{
PULONG_PTR PspCidTable;
PHANDLE_TABLE HandleTable;
PHANDLE_TABLE_ENTRY TableLevel1,*TableLevel2;
PVOID ObjectType;
PVOID Object;
UINT32 i, j,k;
PspCidTable = GetPspCidTable();
if (!PspCidTable)
{
return ;
}
HandleTable = (PHANDLE_TABLE)*PspCidTable;
//表级
switch (HandleTable->TableCode & 0x3)
{
case 0:
TableLevel1 = (PHANDLE_TABLE_ENTRY)(HandleTable->TableCode & ~0x3);
KdPrint(("TableLevel1:%p\n", TableLevel1));
if (!TableLevel1)
{
break;
}
for (i = 0; i < 0x1000 / 16; i++)
{
if (TableLevel1[i].Object && MmIsAddressValid(TableLevel1[i].Object))
{
//掩掉低三位
Object = (PVOID)((ULONG_PTR)(TableLevel1[i].Object) & ~0x7);
ObjectType = ObGetObjectType(Object);
if (ObjectType == *PsProcessType)
{
KdPrint(("PID:%d,Object:%p\n",i*4,Object));
}
}
}
break;
case 1:
TableLevel2 = (PHANDLE_TABLE_ENTRY*)(HandleTable->TableCode & ~0x3);
KdPrint(("TableLevel2:%p\n", TableLevel2));
for (i = 0; i < (HandleTable->NextHandleNeedingPool / (0x1000 / 16 * 4)); i++)
{
TableLevel1 = TableLevel2[i];
KdPrint(("TableLevel1:%p\n", TableLevel1));
if (!TableLevel1)
{
break;
}
for (j = 0; j < 0x1000 / 16; j++)
{
if (TableLevel1[j].Object && MmIsAddressValid(TableLevel1[j].Object))
{
//掩掉低三位
Object = (PVOID)((ULONG_PTR)(TableLevel1[j].Object) & ~0x7);
ObjectType = ObGetObjectType(Object);
if (ObjectType == *PsProcessType)
{
KdPrint(("PID:%d,Object:%p,Name:%s\n",(i*(0x1000/16)+j)*4,Object,PsGetProcessImageFileName(Object)));
}
}
}
}
break;
default:
break;
}
}