Joe1sn's Cabin

windows内核驱动 12-内核hook-1.5

前一篇写了 inline hook和ssdt两种方式,但是相关的的某些东西没讲清楚。

windows rookit防护-Kernel Hook 1.5

前一篇写了 inline hook和ssdt两种方式,但是相关的的某些东西没讲清楚。

TL,DR:介绍驱动加载的方法,PatchGuard简介,如何在测试中关闭/开启KVAS保护(Shadow SSDT中的KiSystemCall64Shadow相关),然后测试ssdt/shadow ssdt hook方法,最后内容是Shadow SSDT枚举。

Ring3 加载一个sys文件

首先有一个hello world的驱动,尝试使用命令行加载他(使用管理员权限)

1
2
3
4
5
sc create <使用的名称> type= kernel start= demand binPath="/??/<驱动文件路径,这里应该用的是描述符格式>"

sc start <使用的名称>
sc stop <使用的名称>
sc delete <使用的名称>

image-20260407135338908

sc是Service Control的简称,本质是操作 Windows 的 SCM(服务控制管理器),这里的加载驱动本质上是创建一个 kernel类型的服务,然后启动。

服务控制管理器(SCM)在系统启动时启动。 它是远程过程调用 (RPC) 服务器,以便服务配置和服务控制程序可以作远程计算机上的服务。

服务函数为 SCM 执行的以下任务提供接口:

  • 维护已安装服务的数据库。
  • 在系统启动时或按需启动服务和驱动程序服务。
  • 枚举已安装的服务和驱动程序服务。
  • 维护运行服务和驱动程序服务的状态信息。
  • 将控制请求传输到正在运行的服务。
  • 锁定和解锁服务数据库。

https://learn.microsoft.com/zh-cn/windows/win32/services/service-control-manager

那么sc是如何完成这一过程的呢?对应的代码在附录的《简单的ring3驱动加载器》

image-20260407140946416

Ring0 加载一个sys文件

正常做法就是先注册表HKLM\SYSTEM\CurrentControlSet\Services\<service name>

然后调用ZwLoadDriver API

1
2
3
4
5
6
7
UNICODE_STRING regPath;
RtlInitUnicodeString(
&regPath,
L"\\Registry\\Machine\\System\\CurrentControlSet\\Services\\MyDriver2"
);

NTSTATUS status = ZwLoadDriver(&regPath);

**那么使用手动加载呢?**这里需要PE 文件格式的基础,可以看:PE文件格式解析

这里修改下,就可以的得到内核手动映射,不过这个暂时过不了__security_cookie的检测,所以得自己设置AddressofEntry的值或者改写cookie,代码在附录的《手动映射SYS》,注意修改

  • main中的文件路径
  • ExecuteMappedDriver中的entry偏移
  • FixImportTablewhile循环中的IAT个数大小

PatchGuard

具体的分析可能会后续重新写一篇文章,如果实在想提前了解可以参考

https://blog.tetrane.com/downloads/Tetrane_PatchGuard_Analysis_RS4_v1.01.pdf

基本上所有的入门介绍都或多或少参考了这个PDF

本质上,它是内核的重要组成部分,与内核的其他部分一样在 Ring 0 中运行。它并非某种对 Ring 0 代码拥有更大权限的 Ring -1 机制(至少本文讲的范围内没有)。它的主要目的是检查关键的内核结构和代码,确保它们没有被篡改。如果检测到任何未经授权的修改,它会触发蓝屏死机,并附带错误代码CRITICAL_STRUCTURE_CORRUPTION和错误检查代码0x109,从而避免任何错误或混淆。它明确地表明 Ring 0 中某些不应该被修改的内容已被修改。

并且由于PG的异步机制,因此无法确定它何时会检查关键结构和代码。

主要检测的内容有

  • IDT(中断描述符表)和 GDT(全局描述符表)
    • GDT:是IA-32和x86-64架构特有的二进制数据结构。它包含向CPU提供有关内存段的条目。
    • IDT:是一种专用于 IA-32 和 x86-64 架构的二进制数据结构。它是保护模式和长模式下与实模式中断向量表 (IVT) 对应的机制,用于告知 CPU 中断服务例程 (ISR) 的位置(每个中断向量对应一个 ISR)。
  • MSR(模型特定寄存器):CPU 寄存器,用于控制高级行为,例如功能、限制和执行流程管理。
  • SSDT(系统服务描述符表):一个包含指向实现系统调用(例如NtCreateFile,、NtOpenProcess等)的内核函数的指针的表。
  • 内核栈
  • 内核结构
  • 全局变量
  • KPP引擎

关于KVAS保护

KVAS全称是Kernel Virtual Address Shadow,它的出现与MeltDown(CVE-2017-5754)和TotalMeltDown(CVE-2018-1038)有关。

