首页
论坛
课程
招聘
[原创] 驱动开发:内核枚举PspCidTable句柄表
2022-10-16 16:13 13121

[原创] 驱动开发:内核枚举PspCidTable句柄表

2022-10-16 16:13
13121

在上一篇文章《驱动开发:内核枚举DpcTimer定时器》中我们通过枚举特征码的方式找到了DPC定时器基址并输出了内核中存在的定时器列表,本章将学习如何通过特征码定位的方式寻找Windows 10系统下面的PspCidTable内核句柄表地址。

 

首先引入一段基础概念;

  • 1.在windows下所有的资源都是用对象的方式进行管理的(文件、进程、设备等都是对象),当要访问一个对象时,如打开一个文件,系统就会创建一个对象句柄,通过这个句柄可以对这个文件进行各种操作。
  • 2.句柄和对象的联系是通过句柄表来进行的,准确来说一个句柄就是它所对应的对象在句柄表中的索引。
  • 3.通过句柄可以在句柄表中找到对象的指针,通过指针就可以对,对象进行操作。

PspCidTable 就是这样的一种表(内核句柄表),表的内部存放的是进程EPROCESS线程ETHREAD的内核对象,并通过进程PID线程TID进行索引,ID号以4递增,内核句柄表不属于任何进程,也不连接在系统的句柄表上,通过它可以返回系统的任何对象。

 

内核句柄表与普通句柄表完全一样,但它与每个进程私有的句柄表有以下不同;

  • 1.PspCidTable 中存放的对象是系统中所有的进程线程对象,其索引就是PIDTID
  • 2.PspCidTable 中存放的直接是对象体EPROCESS和ETHREAD,而每个进程私有的句柄表则存放的是对象头OBJECT_HEADER
  • 3.PspCidTable 是一个独立的句柄表,而每个进程私有的句柄表以一个双链连接起来。
  • 4.PspCidTable 访问对象时要掩掉低三位,每个进程私有的句柄表是双链连接起来的。

那么在Windows10系统中该如何枚举句柄表;

  • 1.首先找到PsLookupProcessByProcessId函数地址,该函数是被导出的可以动态拿到。
  • 2.其次在PsLookupProcessByProcessId地址中搜索PspReferenceCidTableEntry函数。
  • 3.最后在PspReferenceCidTableEntry地址中找到PspCidTable函数。

首先第一步先要得到PspCidTable函数内存地址,输入dp PspCidTable即可得到,如果在程序中则是调用MmGetSystemRoutineAddress取到。

 

图片描述

 

PspCidTable是一个HANDLE_TALBE结构,当新建一个进程时,对应的会在PspCidTable存在一个该进程和线程对应的HANDLE_TABLE_ENTRY项。在windows10中依然采用动态扩展的方法,当句柄数少的时候就采用下层表,多的时候才启用中层表或上层表。

 

接着我们解析ffffdc88-79605dc0这个内存地址,执行dt _HANDLE_TABLE 0xffffdc8879605dc0得到规范化结构体。

 

图片描述

 

内核句柄表分为三层如下;

  • 下层表:是一个HANDLE_TABLE_ENTRY项的索引,整个表共有256个元素,每个元素是一个8个字节长的HANDLE_TABLE_ENTRY项及索引,HANDLE_TABLE_ENTRY项中保存着指向对象的指针,下层表可以看成是进程和线程的稠密索引。
  • 中层表:共有256个元素,每个元素是4个字节长的指向下层表的入口指针及索引,中层表可以看成是进程和线程的稀疏索引。
  • 上层表:共有256个元素,每个元素是4个字节长的指向中层表的入口指针及索引,上层表可以看成是中层表的稀疏索引。

总结起来一个句柄表有一个上层表,一个上层表最多可以有256个中层表的入口指针,每个中层表最多可以有256个下层表的入口指针,每个下层表最多可以有256个进程和线程对象的指针。PspCidTable表可以看成是HANDLE_TBALE_ENTRY项的多级索引。

 

如上图所示TableCode是指向句柄表的指针,低二位(二进制)记录句柄表的等级:0(00)表示一级表,1(01)表示二级表,2(10)表示三级表。这里的 0xffffdc88-7d09b001 就说名它是一个二级表。

 

图片描述

 

一级表里存放的就是进程和线程对象(加密过的,需要一些计算来解密),二级表里存放的是指向某个一级表的指针,同理三级表存放的是指向二级表的指针。

 

