首页
社区
课程
招聘
[原创]x64内核实验7.1补充-句柄表
2023-11-12 16:13 6664

[原创]x64内核实验7.1补充-句柄表

2023-11-12 16:13
6664

x64内核实验7.1补充-句柄表

全局句柄表

全局句柄表是一个内核中存储进程线程内核对象引用的一个结构,句柄表是啥能干啥我就不说了,下面我们来看一下我们经常用的一个导出函数PsLookupProcessByProcessId

首先我们在导出表里找一下这个函数,跟进去
图片描述

如果大家有xp下搜索全局句柄表的经验的话会记得在win732或xp时候我们要找的PspCidTable就在这个函数里被引用了,不过在我这个win10-22h2下面是如下的引用方式
图片描述
跟进去
图片描述

可以看到是在PspReferenceCidTableEntry这个函数中直接引用的全局句柄表结构体地址
我们也可以通过从PsCidTable反过来找引用位置来确定自己的特征码搜索位置和方式

  1. out + t 全局搜索文本PspCidTable
  2. 找到PspCidTable的数据地址然后 ctrl + x查看交叉引用
  3. 在在交叉引用的函数里找到自己想要的函数即可
    这里就不带大家一步一步看了

根据上面的引用方式我们可以确定下来我们的一个搜索方法,这是最直观也最简单的搜索方法,方便大家理解这个过程

  1. 找到PsLookupProcessByProcessId函数地址
  2. 搜索第一个E8,他后面跟的4个字节就是PspReferenceCidTableEntry函数的偏移,我门用e8的地址+这个偏移+4即可
  3. 在PspReferenceCidTableEntry中搜索4C 8B 35(或者4C 8B)他后面的四个字节(或者多跑一个字节然后的四个字节)就是偏移,我们依旧用 35的地址+4+偏移即可

下面我们来了解一下全局句柄表的结构以及遍历方式
PspCidTable: 地址是一个结构体 HANDLE_TABLE—>TableCode指向一张全局句柄表。
TableCode:地址的低2位记录这个表是一级句柄表,还是二级或者三级;00表示一级,01表示二级,10表示三级;
如果是一级:那么TableCode指向的表直接是内核地址;
如果是二级:那么TableCode指向的是一个存储了句柄表地址的表,再由这个表指向的一个表这个表里存储的地址指向内核地址.
如果是三级:则一层一层下去。一般不会不存在三级。

下面我们到环境里找一个看看,我这边启动了一个dbgview他的pid是1824
图片描述
我先来说一下win10-22h2和win7下的变化吧
在我这个环境上现在的句柄表是这样计算的

  1. 找到PsCidTable他指向了一个结构体_HANDLE_TABLE,这个结构体里的+0x8 TableCode的位置存储了句柄表的地址和属性的与运算结果,这个数据的后2位是属性如果这两位是0则是1级句柄表1的话就是二级否则是3级这都和win7时候一样
  2. TableCode去掉一层一层的表之后指向的是一个数组里面存储的是一个一个的_HANDLE_TABLE_ENTRY结构体
  3. _HANDLE_TABLE_ENTRY这个结构体和win7x64时候不一样了他现在的对象地址是存储在+0x0 ObjectPointerBits的位置占据了44个字节,如果要把他转换为对象地址的话需要做如下运算
  4. Object = Entry->ObjectPointerBits;
  5. Object <<= 4;
  6. Object |= 0xFFFF000000000000;
  7. 这样得到的Object就是我们的进程线程对象地址了
    下面我们来验证一下

首先我们的pid是1824,跟win7时候一样我们拿1824/4 = 456(0x1c8)