我的描述不一定准确,大致上来说这两个漏洞利用了CPU的乱序执行技术,即CPU在执行时不一定会按照流程执行。当我们访问一个不能被用户模式访问的内存页时,CPU会执行该语句然后将其缓存到内存中,等到发现不能访问后返回错误,但是该数据依旧存在于缓存当中。利用这种思路就可以完全读取内核中的数据,实现权限提升等。

微软为了缓解该漏洞,从用户页表中隔离出内核页表,让用户态访问到的内核页表也是经过映射的,并且会将用户页表自动标记为NX,让我们的shellcode无法执行

笔者在这里遇到了很有趣的一点就是在最新的AMD Ryzen9 8945HX

1
2
3
4
5
DbgPrint("Driver Loaded\n");
DriverObject->DriverUnload = DriverUnload;

DWORD64 dmsr = __readmsr(0xC0000082);
DbgPrint("KiSystemCall64 at %p\n", dmsr);

无法让结果是**KiSystemCall64Shadow**(Shadow SSDT依旧是存在的!),反倒是在Intel i5 9300H的CPU上成功实现了。

所以这里只讲在受影响的Intel CPU上如何实现的

Windows内核缓解机制使用了Kva Shadow内存,尝试将其关闭

在注册表HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Memory Management

创建两个DWORD值:FeatureSettingsOverride FeatureSettingsOverrideMask,设置如下后重启

image-20260411103229487

设置值为3,然后重启

image-20240118140220468

**这两个值存在并且值正确的时候,关闭KVAS,**使用的就是KiSystemCall64,找到的是SSDT

image-20260410205731607

image-20260410205814991

删除这两个,开启KVAS的时候就为KiSystemCall64Shadow,需要处理的是Shadow SSDT

image-20260411205104804

image-20260411204856925

image-20260411204910559

Full SSDT Hook

