Ikoct的饮冰室

你愿意和我学一辈子二进制吗?

0%

跟着2024年腾讯游戏安全竞赛(决赛(前3问))从零开始学Windows内核驱动Hook

记录一下第一次深入驱动层hook学到的东西

题目给了一个驱动和写了username-key的文本 要求将文本放在C:\下并手动加载驱动

创建并启用内核驱动

这里使用Windows自带的sc.exe来加载 使用

sc create [Service name] binpath="\path\to\driver type=kernel start=[Way to start]"

来加载一个驱动服务 需要指定服务名 驱动路径 启动方式

启动方式有:

demand: 需要手动启用
boot: 系统启动时加载
system: 内核初始化时加载
auto: 自动启动
disabled: 禁用

这里选择使用手动启用来制造hook时机:

sc start ACE2024

sc还有其他几个指令:

命令 功能
sc query ACE2024 查询服务状态
sc stop ACE2024 停止服务 (卸载驱动)
sc delete ACE2024 删除服务 (需先停止)
sc config ACE2024 start= auto 修改服务为自动启动

IDA…无法静态分析

驱动本体就有10mb IDA打开后可以看到驱动入口之后都被混淆了 并且后续IDA一直在加载不知道什么东西 而且看起来像报错:

QQ_1744276810321

导致IDA很卡 所以后面都会用Binaryninja来进行静态分析

内核驱动的hook

因为代码的混淆 硬看基本上看不出逻辑 这个时候就应该使用hook技术了 而在这题中 一个内核驱动要读取文件内容大概率要使用NtReadFile()NtCreateFile() 所以这两个函数就是我们hook的目标

环境搭建

参考微软的内核驱动编写教程 VS需要安装以下组件:

QQ_1744293792629

接下来安装相同版本的WDKWSDK 安装这个版本的WDK 其他的不保证能正常编译驱动:

QQ_1744293906177

绕过PatchGuard

而64位Windows驱动有内核保护(PatchGuard) 正常情况下不会允许驱动修改内存 这时候就要通过一些手段来绕过这个保护了

这里参考这位大佬的博客 通过以下两个函数开关PG:

1
2
3
4
5
6
7
8
9
10
11
12
13
KIRQL writeProtectOFFx64() {
KIRQL OldIrql;
OldIrql = KeRaiseIrqlToDpcLevel();
__writecr0(__readcr0() & ~0x10000);
_disable();
return OldIrql;
}

void writeProtectONx64(KIRQL OldIrql) {
_enable();
__writecr0(__readcr0() | 0x10000);
KeLowerIrql(OldIrql);
}

以下是对上述代码的解释

代码 作用
KeRaiseIrqlToDpcLevel() 将当前处理器中断请求级别(IRQL)提升至 DISPATCH_LEVEL(DPC 级别) 防止线程切换干扰
__readcr0() & ~0x10000 读取 CR0 寄存器并清除第 16 位(WP 位) 禁用内存写保护
__writecr0(...) 将修改后的值写回 CR0 寄存器
_disable() 执行 CLI 指令禁用处理器中断 防止中断上下文破坏关键操作

接下来就能进行对上述两个内核函数的hook了 先尝试使用最简单的 inline hoook

对内核函数进行inline hook

被加载到内核的驱动共用API模块 我们可以直接在编写的驱动中通过函数名获取到其地址 接下来的关键就是如何判别读取的文件是否是\??\C:card.txt了 通过这个网站来查找要hook的内核函数的信息

NtCreateFile()的原型:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
NTSYSCALLAPI
NTSTATUS
NTAPI
NtCreateFile(
_Out_ PHANDLE FileHandle,
_In_ ACCESS_MASK DesiredAccess,
_In_ PCOBJECT_ATTRIBUTES ObjectAttributes,
_Out_ PIO_STATUS_BLOCK IoStatusBlock,
_In_opt_ PLARGE_INTEGER AllocationSize,
_In_ ULONG FileAttributes,
_In_ ULONG ShareAccess,
_In_ ULONG CreateDisposition,
_In_ ULONG CreateOptions,
_In_reads_bytes_opt_(EaLength) PVOID EaBuffer,
_In_ ULONG EaLength
);

