在上一篇文章《驱动开发:内核枚举DpcTimer定时器》
中我们通过枚举特征码的方式找到了DPC
定时器基址并输出了内核中存在的定时器列表,本章将学习如何通过特征码定位的方式寻找Windows 10
系统下面的PspCidTable
内核句柄表地址。
首先引入一段基础概念;
windows
下所有的资源都是用对象的方式进行管理的(文件、进程、设备等都是对象)
,当要访问一个对象时,如打开一个文件,系统就会创建一个对象句柄,通过这个句柄可以对这个文件进行各种操作。PspCidTable 就是这样的一种表(内核句柄表)
,表的内部存放的是进程EPROCESS
和线程ETHREAD
的内核对象,并通过进程PID
和线程TID
进行索引,ID号以4递增,内核句柄表不属于任何进程,也不连接在系统的句柄表上,通过它可以返回系统的任何对象。
内核句柄表与普通句柄表完全一样,但它与每个进程私有的句柄表有以下不同;
PID
和TID
。EPROCESS和ETHREAD
,而每个进程私有的句柄表则存放的是对象头OBJECT_HEADER
。那么在Windows10
系统中该如何枚举句柄表;
PsLookupProcessByProcessId
函数地址,该函数是被导出的可以动态拿到。PsLookupProcessByProcessId
地址中搜索PspReferenceCidTableEntry
函数。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进程。
理论知识总结已经结束了,接下来就是如何实现枚举进程线程了,枚举流程如下:
PspCidTable
的地址。HANDLE_TBALE
的地址。TableCode
来判断层次结构。这里先来实现获取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
更多【 驱动开发:内核枚举PspCidTable句柄表】相关视频教程:www.yxfzedu.com