上一篇文章提到主要的问题是在64位系统上根据 ssdt 的算法,不能直接将新的函数的地址直接转为数组的下表(寻址范围太小

image-20260411142643283

  1. 找到临近的空位内存并申请

    我手动调试找到了一块合适的位置进行测试,这一过程可以被自动化

    我选择了在ntoskrl.exe.text最后一部分,根据RVA找到位置,而且调试后发现距离ssdt->table仅有0x04ff189

    image-20260411142739249

    image-20260411143034601

  2. 在新申请的临近位置内编写trampoline,跳转到新的函数

    image-20260411144059555

    最后的效果:

image-20260411144205405

实现代码在附录[《完整SSDT Hook》](##完整SSDT Hook)部分

枚举Shadow SSDT

由于前文关于KVAS保护中所提到的内容,这里要分两种情况进行探讨了

  1. KiSystemCall64到Shadow SSDT(我使用AMD CPU)
  2. KiSystemCall64Shadow到Shadow SSDT(我使用Intel CPU)

从KiSystemCall64开始

回顾前文

然后继续向下查到KiSystemServiceRepeat,主要是为了拿到KeServiceDescriptorTable或者KeServiceDescriptorTableShadow的值,在这里就是

35 05 9F 00AE B6 8E 00

image-20260314213520421

我们只需要改特征值找到KeServiceDescriptorTableShadow

1
2
3
4
5
6
7
8
for (size_t i = 0; i < 0x1000; i++)
{
if (*(tempptr + i) == 0x4c && *(tempptr + i + 1) == 0x8d && *(tempptr + i + 2) == 0x1D) {
offset = *((PLONG)(tempptr + i + 3));
table = (PSYSTEM_SERVICE_TABLE)(tempptr + i + 7 + offset);
break;
}
}

image-20260411150221090

1
dps nt!KeServiceDescriptorTableShadow

image-20260411150438477

对比一下SSDT

image-20260411150458647

多了更多的win32k的函数,并且这里是看不了win32k!W32pServiceTable

image-20260411150712682

附加到使用了win32k的GUI用户态进程就可以看到了,这里attach winlogon.exe

image-20260411152028247

这个是文件管理器explore.exe看到的

image-20260411152411478

到了这里大多数网上的教程就讲的不清楚了,所以我也是慢慢调试出来的

突破点是尝试看下win32k!NtUserQueryWindow

image-20260411160950412

地址居然在win32k!W32pServiceTable之前(比win32k!W32pServiceTable小),而且注意到win32k!W32pServiceTable中的值均为ff开头,不免的让人想起这是做减法

image-20260411161229410

win32k!NtUserQueryWindowtable0x70c3c,尝试使用table的第一个数做减法

image-20260411161354291

总结出公式

1
RealFuncAddr = table - (0xFFFFFFFF-(DWORD)W32pServiceTable[index])>>4

测试代码可见附录[《Shadow SSDT 枚举》](##Shadow SSDT 枚举)

  • DriverEntry中的HANDLE pid = (HANDLE)600;需要修改,这里我用的是winlogon.exe的pid
  • 关于结构体SYSTEM_SERVICE_TABLE的定义可以精简
  • Hook:手法和SSDT的一致

image-20260411165217065

image-20260411165133268

从KiSystemCall64Shadow开始

这个时候要完成两件事情了,KiSystemCall64Shadow找到SSDTShadowSSDT

跟踪KiSystemCall64Shadow找到KiSystemServiceUser

image-20260411210154482

image-20260411210206777

KiSystemServiceUser向下就会找到老熟人

image-20260411210116285

然后用老方法即可,无非就是多几段字节序列搜索

引用

https://learn.microsoft.com/zh-cn/windows/win32/services/service-control-manager

https://r0keb.github.io/posts/PatchGuard-Internals/

https://standa-note.blogspot.com/2015/10/some-tips-to-analyze-patchguard.html

https://blog.tetrane.com/downloads/Tetrane_PatchGuard_Analysis_RS4_v1.01.pdf

https://joe1sn.eu.org/2024/01/25/win-hevd-exp-stackoverflow-III/

附录

简单的ring3驱动加载器

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
#include <windows.h>
#include <iostream>
#include <string>

bool CreateDriver(const std::string& name, const std::string& path) {
SC_HANDLE hSCM = OpenSCManager(NULL, NULL, SC_MANAGER_CREATE_SERVICE);
if (!hSCM) {
std::cerr << "OpenSCManager failed: " << GetLastError() << std::endl;
return false;
}
std::string ntPath = "\\??\\" + path;
SC_HANDLE hService = CreateServiceA(
hSCM,
name.c_str(),
name.c_str(),
SERVICE_START | DELETE | SERVICE_STOP,
SERVICE_KERNEL_DRIVER,
SERVICE_DEMAND_START,
SERVICE_ERROR_NORMAL,
ntPath.c_str(),
NULL, NULL, NULL, NULL, NULL
);

if (!hService) {
DWORD err = GetLastError();
if (err == ERROR_SERVICE_EXISTS) {
std::cout << "Service already exists." << std::endl;
}
else {
std::cerr << "CreateService failed: " << err << std::endl;
CloseServiceHandle(hSCM);
return false;
}
}
else {
std::cout << "Service created successfully." << std::endl;
CloseServiceHandle(hService);
}

CloseServiceHandle(hSCM);
return true;
}

bool StartDriver(const std::string& name) {
SC_HANDLE hSCM = OpenSCManager(NULL, NULL, SC_MANAGER_CONNECT);
if (!hSCM) return false;

SC_HANDLE hService = OpenServiceA(hSCM, name.c_str(), SERVICE_START);
if (!hService) {
std::cerr << "OpenService failed: " << GetLastError() << std::endl;
CloseServiceHandle(hSCM);
return false;
}

if (!StartService(hService, 0, NULL)) {
DWORD err = GetLastError();
if (err == ERROR_SERVICE_ALREADY_RUNNING) {
std::cout << "Already running." << std::endl;
}
else {
std::cerr << "StartService failed: " << err << std::endl;
}
}
else {
std::cout << "Driver started." << std::endl;
}

CloseServiceHandle(hService);
CloseServiceHandle(hSCM);
return true;
}

bool StopDriver(const std::string& name) {
SC_HANDLE hSCM = OpenSCManager(NULL, NULL, SC_MANAGER_CONNECT);
if (!hSCM) return false;

SC_HANDLE hService = OpenServiceA(hSCM, name.c_str(), SERVICE_STOP);
if (!hService) {
std::cerr << "OpenService failed: " << GetLastError() << std::endl;
CloseServiceHandle(hSCM);
return false;
}

SERVICE_STATUS status;
if (!ControlService(hService, SERVICE_CONTROL_STOP, &status)) {
std::cerr << "Stop failed: " << GetLastError() << std::endl;
}
else {
std::cout << "Driver stopped." << std::endl;
}

CloseServiceHandle(hService);
CloseServiceHandle(hSCM);
return true;
}

bool DeleteDriver(const std::string& name) {
SC_HANDLE hSCM = OpenSCManager(NULL, NULL, SC_MANAGER_CONNECT);
if (!hSCM) return false;

SC_HANDLE hService = OpenServiceA(hSCM, name.c_str(), DELETE);
if (!hService) {
std::cerr << "OpenService failed: " << GetLastError() << std::endl;
CloseServiceHandle(hSCM);
return false;
}

if (!DeleteService(hService)) {
std::cerr << "Delete failed: " << GetLastError() << std::endl;
}
else {
std::cout << "Service deleted." << std::endl;
}

CloseServiceHandle(hService);
CloseServiceHandle(hSCM);
return true;
}

int main(int argc, char* argv[]) {
if (argc < 3) {
std::cout << "Usage:\n"
<< " create <name> <path>\n"
<< " start <name>\n"
<< " stop <name>\n"
<< " delete <name>\n";
return 0;
}

std::string action = argv[1];
std::string name = argv[2];

if (action == "create") {
if (argc < 4) {
std::cerr << "Missing driver path.\n";
return 1;
}
std::string path = argv[3];
CreateDriver(name, path);
}
else if (action == "start") {
StartDriver(name);
}
else if (action == "stop") {
StopDriver(name);
}
else if (action == "delete") {
DeleteDriver(name);
}
else {
std::cerr << "Unknown action.\n";
}

return 0;
}

手动映射SYS

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
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
#include <ntifs.h>
#include <ntimage.h>

typedef NTSTATUS(*DriverEntry_t)(
PDRIVER_OBJECT DriverObject,
PUNICODE_STRING RegistryPath
);

typedef struct _SYSTEM_MODULE_ENTRY {
PVOID Reserved[2];
PVOID ImageBase;
ULONG ImageSize;
ULONG Flags;
USHORT Index;
USHORT Unknown;
USHORT LoadCount;
USHORT ModuleNameOffset;
CHAR FullPathName[256];
} SYSTEM_MODULE_ENTRY, * PSYSTEM_MODULE_ENTRY;

typedef struct _SYSTEM_MODULE_INFORMATION {
ULONG NumberOfModules;
SYSTEM_MODULE_ENTRY Modules[1];
} SYSTEM_MODULE_INFORMATION, * PSYSTEM_MODULE_INFORMATION;

// ZwQuerySystemInformation 原型
typedef enum _SYSTEM_INFORMATION_CLASS {
SystemBasicInformation = 0,
SystemModuleInformation = 11
} SYSTEM_INFORMATION_CLASS;

NTSYSAPI
NTSTATUS
NTAPI
ZwQuerySystemInformation(
SYSTEM_INFORMATION_CLASS SystemInformationClass,
PVOID SystemInformation,
ULONG SystemInformationLength,
PULONG ReturnLength
);

//typedef struct _SYSTEM_MODULE_ENTRY {
// PVOID Reserved[2];
// PVOID ImageBase;
// ULONG ImageSize;
// ULONG Flags;
// USHORT Index;
// USHORT Unknown;
// USHORT LoadCount;
// USHORT ModuleNameOffset;
// CHAR FullPathName[256];
//} SYSTEM_MODULE_ENTRY, * PSYSTEM_MODULE_ENTRY;

//typedef struct _SYSTEM_MODULE_INFORMATION {
// ULONG NumberOfModules;
// SYSTEM_MODULE_ENTRY Modules[1];
//} SYSTEM_MODULE_INFORMATION, * PSYSTEM_MODULE_INFORMATION;

PVOID GetKernelBase(PCSTR ModuleName)
{
NTSTATUS status;
ULONG size = 0;
PVOID buffer = NULL;
PVOID base = NULL;

status = ZwQuerySystemInformation(
SystemModuleInformation, NULL, 0, &size
);

if (status != STATUS_INFO_LENGTH_MISMATCH)
return NULL;

buffer = ExAllocatePool2(
POOL_FLAG_NON_PAGED, size, 'bKmM'
);

if (!buffer)
return NULL;

status = ZwQuerySystemInformation(
SystemModuleInformation, buffer, size, &size
);

if (!NT_SUCCESS(status))
{
ExFreePool(buffer);
return NULL;
}

PSYSTEM_MODULE_INFORMATION modules =
(PSYSTEM_MODULE_INFORMATION)buffer;

for (ULONG i = 0; i < modules->NumberOfModules; i++)
{
PSYSTEM_MODULE_ENTRY mod = &modules->Modules[i];

PCSTR fullPath = mod->FullPathName;
PCSTR fileName = fullPath + mod->ModuleNameOffset;

if (_stricmp(fileName, ModuleName) == 0)
{
base = mod->ImageBase;
break;
}
}

ExFreePool(buffer);
return base;
}

PVOID GetKernelModuleBaseByName(PCSTR moduleName)
{
NTSTATUS status;
ULONG size = 0;
PVOID buffer = NULL;
PVOID base = NULL;

// 先获取长度
status = ZwQuerySystemInformation(SystemModuleInformation, NULL, 0, &size);
if (status != STATUS_INFO_LENGTH_MISMATCH)
return NULL;

// 分配非分页池
buffer = ExAllocatePool2(POOL_FLAG_NON_PAGED, size, 'tag1');
if (!buffer)
return NULL;

status = ZwQuerySystemInformation(SystemModuleInformation, buffer, size, &size);
if (!NT_SUCCESS(status)) {
ExFreePool(buffer);
return NULL;
}

PSYSTEM_MODULE_INFORMATION sysModules = (PSYSTEM_MODULE_INFORMATION)buffer;
for (ULONG i = 0; i < sysModules->NumberOfModules; i++)
{
PSYSTEM_MODULE_ENTRY entry = &sysModules->Modules[i];
PCSTR name = entry->FullPathName + entry->ModuleNameOffset; // 获取文件名
if (_stricmp(name, moduleName) == 0)
{
base = entry->ImageBase;
break;
}
}

ExFreePool(buffer);
return base;
}

PVOID GetKernelExport(PVOID moduleBase, PCSTR functionName)
{
if (!moduleBase || !functionName)
return NULL;

PIMAGE_DOS_HEADER dosHeader = (PIMAGE_DOS_HEADER)moduleBase;
if (dosHeader->e_magic != IMAGE_DOS_SIGNATURE)
return NULL;

PIMAGE_NT_HEADERS64 ntHeaders = (PIMAGE_NT_HEADERS64)((PUCHAR)moduleBase + dosHeader->e_lfanew);
if (ntHeaders->Signature != IMAGE_NT_SIGNATURE)
return NULL;

IMAGE_DATA_DIRECTORY exportDirData = ntHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT];
if (!exportDirData.Size)
return NULL;

PIMAGE_EXPORT_DIRECTORY exportDir = (PIMAGE_EXPORT_DIRECTORY)((PUCHAR)moduleBase + exportDirData.VirtualAddress);

ULONG* nameRvas = (ULONG*)((PUCHAR)moduleBase + exportDir->AddressOfNames);
USHORT* ordinals = (USHORT*)((PUCHAR)moduleBase + exportDir->AddressOfNameOrdinals);
ULONG* functions = (ULONG*)((PUCHAR)moduleBase + exportDir->AddressOfFunctions);

for (ULONG i = 0; i < exportDir->NumberOfNames; i++)
{
PCSTR name = (PCSTR)((PUCHAR)moduleBase + nameRvas[i]);
if (_stricmp(name, functionName) == 0)
{
USHORT ordinal = ordinals[i];
ULONG funcRva = functions[ordinal];
return (PVOID)((PUCHAR)moduleBase + funcRva);
}
}

return NULL; // 未找到
}



BOOLEAN FixRelocation(PVOID newBase, PIMAGE_NT_HEADERS64 nt)
{
ULONGLONG delta = (ULONGLONG)newBase - nt->OptionalHeader.ImageBase;
if (delta == 0) return TRUE;

IMAGE_DATA_DIRECTORY relocDir =
nt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC];

if (!relocDir.Size) return TRUE;

PIMAGE_BASE_RELOCATION reloc =
(PIMAGE_BASE_RELOCATION)((PUCHAR)newBase + relocDir.VirtualAddress);

PUCHAR end = (PUCHAR)reloc + relocDir.Size;

while ((PUCHAR)reloc < end && reloc->SizeOfBlock)
{
if (reloc->SizeOfBlock < sizeof(IMAGE_BASE_RELOCATION))
break;

UINT32 count =
(reloc->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION)) / sizeof(USHORT);

PUSHORT list =
(PUSHORT)((PUCHAR)reloc + sizeof(IMAGE_BASE_RELOCATION));

for (UINT32 i = 0; i < count; i++)
{
USHORT type = list[i] >> 12;
USHORT offset = list[i] & 0xFFF;

if (type == IMAGE_REL_BASED_ABSOLUTE)
continue;

if (type == IMAGE_REL_BASED_DIR64)
{
PULONGLONG addr =
(PULONGLONG)((PUCHAR)newBase + reloc->VirtualAddress + offset);

*addr += delta;
}
}

reloc = (PIMAGE_BASE_RELOCATION)((PUCHAR)reloc + reloc->SizeOfBlock);
}

return TRUE;
}