而要得到的文件句柄来自ObjectAttributes->ObjectName->Buffer路径的文件 所以这里就通过对这个参数来判断是否要打开card.txt

同时我们就可以保留FileHandle来为未来hook NtReadFile()做准备

NtReadFile()的原型:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
NTSYSCALLAPI
NTSTATUS
NTAPI
NtReadFile(
_In_ HANDLE FileHandle,
_In_opt_ HANDLE Event,
_In_opt_ PIO_APC_ROUTINE ApcRoutine,
_In_opt_ PVOID ApcContext,
_Out_ PIO_STATUS_BLOCK IoStatusBlock,
_Out_writes_bytes_(Length) PVOID Buffer,
_In_ ULONG Length,
_In_opt_ PLARGE_INTEGER ByteOffset,
_In_opt_ PULONG Key
);

只需要对比FileHandle和自己保存的文件句柄就能判断是否读取card.txt了

以下就是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
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
#include <string.h>
#include <ntifs.h>
#include <ntdef.h>
#include <ntstatus.h>
#include <ntddk.h>
#define kprintf(format, ...) DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, format, __VA_ARGS__)
typedef UINT64 u64;
typedef UINT32 u32;
typedef UINT16 u16;
typedef UINT8 u8;

u8 mov_jmp_rax1[12] = {
0x48,0xB8,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, //mov rax,xxx
0xFF,0xE0 //jmp rax
}, mov_jmp_rax2[12] = {
0x48,0xB8,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, //mov rax,xxx
0xFF,0xE0 //jmp rax
}, origin1[12] = {
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00
}, origin2[12] = {
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00
};

u8* HookAddr1 = NULL, * HookAddr2 = NULL, Hooked1 = FALSE, Hooked2 = FALSE;

KIRQL writeProtectOFFx64() {
KIRQL OldIrql;
OldIrql = KeRaiseIrqlToDpcLevel();
__writecr0(__readcr0() & ~0x10000);
_disable();
return OldIrql;
}

void writeProtectONx64(KIRQL OldIrql) {
_enable();
__writecr0(__readcr0() | 0x10000);
KeLowerIrql(OldIrql);
}

NTSTATUS dohook1(u8 HOOK) {
Hooked1 = HOOK;
KIRQL irql = writeProtectOFFx64();
if (HookAddr1) {
if (!HOOK) {
memcpy(HookAddr1, origin1, sizeof(origin1));
}
else {
memcpy(HookAddr1, mov_jmp_rax1, sizeof(mov_jmp_rax1));
}

}
writeProtectONx64(irql);
return STATUS_SUCCESS;
}
NTSTATUS dohook2(u8 HOOK) {
Hooked2 = HOOK;
KIRQL irql = writeProtectOFFx64();
if (HookAddr2) {
if (!HOOK) {
memcpy(HookAddr2, origin2, sizeof(origin2));
}
else {
memcpy(HookAddr2, mov_jmp_rax2, sizeof(mov_jmp_rax2));
}

}
writeProtectONx64(irql);
return STATUS_SUCCESS;
}

typedef NTSTATUS(*createfile_ptr)(
_Out_ PHANDLE FileHandle,
_In_ ACCESS_MASK DesiredAccess,
_In_ PCOBJECT_ATTRIBUTES ObjectAttributes,
_Out_ PIO_STATUS_BLOCK IoStatusBlock,
_In_opt_ PLARGE_INTEGER AllocationSize,
_In_ ULONG FileAttributes,
_In_ ULONG ShareAccess,
_In_ ULONG CreateDisposition,
_In_ ULONG CreateOptions,
_In_reads_bytes_opt_(EaLength) PVOID EaBuffer,
_In_ ULONG EaLength
);

typedef NTSTATUS(*readfile_ptr)(
_In_ HANDLE FileHandle,
_In_opt_ HANDLE Event,
_In_opt_ PIO_APC_ROUTINE ApcRoutine,
_In_opt_ PVOID ApcContext,
_Out_ PIO_STATUS_BLOCK IoStatusBlock,
_Out_writes_bytes_(Length) PVOID Buffer,
_In_ ULONG Length,
_In_opt_ PLARGE_INTEGER ByteOffset,
_In_opt_ PULONG Key
);