x64 系统中,每张表的大小是 0x1000(4096),一级表中存放的是 _handle_table_entry 结构(大小 = 16),二级表和三级表存放的是指针(大小 = 8)

 

我们对 0xffffdc88-7d09b001 抹去低二位,输入dp 0xffffdc887d09b000 输出的结果就是一张二级表,里面存储的就是一级表指针。

 

图片描述

 

继续查看第一张一级表,输入dp 0xffffdc887962a000命令,我们知道一级句柄表是根据进程或线程ID来索引的,且以4累加,所以第一行对应id = 0,第二行对应id = 4。根据尝试,PID = 4的进程是System

 

图片描述

 

所以此处的第二行0xb281de28-3300ffa7就是加密后的System进程的EPROCESS结构,对于Win10系统来说解密算法(value >> 0x10) & 0xfffffffffffffff0是这样的,我们通过代码计算出来。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <Windows.h>
#include <iostream>
 
int _tmain(int argc, _TCHAR* argv[])
{
    std::cout << "hello lyshark.com" << std::endl;
 
    ULONG64 ul_recode = 0xb281de283300ffa7;
 
    ULONG64 ul_decode = (LONG64)ul_recode >> 0x10;
    ul_decode &= 0xfffffffffffffff0;
 
    std::cout << "解密后地址: " << std::hex << ul_decode << std::endl;
    getchar();
 
    return 0;
}

运行程序得到如下输出,即可知道System系统进程解密后的EPROCESS结构地址是0xffffb281de283300

 

图片描述

 

回到WinDBG调试器,输入命令dt _EPROCESS 0xffffb281de283300解析以下这个结构,输出结果是System进程。

 

图片描述

 

理论知识总结已经结束了,接下来就是如何实现枚举进程线程了,枚举流程如下:

  • 1.首先找到PspCidTable的地址。
  • 2.然后找到HANDLE_TBALE的地址。
  • 3.根据TableCode来判断层次结构。
  • 4.遍历层次结构来获取对象地址。
  • 5.判断对象类型是否为进程对象。
  • 6.判断进程是否有效。

这里先来实现获取PspCidTable函数的动态地址,代码如下。

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
#include <ntifs.h>
#include <windef.h>
 
// 获取 PspCidTable
// By: LyShark.com
BOOLEAN get_PspCidTable(ULONG64* tableAddr)
{
    // 获取 PsLookupProcessByProcessId 地址
    UNICODE_STRING uc_funcName;
    RtlInitUnicodeString(&uc_funcName, L"PsLookupProcessByProcessId");
    ULONG64 ul_funcAddr = MmGetSystemRoutineAddress(&uc_funcName);
    if (ul_funcAddr == NULL)
    {
        return FALSE;
    }
    DbgPrint("PsLookupProcessByProcessId addr = %p \n", ul_funcAddr);
 
    // 40 字节有 call(PspReferenceCidTableEntry)
    /*
    0: kd> uf PsLookupProcessByProcessId
        nt!PsLookupProcessByProcessId:
        fffff802`0841cfe0 48895c2418      mov     qword ptr [rsp+18h],rbx
        fffff802`0841cfe5 56              push    rsi
        fffff802`0841cfe6 4883ec20        sub     rsp,20h
        fffff802`0841cfea 48897c2438      mov     qword ptr [rsp+38h],rdi
        fffff802`0841cfef 488bf2          mov     rsi,rdx
        fffff802`0841cff2 65488b3c2588010000 mov   rdi,qword ptr gs:[188h]
        fffff802`0841cffb 66ff8fe6010000  dec     word ptr [rdi+1E6h]
        fffff802`0841d002 b203            mov     dl,3
        fffff802`0841d004 e887000000      call    nt!PspReferenceCidTableEntry (fffff802`0841d090)
        fffff802`0841d009 488bd8          mov     rbx,rax
        fffff802`0841d00c 4885c0          test    rax,rax
        fffff802`0841d00f 7435            je      nt!PsLookupProcessByProcessId+0x66 (fffff802`0841d046)  Branch
    */
    ULONG64 ul_entry = 0;
    for (INT i = 0; i < 100; i++)
    {
        // fffff802`0841d004 e8 87 00 00 00      call    nt!PspReferenceCidTableEntry (fffff802`0841d090)
        if (*(PUCHAR)(ul_funcAddr + i) == 0xe8)
        {
            ul_entry = ul_funcAddr + i;
            break;
        }
    }
 
    if (ul_entry != 0)
    {
        // 解析 call 地址
        INT i_callCode = *(INT*)(ul_entry + 1);
        DbgPrint("i_callCode = %p \n", i_callCode);
        ULONG64 ul_callJmp = ul_entry + i_callCode + 5;
        DbgPrint("ul_callJmp = %p \n", ul_callJmp);
 
        // 来到 call(PspReferenceCidTableEntry) 内找 PspCidTable
        /*
        0: kd> uf PspReferenceCidTableEntry
            nt!PspReferenceCidTableEntry+0x115:
            fffff802`0841d1a5 488b0d8473f5ff  mov     rcx,qword ptr [nt!PspCidTable (fffff802`08374530)]
            fffff802`0841d1ac b801000000      mov     eax,1
            fffff802`0841d1b1 f0480fc107      lock xadd qword ptr [rdi],rax
            fffff802`0841d1b6 4883c130        add     rcx,30h
            fffff802`0841d1ba f0830c2400      lock or dword ptr [rsp],0
            fffff802`0841d1bf 48833900        cmp     qword ptr [rcx],0
            fffff802`0841d1c3 0f843fffffff    je      nt!PspReferenceCidTableEntry+0x78 (fffff802`0841d108)  Branch
        */
        for (INT i = 0; i < 0x120; i++)
        {
            // fffff802`0841d1a5 48 8b 0d 84 73 f5 ff  mov     rcx,qword ptr [nt!PspCidTable (fffff802`08374530)]
            if (*(PUCHAR)(ul_callJmp + i) == 0x48 && *(PUCHAR)(ul_callJmp + i + 1) == 0x8b && *(PUCHAR)(ul_callJmp + i + 2) == 0x0d)
            {
                // 解析 mov 地址
                INT i_movCode = *(INT*)(ul_callJmp + i + 3);
                DbgPrint("i_movCode = %p \n", i_movCode);
                ULONG64 ul_movJmp = ul_callJmp + i + i_movCode + 7;
                DbgPrint("ul_movJmp = %p \n", ul_movJmp);
 
                // 得到 PspCidTable
                *tableAddr = ul_movJmp;
                return TRUE;
            }
        }
    }
    return FALSE;
}
 
