ObReferenceObjectByHandle / PspCidTable
参考:
句柄表详解 https://zhuanlan.zhihu.com/p/27089753
PspCidTable 完全解读 https://blog.csdn.net/whatday/article/details/17189093
1. Handle值作为索引,取其高30位,相当于 Handle >> 2 或是除以4,拿这个值去索引表里去找对应的Object
typedef struct _EXHANDLE {
union {
struct {
ULONG TagBits : 2;
ULONG Index : 30; // 高30位
};
HANDLE GenericHandleOverlay;
ULONG_PTR Value;
};
} EXHANDLE, *PEXHANDLE;
EXHANDLE LocalHandle;
LocalHandle.GenericHandleOverlay = Handle; //
Index = LocalHandle.Index; // 可以看到真正的索引值位handle的高30位, Index = Handle >> 2
2. 索引表 _Handle_Table -> TableCode, 有3种索引表,根据TableCode后两位的值来判断,0表示是一维数组,1表示是二维
typedef struct _HANDLE_TABLE_ENTRY { // 大小为8
PVOID Object; // 找的就是这个 Entry-> Object, 是一个 _Object_header 结构
ACCESS_MASK GrantedAccess;
} HANDLE_TABLE_ENTRY, *PHANDLE_TABLE_ENTRY;
PHANDLE_TABLE_ENTRY Entry;
typedef HANDLE_TABLE_ENTRY *L1P;
typedef volatile L1P *L2P;
typedef volatile L2P *L3P;
L1P TableLevel1;
L2P TableLevel2;
L3P TableLevel3;
Index = Handle.Index;
CapturedTable = *(volatile ULONG_PTR *) &HandleTable->TableCode;
TableLevel = (ULONG)(CapturedTable & LEVEL_CODE_MASK); // LEVEL_CODE_MASK = 0x3, 判断是几维数组
CapturedTable = CapturedTable & ~LEVEL_CODE_MASK; // 索引表的低2位清0
switch (TableLevel) {
case 0: // 一维的
TableLevel1 = (L1P) CapturedTable;
Entry = &(TableLevel1[Index]);
break;
case 1: // 二维的
TableLevel2 = (L2P) CapturedTable; // xp x86 PAGE_SIZE 为4k
i = Index / LOWLEVEL_COUNT; // #define LOWLEVEL_COUNT (PAGE_SIZE / sizeof(HANDLE_TABLE_ENTRY))
j = Index % LOWLEVEL_COUNT;
Entry = &(TableLevel2[i][j]);
break;
case 2:
TableLevel3 = (L3P) CapturedTable;
// #define MIDLEVEL_COUNT (PAGE_SIZE / sizeof(PHANDLE_TABLE_ENTRY))
i = Index / (MIDLEVEL_THRESHOLD); // #define MIDLEVEL_THRESHOLD (MIDLEVEL_COUNT * LOWLEVEL_COUNT)
RemainingIndex = Index - i * MIDLEVEL_THRESHOLD;
j = RemainingIndex / LOWLEVEL_COUNT;
k = RemainingIndex % LOWLEVEL_COUNT;
Entry = &(TableLevel3[i][j][k]);
break;
default:
...
}
3. 获取的Object的低3位清0,
// ObjectTableEntry 为上面 根据索引值在索引表找 到的一个HANDLE_TABLE_ENTRY结构
// ObjectTableEntry->Object 是个 _OBJECT_HEADER 结构, 还要把低3位清0,不然找Body的偏移可能不正确了
// OBJ_HANDLE_ATTRIBUTES = 0x1 | 0x2 | 0x4
(POBJECT_HEADER)(((ULONG_PTR)(ObjectTableEntry->Object)) & ~OBJ_HANDLE_ATTRIBUTES);
4. PspCidTable是一个_Handle_Table,找法和上面类似,区别是上面找出来的是Object,在PspCidTable里找出来的是eprocess
lkd> dd pspcidtable
8056a760 e1000c48 00000002 00000002 00000000
===
lkd> dt _handle_table e1000c48
nt!_HANDLE_TABLE
+0x000 TableCode : 0xe329a001 // 索引表,后2位为1,表示为二维数组
+0x004 QuotaProcess : (null)
+0x008 UniqueProcessId : (null)
...
===
lkd> dd 0xe329a000
e329a000 e1005000 e329b000 00000000 00000000 // 第一个表的地址 e1005000
// 第二个表的地址 e329b000
二维数组,xp32位每页大小4096,每个_HANDLE_TABLE_ENTRY大小为8,每个表最多容纳4096/8 = 512(0x200)项, 根据之前提到的高30位才是索引号
所以第1个表能容纳的pid最大值为0x800 == 0x200 << 2 或者是乘以4,pid值大于0x800的就在第二个表里了,两个表合起来最大的pid值也才0x1000(4096)xp系统
在win10系统,pid有10000多的,估计每页要大于0x1000了,这样一张表能容纳更多的项
// i = Index / LOWLEVEL_COUNT; LOWLEVEL_COUNT == 0x200
// j = Index % LOWLEVEL_COUNT;
// Entry = &(TableLevel2[i][j]);
以system进程为例,pid为4,所以索引值就是1了,【 i = 1 / 0x200 == 0, j = 1 % 0x200 == 1 】
Entry = &(TableLevel2[0][1]) == e1005000 +1*8
lkd> dd e1005000 +1*8
e1005008 8a17e5f1 00000000 8a17e371 00000000 // 8a17e5f1 后三位清0,变成 8a17e5f0
lkd> dt _eprocess 8a17e5f0
nt!_EPROCESS
+0x000 Pcb : _KPROCESS
+0x06c ProcessLock : _EX_PUSH_LOCK
...
+0x0c4 ObjectTable : 0xe1002de0 _HANDLE_TABLE // 每个进程都有一个ObjectTable,保存着自己使用的的一些Object
// 譬如在程序A里OpenProcess打开一个进程B,通过返回的那个handle
// 在A的这个ObjectTable里找到B的Object
+0x0c8 Token : _EX_FAST_REF
...
+0x160 PhysicalVadList : _LIST_ENTRY [ 0x8a17e750 - 0x8a17e750 ]
+0x168 PageDirectoryPte : _HARDWARE_PTE
+0x168 Filler : 0
+0x170 Session : (null)
+0x174 ImageFileName : [16] "System" // "System" 进程
+0x184 JobLinks : _LIST_ENTRY [ 0x0 - 0x0 ]
+0x18c LockedPagesList : (null)
+0x190 ThreadListHead : _LIST_ENTRY [ 0x8a17e59c - 0x882ad24c ]
+0x198 SecurityPort : 0xe1be29a0 Void
+0x19c PaeTop : (null)
...
换一个大于0x800的pid测试,电脑上windbg pid==2832(0xb10),索引值为0xb10>>2 == 2c4, 【 i= 2c4/0x200 == 1, j = 2c4%0x200 == 0xc4 】
Entry = &(TableLevel2[1][0xc4]) == e329b000 + 0xc4 * 8
lkd> dd e329b000 + 0xc4*8 // == e329a000 + 0x2c4*8
e329b620 8841e6f1 00000000 00000000 00000cb4
===
lkd> dt _eprocess 8841e6f0
nt!_EPROCESS
+0x000 Pcb : _KPROCESS
...
+0x0c4 ObjectTable : 0xe44d08f8 _HANDLE_TABLE
...
+0x170 Session : 0xf79e5000 Void
+0x174 ImageFileName : [16] "windbg.exe" // "windbg.exe" 进程
+0x184 JobLinks : _LIST_ENTRY [ 0x0 - 0x0 ]
+0x18c LockedPagesList : (null)
+0x190 ThreadListHead : _LIST_ENTRY [ 0x8824facc - 0x8a07124c ]
+0x198 SecurityPort : (null)
5. 在进程A中打开进程B,根据返回的handle值,在A的_HANDLE_TABLE中找到B的Object
例子,在pvoid.exe中调用OpenProcess打开calc.exe,返回句柄0x38, 索引值为 0x38>>2 == 0xE
// 应用层_HANDLE_TABLE 保存在每个进程的_eprocess结构中
// 内核层_HANDLE_TABLE 保存在一个全局变量里
lkd> !process 0 0 pvoid.exe
PROCESS 883ae920 SessionId: 0 Cid: 0c30 Peb: 7ffdb000 ParentCid: 0790 // cid=0xc30,拿这个值去上面pspcidtable里找
// index = 0xc30>>2 == 0x30c, i=1, j=0x10c
// entry = e329b000 + 0x10c *8
// lkd> dd e329b000+0x10c*8
// e329b860 883ae921 00000000 00000000 00000c50 后3位清0 和PROCESS值一样
DirBase: 8eb58000 ObjectTable: e46114a0 HandleCount: 24. // "pvoid.exe" 进程的_HANDLE_TABLE 为 e46114a0
Image: pvoid.exe
===
lkd> dt _handle_table e46114a0
nt!_HANDLE_TABLE
+0x000 TableCode : 0xe23eb000 // 后2位为0,1维数组
+0x004 QuotaProcess : 0x883ae920 _EPROCESS
+0x008 UniqueProcessId : 0x00000c30 Void
...
===
lkd> dd 0xe23eb000 + 0xE*8
e23eb070 882d6b81 001f0fff 8840a6b1 001f0003 // 后三位清0,882d6b80, _object_header 结构
===
lkd> dt _object_header 882d6b80
nt!_OBJECT_HEADER
...
+0x014 SecurityDescriptor : 0xe194e42a Void
+0x018 Body : _QUAD // 这里指向"calc.exe"的eprocess
===
lkd> dt _eprocess 882d6b80 + 0x18 // eprocess == 882d6b98 这里打开的是进程,则偏移0x18为一个eprocess结构,
// 有人测试出来偏移为+0x17,就是因为后3位没清0
nt!_EPROCESS
+0x000 Pcb : _KPROCESS
...
+0x168 PageDirectoryPte : _HARDWARE_PTE
+0x168 Filler : 0
+0x170 Session : 0xf79e5000 Void
+0x174 ImageFileName : [16] "calc.exe" // "calc.exe"
+0x184 JobLinks : _LIST_ENTRY [ 0x0 - 0x
...
===
lkd> !process 0 0 calc.exe
PROCESS 882d6b98 SessionId: 0 Cid: 0238 Peb: 7ffdf000 ParentCid: 0790 // PROCESS 882d6b98, 和上面一致
DirBase: a70ee000 ObjectTable: e3a8e5f0 HandleCount: 49.
Image: calc.exe
6. 引申,CreateFile打开一个文件,则获得的Object + 0x18 为一个_file_object结构了,上面是打开进程,所以是_eprocess,对象类型不同
打开 "a.txt"
HANDLE hOpenFile = (HANDLE)CreateFile(L"E:\\a.txt", GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, NULL, NULL);
又是返回0x38,呵呵
lkd> !process 0 0 pvoid.exe
Unable to read selector for PCR for processor 0
PROCESS 88461780 SessionId: 0 Cid: 0d54 Peb: 7ffd9000 ParentCid: 0790 // cid: 0d54
DirBase: 50ff9000 ObjectTable: e56f9418 HandleCount: 14. // ObjectTable: e56f9418
Image: pvoid.exe
===
lkd> !handle 38 2 0d54 // 直接先显示点0x38句柄的信息看看
Searching for Process with Cid == d54
PROCESS 88461780 SessionId: 0 Cid: 0d54 Peb: 7ffd9000 ParentCid: 0790
DirBase: 50ff9000 ObjectTable: e56f9418 HandleCount: 14.
Image: pvoid.exe
Handle table at e3f39000 with 14 entries in use
0038: Object: 8825c8e0 GrantedAccess: 00120089 Entry: e3f39070
Object: 8825c8e0 Type: (8a1b0730) File // Object: 8825c8e0 指向的就是_file_object, 类型 Type: (8a1b0730) File
ObjectHeader: 8825c8c8 (old version)
===
继续按照前面的方法在HandleTable里找
lkd> dt _handle_table e56f9418
nt!_HANDLE_TABLE
+0x000 TableCode : 0xe3f39000
...
lkd> dd 0xe3f39000 + 0xe*8
e3f39070 8825c8c9 00120089 00000000 00000040 // 低3位清0 ,变为8825c8c8, +0x18 = 8825c8e0, 和上面!handle命令打印出来的一致
e3f39080 00000000 00000044 00000000 00000048
有了这个_file_object能干点啥呢? _file_object->vpb->device,找到底层设备对象,之后ReadFile内部就是给这个deviceobject发送IRP_MJ_READ
DeviceIoControl也是类似,
lkd> dt _file_object 8825c8e0
nt!_FILE_OBJECT
+0x000 Type : 0n5
+0x002 Size : 0n112
+0x004 DeviceObject : 0x8a0e3898 _DEVICE_OBJECT
+0x008 Vpb : 0x8a0f8188 _VPB // _vpb
+0x00c FsContext : 0xe58e0d90 Void
+0x010 FsContext2 : 0xe58e0ee8 Void
...
+0x02c Flags : 0x40042
+0x030 FileName : _UNICODE_STRING "\a.txt" // 打开的那个文件
+0x038 CurrentByteOffset : _LARGE_INTEGER 0x0
...
lkd> dt _vpb 0x8a0f8188
nt!_VPB
+0x000 Type : 0n10
+0x002 Size : 0n88
+0x004 Flags : 1
+0x006 VolumeLabelLength : 4
+0x008 DeviceObject : 0x896665b8 _DEVICE_OBJECT // DeviceObject
+0x00c RealDevice : 0x8a0e3898 _DEVICE_OBJECT // RealDevice
+0x010 SerialNumber : 0x8b0e472a
+0x014 ReferenceCount : 0x182
+0x018 VolumeLabel : [32] 0x6587
lkd> !devstack 0x896665b8 // DeviceObject
!DevObj !DrvObj !DevExt ObjectName
894cbb70 \Driver\qutmdserv 894cbc28
894b4ee8 \FileSystem\FltMgr 894b4fa0
> 896665b8 \FileSystem\Ntfs 89666670
lkd> !devstack 0x8a0e3898 // RealDevice
!DevObj !DrvObj !DevExt ObjectName
896caae0 \Driver\VolSnap 896cab98
> 8a0e3898 \Driver\Ftdisk 8a0e3950 HarddiskVolume3
!DevNode 8a0e3510 :
DeviceInst is "STORAGE\Volume\1&30a96598&0&SignatureB12CB12COffset1A40112000LengthB031EE000"
ServiceName is "VolSnap"
看的出来与文件系统有关

浙公网安备 33010602011771号