HANDLE FileHandler = NULL;
NTSTATUS mycreatefile(
_Out_ PHANDLE FileHandle,
_In_ ACCESS_MASK DesiredAccess,
_In_ PCOBJECT_ATTRIBUTES ObjectAttributes,
_Out_ PIO_STATUS_BLOCK IoStatusBlock,
_In_opt_ PLARGE_INTEGER AllocationSize,
_In_ ULONG FileAttributes,
_In_ ULONG ShareAccess,
_In_ ULONG CreateDisposition,
_In_ ULONG CreateOptions,
_In_reads_bytes_opt_(EaLength) PVOID EaBuffer,
_In_ ULONG EaLength
) {
dohook1(FALSE);
createfile_ptr createfile = (createfile_ptr)HookAddr1;
NTSTATUS status = createfile(
FileHandle,
DesiredAccess,
ObjectAttributes,
IoStatusBlock,
AllocationSize,
FileAttributes,
ShareAccess,
CreateDisposition,
CreateOptions,
EaBuffer,
EaLength
);
if (!wcscmp(ObjectAttributes->ObjectName->Buffer, L"\\??\\C:\\card.txt")) {
kprintf("Line %d: Reading card.txt detected in NtCreateFile\n", __LINE__);
FileHandler = *FileHandle;
}
dohook1(TRUE);
return status;
}

NTSTATUS myreadfile(
_In_ HANDLE FileHandle,
_In_opt_ HANDLE Event,
_In_opt_ PIO_APC_ROUTINE ApcRoutine,
_In_opt_ PVOID ApcContext,
_Out_ PIO_STATUS_BLOCK IoStatusBlock,
_Out_writes_bytes_(Length) PVOID Buffer,
_In_ ULONG Length,
_In_opt_ PLARGE_INTEGER ByteOffset,
_In_opt_ PULONG Key
) {
dohook2(FALSE);
readfile_ptr readfile = (readfile_ptr)HookAddr2;
NTSTATUS status = readfile(
FileHandle,
Event,
ApcRoutine,
ApcContext,
IoStatusBlock,
Buffer,
Length,
ByteOffset,
Key
);
if (FileHandler && FileHandler == FileHandle) {
kprintf("Line %d: Reading card.txt detected in NtReadFile\n", __LINE__);
}
dohook2(TRUE);
return status;
}

void DeleteDevice(PDRIVER_OBJECT pDriver) {
kprintf("Line %d: Deleting Driver\n", __LINE__);
if (pDriver->DeviceObject) {
UNICODE_STRING DeviceName;
RtlInitUnicodeString(&DeviceName, SYM);
IoDeleteSymbolicLink(&DeviceName);
IoDeleteDevice(pDriver->DeviceObject);
}
kprintf("Line %d: Driver Deleted\n", __LINE__);
}

void DriverUnload(PDRIVER_OBJECT pDriver) {
kprintf("Line %d: Unloading Driver\n", __LINE__);
if (Hooked1) {
dohook1(FALSE);
}
if (Hooked2) {
dohook2(FALSE);
}
DeleteDevice(pDriver);
kprintf("Line %d: Driver Unloaded\n", __LINE__);
}

NTSTATUS DriverEntry(
_In_ PDRIVER_OBJECT DriverObject,
_In_ PUNICODE_STRING RegistryPath
) {
DriverObject->DriverUnload = DriverUnload;
kprintf("Line %d: Driver Loaded, RegistryPath : %S\n", __LINE__, RegistryPath->Buffer);
HookAddr1 = (u64)NtCreateFile;
HookAddr2 = (u64)NtReadFile;
kprintf("Line %d: HookAddr1 : 0x%llx, HookAddr2 : 0x%llx\n", __LINE__, HookAddr1, HookAddr2);
if (!HookAddr1 || !HookAddr2) {
kprintf("Line %d: HookAddr1 or HookAddr2 is NULL\n", __LINE__);
return STATUS_UNSUCCESSFUL;
}
memcpy(origin1, HookAddr1, sizeof(origin1));
memcpy(origin2, HookAddr2, sizeof(origin2));
*((u64*)(mov_jmp_rax1 + 2)) = (u64)mycreatefile;
*((u64*)(mov_jmp_rax2 + 2)) = (u64)myreadfile;
dohook1(TRUE);
dohook2(TRUE);
kprintf("Line %d: Hooked NtCreateFile and NtReadFile\n", __LINE__);
return STATUS_SUCCESS;
}