VOID UnDriver(PDRIVER_OBJECT driver)
{
    DbgPrint(("Uninstall Driver Is OK \n"));
}
 
NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath)
{
    DbgPrint(("hello lyshark \n"));
 
    ULONG64 tableAddr = 0;
 
    get_PspCidTable(&tableAddr);
 
    DbgPrint("PspCidTable Address = %p \n", tableAddr);
 
    Driver->DriverUnload = UnDriver;
    return STATUS_SUCCESS;
}

运行后即可得到动态地址,我们可以验证一下是否一致:

 

图片描述

 

继续增加对与三级表的动态解析代码,最终代码如下所示:

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
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
#include <ntifs.h>
#include <windef.h>
 
// 获取 PspCidTable
// By: LyShark.com
BOOLEAN get_PspCidTable(ULONG64* tableAddr)
{
    // 获取 PsLookupProcessByProcessId 地址
    UNICODE_STRING uc_funcName;
    RtlInitUnicodeString(&uc_funcName, L"PsLookupProcessByProcessId");
    ULONG64 ul_funcAddr = MmGetSystemRoutineAddress(&uc_funcName);
    if (ul_funcAddr == NULL)
    {
        return FALSE;
    }
    DbgPrint("PsLookupProcessByProcessId addr = %p \n", ul_funcAddr);
 
    // 40 字节有 call(PspReferenceCidTableEntry)
    /*
    0: kd> uf PsLookupProcessByProcessId
        nt!PsLookupProcessByProcessId:
        fffff802`0841cfe0 48895c2418      mov     qword ptr [rsp+18h],rbx
        fffff802`0841cfe5 56              push    rsi
        fffff802`0841cfe6 4883ec20        sub     rsp,20h
        fffff802`0841cfea 48897c2438      mov     qword ptr [rsp+38h],rdi
        fffff802`0841cfef 488bf2          mov     rsi,rdx
        fffff802`0841cff2 65488b3c2588010000 mov   rdi,qword ptr gs:[188h]
        fffff802`0841cffb 66ff8fe6010000  dec     word ptr [rdi+1E6h]
        fffff802`0841d002 b203            mov     dl,3
        fffff802`0841d004 e887000000      call    nt!PspReferenceCidTableEntry (fffff802`0841d090)
        fffff802`0841d009 488bd8          mov     rbx,rax
        fffff802`0841d00c 4885c0          test    rax,rax
        fffff802`0841d00f 7435            je      nt!PsLookupProcessByProcessId+0x66 (fffff802`0841d046)  Branch
    */
    ULONG64 ul_entry = 0;
    for (INT i = 0; i < 100; i++)
    {
        // fffff802`0841d004 e8 87 00 00 00      call    nt!PspReferenceCidTableEntry (fffff802`0841d090)
        if (*(PUCHAR)(ul_funcAddr + i) == 0xe8)
        {
            ul_entry = ul_funcAddr + i;
            break;
        }
    }
 
    if (ul_entry != 0)
    {
        // 解析 call 地址
        INT i_callCode = *(INT*)(ul_entry + 1);
        DbgPrint("i_callCode = %p \n", i_callCode);
        ULONG64 ul_callJmp = ul_entry + i_callCode + 5;
        DbgPrint("ul_callJmp = %p \n", ul_callJmp);
 
        // 来到 call(PspReferenceCidTableEntry) 内找 PspCidTable
        /*
        0: kd> uf PspReferenceCidTableEntry
            nt!PspReferenceCidTableEntry+0x115:
            fffff802`0841d1a5 488b0d8473f5ff  mov     rcx,qword ptr [nt!PspCidTable (fffff802`08374530)]
            fffff802`0841d1ac b801000000      mov     eax,1
            fffff802`0841d1b1 f0480fc107      lock xadd qword ptr [rdi],rax
            fffff802`0841d1b6 4883c130        add     rcx,30h
            fffff802`0841d1ba f0830c2400      lock or dword ptr [rsp],0
            fffff802`0841d1bf 48833900        cmp     qword ptr [rcx],0
            fffff802`0841d1c3 0f843fffffff    je      nt!PspReferenceCidTableEntry+0x78 (fffff802`0841d108)  Branch
        */
        for (INT i = 0; i < 0x120; i++)
        {
            // fffff802`0841d1a5 48 8b 0d 84 73 f5 ff  mov     rcx,qword ptr [nt!PspCidTable (fffff802`08374530)]
            if (*(PUCHAR)(ul_callJmp + i) == 0x48 && *(PUCHAR)(ul_callJmp + i + 1) == 0x8b && *(PUCHAR)(ul_callJmp + i + 2) == 0x0d)
            {
                // 解析 mov 地址
                INT i_movCode = *(INT*)(ul_callJmp + i + 3);
                DbgPrint("i_movCode = %p \n", i_movCode);
                ULONG64 ul_movJmp = ul_callJmp + i + i_movCode + 7;
                DbgPrint("ul_movJmp = %p \n", ul_movJmp);
 
                // 得到 PspCidTable
                *tableAddr = ul_movJmp;
                return TRUE;
            }
        }
    }
    return FALSE;
}
 