BOOLEAN FixImportTable(PVOID imageBase, PIMAGE_NT_HEADERS64 nt)
{
IMAGE_DATA_DIRECTORY importDir =
nt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT];

if (!importDir.Size)
return TRUE;

PIMAGE_IMPORT_DESCRIPTOR import =
(PIMAGE_IMPORT_DESCRIPTOR)((PUCHAR)imageBase + importDir.VirtualAddress);
size_t i = 0;
while (i < 2)
{
++i;

PCSTR moduleName = (PCSTR)((PUCHAR)imageBase + import->Name);

PVOID moduleBase = GetKernelBase(moduleName);
//PVOID moduleBase = GetKernelModuleBaseByName(moduleName);

if (!moduleBase)
return FALSE;

PIMAGE_THUNK_DATA64 thunk =
(PIMAGE_THUNK_DATA64)((PUCHAR)imageBase + import->FirstThunk);

PIMAGE_THUNK_DATA64 origThunk =
(PIMAGE_THUNK_DATA64)((PUCHAR)imageBase + import->OriginalFirstThunk);

//PIMAGE_THUNK_DATA64 origThunk;

if (import->OriginalFirstThunk)
{
origThunk =
(PIMAGE_THUNK_DATA64)((PUCHAR)imageBase + import->OriginalFirstThunk);
}
else
{
origThunk =
(PIMAGE_THUNK_DATA64)((PUCHAR)imageBase + import->FirstThunk);
}

while (origThunk->u1.AddressOfData)
{
PIMAGE_IMPORT_BY_NAME importName =
(PIMAGE_IMPORT_BY_NAME)((PUCHAR)imageBase + origThunk->u1.AddressOfData);

PVOID fn = GetKernelExport(moduleBase, importName->Name);

if (!fn)
return FALSE;

thunk->u1.Function = (ULONGLONG)fn;

origThunk++;
thunk++;
}
import++;
}
return TRUE;
}