自己编写的inline hook要在进入用户函数时就马上进行unhook 否则会递归调用自身

现在 先加载自己编写的驱动 然后加载题目驱动就能记录到读取card.txt的痕迹了 接下来要解决的问题就是如何分析驱动读取用户名和密钥后的行为了 这里可以直接使用DbgBreakPoint()来下断并让Windbg进行托管

Windbg托管驱动设置的断点

修改我们的读取函数:

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
NTSTATUS myreadfile(
_In_ HANDLE FileHandle,
_In_opt_ HANDLE Event,
_In_opt_ PIO_APC_ROUTINE ApcRoutine,
_In_opt_ PVOID ApcContext,
_Out_ PIO_STATUS_BLOCK IoStatusBlock,
_Out_writes_bytes_(Length) PVOID Buffer,
_In_ ULONG Length,
_In_opt_ PLARGE_INTEGER ByteOffset,
_In_opt_ PULONG Key
) {
dohook2(FALSE);
readfile_ptr readfile = (readfile_ptr)HookAddr2;
NTSTATUS status = readfile(
FileHandle,
Event,
ApcRoutine,
ApcContext,
IoStatusBlock,
Buffer,
Length,
ByteOffset,
Key
);
if (FileHandler && FileHandler == FileHandle) {
kprintf("Line %d: Reading card.txt detected in NtReadFile\n", __LINE__);
DbgBreakPoint();
}
dohook2(TRUE);
return status;
}

此时当驱动再读到card.txt时 系统就会断在DbgBreakPoint()处 再用Windbg进行双机调试就能接管到这个断点:

QQ_1744295119223

而读取到的文件内容在变量Buffer(RSP+0x28)中(这里的card.txt内容换掉了 为了测试Task2的):

QQ_1744295305760

下个硬件断点跟到题目驱动里 ba r4 0xffffc589a0ece3f0 在bnj中搜索一下字节码特征定位内存位置:

QQ_1744295470896

接下来可以先静态分析一下了 这里继续往下调试一下也能知道是个复制函数 一路交叉引用向上找到一个看起来像是主逻辑的地方:

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
14000a0f4    int64_t sub_14000a0f4()

14000a0f4 {
14000a0f4 int64_t* from;
14000a100 sub_1400054cc(&from);
14000a105 int32_t count = 0;
14000a105
14000a122 while ((int64_t)count < 0x10)
14000a122 {
14000a122 *(uint16_t*)(&data_140675e20 + ((int64_t)count << 1)) ^= 0xace;
14000a115 count += 1;
14000a122 }
14000a122
14000a17d void* len;
14000a17d int32_t result;
14000a17d int64_t r8;
14000a17d int64_t r9;
14000a17d
14000a17d if (j_sub_14000a343(&data_140675e20, &from, r8, r9) < 0 || !from || !len)
14000a2d3 result = 0;
14000a17d else
14000a17d {
14000a192 void to; // Copy content of card.txt into to
14000a192 str_copy_(&to, from, len);
14000a192
14000a1a6 if ((uint32_t)sub_140009ad8(&to))
14000a1a6 {
14000a2ce free(&to);
14000a2d3 result = 0;
14000a1a6 }
14000a1a6 else
14000a1a6 {
14000a1bb void* pos_0x2d = find(&to, u"-…", nullptr);
14000a1bb
14000a1cb if (pos_0x2d == -1)
14000a1cb {
14000a2ce free(&to);
14000a2d3 result = 0;
14000a1cb }
14000a1cb else
14000a1cb {
14000a1e6 void usrname;
14000a1e6 split(&to, &usrname, 0, pos_0x2d);
14000a20a void password;
14000a20a char r11_1 = split(&to, &password, (char*)pos_0x2d + 1, -ffffffffffffffff);
14000a217 int64_t* rax_11 = str_copy__(&password);
14000a22a char* var_88;
14000a22a int32_t decnum = basen(rax_11, &var_88, 0xa, rax_11);
14000a22a
14000a23d if ((int32_t)*(uint8_t*)var_88)
14000a23d {
14000a2b7 label_14000a2b7:
14000a2b7 free(&password);
14000a2c4 free(&usrname);
14000a2ce free(&to);
14000a2d3 result = 0;
14000a23d }
14000a23d else
14000a23d {
14000a247 int64_t len_1 = lenth(&usrname);
14000a259 int64_t* usrname_1 = str_copy__(&usrname);
14000a259
14000a27e if (decnum != encrypt(usrname_1, len_1, usrname_1, r11_1))
14000a27e goto label_14000a2b7;
14000a27e
14000a28d free(&password);
14000a29a free(&usrname);
14000a2a4 free(&to);
14000a2a9 result = 1;
14000a23d }
14000a1cb }
14000a1a6 }
14000a17d }
14000a17d
14000a2dc return result;
14000a0f4 }