/* 解析一级表
// By: LyShark.com
BaseAddr:一级表的基地址
index1:第几个一级表
index2:第几个二级表
*/
VOID parse_table_1(ULONG64 BaseAddr, INT index1, INT index2)
{
    // 遍历一级表(每个表项大小 16 ),表大小 4k,所以遍历 4096/16 = 526
    PEPROCESS p_eprocess = NULL;
    PETHREAD p_ethread = NULL;
    INT i_id = 0;
    for (INT i = 0; i < 256; i++)
    {
        if (!MmIsAddressValid((PVOID64)(BaseAddr + i * 16)))
        {
            DbgPrint("非法地址= %p \n", BaseAddr + i * 16);
            continue;
        }
 
        ULONG64 ul_recode = *(PULONG64)(BaseAddr + i * 16);
 
        // 解密
        ULONG64 ul_decode = (LONG64)ul_recode >> 0x10;
        ul_decode &= 0xfffffffffffffff0;
 
        // 判断是进程还是线程
        i_id = i * 4 + 1024 * index1 + 512 * index2 * 1024;
        if (PsLookupProcessByProcessId(i_id, &p_eprocess) == STATUS_SUCCESS)
        {
            DbgPrint("进程PID: %d | ID: %d | 内存地址: %p | 对象: %p \n", i_id, i, BaseAddr + i * 0x10, ul_decode);
        }
        else if (PsLookupThreadByThreadId(i_id, &p_ethread) == STATUS_SUCCESS)
        {
            DbgPrint("线程TID: %d | ID: %d | 内存地址: %p | 对象: %p \n", i_id, i, BaseAddr + i * 0x10, ul_decode);
        }
    }
}
 
