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"

看的出来与文件系统有关
posted @ 2020-12-01 14:48  一条小鳄鱼  阅读(470)  评论(0)    收藏  举报