456/256=1 456%256=200(0xc8)
说明我们在第二张表上的第c8个HANDLE_TABLE_ENTRY

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
1: kd> dq PspCidTable
fffff801`302fc5c8  ffffd08d`38a1ed00 ffffe081`b22bc920
fffff801`302fc5d8  ffffe081`b22b8f00 00000000`00000002
fffff801`302fc5e8  00000000`00000000 00010000`00001000
fffff801`302fc5f8  00000000`00000000 00009705`00000000
fffff801`302fc608  00000000`00000000 00000000`00000000
fffff801`302fc618  00000000`00000000 00000000`00000000
fffff801`302fc628  00000000`00000000 00000000`00000000
fffff801`302fc638  ffffe081`b22fdae0 fffff801`30646000
1: kd> dt _HANDLE_TABLE ffffd08d`38a1ed00
ntdll!_HANDLE_TABLE
   +0x000 NextHandleNeedingPool : 0x3400
   +0x004 ExtraInfoPages   : 0n0
   +0x008 TableCode        : 0xffffd08d`3c3c6001
   +0x010 QuotaProcess     : (null)
   +0x018 HandleTableList  : _LIST_ENTRY [ 0xffffd08d`38a1ed18 - 0xffffd08d`38a1ed18 ]
   +0x028 UniqueProcessId  : 0
   +0x02c Flags            : 1
   +0x02c StrictFIFO       : 0y1
   +0x02c EnableHandleExceptions : 0y0
   +0x02c Rundown          : 0y0
   +0x02c Duplicated       : 0y0
   +0x02c RaiseUMExceptionOnInvalidHandleClose : 0y0
   +0x030 HandleContentionEvent : _EX_PUSH_LOCK
   +0x038 HandleTableLock  : _EX_PUSH_LOCK
   +0x040 FreeLists        : [1] _HANDLE_TABLE_FREE_LIST
   +0x040 ActualEntry      : [32]  ""
   +0x060 DebugInfo        : (null)