大部分都是string的成员函数 密钥的处理只有一个转化为10进制数 而用户名的操作在encrypt中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
140afc131    uint64_t sub_140afc131(char* usrname, int64_t len, int16_t end @ rax, char arg4 @ r11)

140afc131 {
140afc131 bool c;
140afc136 end = end + arg4;
140afc15d int32_t res = 0;
140afc165 int32_t count = 0;
140afc165
140afc198 while ((int64_t)count < len)
140afc198 {
140afc198 int64_t now;
140afc1ab now = usrname[(int64_t)count];
140afc1ae char var_28_1 = now;
140afc1b1 char temp0_1 = *(uint8_t*)((char*)now)[1];
140afc1b1 *(uint8_t*)((char*)now)[1] = *(uint8_t*)((char*)now)[1];
140afc1b1 *(uint8_t*)((char*)now)[1] = temp0_1;
140afc1cf res = ((uint32_t)var_28_1 + res) * 0x1003f;
140afc17d count += 1;
140afc198 }
140afc198
140afc1f3 return (uint64_t)res;
140afc131 }

一个类似CRC32的校验和计算 到这里Task1&2就能轻松完成了

找到用户名为administrator的KEY 作为答案提交 && 编写注册机

注册机:

1
2
3
4
5
6
7
8
mask = (1 << 32) - 1
username = b'administrator'
checksum = 0
for c in username:
checksum = ((c + checksum) * 0x1003f) & mask

print(f'{username.decode()}-{checksum}')
# administrator-4007951923

而Task3需要无论加载到的card.txt内容是什么都能正常加载驱动 上面的逻辑也能看出来主要就是用户名的校验和验证 这里直接通过修改我们的读取函数将正确的用户名和密钥复制到从文件句柄读取到的内存里即可

编写一个exp 在exp程序运行后 对于任意的用户名-key Loader.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
NTSTATUS myreadfile(
_In_ HANDLE FileHandle,
_In_opt_ HANDLE Event,
_In_opt_ PIO_APC_ROUTINE ApcRoutine,
_In_opt_ PVOID ApcContext,
_Out_ PIO_STATUS_BLOCK IoStatusBlock,
_Out_writes_bytes_(Length) PVOID Buffer,
_In_ ULONG Length,
_In_opt_ PLARGE_INTEGER ByteOffset,
_In_opt_ PULONG Key
) {
dohook2(FALSE);
readfile_ptr readfile = (readfile_ptr)HookAddr2;
NTSTATUS status = readfile(
FileHandle,
Event,
ApcRoutine,
ApcContext,
IoStatusBlock,
Buffer,
Length,
ByteOffset,
Key
);
if (FileHandler && FileHandler == FileHandle) {
kprintf("Line %d: Reading card.txt detected in NtReadFile\n", __LINE__);
//DbgBreakPoint();
/* Replace the user-key with the right one */
u8 CorrectPair[] = "ACE-4051574845\x00";
memset(Buffer, 0, 0x1000);
memcpy(Buffer, CorrectPair, strlen(CorrectPair));
/* Replace the user-key with the right one */
}
dohook2(TRUE);
return status;
}