NTSTATUS ExecuteMappedDriver(PDRIVER_OBJECT DriverObject, PVOID imageBase)
{
PIMAGE_DOS_HEADER dos = (PIMAGE_DOS_HEADER)imageBase;
PIMAGE_NT_HEADERS nt = (PIMAGE_NT_HEADERS)((PUCHAR)imageBase + dos->e_lfanew);

ULONG entryRVA = nt->OptionalHeader.AddressOfEntryPoint;

//DriverEntry_t entry = (DriverEntry_t)((PUCHAR)imageBase + 0x3208);
DriverEntry_t entry = (DriverEntry_t)((PUCHAR)imageBase + 0x2C);

UNICODE_STRING fakeReg;
RtlInitUnicodeString(&fakeReg, L"\\Registry\\Machine\\System\\Fake");

return entry(DriverObject, &fakeReg); // DriverObject = NULL
}

NTSTATUS MapImage(PDRIVER_OBJECT DriverObject, PVOID buffer)
{
PIMAGE_DOS_HEADER dos = (PIMAGE_DOS_HEADER)buffer;
PIMAGE_NT_HEADERS nt = (PIMAGE_NT_HEADERS)((PUCHAR)buffer + dos->e_lfanew);

SIZE_T imageSize = nt->OptionalHeader.SizeOfImage;

PVOID imageBase = ExAllocatePool(NonPagedPool, imageSize);
if (!imageBase)
return STATUS_INSUFFICIENT_RESOURCES;

RtlZeroMemory(imageBase, imageSize);

// 拷贝 headers
RtlCopyMemory(imageBase, buffer, nt->OptionalHeader.SizeOfHeaders);

// 拷贝 section
PIMAGE_SECTION_HEADER section = IMAGE_FIRST_SECTION(nt);
for (int i = 0; i < nt->FileHeader.NumberOfSections; i++)
{
PVOID dest = (PUCHAR)imageBase + section[i].VirtualAddress;
PVOID src = (PUCHAR)buffer + section[i].PointerToRawData;

RtlCopyMemory(dest, src, section[i].SizeOfRawData);
}
// - 重定位修复
FixRelocation(imageBase, nt);

// - IAT 导入解析
FixImportTable(imageBase, nt);

return ExecuteMappedDriver(DriverObject, imageBase);
}

