(一)介绍
PspCidTable是一个句柄表,但是这个句柄表不同于普通的句柄表,它不是私有的存放的是系统中所有进程和线程的对象信息的句柄表,其索引是PID和CID。其格式和普通的句柄表完全一样,但也有不同之处:
- PspCidTable中存的是对象体(EProcess、EThread),而每个进程中私有的句柄表存放的是对象头(Objece_Header)
- PspCidTable是一个独立的句柄表,而进程中私有的句柄表是通过双向链表链接起来的。
ZwQuerySystemInformation枚举系统句柄表时,就是通过每个进程的双向链表来枚举的。
下面时Reactos源码
PspCidTable结构
0: kd> dd PspCidTable
84960bc4 8e4010a8 00000000 80000020 00000101
84960bd4 800002cc 80000024 00000000 00000113
84960be4 00000000 00000000 849121f0 00000000
84960bf4 00000000 00000000 00000000 00000008
84960c04 00000000 84960c08 84960c08 00000000
84960c14 00000000 00000000 00000000 00000000
84960c24 00000000 807cdc38 807d1c38 00000000
84960c34 00000000 00000000 00000000 00000001
0: kd> dt _HANDLE_TABLE
nt!_HANDLE_TABLE
+0x000 TableCode : Uint4B
+0x004 QuotaProcess : Ptr32 _EPROCESS
+0x008 UniqueProcessId : Ptr32 Void
+0x00c HandleLock : _EX_PUSH_LOCK
+0x010 HandleTableList : _LIST_ENTRY
+0x018 HandleContentionEvent : _EX_PUSH_LOCK
+0x01c DebugInfo : Ptr32 _HANDLE_TRACE_DEBUG_INFO
+0x020 ExtraInfoPages : Int4B
+0x024 Flags : Uint4B
+0x024 StrictFIFO : Pos 0, 1 Bit
+0x028 FirstFreeHandle : Uint4B
+0x02c LastFreeHandleEntry : Ptr32 _HANDLE_TABLE_ENTRY
+0x030 HandleCount : Uint4B
+0x034 NextHandleNeedingPool : Uint4B
+0x038 HandleCountHighWatermark : Uint4B
0: kd> dt _HANDLE_TABLE 84960bc4
nt!_HANDLE_TABLE
+0x000 TableCode : 0x8e4010a8
+0x004 QuotaProcess : (null)
+0x008 UniqueProcessId : 0x80000020 Void
+0x00c HandleLock : _EX_PUSH_LOCK
+0x010 HandleTableList : _LIST_ENTRY [ 0x800002cc - 0x80000024 ]
+0x018 HandleContentionEvent : _EX_PUSH_LOCK
+0x01c DebugInfo : 0x00000113 _HANDLE_TRACE_DEBUG_INFO
+0x020 ExtraInfoPages : 0n0
+0x024 Flags : 0
+0x024 StrictFIFO : 0y0
+0x028 FirstFreeHandle : 0x849121f0
+0x02c LastFreeHandleEntry : (null)
+0x030 HandleCount : 0
+0x034 NextHandleNeedingPool : 0
+0x038 HandleCountHighWatermark : 0
PspCidTable结构也是HANDLE_TABLE类型,其中最重要的就是第一字段TableCode,它是一个指针,它的高30位指向一个地址,存放句柄表结构,是一个4K大小的一个页面。低2位是代表当前句柄表的层数,00代表一层结构,01代表二层结构,10代表三层结构。
- 最低层句柄表存放的项数有 4096/8 = 512 个,其中第一项做审计用,所以最多有 511个有效项。
- 中间层句柄表存放的页表指针数有 4096/4 = 1024 个。
- WindowsXp 及以后限定了每个进程句柄表存储的句柄表项不得超过:2^24=16777216,所以最高层最多有 2^24/(1024*512) = 32 项。
- 二级表最大可以存放 511*1024 = 523264 个对象引用,没有特殊情况一般来说已经够了,所以我们一般只能观察到一层,两层句柄表。
(二)枚举PspCidTable句柄表信息
如何去获取PspCidTable呢?可以通过从几个函数中搜索特征码来找到地址。
PsLookupProcessThreadByCid()
PsLookupProcessByProcessId()
PsLookupThreadByThreadId()
从PsLookupProcessByProcessId中获取 ,偏移为 0x1e+2,得到PspCidTable的值。
获取得到PspCidTable以后就可以开始枚举了。
NTSTATUS MaGetPspCidTable(PVOID InputBuffer, ULONG InputBufferLength, PVOID OutputBuffer, ULONG OutputBufferLength, ULONG * RertunLength)
{
NTSTATUS Status = STATUS_UNSUCCESSFUL;
PPSPCIDTABLE_INFO_CONTEXT PspCidTableContext = (PPSPCIDTABLE_INFO_CONTEXT)OutputBuffer;
ULONG Count = (OutputBufferLength - sizeof(PSPCIDTABLE_INFO_CONTEXT)) / sizeof(PSPCIDTABLEINFO);
//检查参数
if (!InputBuffer ||
!OutputBuffer ||
InputBufferLength != sizeof(OPERATION_TYPE) ||
OutputBufferLength < sizeof(PSPCIDTABLE_INFO_CONTEXT))
{
return STATUS_INVALID_PARAMETER;
}
ULONG PspCidTableAddress = GetPspCidTableAddress();
/* PspCidTableAddress 0x84960bc4
2: kd> dd 0x84960bc4
84960bc4 8e4010a8 00000000 80000020 00000101
84960bd4 800002cc 80000024 00000000 00000113
84960be4 00000000 00000000 849121f0 00000000
84960bf4 00000000 00000000 00000000 00000008
84960c04 00000000 84960c08 84960c08 00000000
84960c14 00000000 00000000 00000000 00000000
84960c24 00000000 807cdc38 807d1c38 00000000
84960c34 00000000 00000000 00000000 00000001
*/
if (PspCidTableAddress == 0)
{
return Status;
}
ULONG HandleTable = *(PULONG)PspCidTableAddress;
ULONG TableCode = *(PULONG)HandleTable;
ULONG Flag = TableCode & 3; //获取TableCode的最后两位 来确定层数
/*
HandleTable 0x8e4010a8
2: kd> dd 8e4010a8
8e4010a8 95d2a001 00000000 00000000 00000000
8e4010b8 8e4010b8 8e4010b8 00000000 00000000
8e4010c8 00000000 00000001 00000914 95d2b830
8e4010d8 000002c6 00001000 000002fa 0020006f
8e4010e8 00010209 6e54624f 06030201 6944624f
8e4010f8 00000000 8784e570 06b87a1c 00200000
8e401108 06060203 6d4e624f 006f0049 006f0043
8e401118 0070006d 0065006c 00690074 006e006f
TableCode 0x95d2a001
Flag = TableCode & 3 = 1
*/
PspCidTableContext->Flag = Flag;
TableCode &= 0xFFFFFFFC; //如果系统采用了两层或者三层的时候,
//TableCode就不是句柄表的地址了,把这个值后两位置为0之后,
//则是一个指向多层表的指针。
// TableCode 0x95d2a000
switch (Flag)
{
case 0: //1层结构
Operation1(TableCode, PspCidTableContext,Count);
break;
case 1: //2层结构
Operation2(TableCode, PspCidTableContext,Count);
break;
case 2: //3层结构
Operation3(TableCode, PspCidTableContext,Count);
break;
default:
break;
}
if (Count >= PspCidTableContext->NumberOfInfo)
{
Status = STATUS_SUCCESS;
}
else
{
Status = STATUS_BUFFER_TOO_SMALL;
}
return Status;
}
ULONG Operation1(ULONG Address, PPSPCIDTABLE_INFO_CONTEXT PspCidTableContext, ULONG Count)
{
LONG Object = 0;
ULONG i = PspCidTableContext->NumberOfInfo;
ULONG ItemCount = 511;
ULONG ObjectID = 0;
ULONG ObjectType = 0;
ULONG HandleTableAddress = Address + 8;
/*
HandleTableAddress 0x8e404000
3: kd> dd 0x8e404000
8e404000 00000000 fffffffe 878dd799 00000000
8e404010 878dd4c1 00000000 8793c431 00000000
8e404020 8793c921 00000000 87938d49 00000000
8e404030 87938931 00000000 8792cd49 00000000
8e404040 8792ca71 00000000 87928d49 00000000
8e404050 87928a71 00000000 87914d49 00000000
8e404060 87914a71 00000000 87904d49 00000000
8e404070 87904a71 00000000 878ecd49 00000000
至于0,句柄值为0×0000代表是NULL
刚好_HANDLE_TABLE_ENTRY的第0个表项为无效值
句柄值为0×0004有效
其实这个对象就是pid为4的system.exe进程。 EProcess 是 878dd798
记事本的PID为3708(十进制),应当位于第3708/4=927 个表项,
我们每一个1级索引表能容纳512个表项,毋庸置疑,
PID3708 应该在第2个二级索引指向的1级索引的第927-512=415=0x19F个表项(每个表项8Byte)
*/
do
{
if (Count > i)
{
Object = *(PULONG)HandleTableAddress;
Object = Object & 0xFFFFFFF8; //Object最后三位置0,Object即为EPROCESS的地址
if (Object)
{
PspCidTableContext->PspCidTableInfo[i].Object = Object;
ObjectType = MaGetObjectType(Object);
if (ObjectType == *PsProcessType)
{
PspCidTableContext->PspCidTableInfo[i].ObjectType = 0;
ObjectID = *(PULONG)(Object + GetGlobalVariable(E_UNIQUE_PROCESS_IDENTIFY_1));
}
if (ObjectType == *PsThreadType)
{
PspCidTableContext->PspCidTableInfo[i].ObjectType = 1;
CLIENT_ID ID = { 0 };
ID = *(PCLIENT_ID)(Object + GetGlobalVariable(E_CID_OFFSET));
// +0x22c Cid : _CLIENT_ID
ObjectID = ID.UniqueThread;
}
PspCidTableContext->PspCidTableInfo[i].ObjectID = ObjectID;
i++;
}
}
PspCidTableContext->NumberOfInfo++;
HandleTableAddress += 8;
} while ( --ItemCount>0);
return 0;
}
ULONG Operation2(ULONG Address, PPSPCIDTABLE_INFO_CONTEXT PspCidTableContext, ULONG Count)
{
do {
Operation1(*(PULONG)Address, PspCidTableContext, Count);
Address += 4;
} while ((*(PULONG)Address) != 0);
return 0;
}
ULONG Operation3(ULONG Address, PPSPCIDTABLE_INFO_CONTEXT PspCidTableContext, ULONG Count)
{
do {
Operation2(*(PULONG)Address, PspCidTableContext,Count);
Address += 4;
} while ((*(PULONG)Address) != 0);
return 0;
}
ULONG GetPspCidTableAddress()
{
ULONG PspCidTableAddress = 0;
//搜索PsLookUpProcessByProcessId
UNICODE_STRING v1;
RtlInitUnicodeString(&v1, L"PsLookupProcessByProcessId");
PVOID lpPsLookupProcessByProcessId = MmGetSystemRoutineAddress(&v1);
if (lpPsLookupProcessByProcessId == NULL)
{
return PspCidTableAddress;
}
//Win7 x86的偏移位置
const ULONG PSPCIDTABLE_OFFSEET = 0x1E + 0x02;
PspCidTableAddress = *(ULONG*)((ULONG)lpPsLookupProcessByProcessId + PSPCIDTABLE_OFFSEET);
/*
2: kd> uf PsLookupProcessByProcessId
nt!PsLookupProcessByProcessId:
84a7a950 8bff mov edi,edi
.......
84a7a96e 8b3dc40b9684 mov edi,dword ptr [nt!PspCidTable (84960bc4)]
84a7a974 e83757feff call nt!ExMapHandleToPointer (84a600b0)
PspCidTableAddress = 0x84960bc4
*/
return PspCidTableAddress;
}
(三)结果