完整驱动代码:

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
#include <string.h>
#include <ntifs.h>
#include <ntdef.h>
#include <ntstatus.h>
#include <ntddk.h>
#include <ntstrsafe.h>
#define MAX_BACKTRACE_DEPTH 0x20
#define SYM L"\\??\\Hook_API"
#define kprintf(format, ...) DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, format, __VA_ARGS__)
typedef UINT64 u64;
typedef UINT32 u32;
typedef UINT16 u16;
typedef UINT8 u8;

u8 mov_jmp_rax1[12] = {
0x48,0xB8,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, //mov rax,xxx
0xFF,0xE0 //jmp rax
}, mov_jmp_rax2[12] = {
0x48,0xB8,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, //mov rax,xxx
0xFF,0xE0 //jmp rax
}, origin1[12] = {
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00
}, origin2[12] = {
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00
};

u8* HookAddr1 = NULL, * HookAddr2 = NULL, Hooked1 = FALSE, Hooked2 = FALSE;

KIRQL writeProtectOFFx64() {
KIRQL OldIrql;
OldIrql = KeRaiseIrqlToDpcLevel();
__writecr0(__readcr0() & ~0x10000);
_disable();
return OldIrql;
}

void writeProtectONx64(KIRQL OldIrql) {
_enable();
__writecr0(__readcr0() | 0x10000);
KeLowerIrql(OldIrql);
}

NTSTATUS dohook1(u8 HOOK) {
Hooked1 = HOOK;
KIRQL irql = writeProtectOFFx64();
if (HookAddr1) {
if (!HOOK) {
memcpy(HookAddr1, origin1, sizeof(origin1));
}
else {
memcpy(HookAddr1, mov_jmp_rax1, sizeof(mov_jmp_rax1));
}

}
writeProtectONx64(irql);
return STATUS_SUCCESS;
}
NTSTATUS dohook2(u8 HOOK) {
Hooked2 = HOOK;
KIRQL irql = writeProtectOFFx64();
if (HookAddr2) {
if (!HOOK) {
memcpy(HookAddr2, origin2, sizeof(origin2));
}
else {
memcpy(HookAddr2, mov_jmp_rax2, sizeof(mov_jmp_rax2));
}

}
writeProtectONx64(irql);
return STATUS_SUCCESS;
}

typedef NTSTATUS(*createfile_ptr)(
_Out_ PHANDLE FileHandle,
_In_ ACCESS_MASK DesiredAccess,
_In_ PCOBJECT_ATTRIBUTES ObjectAttributes,
_Out_ PIO_STATUS_BLOCK IoStatusBlock,
_In_opt_ PLARGE_INTEGER AllocationSize,
_In_ ULONG FileAttributes,
_In_ ULONG ShareAccess,
_In_ ULONG CreateDisposition,
_In_ ULONG CreateOptions,
_In_reads_bytes_opt_(EaLength) PVOID EaBuffer,
_In_ ULONG EaLength
);

typedef NTSTATUS(*readfile_ptr)(
_In_ HANDLE FileHandle,
_In_opt_ HANDLE Event,
_In_opt_ PIO_APC_ROUTINE ApcRoutine,
_In_opt_ PVOID ApcContext,
_Out_ PIO_STATUS_BLOCK IoStatusBlock,
_Out_writes_bytes_(Length) PVOID Buffer,
_In_ ULONG Length,
_In_opt_ PLARGE_INTEGER ByteOffset,
_In_opt_ PULONG Key
);

HANDLE FileHandler = NULL;
NTSTATUS mycreatefile(
_Out_ PHANDLE FileHandle,
_In_ ACCESS_MASK DesiredAccess,
_In_ PCOBJECT_ATTRIBUTES ObjectAttributes,
_Out_ PIO_STATUS_BLOCK IoStatusBlock,
_In_opt_ PLARGE_INTEGER AllocationSize,
_In_ ULONG FileAttributes,
_In_ ULONG ShareAccess,
_In_ ULONG CreateDisposition,
_In_ ULONG CreateOptions,
_In_reads_bytes_opt_(EaLength) PVOID EaBuffer,
_In_ ULONG EaLength
) {
dohook1(FALSE);
createfile_ptr createfile = (createfile_ptr)HookAddr1;
NTSTATUS status = createfile(
FileHandle,
DesiredAccess,
ObjectAttributes,
IoStatusBlock,
AllocationSize,
FileAttributes,
ShareAccess,
CreateDisposition,
CreateOptions,
EaBuffer,
EaLength
);
if (!wcscmp(ObjectAttributes->ObjectName->Buffer, L"\\??\\C:\\card.txt")) {
kprintf("Line %d: Reading card.txt detected in NtCreateFile\n", __LINE__);
FileHandler = *FileHandle;
}
dohook1(TRUE);
return status;
}