NTSTATUS ReadFileToBuffer(
PCWSTR filePath,
PVOID* outBuffer,
PULONG outSize
)
{
NTSTATUS status;
HANDLE fileHandle;
OBJECT_ATTRIBUTES objAttr;
IO_STATUS_BLOCK ioStatus;
UNICODE_STRING uFilePath;

*outBuffer = NULL;
*outSize = 0;

RtlInitUnicodeString(&uFilePath, filePath);

InitializeObjectAttributes(
&objAttr,
&uFilePath,
OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE,
NULL,
NULL
);

// 打开文件
status = ZwCreateFile(
&fileHandle, GENERIC_READ, &objAttr, &ioStatus, NULL, FILE_ATTRIBUTE_NORMAL, FILE_SHARE_READ, FILE_OPEN, FILE_NON_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT, NULL, 0
);

if (!NT_SUCCESS(status)) {
DbgPrint("ZwCreateFile failed: 0x%X\n", status);
return status;
}

// 获取文件大小
FILE_STANDARD_INFORMATION fileInfo;
status = ZwQueryInformationFile(
fileHandle,
&ioStatus,
&fileInfo,
sizeof(fileInfo),
FileStandardInformation
);

if (!NT_SUCCESS(status)) {
ZwClose(fileHandle);
return status;
}

ULONG fileSize = (ULONG)fileInfo.EndOfFile.QuadPart;

// 分配内存
PVOID buffer = ExAllocatePool(NonPagedPool, fileSize);
if (!buffer) {
ZwClose(fileHandle);
return STATUS_INSUFFICIENT_RESOURCES;
}

// 读取文件
status = ZwReadFile(
fileHandle, NULL, NULL, NULL, &ioStatus, buffer, fileSize, NULL, NULL
);

if (!NT_SUCCESS(status)) {
ExFreePool(buffer);
ZwClose(fileHandle);
return status;
}

ZwClose(fileHandle);

*outBuffer = buffer;
*outSize = fileSize;

DbgPrint("Read file success, size: %lu\n", fileSize);

return STATUS_SUCCESS;
}

VOID
DriverUnload(PDRIVER_OBJECT DriverObject)
{
UNREFERENCED_PARAMETER(DriverObject);
DbgPrint("Driver Stopping -> %wZ\n", &DriverObject->DriverName);
}

NTSTATUS
DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath)
{
UNREFERENCED_PARAMETER(RegistryPath);
DbgPrint("Driver Running -> %wZ\n", &DriverObject->DriverName);
DriverObject->DriverUnload = DriverUnload;
PVOID buffer;
ULONG size;

NTSTATUS status = ReadFileToBuffer(
L"\\??\\C:\\Users\\WDKRemoteUser\\Desktop\\test\\helloworld.sys",
//C:\Users\WDKRemoteUser\Desktop\test
&buffer,
&size
);

if (!NT_SUCCESS(status)) {
DbgPrint("Can't read file\n");
return status;
}

MapImage(DriverObject, buffer);
status = STATUS_SUCCESS;
return status;
}

