句柄表

  • Q:什么是句柄?
  • A:当一个进程创建或者打开一个内核对象时,将获得一个句柄,通过这个句柄可以访问内核对象。

句柄表

为什么要有句柄?

句柄存在的目的是为了避免在应用层直接修改内核对象

[[windows开发#句柄表|windows开发笔记中也有句柄表的知识]]

1
HANDLE g_hEvent = CreateEvent(NULL,TRUE,FALSE,NULL);

如果g_hEvent存储的就是EVENT内核对象的地址,那么就意味着我们可以在应用层修改这个地址,一旦指向了无效的内核内存地址就会蓝屏。

因此微软设计了一张表

image-20210918112948365

windows的设计理念:

  1. 隐藏内核对象指针
  2. 句柄就是个索引

句柄表只存放有内核对象的句柄,窗口句柄并非内核对象句柄,是个用户对象。

局部句柄表

继承私有的句柄表

逆向NtOpenProcess有句柄表相关

句柄表在哪里

image-20210918113340386

_HANDLE_TABLE结构体

描述句柄表的信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
kd> dt _HANDLE_TABLE 
nt!_HANDLE_TABLE
+0x000 TableCode : Uint4B//句柄表地址(低2位二进制为句柄表的层级)
+0x004 QuotaProcess : Ptr32 _EPROCESS//所属的进程结构体
+0x008 UniqueProcessId : Ptr32 Void//pid
+0x00c HandleTableLock : [4] _EX_PUSH_LOCK//句柄表锁,仅在句柄表拓展时使用
+0x01c HandleTableList : _LIST_ENTRY//所有句柄表形成一个链表,链表头为全局变量HandleTableListHead
+0x024 HandleContentionEvent : _EX_PUSH_LOCK//若在访问句柄时发生竞争,则在此推锁上等待
+0x028 DebugInfo : Ptr32 _HANDLE_TRACE_DEBUG_INFO
+0x02c ExtraInfoPages : Int4B
+0x030 FirstFree : Uint4B//空闲链表标头的句柄索引
+0x034 LastFree : Uint4B
+0x038 NextHandleNeedingPool : Uint4B//下一次句柄表拓展的起始句柄索引
+0x03c HandleCount : Int4B//正在使用的句柄表项的数量
+0x040 Flags : Uint4B
+0x040 StrictFIFO : Pos 0, 1 Bit

TableCode低两位表示的是句柄表的层级

image-20210918134227975

一级句柄表如下:(每8字节是一个句柄描述)

image-20210918132516367

3环目标句柄的句柄描述内容如下:

image-20210918132245294

  1. 一个进程可以创建、打开很多内核对象,这些内核对象的地址存储在当前进程的句柄表中。我们在应用层得到的句柄值,实际上就是当前进程句柄表的索引(为应用层得到的句柄值除以4)
  2. 同一个内核对象可以被不同的进程所引用,但句柄的值可能一样也可能不一样。

句柄索引为什么要除以4的详解

句柄的结构

image-20210918131235770

image-20210918131210877

  • 第0位:表示调用者是否允许关闭该句柄,因此通常是1
  • 第2位:指示关闭该对象时是否会产生一个审计事件

句柄结构:

1
2
3
4
5
6
7
8
9
10
dt _HANDLE_TABLE_ENTRY
nt!_HANDLE_TABLE_ENTRY
+0x000 Object : Ptr32 Void//内核对象
+0x000 ObAttributes : Uint4B
+0x000 InfoTable : Ptr32 _HANDLE_TABLE_ENTRY_INFO
+0x000 Value : Uint4B
+0x004 GrantedAccess : Uint4B//访问掩码
+0x004 GrantedAccessIndex : Uint2B
+0x006 CreatorBackTraceIndex : Uint2B
+0x004 NextFreeTableEntry : Int4B//如果这是一个空闲的句柄表项,那么,这个成员将加入到句柄表的空闲单链表中

该内核对象在内核中的具体地址指向的是_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
kd> dt _OBJECT_HEADER
nt!_OBJECT_HEADER
+0x000 PointerCount : Int4B//内核模式对象被引用数量?
+0x004 HandleCount : Int4B//被使用的句柄数量?
+0x004 NextToFree : Ptr32 Void
+0x008 Type : Ptr32 _OBJECT_TYPE//内含对象名
+0x00c NameInfoOffset : UChar
+0x00d HandleInfoOffset : UChar
+0x00e QuotaInfoOffset : UChar
+0x00f Flags : UChar
+0x010 ObjectCreateInfo : Ptr32 _OBJECT_CREATE_INFORMATION
+0x010 QuotaBlockCharged : Ptr32 Void
+0x014 SecurityDescriptor : Ptr32 Void
+0x018 Body : _QUAD//内核对象,如_ETHREAD,_EPROCESS等等

kd> dt _OBJECT_TYPE
ntdll!_OBJECT_TYPE
+0x000 Mutex : _ERESOURCE
+0x038 TypeList : _LIST_ENTRY
+0x040 Name : _UNICODE_STRING//类型名
+0x048 DefaultObject : Ptr32 Void
+0x04c Index : Uint4B
+0x050 TotalNumberOfObjects : Uint4B
+0x054 TotalNumberOfHandles : Uint4B
+0x058 HighWaterNumberOfObjects : Uint4B
+0x05c HighWaterNumberOfHandles : Uint4B
+0x060 TypeInfo : _OBJECT_TYPE_INITIALIZER
+0x0ac Key : Uint4B
+0x0b0 ObjectLocks : [4] _ERESOURCE

理解全局句柄表和局部句柄表流程

当在A进程中调用OpenProcess函数,需要传入pid(即全局句柄表的索引),在全局句柄表中找到打开目标进程的进程结构体的_OBJECT_HEAD。将该_OBJECT_HEAD结构体的地址根据OpenProcess参数以及A进程是否可继承等信息封装后填入A进程的局部句柄表,返回一个属于A进程的局部句柄,即OpenProcess的返回值。

ObReferenceObjectByHandle函数:根据提供的 Handle 值得到 Object,同时引用计数+1

一个有效的句柄表有4种可能:

  1. -1,代表当前句柄(无需查句柄表)
  2. -2,代表当前线程(无需查句柄表)
  3. 负值,其绝对值为全局句柄表中的索引。仅限于内核模式的函数可以引用
  4. 不超过226的正值,当前进程的句柄表中的索引

实现进程局部句柄表遍历实验

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
#include <ntddk.h>

//卸载函数
VOID DriverUnload(PDRIVER_OBJECT driver)
{

DbgPrint("停止运行了\n");
}

//遍历进程链表取目标进程(名称不能>=16位)
ULONG getProcessInLink(char* name)
{
ULONG curProcess;
__asm
{
mov eax, dword ptr fs : [0x124] ;
mov ecx, [eax + 0x44];
mov curProcess, ecx;
}
PLIST_ENTRY plistProcess = (PLIST_ENTRY)(curProcess + 0x88);
while (plistProcess->Flink != (PLIST_ENTRY)(curProcess + 0x88))
{
ULONG nextProcess = ((ULONG)(plistProcess)) - 0x88;
plistProcess = plistProcess->Flink;
//KdPrint(("%s\n", (PCHAR)(nextProcess + 0x174)));
if (strncmp(name, (PCHAR)(nextProcess + 0x174),strlen(name)>15?15: strlen(name)) == 0)//174这个名称只能存15位
{
return nextProcess;//找到目标进程
}
}
return NULL;
}

//根据_EPROCESS结构体地址获取进程路径名
PUNICODE_STRING getAllNameByEprocessAddr(ULONG eprocessAddr)
{
return *(PULONG)(eprocessAddr + 0x1F4);//取一次就可以了,因为返回的是PUNICODE_STRING,而非UNICODE_STRING
}

//传入句柄表项地址,解析局部句柄信息函数
UNICODE_STRING processStr = RTL_CONSTANT_STRING(L"Process");;
UNICODE_STRING threadStr = RTL_CONSTANT_STRING(L"Thread");;
void analyseLocalHandleDescAddr(ULONG handleDescAddr)
{
ULONG objectAddr = *(PULONG)(handleDescAddr);
objectAddr &= 0xFFFFFFF8;//处理后三位二进制
if (objectAddr != NULL)
{
PUNICODE_STRING pTypeName = (*(PULONG)(objectAddr + 0x8)) + 0x040;
if (RtlCompareUnicodeString(pTypeName, &processStr, FALSE) == 0)//如果是进程
{
KdPrint(("进程内核对象地址为%p,进程ID:%d,全路径进程名:%wZ\n", objectAddr + 0x18, *(INT*)(objectAddr+0x18 + 0x084), getAllNameByEprocessAddr(objectAddr+0x18)));
}
else if (RtlCompareUnicodeString(pTypeName, &threadStr, FALSE) == 0)
{
KdPrint(("线程内核对象地址为%p,线程ID:%d\n", objectAddr + 0x18, *(INT*)(objectAddr + 0x18 +0x1f0)));
}
else {
KdPrint(("%wZ内核对象地址为%p\n", pTypeName,objectAddr + 0x18));
}
}
}

//指定进程局部句柄表遍历(只针对一级句柄表)
void forEachHandleFromProcess(char* name)
{
ULONG eprocessAddress = getProcessInLink(name);
if (!eprocessAddress)
{
KdPrint(("指定进程不存在\n"));
return;
}

ULONG handleTableDescripAddr = *(PULONG)(eprocessAddress + 0xc4);
int handleNum = *(INT*)(handleTableDescripAddr + 0x03c);
KdPrint(("指定进程_HANDLE_TABLE中句柄计数为%d\n", handleNum));
ULONG handleTableAddr = *(PULONG)handleTableDescripAddr;
KdPrint(("当前进程句柄表层级为%d\n", (handleTableAddr & 0x3)+1));
if ((handleTableAddr & 0x3)!=0)
{
KdPrint(("当前进程句柄表并非一级句柄表,不处理\n"));
return;
}
//处理一级句柄表,因为是一级句柄表,后两位不处理
for (size_t i = 0; i < handleNum; i++)
{
analyseLocalHandleDescAddr(handleTableAddr + i * 8);
}
}

//入口函数,相当于main函数
NTSTATUS DriverEntry(PDRIVER_OBJECT pdriver, PUNICODE_STRING pReg)
{
//设置一个卸载函数,用于退出
pdriver->DriverUnload = DriverUnload;
forEachHandleFromProcess("test.exe");
return STATUS_SUCCESS;
}

test进程只OpenProcess了system进程,然后就system(“pause”)。结果如下:

image-20210924172926629

全局句柄表

全局句柄表,也是System进程的局部句柄表

  • 所有的进程和线程无论是否打开,都在这个表中。
  • 每个进程和线程都有一个唯一的编号:PID和CID,这两个值其实就是全局句柄表中的索引

进程和线程的查询,主要是以下三个函数,按照给定的PID或CID从全局变量PspCidTable(未导出,需要自己定位地址)查找相应的进线程对象

  • PsLookupProcessThreadByCid()
  • PsLookupProcessByProcessId()
  • PsLookupThreadByThreadId()

全局变量PspCidTable储存的就是**_HANDLE_TABLE的地址**。

【注意】全局句柄表和局部句柄表的区别

  • PspCidTable中存放的对象是系统中所有的进线程对象指针,其索引就是 PID 和 CID,而局部句柄表存放着所有该进程使用的内核对象句柄
  • PspCidTable中存放是对象体(指向 EPROCESS 和 ETHREAD),而每个进程私有的句柄表则存放的是对象头(OBJECT_HEADER)
  • PspCidTable是一个独立的句柄表,而每个进程私有的句柄表以一个双链连接起来

PspCidTble 的定位

  1. PsLookupProcessByProcessId(PsLookupThreadByThreadId或者PsLookupProcessThreadByCid)函数中搜索特征串(0x35ff 和 0x8e)定位 PspCidTalbe

CODE:

image-20210923113740403

  1. 利用 KDDEBUGGER_DATA(32/64)结构得到 pspCidTableKdEnableDebugger->KdInitSystem->KdDebuggerDataBlock->KDDEBUGGER_DATA32->PspCidTable

  2. 利用 KPCR 中取,KRCR 地址ffdff000处是一个叫做 KPCR的结构,PCR即 Processor Control Region,处理器控制域。这是一个很有用的结构。系统本身就大量使用。0xffdff000是 KPCR这个结构变量的地址,那么+0x34就是KdVersionBlock成员变量在该结构中的偏移,但是在 0xffdff034指向的地方对应有个结构_DBGKD_GET_VERSION64,可惜的是这个结构只有 0x28字节大小但是….嘿嘿这个结构后面藏着 N多超级重要的内核变量。我们的 pspcidtable这个变量其实就在这个结构起始位置的 0x80字节偏移处~

image-20210923113801343

PspCidTable完全解读

句柄表表索引算法

  • 第一层算法
    1. index = 句柄 >> 2
    2. 句柄表CODE + 句柄INDEX *8
  • 第二层算法
    1. index = 句柄 >> 2
    2. 目录索引index = 句柄 >> 11
    3. 目录=句柄表CODE + 句柄目录INDEX * 4
    4. 对象=目录 + (index & 0x1ff) * 8
  • 第三层算法
    1. index = 句柄 >> 2
    2. 目录1索引index = index >> 19
    3. 目录1=句柄表CODE + 目录1索引index * 4
    4. index3=index - (目录1索引index << 19);
    5. 目录2Index = index3 >> 9
    6. 目录2=目录1 + 目录2Index * 4
    7. 对象=目录2 + (index3 & 0x1ff) * 8
  • 第一层
    ID /4 *8
  • 第二层
    ID/4 /512 —计算是那个目录
    ID/4%512 *8
  • 第三层
    ID/4/512/1024—计算是那个目录
    ID/4/512—计算是那个目录
    ID/4%512 *8

实现

全局句柄表遍历所有进程

实际上全局句柄表中也可以把想隐藏的目标裁掉,但是不太稳定(某些时刻一下就蓝屏了)

全局句柄表可以实现不怕断链的全局进程遍历

1.利用未导出的ExEnumHandleTable函数

2.直接自己获取PHANDLE_TABLE_ENTRY等,然后 PsLookupProcessByProcessId

3.自己写实现遍历(完成)

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
//此代码仅针对一级全局表
#include <ntddk.h>

//卸载函数
VOID DriverUnload(PDRIVER_OBJECT driver)
{

DbgPrint("停止运行了\n");
}

//获取内核函数地址
ULONG getFuncAddr(PCWSTR funcName)
{
UNICODE_STRING UFuncName;
RtlInitUnicodeString(&UFuncName, funcName);
ULONG funcAddr = (ULONG)MmGetSystemRoutineAddress(&UFuncName);
return funcAddr;
}

//获取PspCidTable(没有任何通用性)
ULONG GetPspCidTable()
{
return *(PULONG)(*(PULONG)(getFuncAddr(L"PsLookupProcessByProcessId") + 0x1A));
}


//根据_EPROCESS结构体地址获取进程路径名
PUNICODE_STRING getAllNameByEprocessAddr(ULONG eprocessAddr)
{
return *(PULONG)(eprocessAddr + 0x1F4);//取一次就可以了,因为返回的是PUNICODE_STRING,而非UNICODE_STRING
}

//传入句柄表项地址,解析全局句柄表项信息函数
UNICODE_STRING processStr = RTL_CONSTANT_STRING(L"Process");;
UNICODE_STRING threadStr = RTL_CONSTANT_STRING(L"Thread");;
void analyseGlobalHandleDescAddr(ULONG handleDescAddr)
{
ULONG objectAddr = *(PULONG)(handleDescAddr);
objectAddr &= 0xFFFFFFF8;//处理后三位二进制
if (objectAddr!=NULL)
{
PUNICODE_STRING pTypeName = (*(PULONG)(objectAddr - 0x18 + 0x8)) + 0x040;
if (RtlCompareUnicodeString(pTypeName,&processStr, FALSE)==0)//如果是进程
{
KdPrint(("进程内核对象地址为%p,进程ID:%d,全路径进程名:%wZ\n", objectAddr, *(INT*)(objectAddr +0x084), getAllNameByEprocessAddr(objectAddr)));
}
else if (RtlCompareUnicodeString(pTypeName, &threadStr, FALSE)==0)
{
KdPrint(("线程内核对象地址为%p,线程ID:%d\n", objectAddr, *(INT*)(objectAddr +0x1f0)));
}
else {
KdPrint(("未知内核对象地址为%p\n", objectAddr));
}
}
}


//遍历全局句柄表
void forEachGlobalHandle()
{
ULONG pspCidTable = GetPspCidTable();
int handleNum = *(INT*)(pspCidTable + 0x03c);
KdPrint(("全局句柄_HANDLE_TABLE中句柄计数为%d\n", handleNum));
ULONG tableCode= *(PULONG)(pspCidTable);
KdPrint(("全局句柄层级为%d\n", (tableCode & 0x3)+1));

if ((tableCode & 0x3) != 0)
{
KdPrint(("全局句柄表非一级,未处理\n"));
return;
}
//一级全局句柄表遍历
ULONG handleTableAddr = tableCode;//因为是一级句柄表,所以不需要处理tableCode后两位二进制
for (size_t i = 0; i < 512; i++)
{
analyseGlobalHandleDescAddr(handleTableAddr + i * 8);
}
}


//入口函数,相当于main函数
NTSTATUS DriverEntry(PDRIVER_OBJECT pdriver, PUNICODE_STRING pReg)
{
//设置一个卸载函数,用于退出
pdriver->DriverUnload = DriverUnload;
KdPrint(("PspCidTable:%p\n", GetPspCidTable()));
forEachGlobalHandle();
return STATUS_SUCCESS;
}

结果:(截取部分显示结果)

image-20210924165250885image-20210924165319826

如果只打印进程信息:

image-20210924165427748

用进程句柄表实现反调试

要求知道是哪个进程调试我,并且关闭这个调试进程

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
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
#include <ntddk.h>

//卸载函数
VOID DriverUnload(PDRIVER_OBJECT driver)
{

DbgPrint("停止运行了\n");
}

//遍历进程链表取目标进程(名称不能>=16位)
ULONG getProcessInLink(char* name)
{
ULONG curProcess;
__asm
{
mov eax, dword ptr fs : [0x124] ;
mov ecx, [eax + 0x44];
mov curProcess, ecx;
}
PLIST_ENTRY plistProcess = (PLIST_ENTRY)(curProcess + 0x88);
while (plistProcess->Flink != (PLIST_ENTRY)(curProcess + 0x88))
{
ULONG nextProcess = ((ULONG)(plistProcess)) - 0x88;
plistProcess = plistProcess->Flink;
//KdPrint(("%s\n", (PCHAR)(nextProcess + 0x174)));
if (strncmp(name, (PCHAR)(nextProcess + 0x174),strlen(name)>15?15: strlen(name)) == 0)//174这个名称只能存15位
{
return nextProcess;//找到目标进程
}
}
return NULL;
}

//根据_EPROCESS结构体地址获取进程路径名
PUNICODE_STRING getAllNameByEprocessAddr(ULONG eprocessAddr)
{
return *(PULONG)(eprocessAddr + 0x1F4);//取一次就可以了,因为返回的是PUNICODE_STRING,而非UNICODE_STRING
}

//传入句柄表项地址,解析局部句柄信息函数
UNICODE_STRING processStr = RTL_CONSTANT_STRING(L"Process");;
UNICODE_STRING threadStr = RTL_CONSTANT_STRING(L"Thread");;
void analyseLocalHandleDescAddr(ULONG handleDescAddr)
{
ULONG objectAddr = *(PULONG)(handleDescAddr);
objectAddr &= 0xFFFFFFF8;//处理后三位二进制
if (objectAddr != NULL)
{
PUNICODE_STRING pTypeName = (*(PULONG)(objectAddr + 0x8)) + 0x040;
if (RtlCompareUnicodeString(pTypeName, &processStr, FALSE) == 0)//如果是进程
{
KdPrint(("进程内核对象地址为%p,进程ID:%d,全路径进程名:%wZ\n", objectAddr + 0x18, *(INT*)(objectAddr+0x18 + 0x084), getAllNameByEprocessAddr(objectAddr+0x18)));
}
else if (RtlCompareUnicodeString(pTypeName, &threadStr, FALSE) == 0)
{
KdPrint(("线程内核对象地址为%p,线程ID:%d\n", objectAddr + 0x18, *(INT*)(objectAddr + 0x18 +0x1f0)));
}
else {
KdPrint(("%wZ内核对象地址为%p\n", pTypeName,objectAddr + 0x18));
}
}
}

//指定进程局部句柄表遍历(只针对一级句柄表)
void forEachHandleFromProcess(char* name)
{
ULONG eprocessAddress = getProcessInLink(name);
if (!eprocessAddress)
{
KdPrint(("指定进程不存在\n"));
return;
}

ULONG handleTableDescripAddr = *(PULONG)(eprocessAddress + 0xc4);
int handleNum = *(INT*)(handleTableDescripAddr + 0x03c);
KdPrint(("指定进程_HANDLE_TABLE中句柄计数为%d\n", handleNum));
ULONG handleTableAddr = *(PULONG)handleTableDescripAddr;
KdPrint(("当前进程句柄表层级为%d\n", (handleTableAddr & 0x3)+1));
if ((handleTableAddr & 0x3)!=0)
{
KdPrint(("当前进程句柄表并非一级句柄表,不处理\n"));
return;
}
//处理一级句柄表,因为是一级句柄表,后两位不处理
for (size_t i = 0; i < handleNum; i++)
{
analyseLocalHandleDescAddr(handleTableAddr + i * 8);
}
}

//获取内核函数地址
ULONG getFuncAddr(PCWSTR funcName)
{
UNICODE_STRING UFuncName;
RtlInitUnicodeString(&UFuncName, funcName);
ULONG funcAddr = (ULONG)MmGetSystemRoutineAddress(&UFuncName);
return funcAddr;
}

//获取PspCidTable(没有任何通用性)
ULONG GetPspCidTable()
{
return *(PULONG)(*(PULONG)(getFuncAddr(L"PsLookupProcessByProcessId") + 0x1A));
}

//分析8字节的全局句柄描述地址
void analyseGlobalHandleDescAddr(ULONG handleDescAddr)
{
ULONG objectAddr = *(PULONG)(handleDescAddr);
objectAddr &= 0xFFFFFFF8;//处理后三位二进制
if (objectAddr != NULL)
{
PUNICODE_STRING pTypeName = (*(PULONG)(objectAddr - 0x18 + 0x8)) + 0x040;
if (RtlCompareUnicodeString(pTypeName, &processStr, FALSE) == 0)//如果是进程
{
KdPrint(("进程内核对象地址为%p,进程ID:%d,全路径进程名:%wZ\n", objectAddr, *(INT*)(objectAddr + 0x084), getAllNameByEprocessAddr(objectAddr)));
}
else if (RtlCompareUnicodeString(pTypeName, &threadStr, FALSE) == 0)
{
KdPrint(("线程内核对象地址为%p,线程ID:%d\n", objectAddr, *(INT*)(objectAddr + 0x1f0)));
}
else {
KdPrint(("未知内核对象地址为%p\n", objectAddr));
}
}
}

//遍历全局句柄表
void forEachGlobalHandle()
{
ULONG pspCidTable = GetPspCidTable();
int handleNum = *(INT*)(pspCidTable + 0x03c);
KdPrint(("全局句柄_HANDLE_TABLE中句柄计数为%d\n", handleNum));
ULONG tableCode= *(PULONG)(pspCidTable);
KdPrint(("全局句柄层级为%d\n", (tableCode & 0x3)+1));

if ((tableCode & 0x3) != 0)
{
KdPrint(("全局句柄表非一级,未处理\n"));
return;
}
//一级全局句柄表遍历
ULONG handleTableAddr = tableCode;//因为是一级句柄表,所以不需要处理tableCode后两位二进制
for (size_t i = 0; i < 512; i++)
{
analyseGlobalHandleDescAddr(handleTableAddr + i * 8);
}
}

//全局句柄表找指定进程(只针对一级句柄表)
ULONG findEprocessAddrByGlobalHandle(char* processName)
{
ULONG pspCidTable = GetPspCidTable();
ULONG tableCode = *(PULONG)(pspCidTable);
if ((tableCode & 0x3) != 0)
{
KdPrint(("全局句柄表非一级,未处理\n"));
return NULL;
}
for (size_t i = 0; i < 512; i++)
{
ULONG handleDesc = *(PULONG)(tableCode + i * 8);
handleDesc &= 0xFFFFFFF8;
if (handleDesc != NULL)
{
PUNICODE_STRING pTypeName = (*(PULONG)(handleDesc - 0x18 + 0x8)) + 0x040;
if (RtlCompareUnicodeString(pTypeName, &processStr, FALSE) == 0)//是进程
{
//通过进程名找到目标进程
int length = strlen(processName) > 15 ? 15 : strlen(processName);
if (strncmp(handleDesc + 0x174, processName, length) == 0)
{
//已找到目标进程
return handleDesc;
}
}
}
}
return NULL;
}

//标准方法中止进程
void ZwKillProcess(ULONG pid)
{
HANDLE ProcessHandle = NULL;
OBJECT_ATTRIBUTES obj;
CLIENT_ID cid = { 0 };
NTSTATUS ntStatus = STATUS_UNSUCCESSFUL;
InitializeObjectAttributes(&obj, NULL, OBJ_KERNEL_HANDLE | OBJ_CASE_INSENSITIVE, NULL, NULL);
cid.UniqueProcess = (HANDLE)pid;
cid.UniqueThread = 0;
ntStatus = ZwOpenProcess(&ProcessHandle, GENERIC_ALL, &obj, &cid);
if (NT_SUCCESS(ntStatus))
{
ZwTerminateProcess(ProcessHandle, 0);
ZwClose(ProcessHandle);
}
ZwClose(ProcessHandle);
}

//遍历所有全局进程表中除目标进程外所有进程的局部进程表是否存在局部进程
BOOLEAN isHandleSave(char* processName)
{
ULONG eprocessAddr = findEprocessAddrByGlobalHandle(processName);
//KdPrint(("保护的进程结构体:%p\n", eprocessAddr));
if (!eprocessAddr)
{
KdPrint(("目标进程未开启"));
return TRUE;
}
//遍历除目标进程外的其他进程
ULONG pspCidTable = GetPspCidTable();
ULONG tableCode = *(PULONG)(pspCidTable);
if ((tableCode & 0x3) != 0)
{
KdPrint(("全局句柄表非一级,未处理\n"));
return FALSE;
}
for (size_t i = 0; i < 512; i++)
{
ULONG handleDesc = *(PULONG)(tableCode + i * 8);
handleDesc &= 0xFFFFFFF8;
if (handleDesc != NULL)
{
PUNICODE_STRING pTypeName = (*(PULONG)(handleDesc - 0x18 + 0x8)) + 0x040;
if (RtlCompareUnicodeString(pTypeName, &processStr, FALSE) == 0)//是进程
{
int length = strlen(processName) > 15 ? 15 : strlen(processName);
if (strncmp(handleDesc + 0x174, processName, length) != 0)//是非目标进程
{
//遍历非目标进程局部句柄!!!!!!!!!!!!!!!!!!!!!!!!
ULONG localHandleTableDescripAddr = *(PULONG)(handleDesc + 0xc4);
ULONG localTableCode = *(PULONG)localHandleTableDescripAddr;
//KdPrint(("当前进程句柄表层级为%d\n", (handleTableAddr & 0x3) + 1));
switch (localTableCode & 0x3)
{
case 0:
//处理一级句柄表,因为是一级句柄表,后两位不处理
for (size_t i = 0; i < 512; i++)
{
//analyseLocalHandleDescAddr(localTableCode + i * 8);
ULONG eprocessInProcess = ((*(PULONG)(localTableCode + i * 8)) & 0xFFFFFFF8) + 0x18;
//KdPrint(("查找的进程结构体:%p\n", eprocessInProcess));
if (eprocessInProcess == eprocessAddr)
{
//排除csrss.exe的干扰
if (strcmp(handleDesc + 0x174, "csrss.exe") == 0)
continue;
//显示是什么进程收纳了目标进程的句柄
KdPrint(("中止进程%d进程id的进程 进程:%wZ 疑似攻击 %s进程", *(PULONG)(handleDesc +0x84), getAllNameByEprocessAddr(handleDesc), processName));
ZwKillProcess(*(PULONG)(handleDesc + 0x84));
return FALSE;
}
}
break;
case 1:
//处理二级句柄表
localTableCode &= 0xFFFFFFFC;
for (size_t i = 0; i < 1024; i++)
{
ULONG contentItem = *(PULONG)(localTableCode + i * 4);
if (contentItem==NULL)
{
break;
}
for (size_t j = 0; j < 512; j++)
{
//analyseLocalHandleDescAddr(contentItem + i * 8);
ULONG eprocessInProcess = ((*(PULONG)(contentItem + j * 8)) & 0xFFFFFFF8)+0x18;
//KdPrint(("查找的进程结构体:%p\n", eprocessInProcess));
if (eprocessInProcess== eprocessAddr)
{
//排除csrss.exe的干扰
if (strcmp(handleDesc + 0x174, "csrss.exe") == 0)
continue;
//显示是什么进程收纳了目标进程的句柄
KdPrint(("中止进程%d进程id的进程 进程:%wZ 疑似攻击 %s进程", *(PULONG)(handleDesc + 0x84), getAllNameByEprocessAddr(handleDesc),processName));
ZwKillProcess(*(PULONG)(handleDesc + 0x84));
return FALSE;
}
}
}
break;
default:
KdPrint(("当前进程句柄表为3级句柄,不处理\n"));
return FALSE;
break;
}
}
}
}
}
//检查完毕,未找到可疑进程,安全
return TRUE;
}

//入口函数,相当于main函数
NTSTATUS DriverEntry(PDRIVER_OBJECT pdriver, PUNICODE_STRING pReg)
{
//设置一个卸载函数,用于退出
pdriver->DriverUnload = DriverUnload;
KdPrint(("wantProtect.exe是安全的?:%d",isHandleSave("wantProtect.exe")));
return STATUS_SUCCESS;
}

image-20210925151015929

OD附加wanrProtect.exe进程的情况下,sys文件启动时成功将OD进程杀死。

回调函数

_OBJECT_TYPE_INITIALIZER

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//此结构在_OBJECT_HEADER中的_OBJECT_TYPE结构中有记录
kd> dt _OBJECT_TYPE_INITIALIZER
ntdll!_OBJECT_TYPE_INITIALIZER
+0x000 Length : Uint2B
+0x002 UseDefaultObject : UChar
+0x003 CaseInsensitive : UChar
+0x004 InvalidAttributes : Uint4B
+0x008 GenericMapping : _GENERIC_MAPPING
+0x018 ValidAccessMask : Uint4B
+0x01c SecurityRequired : UChar
+0x01d MaintainHandleCount : UChar
+0x01e MaintainTypeList : UChar
+0x020 PoolType : _POOL_TYPE
+0x024 DefaultPagedPoolCharge : Uint4B
+0x028 DefaultNonPagedPoolCharge : Uint4B
+0x02c DumpProcedure : Ptr32 void
+0x030 OpenProcedure : Ptr32 long
+0x034 CloseProcedure : Ptr32 void
+0x038 DeleteProcedure : Ptr32 void
+0x03c ParseProcedure : Ptr32 long
+0x040 SecurityProcedure : Ptr32 long
+0x044 QueryNameProcedure : Ptr32 long
+0x048 OkayToCloseProcedure : Ptr32 unsigned char

三环与零环函数盘点

三环 零环 作用
GetProcAddress MmGetSystemRoutineAddress 获取函数地址(这样找到的函数调用不会被IAT hook,不走导入表,而是从内核模块导出表中找函数地址。有些内核函数虽然导出了,但并没有函数说明,无法直接使用,也要用该函数)
MmIsAddressValid 判断线性地址是否有效