NTSTATUS myreadfile(
_In_ HANDLE FileHandle,
_In_opt_ HANDLE Event,
_In_opt_ PIO_APC_ROUTINE ApcRoutine,
_In_opt_ PVOID ApcContext,
_Out_ PIO_STATUS_BLOCK IoStatusBlock,
_Out_writes_bytes_(Length) PVOID Buffer,
_In_ ULONG Length,
_In_opt_ PLARGE_INTEGER ByteOffset,
_In_opt_ PULONG Key
) {
dohook2(FALSE);
readfile_ptr readfile = (readfile_ptr)HookAddr2;
NTSTATUS status = readfile(
FileHandle,
Event,
ApcRoutine,
ApcContext,
IoStatusBlock,
Buffer,
Length,
ByteOffset,
Key
);
if (FileHandler && FileHandler == FileHandle) {
kprintf("Line %d: Reading card.txt detected in NtReadFile\n", __LINE__);
//DbgBreakPoint();
/* Replace the user-key with the right one */
u8 CorrectPair[] = "ACE-4051574845\x00";
memset(Buffer, 0, 0x1000);
memcpy(Buffer, CorrectPair, strlen(CorrectPair));
/* Replace the user-key with the right one */
}
dohook2(TRUE);
return status;
}

void DeleteDevice(PDRIVER_OBJECT pDriver) {
kprintf("Line %d: Deleting Driver\n", __LINE__);
if (pDriver->DeviceObject) {
UNICODE_STRING DeviceName;
RtlInitUnicodeString(&DeviceName, SYM);
IoDeleteSymbolicLink(&DeviceName);
IoDeleteDevice(pDriver->DeviceObject);
}
kprintf("Line %d: Driver Deleted\n", __LINE__);
}

void DriverUnload(PDRIVER_OBJECT pDriver) {
kprintf("Line %d: Unloading Driver\n", __LINE__);
if (Hooked1) {
dohook1(FALSE);
}
if (Hooked2) {
dohook2(FALSE);
}
DeleteDevice(pDriver);
kprintf("Line %d: Driver Unloaded\n", __LINE__);
}

NTSTATUS DriverEntry(
_In_ PDRIVER_OBJECT DriverObject,
_In_ PUNICODE_STRING RegistryPath
) {
DriverObject->DriverUnload = DriverUnload;
kprintf("Line %d: Driver Loaded, RegistryPath : %S\n", __LINE__, RegistryPath->Buffer);
HookAddr1 = (u64)NtCreateFile;
HookAddr2 = (u64)NtReadFile;
kprintf("Line %d: HookAddr1 : 0x%llx, HookAddr2 : 0x%llx\n", __LINE__, HookAddr1, HookAddr2);
if (!HookAddr1 || !HookAddr2) {
kprintf("Line %d: HookAddr1 or HookAddr2 is NULL\n", __LINE__);
return STATUS_UNSUCCESSFUL;
}
memcpy(origin1, HookAddr1, sizeof(origin1));
memcpy(origin2, HookAddr2, sizeof(origin2));
*((u64*)(mov_jmp_rax1 + 2)) = (u64)mycreatefile;
*((u64*)(mov_jmp_rax2 + 2)) = (u64)myreadfile;
dohook1(TRUE);
dohook2(TRUE);
kprintf("Line %d: Hooked NtCreateFile and NtReadFile\n", __LINE__);
return STATUS_SUCCESS;
}

参考

腾讯游戏安全大赛2024决赛题解 https://xia0ji233.pro/2024/04/22/tencent-race-2024-pre-final/index.html