/* 解析二级表
// By: LyShark.com
BaseAddr:二级表基地址
index2:第几个二级表
*/
VOID parse_table_2(ULONG64 BaseAddr, INT index2)
{
    // 遍历二级表(每个表项大小 8),表大小 4k,所以遍历 4096/8 = 512
    ULONG64 ul_baseAddr_1 = 0;
    for (INT i = 0; i < 512; i++)
    {
        if (!MmIsAddressValid((PVOID64)(BaseAddr + i * 8)))
        {
            DbgPrint("非法二级表指针(1):%p \n", BaseAddr + i * 8);
            continue;
        }
        if (!MmIsAddressValid((PVOID64)*(PULONG64)(BaseAddr + i * 8)))
        {
            DbgPrint("非法二级表指针(2):%p \n", BaseAddr + i * 8);
            continue;
        }
        ul_baseAddr_1 = *(PULONG64)(BaseAddr + i * 8);
        parse_table_1(ul_baseAddr_1, i, index2);
    }
}
 
/* 解析三级表
// By: LyShark.com
BaseAddr:三级表基地址
*/
VOID parse_table_3(ULONG64 BaseAddr)
{
    // 遍历三级表(每个表项大小 8),表大小 4k,所以遍历 4096/8 = 512
    ULONG64 ul_baseAddr_2 = 0;
    for (INT i = 0; i < 512; i++)
    {
        if (!MmIsAddressValid((PVOID64)(BaseAddr + i * 8)))
        {
            continue;
        }
        if (!MmIsAddressValid((PVOID64)* (PULONG64)(BaseAddr + i * 8)))
        {
            continue;
        }
        ul_baseAddr_2 = *(PULONG64)(BaseAddr + i * 8);
        parse_table_2(ul_baseAddr_2, i);
    }
}
 
VOID UnDriver(PDRIVER_OBJECT driver)
{
    DbgPrint(("Uninstall Driver Is OK \n"));
}
 
NTSTATUS DriverEntry(IN PDRIVER_OBJECT Driver, PUNICODE_STRING RegistryPath)
{
    DbgPrint(("hello lyshark.com \n"));
 
    ULONG64 tableAddr = 0;
 
    get_PspCidTable(&tableAddr);
 
    DbgPrint("PspCidTable Address = %p \n", tableAddr);
 
    // 获取 _HANDLE_TABLE 的 TableCode
    ULONG64 ul_tableCode = *(PULONG64)(((ULONG64)*(PULONG64)tableAddr) + 8);
    DbgPrint("ul_tableCode = %p \n", ul_tableCode);
 
    // 取低 2位(二级制11 = 3
    INT i_low2 = ul_tableCode & 3;
    DbgPrint("i_low2 = %X \n", i_low2);
 
    // 一级表
    if (i_low2 == 0)
    {
        // TableCode 低 2位抹零(二级制11 = 3
        parse_table_1(ul_tableCode & (~3), 0, 0);
    }
    // 二级表
    else if (i_low2 == 1)
    {
        // TableCode 低 2位抹零(二级制11 = 3
        parse_table_2(ul_tableCode & (~3), 0);
    }
    // 三级表
    else if (i_low2 == 2)
    {
        // TableCode 低 2位抹零(二级制11 = 3
        parse_table_3(ul_tableCode & (~3));
    }
    else
    {
        DbgPrint("LyShark提示: 错误,非法! ");
        return FALSE;
    }
 
    Driver->DriverUnload = UnDriver;
    return STATUS_SUCCESS;
}

运行如上完整代码,我们可以在WinDBG中捕捉到枚举到的进程信息:

 

图片描述

 

线程信息在进程信息的下面,枚举效果如下:

 

图片描述

 

至此文章就结束了,这里多说一句,实际上ZwQuerySystemInformation枚举系统句柄时就是走的这条双链,枚举系统进程如果使用的是这个API函数,那么不出意外它也是在这些内核表中做的解析。

参考文献

http://www.blogfshare.com/details-in-pspcidtbale.html
https://blog.csdn.net/whatday/article/details/17189093
https://www.cnblogs.com/kuangke/p/5761615.html
https://www.cnblogs.com/LyShark/p/16796158.html


[招生]科锐逆向工程师培训46期预科班将于 2023年02月09日 正式开班

收藏
点赞2
打赏
分享
最新回复 (1)
雪    币: 6838
活跃值: 活跃值 (3688)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
大道在我 活跃值 2022-10-18 20:14
2
0
请问1709下没有pspcidtable如何找到全局句柄表呢
游客
登录 | 注册 方可回帖
返回