1: kd> dq ffffd08d`3c3c6000
ffffd08d`3c3c6000  ffffd08d`38ab2000 ffffd08d`3c3c7000
ffffd08d`3c3c6010  ffffd08d`3c8cb000 ffffd08d`3d1f9000
ffffd08d`3c3c6020  ffffd08d`3db2e000 ffffd08d`3e47b000
ffffd08d`3c3c6030  ffffd08d`3f129000 ffffd08d`3db2d000
ffffd08d`3c3c6040  ffffd08d`40efe000 ffffd08d`460d1000
ffffd08d`3c3c6050  ffffd08d`40cff000 ffffd08d`3d238000
ffffd08d`3c3c6060  ffffd08d`67cff000 00000000`00000000
ffffd08d`3c3c6070  00000000`00000000 00000000`00000000
1: kd> dt _HANDLE_TABLE_ENTRY ffffd08d`3c3c7000+0x10*0xc8
ntdll!_HANDLE_TABLE_ENTRY
   +0x000 VolatileLowValue : 0n-2269328954387013655
   +0x000 LowValue         : 0n-2269328954387013655
   +0x000 InfoTable        : 0xe081b956`e340dfe9 _HANDLE_TABLE_ENTRY_INFO
   +0x008 HighValue        : 0n0
   +0x008 NextFreeHandleEntry : (null)
   +0x008 LeafHandleValue  : _EXHANDLE
   +0x000 RefCountField    : 0n-2269328954387013655
   +0x000 Unlocked         : 0y1
   +0x000 RefCnt           : 0y0110111111110100 (0x6ff4)
   +0x000 Attributes       : 0y000
   +0x000 ObjectPointerBits : 0y11100000100000011011100101010110111000110100 (0xe081b956e34)
   +0x008 GrantedAccessBits : 0y0000000000000000000000000 (0)
   +0x008 NoRightsUpgrade  : 0y0
   +0x008 Spare1           : 0y000000 (0)
   +0x00c Spare2           : 0

可以看到ObjectPointerBits是0xe081b956e34
我们先是0xe081b956e34 << 4 = 0xe081b956e340
在0xe081b956e340 | 0xffff000000000000 = ffffe081b956e340
因为我们知道这是个process,所以直接dt一下看看0x5a8的位置
图片描述
可以看到这就是我们的这个dbgview进程

下面我们用驱动代码的方式来实现一下这个过程

我这里只做了给了固定的pid解析到process对象,大家可以根据我的代码拓展一下写一个遍历全局句柄表的方法
注意:全局句柄表里不止存了进程还有线程对象,如何去区分他们两个可以根据OBJECT_HEADER来分辨

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
#pragma once
#include <ntifs.h>
#include <windef.h>
 
typedef struct _HANDLE_TABLE_ENTRY
{
    union
    {
        LONG_PTR VolatileLowValue;
        LONG_PTR LowValue;
        PVOID InfoTable;
        LONG_PTR RefCountField;
        struct
        {
            ULONG_PTR Unlocked : 1;
            ULONG_PTR RefCnt : 16;
            ULONG_PTR Attributes : 3;
            ULONG_PTR ObjectPointerBits : 44;
        };
    };
    /*union
    {
        LONG_PTR HighValue;
        struct _HANDLE_TABLE_ENTRY* NextFreeHandleEntry;
        EXHANDLE LeafHandleValue;
        struct
        {
            ULONG32 GrantedAccessBits : 25;
            ULONG32 NoRightsUpgrade : 1;
            ULONG32 Spare1 : 6;
        };
        ULONG32 Spare2;
    };*/
} HANDLE_TABLE_ENTRY, * PHANDLE_TABLE_ENTRY;
 
typedef struct _HANDLE_TABLE_FREE_LIST
{
    ULONG_PTR FreeListLock;
    PHANDLE_TABLE_ENTRY FirstFreeHandleEntry;
    PHANDLE_TABLE_ENTRY lastFreeHandleEntry;
    LONG32 HandleCount;
    ULONG32 HighWaterMark;
    ULONG32 Reserved[8];
} HANDLE_TABLE_FREE_LIST, * PHANDLE_TABLE_FREE_LIST;
 
 
 
typedef struct _HANDLE_TABLE
{
    ULONG32 NextHandleNeedingPool;
    LONG32 ExtraInfoPages;
    ULONG_PTR TableCode;
    PEPROCESS QuotaProcess;
    LIST_ENTRY HandleTableList;
    ULONG32 UniqueProcessId;
    union
    {
        ULONG32 Flags;
        struct
        {
            BOOLEAN StrictFIFO : 1;
            BOOLEAN EnableHandleExceptions : 1;
            BOOLEAN Rundown : 1;
            BOOLEAN Duplicated : 1;
            BOOLEAN RaiseUMExceptionOnInvalidHandleClose : 1;
        };
    };
    ULONG_PTR HandleContentionEvent;
    ULONG_PTR HandleTableLock;
    union
    {
        HANDLE_TABLE_FREE_LIST FreeLists[1];
        BOOLEAN ActualEntry[32];
    };
    PVOID DebugInfo;
} HANDLE_TABLE, * PHANDLE_TABLE;
 
BOOLEAN TestLookupHandleTable(ULONG64 pid) {
    PUCHAR lookupAddr = (PUCHAR)PsLookupProcessByProcessId;
    PVOID E8Addr = NULL;
    ULONG32 offset = 0;
    for (size_t i = 0; i < 0x100; i++)
    {
        if (lookupAddr[i] == 0xe8) {
            E8Addr = &lookupAddr[i];
            offset = *(PULONG32)(&lookupAddr[i + 1]);
            break;
        }
    }
    if (E8Addr == NULL) {
        return FALSE;
    }
    PUCHAR PspReferenceCidTableEntryAddr = NULL;
    PspReferenceCidTableEntryAddr = (PUCHAR)((ULONG64)E8Addr + 4 + offset);
    PVOID X35Addr = NULL;
    PLONG64 PsCidTable = NULL;
    offset = 0;
    for (size_t i = 0; i < 0x150; i++)
    {
        if (PspReferenceCidTableEntryAddr[i] == 0x4c &&
            PspReferenceCidTableEntryAddr[i + 1] == 0x8b &&
            PspReferenceCidTableEntryAddr[i + 2] == 0x35) {
            offset = *(PULONG32)&PspReferenceCidTableEntryAddr[i + 3];
            X35Addr = &PspReferenceCidTableEntryAddr[i + 3];
            break;
        }
    }
 
    if (offset == 0) {
        return FALSE;
    }
    PsCidTable = (PLONG64)((ULONG64)X35Addr + 4 + offset);
 
    KdPrint(("[PsCidTable Addr] %p\r\n", PsCidTable));
 
 
    //我们这里只做演示所以默认就是二级句柄表
    PHANDLE_TABLE handleTable = PsCidTable[0];
    PULONG64 tableArr = (PULONG64)((ULONG64)handleTable->TableCode & 0xfffffffffffffff0);
    KdPrint(("[HANDLE_TABLE_ENTRY Addr] %p\r\n", tableArr));
    pid /= 4;
    int pages = pid / 256;
    int index = pid % 256;
    KdPrint(("[attributes]pages:%d---index:%d\r\n", pages, index));
    PHANDLE_TABLE_ENTRY handleTableEntry = (PHANDLE_TABLE_ENTRY)(tableArr[pages] + 0x10 * index);
    KdPrint(("[HANDLE_TABLE_ENTRY Addr] %p\r\n", handleTableEntry));
    ULONG64 pointer = handleTableEntry->ObjectPointerBits;
    PUCHAR proc = ((pointer << 4) | 0xffff000000000000);
    KdPrint(("[process name] %s\r\n", (PCHAR)(proc + 0x5a8)));
    return TRUE;
}

图片描述

私有句柄表

全局句柄表和私有句柄表的结构体是共用的,所以说结构是一模一样的,只不过私有句柄表里存储的不只是进程线程还有其他的例如事件、文件、管道等等
既然是私有句柄表根据我们平时的编码过程可以理解,这个表一定是跟进程相关,甚至是存储到了进程对象中

大家可以回到我们之前那篇进程对象的文章里看下,在0x570的位置有个ObjectTable,这就是我们私有句柄表的地址
可以在环境里找个进程看一下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
!process 0 0
。。。。
PROCESS ffff9f8f09d29080
    SessionId: 1  Cid: 1f28    Peb: 344ac2e000  ParentCid: 1084
    DirBase: 0ad95000  ObjectTable: ffffb0871ab44d80  HandleCount: 194.
    Image: dbgview64.exe
。。。。
 
1: kd> dt _HANDLE_TABLE ffffb0871ab44d80
ntdll!_HANDLE_TABLE
   +0x000 NextHandleNeedingPool : 0x400
   +0x004 ExtraInfoPages   : 0n0
   +0x008 TableCode        : 0xffffb087`1a102000
   +0x010 QuotaProcess     : 0xffff9f8f`09d29080 _EPROCESS
   +0x018 HandleTableList  : _LIST_ENTRY [ 0xffffb087`1ab4f558 - 0xffffb087`197b4918 ]
   +0x028 UniqueProcessId  : 0x1f28
   +0x02c Flags            : 0
   +0x02c StrictFIFO       : 0y0
   +0x02c EnableHandleExceptions : 0y0
   +0x02c Rundown          : 0y0
   +0x02c Duplicated       : 0y0
   +0x02c RaiseUMExceptionOnInvalidHandleClose : 0y0
   +0x030 HandleContentionEvent : _EX_PUSH_LOCK
   +0x038 HandleTableLock  : _EX_PUSH_LOCK
   +0x040 FreeLists        : [1] _HANDLE_TABLE_FREE_LIST
   +0x040 ActualEntry      : [32]  ""
   +0x060 DebugInfo        : (null)

解析这个表的过程跟解析我们全局句柄表是一模一样的

大家可以参考我们解析全局句柄表的过程写一个函数来解析目标进程的私有句柄表

第二大家看一下HandleTableList的位置,我们全局句柄表的这个位置指向的是自己,这里则是一个链,大家可以根据这个链看一下能够否遍历所有的进程出来

具体代码我后面找个时间推到git上大家拉下去看吧,距离上一篇已经隔了几周了,这几周太忙了估计后面也不会像国庆时候那样有充足的时间来整理博文,不过所有的内容最终都会有的,我会慢慢找时间一点一点写


[培训]《安卓高级研修班(网课)》月薪三万计划,掌握调试、分析还原ollvm、vmp的方法,定制art虚拟机自动化脱壳的方法

收藏
点赞3
打赏
分享
最新回复 (3)
雪    币: 19461
活跃值: (29125)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
秋狝 2023-11-12 22:53
2
1
感谢分享
雪    币: 1574
活跃值: (722)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
embarassedV 2023-11-17 16:29
3
0
正在学习内核驱动的小白,学习到了,非常感谢,希望能后续多交流多学习
雪    币: 113
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
goren 2024-1-26 20:01
4
0
大佬 得到handle_table_entry后怎么判断是进程还是线程啊
游客
登录 | 注册 方可回帖
返回