完整SSDT Hook

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
#include "helper.h"
#include <ntifs.h>

typedef NTSTATUS(*NTOPENFILE)(
PHANDLE FileHandle,
ACCESS_MASK DesiredAccess,
POBJECT_ATTRIBUTES ObjectAttributes,
PIO_STATUS_BLOCK IoStatusBlock,
ULONG ShareAccess,
ULONG OpenOptions
);

typedef struct _SYSTEM_SERVICE_TABLE
{
PVOID tableBase;
PVOID serviceCountBase;
ULONG64 numberOfServices;
PVOID unkown;
}SYSTEM_SERVICE_TABLE, * PSYSTEM_SERVICE_TABLE;

NTOPENFILE OriginalNtOpenFile = NULL;
ULONG NtOpenFileIndex = 51;
PSYSTEM_SERVICE_TABLE g_Table = NULL;
ULONG64 g_Trampoline = 0x0;

NTSTATUS HookNtOpenFile(
PHANDLE FileHandle,
ACCESS_MASK DesiredAccess,
POBJECT_ATTRIBUTES ObjectAttributes,
PIO_STATUS_BLOCK IoStatusBlock,
ULONG ShareAccess,
ULONG OpenOptions
)
{
if (ObjectAttributes && ObjectAttributes->ObjectName)
{
DbgPrint("HookNtOpenFile: %wZ\n", ObjectAttributes->ObjectName);
}

return OriginalNtOpenFile(
FileHandle,
DesiredAccess,
ObjectAttributes,
IoStatusBlock,
ShareAccess,
OpenOptions
);
}

LONG64 GetFuncAddr(size_t index, PSYSTEM_SERVICE_TABLE ssdt) {
if (index > ssdt->numberOfServices)
return 0;
LONG offset = ((PLONG)(ssdt->tableBase))[index] >> 4;
return (DWORDLONG)(ssdt->tableBase) + offset;
}

VOID DisableWP()
{
ULONG64 cr0 = __readcr0();
cr0 &= 0xfffffffffffeffff;
__writecr0(cr0);
_disable();
}

VOID EnableWP()
{
ULONG64 cr0 = __readcr0();
cr0 |= 0x10000;
__writecr0(cr0);
_enable();
}

VOID HookSSDT(PSYSTEM_SERVICE_TABLE ssdt)
{
PULONG table = ssdt->tableBase;
ULONG entry = table[NtOpenFileIndex];
ULONG64 base = (ULONG64)table;
ULONG64 original = base + (entry >> 4);
OriginalNtOpenFile = (NTOPENFILE)original;
ULONG param = entry & 0xF;
if (g_Trampoline == 0) {
return;
}
ULONG64 hookAddr = (ULONG64)g_Trampoline; //TODO: 修改为trampoline
ULONG newEntry = (ULONG)(((hookAddr - base) << 4) | param);
DisableWP();
table[NtOpenFileIndex] = newEntry;
EnableWP();
}
VOID UnhookSSDT(PSYSTEM_SERVICE_TABLE ssdt)
{

PULONG table = ssdt->tableBase;
ULONG entry = table[NtOpenFileIndex];
ULONG param = entry & 0xF;
ULONG64 base = (ULONG64)table;
ULONG64 original = (ULONG64)OriginalNtOpenFile;
ULONG newEntry = (ULONG)(((original - base) << 4) | param);
DisableWP();
table[NtOpenFileIndex] = newEntry;
EnableWP();
}

PUCHAR BuildTrampoline(ULONG64 funcAddr)
{
static UCHAR trampo[12] = {
0x48, 0xB8, // mov rax, imm64
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // addr placeholder
0xFF, 0xE0 // jmp rax
};

*(PULONG64)&trampo[2] = funcAddr;

return trampo;
}

VOID WriteTpCode(PSYSTEM_SERVICE_TABLE ssdt, PUCHAR jmpCode)
{
UNREFERENCED_PARAMETER(jmpCode);
PUCHAR tpAddr = (PUCHAR)ssdt->tableBase + 0x04ff189;
DbgPrint("[ssdt hook] trampoline code at %p\n", tpAddr);

DisableWP();
RtlCopyMemory(tpAddr, jmpCode, 12);
EnableWP();
g_Trampoline = (ULONG64)tpAddr;
}

VOID DriverUnload(PDRIVER_OBJECT DriverObject)
{
UNREFERENCED_PARAMETER(DriverObject);
UnhookSSDT(g_Table);
DbgPrint("Driver Unloaded\n");
}

NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath)
{
UNREFERENCED_PARAMETER(RegistryPath);

DbgPrint("Driver Loaded\n");
DriverObject->DriverUnload = DriverUnload;
PVOID kernelbase = GetKernelBase("ntoskrnl.exe");
DbgPrint("[ssdt hook]ntoskrnl base: %p\n", kernelbase);


DWORD64 dmsr = __readmsr(0xC0000082);
DbgPrint("KiSystemCall64 at %p\n", dmsr);
PUCHAR tempptr = (PUCHAR)(dmsr);
LONG offset = 0;
PSYSTEM_SERVICE_TABLE table = NULL;
for (size_t i = 0; i < 0x1000; i++)
{
if (*(tempptr + i) == 0x4c && *(tempptr + i + 1) == 0x8d && *(tempptr + i + 2) == 0x15) {
offset = *((PLONG)(tempptr + i + 3));
table = (PSYSTEM_SERVICE_TABLE)(tempptr + i + 7 + offset);
break;
}
}
if (offset == 0) {
DbgPrint("Not found KeServiceDescriptorTable\n");
return STATUS_NOT_FOUND;
}
g_Table = table;
DbgPrint("KeServiceDescriptorTable at: %p\n tableBase: %p\n MyHook at: %p\n", table, table->tableBase, HookNtOpenFile);
DbgPrint("NtOpenFile at: %p\n", GetFuncAddr(NtOpenFileIndex, table));
PUCHAR tpCode = BuildTrampoline((ULONG64)HookNtOpenFile);
DbgPrint("Trampoline code at: %p\n", tpCode);
WriteTpCode(table, tpCode);

HookSSDT(table);
DbgPrint("[hooked] NtOpenFile at: %p\n", GetFuncAddr(NtOpenFileIndex, table));


return STATUS_SUCCESS;
}

Shadow SSDT 枚举

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

extern PEPROCESS NTAPI PsGetNextProcess(_In_opt_ PEPROCESS Process);
extern UCHAR* NTAPI PsGetProcessImageFileName(_In_ PEPROCESS Process);

typedef NTSTATUS(*NTOPENFILE)(
PHANDLE FileHandle,
ACCESS_MASK DesiredAccess,
POBJECT_ATTRIBUTES ObjectAttributes,
PIO_STATUS_BLOCK IoStatusBlock,
ULONG ShareAccess,
ULONG OpenOptions
);

typedef struct _SYSTEM_SERVICE_TABLE
{
PVOID tableBase;
PVOID serviceCountBase;
ULONG64 numberOfServices;
PVOID unkown1;
PVOID shadowTableBase;
PVOID shaodwserviceCountBase;
ULONG64 numberOfShadowServices;
PVOID unkown2;
}SYSTEM_SERVICE_TABLE, * PSYSTEM_SERVICE_TABLE;
PSYSTEM_SERVICE_TABLE g_Table = 0x0;


LONG64 GetFuncAddr(size_t index, PSYSTEM_SERVICE_TABLE ssdt) {
if (index > ssdt->numberOfShadowServices)
return 0;
LONG offset = (0xFFFFFFFF - ((PLONG)(ssdt->shadowTableBase))[index]) >> 4;
return (DWORDLONG)(ssdt->shadowTableBase) - offset;
}

VOID DriverUnload(PDRIVER_OBJECT DriverObject)
{
UNREFERENCED_PARAMETER(DriverObject);
DbgPrint("Driver Unloaded\n");
}

NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath)
{
UNREFERENCED_PARAMETER(RegistryPath);

DbgPrint("Driver Loaded\n");
DriverObject->DriverUnload = DriverUnload;


DWORD64 dmsr = __readmsr(0xC0000082);
DbgPrint("KiSystemCall64 at %p\n", dmsr);
PUCHAR tempptr = (PUCHAR)(dmsr);
LONG offset = 0;
PSYSTEM_SERVICE_TABLE table = NULL;
for (size_t i = 0; i < 0x1000; i++)
{
if (*(tempptr + i) == 0x4c && *(tempptr + i + 1) == 0x8d && *(tempptr + i + 2) == 0x1D) {
offset = *((PLONG)(tempptr + i + 3));
table = (PSYSTEM_SERVICE_TABLE)(tempptr + i + 7 + offset);
break;
}
}
if (offset == 0) {
DbgPrint("Not found KeServiceDescriptorTableShadow\n");
return STATUS_NOT_FOUND;
}
g_Table = table;
DbgPrint("KeServiceDescriptorTableShadow at: %p\n tableBase: %p\n", table, table->shadowTableBase);


HANDLE pid = (HANDLE)600;
PEPROCESS Process = NULL;

if (NT_SUCCESS(PsLookupProcessByProcessId(pid, &Process)))
{
KAPC_STATE state;

KeStackAttachProcess(Process, &state);

for (size_t i = 0; i < table->numberOfShadowServices; i++)
{
DbgPrint("No.%d func at: %p\n", i, GetFuncAddr(i, table));
}

KeUnstackDetachProcess(&state);

ObDereferenceObject(Process);
}
return STATUS_SUCCESS;
}