2024腾讯游戏安全PC初赛

2024腾讯游戏安全PC初赛

小Q是一个PC客户端安全爱好者。有一天他得到了一个未知工具,含有一个exe、一个sys和一份使用说明。说明写道:ACE的宝库钥匙由两串 token组成.运行这组程序.它们会将两串 token藏于内存中。宝库有两位守护者会制裁使用违禁工具的冒险家,在寻宝时务必谨慎小心。

题目

image-20240923132824209

(一)解题过程

拿到hack.exe,浅分析一下发现加了VM,并且有检测黑客工具的行为,检测到了之后即使关闭黑客程序也会影响程序正常运行,但是xdbg稍微改一下还是可以动调的,在xdbg里下一些可能的函数断点,我这里在这些地方下了断点

image-20240923131144727

运行发现程序会多次在WriteProcessMemory下断下,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
//dllmain.cpp
#include "pch.h"
#include <windows.h>
#include <shellapi.h>
#include <detours.h>
#include <tlhelp32.h>
#include <stdlib.h>
#include <stdio.h>
#pragma comment(lib,"detours.lib")
#define _KDEBUG
#define DBGMGEBOX(fmt, ...) \
do { \
/* 假设最大长度为1024,根据需要调整大小 */ \
wsprintfA(out, fmt, __VA_ARGS__); \
MessageBoxA(NULL, out, "提示", MB_OK); \
} while(0)
char out[100];
typedef BOOL(WINAPI* WriteProcessMemory_t)(
_In_ HANDLE hProcess,
_In_ LPVOID lpBaseAddress,
_In_reads_bytes_(nSize) LPCVOID lpBuffer,
_In_ SIZE_T nSize,
_Out_opt_ SIZE_T* lpNumberOfBytesWritten
);
WriteProcessMemory_t TrueWriteProcessMemory = NULL;
BOOL
WINAPI
HookWriteProcessMemory(
_In_ HANDLE hProcess,
_In_ LPVOID lpBaseAddress,
_In_reads_bytes_(nSize) LPCVOID lpBuffer,
_In_ SIZE_T nSize,
_Out_opt_ SIZE_T * lpNumberOfBytesWritten
)
{
char fileName[12] = { 0 };
sprintf(fileName, "out%d.txt", (int)hProcess % 1000);
HANDLE hFile = CreateFile(fileName, FILE_APPEND_DATA, FILE_SHARE_READ, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
if (hFile == INVALID_HANDLE_VALUE)
{
DBGMGEBOX("CreateFile Fail");
return TrueWriteProcessMemory(hProcess, lpBaseAddress, lpBuffer, nSize, lpNumberOfBytesWritten);
}
SetFilePointer(hFile, 0, NULL, FILE_END);
DWORD bytesWritten;
BOOL result = WriteFile(hFile, lpBuffer, nSize, &bytesWritten, NULL);
CloseHandle(hFile);
DBGMGEBOX("findProcess WriteProcessMemory:%p,size:%d\n", hProcess,nSize);
return TrueWriteProcessMemory(hProcess, lpBaseAddress, lpBuffer, nSize, lpNumberOfBytesWritten);
}


BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) {
switch (ul_reason_for_call) {
case DLL_PROCESS_ATTACH:

DetourTransactionBegin();
DetourUpdateThread(GetCurrentThread());

TrueWriteProcessMemory = (WriteProcessMemory_t)DetourFindFunction("kernel32.dll", "WriteProcessMemory");
DetourAttach(&(PVOID&)TrueWriteProcessMemory, HookWriteProcessMemory);

DetourTransactionCommit();
break;
case DLL_PROCESS_DETACH:
DetourTransactionBegin();
DetourUpdateThread(GetCurrentThread());
DetourDetach(&(PVOID&)TrueWriteProcessMemory, HookWriteProcessMemory);

DetourTransactionCommit();
break;
}
return TRUE;
}

这里输出了三个txt文件

image-20240923131546102

其中out200.txt文件有明显的PE头

image-20240923131748329

去除前面的字节,把文件丢到DIE里分析一下发现是dll64文件并且貌似没加壳,所以hack.exe通过WriteProcessMemory往某个进程写入了一个dll?怀疑是远程注入,至于做了什么,有可能跟token有关,继续分析。

ida64打开发现程序的dllMain入口还是被加密了

image-20240923132428865

还是继续动调,随便找了个64位的可执行文件,拖到X64dbg里运行,直接用xdbg的注入方式将out200.dll注入进程,在

入口点下断点,并且对一些可疑的WINDOWS API下断观察进程行为

image-20240913213116556

这里我对下面这几个API下了断点

image-20240913213203300

运行,第一次成功在openProcess函数断下

image-20240913213252406

观察传参窗口,找到了一个系统进程名称字符串winlogon.exe,而这里调用的是openProcess,疑似是对系统进程winlogon.exe做了一些操作。继续分析,运行到返回,回溯一层函数,找到一段没有被加密的代码

image-20240913213951735

汇编代码不是很好看,根据偏移在IDA里反汇编看看

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
__int64 __fastcall sub_1800063D0(_DWORD *Dst, DWORD dwProcessId)
{
__m128i si128; // xmm0
__m128i v6; // xmm0
__int64 result; // rax
__m128i v10; // xmm2
size_t v14; // rbx
__int64 v15; // rax
int v16; // r14d
HANDLE Toolhelp32Snapshot; // rsi
HANDLE v18; // rax
__int64 v21; // rbx
CHAR Caption[16]; // [rsp+40h] [rbp+0h] BYREF

_RBP = (unsigned __int64)Caption & 0xFFFFFFFFFFFFFFE0ui64;
if ( !dwProcessId )
{
*(_QWORD *)(((unsigned __int64)Caption & 0xFFFFFFFFFFFFFFE0ui64) + 0x28) = 0xC65BF3E99CAA093Cui64;
*(_QWORD *)(((unsigned __int64)Caption & 0xFFFFFFFFFFFFFFE0ui64) + 8) = 0xC65BF3E99CAA093Cui64;
*(_QWORD *)_RBP = 0xE795A71250E2465Aui64;
si128 = _mm_load_si128((const __m128i *)_RBP);
*(_QWORD *)(((unsigned __int64)Caption & 0xFFFFFFFFFFFFFFE0ui64) + 0x20) = 0xE795A7603F90341Fui64;
v6 = _mm_xor_si128(si128, *(__m128i *)(((unsigned __int64)Caption & 0xFFFFFFFFFFFFFFE0ui64) + 0x20));
*(_QWORD *)(((unsigned __int64)Caption & 0xFFFFFFFFFFFFFFE0ui64) + 0x20) = 0x84FAD5301FE45158ui64;
*(__m128i *)_RBP = v6;
*(_QWORD *)(((unsigned __int64)Caption & 0xFFFFFFFFFFFFFFE0ui64) + 0x28) = 0xBF19D3ADD5D97A59ui64;
*(_QWORD *)(((unsigned __int64)Caption & 0xFFFFFFFFFFFFFFE0ui64) + 0x1A0) = 0xE795A7603F90341Fui64;
*(_QWORD *)(((unsigned __int64)Caption & 0xFFFFFFFFFFFFFFE0ui64) + 0x30) = 0xBD1C9CA3F4C12EC9ui64;
*(_QWORD *)(((unsigned __int64)Caption & 0xFFFFFFFFFFFFFFE0ui64) + 0x38) = 0xA727C05763438E84ui64;
*(_QWORD *)(((unsigned __int64)Caption & 0xFFFFFFFFFFFFFFE0ui64) + 0x1A8) = 0xC65BF3E99CAA093Cui64;
*(_QWORD *)(((unsigned __int64)Caption & 0xFFFFFFFFFFFFFFE0ui64) + 0x1B0) = 0xCF59BCC699A060E9ui64;
*(_QWORD *)(((unsigned __int64)Caption & 0xFFFFFFFFFFFFFFE0ui64) + 0x1B8) = 0xA727C0574231E1F6ui64;
__asm
{
vmovdqu ymm0, [rbp+210h+var_70]
vpxor ymm1, ymm0, ymmword ptr [rbp+210h+Text]
vmovdqa ymmword ptr [rbp+210h+Text], ymm1
vzeroupper
}
MessageBoxA(0i64, (LPCSTR)(_RBP + 32), (LPCSTR)((unsigned __int64)Caption & 0xFFFFFFFFFFFFFFE0ui64), 0);
LABEL_3:
GetLastError();
*(_QWORD *)(((unsigned __int64)Caption & 0xFFFFFFFFFFFFFFE0ui64) + 0x20) = 0xE795A7603F90341Fui64;
*(_QWORD *)_RBP = 0x88D6871250E2465Aui64;
*(_QWORD *)(((unsigned __int64)Caption & 0xFFFFFFFFFFFFFFE0ui64) + 0x28) = 0xC65BF3E99CAA093Cui64;
*(_QWORD *)(((unsigned __int64)Caption & 0xFFFFFFFFFFFFFFE0ui64) + 8) = 0xC65BF98DB9906C58ui64;
*(__m128i *)_RBP = _mm_xor_si128(
_mm_load_si128((const __m128i *)_RBP),
*(__m128i *)(((unsigned __int64)Caption & 0xFFFFFFFFFFFFFFE0ui64) + 0x20));
return sub_180006A00((void *)((unsigned __int64)Caption & 0xFFFFFFFFFFFFFFE0ui64));
}
if ( !(unsigned __int8)sub_180006FC0() )
{
*(_QWORD *)(((unsigned __int64)Caption & 0xFFFFFFFFFFFFFFE0ui64) + 0x1A0) = 0xE795A7603F90341Fui64;
*(_QWORD *)(((unsigned __int64)Caption & 0xFFFFFFFFFFFFFFE0ui64) + 0x48) = 0x44F651D568826090i64;
*(_QWORD *)(((unsigned __int64)Caption & 0xFFFFFFFFFFFFFFE0ui64) + 0x1C0) = 0x52C9FCDC77FF5FC3i64;
*(_QWORD *)(((unsigned __int64)Caption & 0xFFFFFFFFFFFFFFE0ui64) + 0x20) = 0xDDD2E92971C27548ui64;
*(_QWORD *)(((unsigned __int64)Caption & 0xFFFFFFFFFFFFFFE0ui64) + 0x28) = 0xA43EB7C9E8CF4E1Cui64;
*(_QWORD *)(((unsigned __int64)Caption & 0xFFFFFFFFFFFFFFE0ui64) + 0x30) = 0xA62FD5B4C980079Cui64;
*(_QWORD *)(((unsigned __int64)Caption & 0xFFFFFFFFFFFFFFE0ui64) + 0x38) = 0xD55585772756849Aui64;
*(_QWORD *)(((unsigned __int64)Caption & 0xFFFFFFFFFFFFFFE0ui64) + 0x40) = 0x52C9FCDC7DDE2DACi64;
v10 = _mm_load_si128((const __m128i *)(_RBP + 64));
*(_QWORD *)(((unsigned __int64)Caption & 0xFFFFFFFFFFFFFFE0ui64) + 0x1C8) = 0x44F651D568826090i64;
_XMM2 = _mm_xor_si128(v10, *(__m128i *)(((unsigned __int64)Caption & 0xFFFFFFFFFFFFFFE0ui64) + 0x1C0));
*(_QWORD *)(((unsigned __int64)Caption & 0xFFFFFFFFFFFFFFE0ui64) + 0x1A8) = 0xC65BF3E99CAA093Cui64;
*(_QWORD *)(((unsigned __int64)Caption & 0xFFFFFFFFFFFFFFE0ui64) + 0x1B0) = 0xCF59BCC699A060E9ui64;
*(_QWORD *)(((unsigned __int64)Caption & 0xFFFFFFFFFFFFFFE0ui64) + 0x1B8) = 0xA727C0574231E1F6ui64;
__asm
{
vmovdqu ymm0, [rbp+210h+var_70]
vpxor ymm1, ymm0, ymmword ptr [rbp+210h+Text]
vmovdqa [rbp+210h+var_1D0], xmm2
vmovdqa ymmword ptr [rbp+210h+Text], ymm1
vzeroupper
}
sub_180006A00((void *)(_RBP + 32));
}
v14 = -1i64;
if ( dwProcessId == -1 )
{
Dst[34] = GetCurrentProcessId();
*((_QWORD *)Dst + 13) = -1i64;
}
else
{
Dst[34] = dwProcessId;
v18 = OpenProcess(0x1FFFFFu, 0, dwProcessId);
*((_QWORD *)Dst + 13) = v18;
if ( !v18 )
{
*(_QWORD *)(((unsigned __int64)Caption & 0xFFFFFFFFFFFFFFE0ui64) + 0x38) = 0xA727C0574231E1F6ui64;
*(_QWORD *)(((unsigned __int64)Caption & 0xFFFFFFFFFFFFFFE0ui64) + 0x20) = 0x84FAD53051F54450ui64;
*(_QWORD *)(((unsigned __int64)Caption & 0xFFFFFFFFFFFFFFE0ui64) + 0x1A0) = 0xE795A7603F90341Fui64;
*(_QWORD *)(((unsigned __int64)Caption & 0xFFFFFFFFFFFFFFE0ui64) + 0x28) = 0xA92981ACBCD97A59ui64;
*(_QWORD *)(((unsigned __int64)Caption & 0xFFFFFFFFFFFFFFE0ui64) + 0x30) = 0xCF59BCC699AA419Bui64;
*(_QWORD *)(((unsigned __int64)Caption & 0xFFFFFFFFFFFFFFE0ui64) + 0x1A8) = 0xC65BF3E99CAA093Cui64;
*(_QWORD *)(((unsigned __int64)Caption & 0xFFFFFFFFFFFFFFE0ui64) + 0x1B0) = 0xCF59BCC699A060E9ui64;
*(_QWORD *)(((unsigned __int64)Caption & 0xFFFFFFFFFFFFFFE0ui64) + 0x1B8) = 0xA727C0574231E1F6ui64;
__asm
{
vmovdqu ymm0, [rbp+210h+var_70]
vpxor ymm1, ymm0, ymmword ptr [rbp+210h+Text]
vmovdqa ymmword ptr [rbp+210h+Text], ymm1
vzeroupper
}
sub_180006A00((void *)(_RBP + 32));//打印报错信息
goto LABEL_3;
}
}
Dst[30] = 0x1FFFFF;
v15 = -1i64;
do
++v15;
while ( *((_BYTE *)Dst + v15) );
if ( !v15 )
{
v16 = Dst[34];
Toolhelp32Snapshot = CreateToolhelp32Snapshot(2u, 0);
if ( Toolhelp32Snapshot != (HANDLE)-1i64 )
{
*(_DWORD *)(((unsigned __int64)Caption & 0xFFFFFFFFFFFFFFE0ui64) + 0x60) = 304;
memset((void *)(_RBP + 100), 0, 0x12Cui64);
if ( Process32First(Toolhelp32Snapshot, (LPPROCESSENTRY32)(_RBP + 96)) )
{
while ( *(_DWORD *)(((unsigned __int64)Caption & 0xFFFFFFFFFFFFFFE0ui64) + 0x68) != v16 )
{
if ( !Process32Next(Toolhelp32Snapshot, (LPPROCESSENTRY32)(_RBP + 96)) )
goto LABEL_20;
}
do
++v14;
while ( *(_BYTE *)(_RBP + 140 + v14) );
memmove(Dst, (const void *)(_RBP + 140), v14);
}
LABEL_20:
CloseHandle(Toolhelp32Snapshot);
}
}
*((_QWORD *)Dst + 18) = sub_1800068D0(Dst, Dst);
v21 = 0i64;
*(_DWORD *)(((unsigned __int64)Caption & 0xFFFFFFFFFFFFFFE0ui64) + 8) = Dst[34];
*(_QWORD *)_RBP = 0i64;
EnumWindows(EnumFunc, (unsigned __int64)Caption & 0xFFFFFFFFFFFFFFE0ui64);
result = *(_QWORD *)_RBP;
if ( *(_QWORD *)_RBP )
v21 = *(_QWORD *)_RBP;
*((_QWORD *)Dst + 16) = v21;
return result;
}

这段代码一次进行了获取进程pid,打开进程,遍历模块等操作,并且在函数失败后做了一些奇奇怪怪的东西,对一些地址赋上了一些64位的值,猜测是隐藏字符串来打印调试信息用的,再通过messagebox和outputDebugString给出调试信息,显示打开进程失败,猜测是因为hack.exe启动是管理员启动,这里失去了管理员权限。

image-20240913221222730

分析完这个函数,继续回溯一层,运行到返回,定位到这个地方

image-20240913221520425

继续根据偏移转到IDA里看反汇编

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
int sub_180001990()
{
size_t v0; // rbx
DWORD v1; // eax
__m128i Dst; // [rsp+20h] [rbp-48h] BYREF
__int64 v4; // [rsp+30h] [rbp-38h]
__int64 v5; // [rsp+38h] [rbp-30h]
__m128i Src; // [rsp+40h] [rbp-28h] BYREF

Dst.m128i_i64[0] = 0xE795A7603F90341Fui64;
Dst.m128i_i64[1] = 0xC65BF3E99CAA093Cui64;
Src.m128i_i64[0] = 0x89FAC00F53FE5D68ui64;
Src.m128i_i64[1] = 0xC65BF3E9F9D26C12ui64;
Src = _mm_xor_si128(_mm_load_si128(&Src), Dst);
v0 = -1i64;
do
++v0;
while ( Src.m128i_i8[v0] );
memmove(dword_1800349A0, &Src, v0);
Dst.m128i_i64[0] = 0i64;
v4 = 0i64;
v5 = 15i64;
sub_180004770(&Dst, &Src, v0);
v1 = sub_1800070A0(&Dst);
sub_1800063D0(dword_1800349A0, v1);
return atexit(sub_180020C90);
}

前面应该是一个加密的字符串操作,用python打印出字符串

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
def hex_xor_to_string(a, b):
result = a ^ b
hex_str = hex(result)[2:]
if len(hex_str) % 2 != 0:
hex_str = '0' + hex_str

result_str = ''.join(chr(int(hex_str[i:i+2], 16)) for i in range(0, len(hex_str), 2))
return result_str

x1 = 0xE795A7603F90341F
x2 = 0xC65BF3E99CAA093C
y1 = 0x89FAC00F53FE5D68
y2 = 0xC65BF3E9F9D26C12

result1 = hex_xor_to_string(x1, y1)
result2 = hex_xor_to_string(x2, y2)

print("Result 1:", result1)
print("Result 2:", result2)
#Result 1: nogolniw
#Result 2: exe.

得到的刚好是winlogon.exe字符串,然后程序将这个字符串转移到了dword_1800349A0全局变量中,目的应该是隐藏字符串,接着sub_180004770函数也是一个类似memmove操作,把这个字符串传到了Dst局部变量中,接着在sub_1800070A0中传入这个字符串,貌似是在根据字符串获取进程PID,接着调用sub_1800063D0函数根据pid打开进程,并将进程句柄存储到了某个地方

1
2
v18 = OpenProcess(0x1FFFFFu, 0, dwProcessId);
*((_QWORD *)Dst + 13) = v18;

随后return exit退出。

随后我在退出函数传参的时候看到了一个hProcess

image-20240914102337797

一个全局变量,很有可能在其他地方对句柄进行了读取,交叉引用一下定位到如下函数

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
void sub_180007C10()
{
HANDLE v1; // rcx
void *v2; // rdx
__int128 v3; // xmm0
__int128 v4; // xmm1
HANDLE FileA; // rbx
__int64 v8; // rcx
_BYTE *v9; // rdx
unsigned __int64 v10; // rdx
_QWORD *v11; // rcx
char Buffer; // [rsp+60h] [rbp+0h] BYREF

_RBP = (unsigned __int64)&Buffer & 0xFFFFFFFFFFFFFFE0ui64;
while ( byte_180032C00 )
{
if ( !byte_180034961 && !byte_180034960 )
sub_1800041D0();
v1 = hProcess;
v2 = (void *)(qword_180034968 + 2766);
*(_BYTE *)_RBP = 15;
WriteProcessMemory(v1, v2, (LPCVOID)((unsigned __int64)&Buffer & 0xFFFFFFFFFFFFFFE0ui64), 1ui64, 0i64);
byte_180032C00 = 0;
*(_QWORD *)(((unsigned __int64)&Buffer & 0xFFFFFFFFFFFFFFE0ui64) + 0x20) = 0xAA32D3B2B7C50388ui64;
*(_QWORD *)(((unsigned __int64)&Buffer & 0xFFFFFFFFFFFFFFE0ui64) + 0x10) = 0xA0A195500DCC0E5Cui64;
*(_QWORD *)(((unsigned __int64)&Buffer & 0xFFFFFFFFFFFFFFE0ui64) + 0x18) = 0x943E9588CFCF645Dui64;
v3 = *(_OWORD *)(((unsigned __int64)&Buffer & 0xFFFFFFFFFFFFFFE0ui64) + 0x10);
*(_QWORD *)(((unsigned __int64)&Buffer & 0xFFFFFFFFFFFFFFE0ui64) + 0x28) = 0xA727C0574231D098ui64;
v4 = *(_OWORD *)(((unsigned __int64)&Buffer & 0xFFFFFFFFFFFFFFE0ui64) + 0x20);
*(_QWORD *)(((unsigned __int64)&Buffer & 0xFFFFFFFFFFFFFFE0ui64) + 0x80) = 0xE795A7603F90341Fui64;
*(_OWORD *)(((unsigned __int64)&Buffer & 0xFFFFFFFFFFFFFFE0ui64) + 0x60) = v3;
*(_QWORD *)(((unsigned __int64)&Buffer & 0xFFFFFFFFFFFFFFE0ui64) + 0x88) = 0xC65BF3E99CAA093Cui64;
*(_OWORD *)(((unsigned __int64)&Buffer & 0xFFFFFFFFFFFFFFE0ui64) + 0x70) = v4;
*(_QWORD *)(((unsigned __int64)&Buffer & 0xFFFFFFFFFFFFFFE0ui64) + 0x90) = 0xCF59BCC699A060E9ui64;
*(_QWORD *)(((unsigned __int64)&Buffer & 0xFFFFFFFFFFFFFFE0ui64) + 0x98) = 0xA727C0574231E1F6ui64;
__asm
{
vmovdqu ymm0, [rbp+0D0h+var_50]
vpxor ymm1, ymm0, ymmword ptr [rbp+0D0h+FileName]
vmovdqa ymmword ptr [rbp+0D0h+FileName], ymm1
vzeroupper
}
FileA = CreateFileA((LPCSTR)(_RBP + 96), 0x40000000u, 0, 0i64, 3u, 0x80u, 0i64);
if ( FileA != (HANDLE)-1i64 )
{
((void (__fastcall *)(unsigned __int64))loc_180007A20)(_RBP + 48);
v8 = -1i64;
if ( *(_QWORD *)(((unsigned __int64)&Buffer & 0xFFFFFFFFFFFFFFE0ui64) + 0x48) < 0x10ui64 )
{
do
++v8;
while ( *(_BYTE *)(_RBP + 48 + v8) );
v9 = (_BYTE *)(_RBP + 48);
}
else
{
v9 = *(_BYTE **)(((unsigned __int64)&Buffer & 0xFFFFFFFFFFFFFFE0ui64) + 0x30);
do
++v8;
while ( v9[v8] );
}
WriteFile(FileA, v9, v8, (LPDWORD)(_RBP + 8), 0i64);
CloseHandle(FileA);
v10 = *(_QWORD *)(((unsigned __int64)&Buffer & 0xFFFFFFFFFFFFFFE0ui64) + 0x48);
if ( v10 >= 0x10 )
{
v11 = *(_QWORD **)(((unsigned __int64)&Buffer & 0xFFFFFFFFFFFFFFE0ui64) + 0x30);
if ( v10 + 1 >= 0x1000 )
{
v11 = (_QWORD *)*(v11 - 1);
if ( (unsigned __int64)(*(_QWORD *)(((unsigned __int64)&Buffer & 0xFFFFFFFFFFFFFFE0ui64) + 0x30)
- (_QWORD)v11
- 8i64) > 0x1F )
invalid_parameter_noinfo_noreturn();
}
j_j_free(v11);
}
}
}
}

看到了有WriteProcessMemory写入hProcess内存操作,CreateFileA,WriteFile,打开和写入文件操作,但是并没有找到hProcess的赋值语句,也就是说这个进程句柄还不知道是谁的,猜测赋值被隐藏了,但是可以猜测可能是winlogon.exe进程句柄。byte_180032C00是一个全局的标志变量,强制函数只能执行一次,对应的是运行程序时仅一次的初始化操作。接着看一下CreateFileA函数,同样的文件名被隐藏了,python解析一下

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
def hex_xor_to_string(a, b):
result = a ^ b
hex_str = hex(result)[2:]
if len(hex_str) % 2 != 0:
hex_str = '0' + hex_str

result_str = ''.join(chr(int(hex_str[i:i+2], 16)) for i in range(0, len(hex_str), 2))
return result_str

x1 = 0xA0A195500DCC0E5C
x2 = 0x943E9588CFCF645D
x3 = 0xAA32D3B2B7C50388
x4 = 0xA727C0574231D098
y1 = 0xE795A7603F90341F
y2 = 0xC65BF3E99CAA093C
y3 = 0xCF59BCC699A060E9
y4 = 0xA727C0574231E1F6

result1 = hex_xor_to_string(x1, y1)
result2 = hex_xor_to_string(x2, y2)
result3 = hex_xor_to_string(x3, y3)
result4 = hex_xor_to_string(x4, y4)

print("Result 1:", result1)
print("Result 2:", result2)
print("Result 3:", result3)
print("Result 4:", result4)

res = result1[::-1] + result2[::-1] + result3[::-1] + result4[::-1]
print(res)

image-20240914110434860

整个拼起来是字符串C:\2024GameSafeRace.token1,应该是创建了一个文件,然后向这个文件写入了token1了,接着往下

image-20240914110826942

loc_180007A20这个函数内部被加密了,猜测是对token1的解密过程,然后通过WriteFile写入C:\2024GameSafeRace.token1中,并不是很像去分析这个函数,直接加载驱动看看能不能直接运行得到2024GameSafeRace.token1文件。

image-20240914111407201

找到下没找到,回头看看CreateFileA函数,核查一下后面几个参数

1
FileA = CreateFileA((LPCSTR)(_RBP + 96), 0x40000000u, 0, 0i64, 3u, 0x80u, 0i64);

image-20240914111653640

看来是参数在作怪,CreateFileA函数传入OPEN_EXISTING参数,如果没有指定文件,则函数会返回失败,那也好办,自己创建一个就好了。

image-20240914112419617

C:\2024GameSafeRace.token1成功被写入

image-20240914112536772

010打开找到token1:757F4749AEBB1891EF5AC2A9B5439CEA

token2的寻找就偏简单了,加载驱动后留意一下dbgView的打印信息就可以获取

image-20240914113718466

组合一下就是token2:803f14a24d64f3e697957c252e3a5686

(二)解题过程

题目要求:

编写程序,运行时修改尽量少的内存,让两段token输出成功。(满分2分)

根据之前分析的token1,我们可以知道程序会在CreateFileA后解密token1然后写入到C:\2024GameSafeRace.token1中,但是会因为CreateFileA参数OPEN_EXISTING条件不满足而失败,所以我们只需要修改这个传参,改成OPEN_ALWAYS,即可实现输出token1,那我们只需要hook CreateFileA函数修改传参即可,但是有个问题,因为不是hack.exe本身调用CreateFileA函数,而是hack.exe注入了一个dll到winlogon.exe,然后再winlogon.exe里调用CreateFileA函数,所以我们可以考虑在注入前修改WriteProcessMemory函数参数buffer,从而在注入前patch dll,或者编写代码直接注入winlogon.exe,hook CreateFileA函数修改传参,但是考虑到第二种方式可能不被允许,winlogon.exe毕竟是系统进程,题目应该是要我们通过patch dll的方式解题。

这里我们要patch 传参,通过ida找到传参的汇编代码

image-20240920142928600

在winhex里找到对应所在文件偏移
image-20240920143006628

也就是在0x7171处OPEN_EXISTING:0x3是要patch的地方,把这个参数修改成OPEN_ALWAYS:0x4即可。下面编写代码实现。

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
//dllmain.cpp
#include "pch.h"
#include <windows.h>
#include <shellapi.h>
#include <detours.h>
#include <tlhelp32.h>
#include <stdlib.h>
#include <stdio.h>
#pragma comment(lib,"detours.lib")
#define _KDEBUG
#define DBGMGEBOX(fmt, ...) \
do { \
/* 假设最大长度为1024,根据需要调整大小 */ \
wsprintfA(out, fmt, __VA_ARGS__); \
MessageBoxA(NULL, out, "提示", MB_OK); \
} while(0)
char out[100];
typedef BOOL(WINAPI* WriteProcessMemory_t)(
_In_ HANDLE hProcess,
_In_ LPVOID lpBaseAddress,
_In_reads_bytes_(nSize) LPCVOID lpBuffer,
_In_ SIZE_T nSize,
_Out_opt_ SIZE_T* lpNumberOfBytesWritten
);
WriteProcessMemory_t TrueWriteProcessMemory = NULL;
BOOL
WINAPI
HookWriteProcessMemory(
_In_ HANDLE hProcess,
_In_ LPVOID lpBaseAddress,
_In_reads_bytes_(nSize) LPCVOID lpBuffer,
_In_ SIZE_T nSize,
_Out_opt_ SIZE_T* lpNumberOfBytesWritten
)
{
if (nSize == 4506624 && *((PUCHAR)lpBuffer + 0x7171) == 0x3)
{
*((PUCHAR)lpBuffer + 0x7171) = 0x4;
DBGMGEBOX("Hook Success!\n");
}
return TrueWriteProcessMemory(hProcess, lpBaseAddress, lpBuffer, nSize, lpNumberOfBytesWritten);
}


BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) {
switch (ul_reason_for_call) {
case DLL_PROCESS_ATTACH:

DetourTransactionBegin();
DetourUpdateThread(GetCurrentThread());

TrueWriteProcessMemory = (WriteProcessMemory_t)DetourFindFunction("kernel32.dll", "WriteProcessMemory");
DetourAttach(&(PVOID&)TrueWriteProcessMemory, HookWriteProcessMemory);

DetourTransactionCommit();
break;
case DLL_PROCESS_DETACH:
DetourTransactionBegin();
DetourUpdateThread(GetCurrentThread());
DetourDetach(&(PVOID&)TrueWriteProcessMemory, HookWriteProcessMemory);

DetourTransactionCommit();
break;
}
return TRUE;
}

成功输出token1文件:

image-20240914120306770

然后是token2,既然是内核输出,那只能是在ace.sys里做点手脚,DIE查壳发现ace.sys的大部分代码都被加壳过了,静态代码不好看,只能先猜测token2的输出调用了DbgPrint或者DbgPrintEx,因为之前输出token2的时候开启了Verbose Kernel outPut,猜测之所以正常输出失败是因为DbgPrintEx的level值太低,仅将字符串传递给内核调试器,不执行输出操作。

hook DbgPrintEx函数看一眼传参。

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
#include <ntifs.h>
#include <ntdef.h>
#include <ntstatus.h>
#include <ntddk.h>
#include <stdarg.h>
#include "R0Hook.h"
#define dbgFilter "Kvancy:"
typedef ULONG(*FuncPtr) (ULONG ComponentId, ULONG Level, PCSTR Format, ...);
HOOK_MANAGER hookManager;
ULONG myDbgPrintEx(ULONG ComponentId, ULONG Level, PCSTR Format, ...) {
Unhook(&hookManager);
FuncPtr func = (FuncPtr)hookManager.target;
kPrint("%s DbgPrintEx ComponentId:%lu,Level:%lu\n",dbgFilter, ComponentId, Level);
va_list args;
va_start(args, Format);
NTSTATUS s = func(ComponentId, Level, Format, args);
va_end(args);
ApplyHook(&hookManager);
return s;
}

void DriverUnload(PDRIVER_OBJECT pDriver) {
kPrint("%s DriverUnload\n", dbgFilter);
Unhook(&hookManager);
}

NTSTATUS DriverEntry(
_In_ PDRIVER_OBJECT DriverObject, _In_ PUNICODE_STRING RegistryPath
) {
DriverObject->DriverUnload = DriverUnload;
PVOID dbgPrintEx = DbgPrintEx;
InitializeHookManager(&hookManager, dbgPrintEx, myDbgPrintEx);
ApplyHook(&hookManager);
DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, "%s DriverEntry\n",dbgFilter);
return STATUS_SUCCESS;
}

发现加载ace驱动后,有大量的level:5的调试信息输出

image-20240918212128643

也就是说,程序通过设置调试信息的重要级别来控制调试信息是否正常输出,于是可以提高level级别来输出token2,那么最简单的方式就是hook之后修改level后传回去,编写代码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
#include <ntifs.h>
#include <ntdef.h>
#include <ntstatus.h>
#include <ntddk.h>
#include <stdarg.h>
#include <stdio.h>
#include "R0Hook.h"
#define dbgFilter "Kvancy:"
typedef ULONG(*FuncPtr) (ULONG ComponentId, ULONG Level, PCSTR Format, ...);
HOOK_MANAGER hookManager;
char buffer[1024];
ULONG myDbgPrintEx(ULONG ComponentId, ULONG Level, PCSTR Format, ...) {
Unhook(&hookManager);
FuncPtr func = (FuncPtr)hookManager.target;
va_list args;
va_start(args, Format);
vsprintf(buffer, Format, args);
va_end(args);
NTSTATUS s = func(ComponentId, 0, "%s", buffer);//修改level为0
ApplyHook(&hookManager);
return s;
}

void DriverUnload(PDRIVER_OBJECT pDriver) {
kPrint("Kvancy: DriverUnload\n");
Unhook(&hookManager);
}

NTSTATUS DriverEntry(
_In_ PDRIVER_OBJECT DriverObject, _In_ PUNICODE_STRING RegistryPath
) {
DriverObject->DriverUnload = DriverUnload;
PVOID dbgPrintEx = DbgPrintEx;
InitializeHookManager(&hookManager, dbgPrintEx, myDbgPrintEx);
ApplyHook(&hookManager);
DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, "Kvancy: DriverEntry\n");
return STATUS_SUCCESS;
}

{4981D7A9-1EAA-4A38-9280-429D63ECABF9}

成功输出token2,但是根据题目要求是不能修改系统模块代码的,也就是说hook内核函数的方法不能过这道题,还是得想想别的方法。现在已知的ace.sys的行为就是驱动会在被加载之后做了某些操作会使得系统持续调用DbgPrintEx来输出token2,但是ace.sys其实做了某种操作后就被卸载掉了,如下图所示。

{170126A6-1C28-4BF3-AB47-AB2397C76595}

可以想到就是说驱动启动了一个线程或者进程,让该任务持续输出token2,创建完随后再卸载自己并且不停止这个线程或者进程。先枚举进程看看有没有奇怪的进程出现。

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
VOID WriteToFile(PUNICODE_STRING FilePath, PCHAR Data)
{
OBJECT_ATTRIBUTES objAttr;
IO_STATUS_BLOCK ioStatusBlock;
HANDLE fileHandle;
NTSTATUS status;
UNICODE_STRING unicodeFilePath;

RtlInitUnicodeString(&unicodeFilePath, FilePath->Buffer);

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

status = ZwCreateFile(
&fileHandle,
FILE_APPEND_DATA | SYNCHRONIZE,
&objAttr,
&ioStatusBlock,
NULL,
FILE_ATTRIBUTE_NORMAL,
0,
FILE_OPEN_IF,
FILE_SYNCHRONOUS_IO_NONALERT,
NULL,
0
);

if (NT_SUCCESS(status)) {
size_t dataLength = strlen(Data);
ZwWriteFile(fileHandle, NULL, NULL, NULL, &ioStatusBlock, Data, (ULONG)dataLength, NULL, NULL);
ZwClose(fileHandle);
}
else {
DbgPrint("Failed to create file: %08X\n", status);
}
}

VOID EnumProcesses()
{
NTSTATUS status;
PVOID buffer;
ULONG bufferSize = 0x10000; // Initial buffer size, can grow if needed
ULONG returnLength;
CHAR logBuffer[1024];

UNICODE_STRING filePath;
RtlInitUnicodeString(&filePath, L"\\??\\C:\\Users\\15386\\Desktop\\1.txt");

buffer = ExAllocatePoolWithTag(NonPagedPool, bufferSize, 'proc');
if (!buffer) {
DbgPrint("Failed to allocate buffer for process information\n");
return;
}

status = ZwQuerySystemInformation(SystemProcessInformation, buffer, bufferSize, &returnLength);

if (status == STATUS_INFO_LENGTH_MISMATCH) {
ExFreePool(buffer);
bufferSize = returnLength;
buffer = ExAllocatePoolWithTag(NonPagedPool, bufferSize, 'proc');
if (!buffer) {
DbgPrint("Failed to allocate larger buffer for process information\n");
return;
}
status = ZwQuerySystemInformation(SystemProcessInformation, buffer, bufferSize, &returnLength);
}

if (NT_SUCCESS(status)) {
PSYSTEM_PROCESS_INFORMATION processInfo = (PSYSTEM_PROCESS_INFORMATION)buffer;
while (TRUE) {
if (processInfo->ImageName.Buffer) {
_snprintf(logBuffer, sizeof(logBuffer), "Process ID: %lu, Name: %wZ\n", (ULONG)(ULONG_PTR)processInfo->ProcessId, &processInfo->ImageName);
}
else {
_snprintf(logBuffer, sizeof(logBuffer), "Process ID: %lu, Name: [System Process]\n", (ULONG)(ULONG_PTR)processInfo->ProcessId);
}

WriteToFile(&filePath, logBuffer);

if (processInfo->NextEntryOffset == 0)
break;
processInfo = (PSYSTEM_PROCESS_INFORMATION)((PUCHAR)processInfo + processInfo->NextEntryOffset);
}
}

ExFreePool(buffer);
}

结果发现好像没有奇怪的进程被创建出来,那么有可能是驱动利用PsCreateSystemThread创建了一个内核线程。hookPsCreateSystemThread函数看看驱动加载时是否调用了这个函数。

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
#include <ntifs.h>
#include <ntdef.h>
#include <ntstatus.h>
#include <ntddk.h>
#include <stdarg.h>
#include "R0Hook.h"
#define dbgFilter "Kvancy:"
typedef ULONG(*FuncPtr) (
PHANDLE ThreadHandle,
ULONG DesiredAccess,
POBJECT_ATTRIBUTES ObjectAttributes,
HANDLE ProcessHandle,
PCLIENT_ID ClientId,
PKSTART_ROUTINE StartRoutine,
PVOID StartContext);
HOOK_MANAGER hookManager;


NTSTATUS myPsCreateSystemThread(
PHANDLE ThreadHandle,
ULONG DesiredAccess,
POBJECT_ATTRIBUTES ObjectAttributes,
HANDLE ProcessHandle,
PCLIENT_ID ClientId,
PKSTART_ROUTINE StartRoutine,
PVOID StartContext
)
{
Unhook(&hookManager);
FuncPtr func = (FuncPtr)hookManager.target;
kPrint("%s myPsCreateSystemThread StartRoutine:%p\n", dbgFilter, StartRoutine);
NTSTATUS s = func(ThreadHandle, DesiredAccess, ObjectAttributes, ProcessHandle, ClientId, StartRoutine, StartContext);
ApplyHook(&hookManager);
return s;
}

void DriverUnload(PDRIVER_OBJECT pDriver) {
kPrint("%s DriverUnload\n", dbgFilter);
Unhook(&hookManager);
}

NTSTATUS DriverEntry(
_In_ PDRIVER_OBJECT DriverObject, _In_ PUNICODE_STRING RegistryPath
) {
DriverObject->DriverUnload = DriverUnload;
PVOID dbgPrintEx = PsCreateSystemThread;
InitializeHookManager(&hookManager, dbgPrintEx, myPsCreateSystemThread);
ApplyHook(&hookManager);
DbgPrintEx(DPFLTR_IHVDRIVER_ID, DPFLTR_ERROR_LEVEL, "%s DriverEntry\n", dbgFilter);
return STATUS_SUCCESS;
}

{81A1CDF1-D6A3-454A-BAA1-BAE048F8B7F3}

发现在token2输出前确实有PsCreateSystemThread函数调用,虽然不确定是不是ace.sys创建的。在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
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
0: kd> u FFFFBA0729013DB0 l 100
ffffba07`29013db0 488bc4 mov rax,rsp
ffffba07`29013db3 48895808 mov qword ptr [rax+8],rbx
ffffba07`29013db7 48897818 mov qword ptr [rax+18h],rdi
ffffba07`29013dbb 4c897020 mov qword ptr [rax+20h],r14
ffffba07`29013dbf 55 push rbp
ffffba07`29013dc0 488d68a1 lea rbp,[rax-5Fh]
ffffba07`29013dc4 4881eca0000000 sub rsp,0A0h
ffffba07`29013dcb 48bf4e93328b546b331e mov rdi,1E336B548B32934Eh
ffffba07`29013dd5 49bed520794add1d6d4b mov r14,4B6D1DDD4A7920D5h
ffffba07`29013ddf 0f57c0 xorps xmm0,xmm0
ffffba07`29013de2 488d4d37 lea rcx,[rbp+37h]
ffffba07`29013de6 0f114537 movups xmmword ptr [rbp+37h],xmm0
ffffba07`29013dea e8d1030000 call ffffba07`290141c0
ffffba07`29013def 48b8a14f122fb3276d4b mov rax,4B6D27B32F124FA1h
ffffba07`29013df9 4c8d45e7 lea r8,[rbp-19h]
ffffba07`29013dfd 4889456f mov qword ptr [rbp+6Fh],rax
ffffba07`29013e01 ba05000000 mov edx,5
ffffba07`29013e06 488b456f mov rax,qword ptr [rbp+6Fh]
ffffba07`29013e0a 33c9 xor ecx,ecx
ffffba07`29013e0c 488945e7 mov qword ptr [rbp-19h],rax
ffffba07`29013e10 48897d6f mov qword ptr [rbp+6Fh],rdi
ffffba07`29013e14 488b456f mov rax,qword ptr [rbp+6Fh]
ffffba07`29013e18 488945ef mov qword ptr [rbp-11h],rax
ffffba07`29013e1c 4c89756f mov qword ptr [rbp+6Fh],r14
ffffba07`29013e20 488b456f mov rax,qword ptr [rbp+6Fh]
ffffba07`29013e24 48894517 mov qword ptr [rbp+17h],rax
ffffba07`29013e28 48897d6f mov qword ptr [rbp+6Fh],rdi
ffffba07`29013e2c 488b456f mov rax,qword ptr [rbp+6Fh]
ffffba07`29013e30 660f6f45e7 movdqa xmm0,xmmword ptr [rbp-19h]
ffffba07`29013e35 4889451f mov qword ptr [rbp+1Fh],rax
ffffba07`29013e39 660fef4517 pxor xmm0,xmmword ptr [rbp+17h]
ffffba07`29013e3e 488b0543330000 mov rax,qword ptr [ffffba07`29017188]
ffffba07`29013e45 660f7f45e7 movdqa xmmword ptr [rbp-19h],xmm0
ffffba07`29013e4a ff15c8210000 call qword ptr [ffffba07`29016018]
ffffba07`29013e50 33db xor ebx,ebx
ffffba07`29013e52 48b8f0104b32dd1d6d4b mov rax,4B6D1DDD324B10F0h
ffffba07`29013e5c 4c8d45f7 lea r8,[rbp-9]
ffffba07`29013e60 4889456f mov qword ptr [rbp+6Fh],rax
ffffba07`29013e64 ba05000000 mov edx,5
ffffba07`29013e69 488b456f mov rax,qword ptr [rbp+6Fh]
ffffba07`29013e6d 33c9 xor ecx,ecx
ffffba07`29013e6f 488945f7 mov qword ptr [rbp-9],rax
ffffba07`29013e73 48897d6f mov qword ptr [rbp+6Fh],rdi
ffffba07`29013e77 488b456f mov rax,qword ptr [rbp+6Fh]
ffffba07`29013e7b 488945ff mov qword ptr [rbp-1],rax
ffffba07`29013e7f 4c89756f mov qword ptr [rbp+6Fh],r14
ffffba07`29013e83 488b456f mov rax,qword ptr [rbp+6Fh]
ffffba07`29013e87 48894527 mov qword ptr [rbp+27h],rax
ffffba07`29013e8b 48897d6f mov qword ptr [rbp+6Fh],rdi
ffffba07`29013e8f 488b456f mov rax,qword ptr [rbp+6Fh]
ffffba07`29013e93 660f6f45f7 movdqa xmm0,xmmword ptr [rbp-9]
ffffba07`29013e98 440fb64c1d37 movzx r9d,byte ptr [rbp+rbx+37h]
ffffba07`29013e9e 4889452f mov qword ptr [rbp+2Fh],rax
ffffba07`29013ea2 660fef4527 pxor xmm0,xmmword ptr [rbp+27h]
ffffba07`29013ea7 488b05da320000 mov rax,qword ptr [ffffba07`29017188]
ffffba07`29013eae 660f7f45f7 movdqa xmmword ptr [rbp-9],xmm0
ffffba07`29013eb3 ff155f210000 call qword ptr [ffffba07`29016018]
ffffba07`29013eb9 48ffc3 inc rbx
ffffba07`29013ebc 4883fb10 cmp rbx,10h
ffffba07`29013ec0 7c90 jl ffffba07`29013e52
ffffba07`29013ec2 48b8df20794add1d6d4b mov rax,4B6D1DDD4A7920DFh
ffffba07`29013ecc 4c8d4507 lea r8,[rbp+7]
ffffba07`29013ed0 4889456f mov qword ptr [rbp+6Fh],rax
ffffba07`29013ed4 ba05000000 mov edx,5
ffffba07`29013ed9 488b456f mov rax,qword ptr [rbp+6Fh]
ffffba07`29013edd 33c9 xor ecx,ecx
ffffba07`29013edf 48894507 mov qword ptr [rbp+7],rax
ffffba07`29013ee3 48897d6f mov qword ptr [rbp+6Fh],rdi
ffffba07`29013ee7 488b456f mov rax,qword ptr [rbp+6Fh]
ffffba07`29013eeb 4889450f mov qword ptr [rbp+0Fh],rax
ffffba07`29013eef 4c89756f mov qword ptr [rbp+6Fh],r14
ffffba07`29013ef3 488b456f mov rax,qword ptr [rbp+6Fh]
ffffba07`29013ef7 48894547 mov qword ptr [rbp+47h],rax
ffffba07`29013efb 48897d6f mov qword ptr [rbp+6Fh],rdi
ffffba07`29013eff 488b456f mov rax,qword ptr [rbp+6Fh]
ffffba07`29013f03 660f6f4507 movdqa xmm0,xmmword ptr [rbp+7]
ffffba07`29013f08 4889454f mov qword ptr [rbp+4Fh],rax
ffffba07`29013f0c 660fef4547 pxor xmm0,xmmword ptr [rbp+47h]
ffffba07`29013f11 488b0570320000 mov rax,qword ptr [ffffba07`29017188]
ffffba07`29013f18 660f7f4507 movdqa xmmword ptr [rbp+7],xmm0
ffffba07`29013f1d ff15f5200000 call qword ptr [ffffba07`29016018]
ffffba07`29013f23 b9ce0a0000 mov ecx,0ACEh
ffffba07`29013f28 e833e9ffff call ffffba07`29012860
ffffba07`29013f2d e9adfeffff jmp ffffba07`29013ddf
ffffba07`29013f32 cc int 3
ffffba07`29013f33 cc int 3
ffffba07`29013f34 4152 push r10

导入到ida里看伪代码

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
void __noreturn sub_180005148()
{
__m128i v0; // xmm0
__int64 i; // rbx
__m128i v2; // xmm0
__int64 v3; // r9
__m128i v4; // xmm0
__m128i v5; // [rsp+30h] [rbp-19h] BYREF
__m128i v6; // [rsp+40h] [rbp-9h] BYREF
__m128i v7; // [rsp+50h] [rbp+7h] BYREF
__m128i v8; // [rsp+60h] [rbp+17h]
__m128i v9; // [rsp+70h] [rbp+27h]
__int128 v10; // [rsp+80h] [rbp+37h] BYREF
__m128i v11; // [rsp+90h] [rbp+47h]

while ( 1 )
{
v10 = 0i64;
((void (__fastcall *)(__int128 *))((char *)&loc_180005556 + 2))(&v10);
v5.m128i_i64[0] = 0x4B6D27B32F124FA1i64;
v5.m128i_i64[1] = 0x1E336B548B32934Ei64;
v8.m128i_i64[0] = 0x4B6D1DDD4A7920D5i64;
v0 = _mm_load_si128(&v5);
v8.m128i_i64[1] = 0x1E336B548B32934Ei64;
v5 = _mm_xor_si128(v0, v8);
MEMORY[0x31305A8047353130](0i64, 5i64, &v5);
for ( i = 0i64; i < 16; ++i )
{
v6.m128i_i64[0] = 0x4B6D1DDD324B10F0i64;
v6.m128i_i64[1] = 0x1E336B548B32934Ei64;
v9.m128i_i64[0] = 0x4B6D1DDD4A7920D5i64;
v2 = _mm_load_si128(&v6);
v3 = *((unsigned __int8 *)&v10 + i);
v9.m128i_i64[1] = 0x1E336B548B32934Ei64;
v6 = _mm_xor_si128(v2, v9);
MEMORY[0x31305A8047353130](0i64, 5i64, &v6, v3);
}
v7.m128i_i64[0] = 0x4B6D1DDD4A7920DFi64;
v7.m128i_i64[1] = 0x1E336B548B32934Ei64;
v11.m128i_i64[0] = 0x4B6D1DDD4A7920D5i64;
v4 = _mm_load_si128(&v7);
v11.m128i_i64[1] = 0x1E336B548B32934Ei64;
v7 = _mm_xor_si128(v4, v11);
MEMORY[0x31305A8047353130](0i64, 5i64, &v7);
sub_180003BF8(2766i64);
}
}

好像是做了一个字符串解密然后输出的操作,浅浅用python跑一下解析字符串验证猜想。

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
def hex_xor_to_string(a, b):
result = a ^ b
hex_str = hex(result)[2:]
if len(hex_str) % 2 != 0:
hex_str = '0' + hex_str

result_str = ''.join(chr(int(hex_str[i:i+2], 16)) for i in range(0, len(hex_str), 2))
return result_str

x1 = 0x4B6D27B32F124FA1
x2 = 0x1E336B548B32934E
y1 = 0x4B6D1DDD4A7920D5
y2 = 0x1E336B548B32934E


result1 = hex_xor_to_string(x1, y1)
result2 = hex_xor_to_string(x2, y2)


print("Result 1:", result1)
print("Result 2:", result2)


res = result1[::-1] + result2[::-1]
print(res)

{8FBDE081-6C77-49BA-BEDA-94E3B44A9056}

打印出了token基本上确定了这个线程就是打印token的线程,现在就是要想怎么patch这个线程函数使得token能够输出出来。

image-20240919195412464)

这里有个mov edx,5语句,将DbgPrintEx函数的level设置成5,可以考虑patch这个语句,将5改成0,那么只需要patch一个字节,共三处。但是又要怎么patch呢,首先不能通过现在这种方式hook PsCreateSystemThread函数调用来确定StartRoutine地址(题目要求不能修改系统模块代码),也就是说得想另外一个办法确定这个线程的地址,然后通过偏移来确定需要patch的地址。

那么怎么确定这个线程地址呢,如果通过ZwQuerySystemInformation枚举内核模块然后枚举模块下的所有线程的话,已经卸载了的ace.sys模块还能被枚举到么?问了下GPT好像是不能的,还可以考虑用StartRoutine地址的后几位做特征,匹配所有线程的开始地址的后几位,但是这种方式又感觉怕遇到地址特征一模一样的,感觉还是不大行。又问GPT怎么寻找到某个内核线程,得到答复是除了ZwQuerySystemInformation枚举,还有通过PsLookupThreadByThreadId函数从进程id和线程id查找的。

那么线程id和进程id又从哪获取呢?因为之前hook过PsCreateSystemThread函数,翻阅文档找到了一个ClientId参数,这个参数指向接收新线程的客户端标识符的结构,即一个pid,一个tid,但是pid,tid应该都是系统分配的吧,能是一个固定值么?hook一下看看输出

image-20240919214001840

image-20240919215044101

诶,tid貌似是系统分配的,但是pid一直都是4,很奇怪,pid=4代表的是什么进程呢?之前刚好枚举过进程来找有没有新进程创建,现在正好能派上用场。

image-20240919215837787

貌似是一个系统进程,GPT了一下发现原来如果驱动程序通过内核模式创建系统线程(使用PsCreateSystemThread),这些线程通常会在系统进程下运行,PID为4。原来如此,驱动程序和进程是一个级别的,但是驱动程序创建的这个线程是在系统进程之下的,而不是属于驱动模块,只是线程起始地址隶属于模块地址空间的,驱动卸载并不影响线程的运行。

这样的话,我们要找的线程因为模块被卸载了,所以它不在所有模块地址空间内,只要枚举所有系统进程pid=4下的所有线程,然后通过判断线程的起始地址是否在所有模块地址之内,即可判断它是否是我们要找的线程。这下思路就通了,开始编写代码实现patch。

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

PVOID MoudleBaseAddress[1024];
ULONG64 MoudleSize[1024];
ULONG ModuleCount = 0;
ULONG Offset1 = 0x52, Offset2 = 0xB5, Offset3 = 0x125;

BOOLEAN IsAddressInKnownModules(PVOID Address, PVOID* ModuleBaseAddresses, ULONG64* ModuleSize, ULONG ModuleCount)
{
for (size_t i = 0; i < ModuleCount; i++)
{
if (Address >= ModuleBaseAddresses[i] && Address < (ULONG64)ModuleBaseAddresses[i] + ModuleSize[i])
{
return TRUE;
}
}
return FALSE;
}

PVOID EnumSystemModulesForProcess(HANDLE TargetProcessId)
{
NTSTATUS status;
ULONG bufferSize = 0x10000;
PVOID processBuffer = NULL;
PVOID moduleBuffer = NULL;
ULONG returnLength;

// 查询进程信息
processBuffer = ExAllocatePoolWithTag(NonPagedPool, bufferSize, 'proc');
if (!processBuffer) {
DbgPrint("Failed to allocate buffer for process information\n");
return;
}

status = ZwQuerySystemInformation(SystemProcessInformation, processBuffer, bufferSize, &returnLength);
if (status == STATUS_INFO_LENGTH_MISMATCH) {
ExFreePool(processBuffer);
bufferSize = returnLength;
processBuffer = ExAllocatePoolWithTag(NonPagedPool, bufferSize, 'proc');
if (!processBuffer) {
DbgPrint("Failed to allocate larger buffer for process information\n");
return;
}
status = ZwQuerySystemInformation(SystemProcessInformation, processBuffer, bufferSize, &returnLength);
}

// 查询模块信息
moduleBuffer = ExAllocatePoolWithTag(NonPagedPool, bufferSize, 'modl');
if (!moduleBuffer) {
DbgPrint("Failed to allocate buffer for module information\n");
ExFreePool(processBuffer);
return;
}

status = ZwQuerySystemInformation(SystemModuleInformation, moduleBuffer, bufferSize, &returnLength);
if (status == STATUS_INFO_LENGTH_MISMATCH) {
ExFreePool(moduleBuffer);
bufferSize = returnLength;
moduleBuffer = ExAllocatePoolWithTag(NonPagedPool, bufferSize, 'modl');
if (!moduleBuffer) {
DbgPrint("Failed to allocate larger buffer for module information\n");
ExFreePool(processBuffer);
return;
}
status = ZwQuerySystemInformation(SystemModuleInformation, moduleBuffer, bufferSize, &returnLength);
}

// 遍历模块信息
if (NT_SUCCESS(status)) {
PSYSTEM_MODULE_INFORMATION moduleInfo = (PSYSTEM_MODULE_INFORMATION)moduleBuffer;
ModuleCount = moduleInfo->ModulesCount;
for (ULONG i = 0; i < moduleInfo->ModulesCount; i++) {
PSYSTEM_MODULE_INFORMATION_ENTRY moduleEntry = &moduleInfo->Modules[i];
MoudleBaseAddress[i] = moduleEntry->Base;
MoudleSize[i] = moduleEntry->Size;
}
}

// 遍历进程信息
if (NT_SUCCESS(status)) {
PSYSTEM_PROCESS_INFORMATION processInfo = (PSYSTEM_PROCESS_INFORMATION)processBuffer;
while (TRUE) {
if (processInfo->ProcessId == TargetProcessId) {
PSYSTEM_THREAD_INFORMATION threadInfo = (PSYSTEM_THREAD_INFORMATION)(processInfo + 1);
for (ULONG i = 0; i < processInfo->NumberOfThreads; i++) {
if (!IsAddressInKnownModules(threadInfo[i].StartAddress, MoudleBaseAddress, MoudleSize, ModuleCount))
{
DbgPrint("Find it:%p\n",threadInfo[i].StartAddress);
return threadInfo[i].StartAddress;
}
}
break;
}
if (processInfo->NextEntryOffset == 0)
break;
processInfo = (PSYSTEM_PROCESS_INFORMATION)((PUCHAR)processInfo + processInfo->NextEntryOffset);
}
}

// 清理分配的内存
ExFreePool(processBuffer);
ExFreePool(moduleBuffer);
return 0;
}

VOID UnloadDriver(PDRIVER_OBJECT DriverObject) {
KdPrint(("Driver Unloaded\n"));
}

NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath) {
DriverObject->DriverUnload = UnloadDriver;
KdPrint(("Driver Loaded\n"));

HANDLE targetPid = (HANDLE)4; // 系统进程的 PID
PVOID targetAddress = EnumSystemModulesForProcess(targetPid);

UCHAR valueToWrite = 0x00; // 要写入的字节值
UCHAR valueToRead = 0x05; // 要写入的字节值
ULONG numHasChanged = 0x00;
if (*(PUCHAR)((ULONG64)targetAddress + Offset1) == valueToRead)
{
*(PUCHAR)((ULONG64)targetAddress + Offset1) = valueToWrite;
numHasChanged++;
}
if (*(PUCHAR)((ULONG64)targetAddress + Offset2) == valueToRead)
{
*(PUCHAR)((ULONG64)targetAddress + Offset2) = valueToWrite;
numHasChanged++;
}
if (*(PUCHAR)((ULONG64)targetAddress + Offset3) == valueToRead)
{
*(PUCHAR)((ULONG64)targetAddress + Offset3) = valueToWrite;
numHasChanged++;
}
DbgPrint("numHasChanged:%d\n", numHasChanged);
return STATUS_SUCCESS;
}

成功输出token2

image-20240920125107119

(三)解题过程

题目要求:

编写程序,运行时修改尽量少的内存,让shellcode 往自行指定的位置写入token1成功。(满分3分)

要求任意位置,也就是要修改CreateFileA函数的第一个参数的值,根据之前分析的C:\2024GameSafeRace.token1字符串是由十六进制异或得到的,也就是下面这些
image-20240920145014956

可以考虑的是patch这些十六进制数据,把异或的key改成0,然后密文改成明文即可,因为明文异或0还是明文,但是考虑到要尽量修改少量的内存,我们最好还是保持key不变,自定义密文解密到我们所要的文件地址。跑个python脚本解出新的密文,得到新的密文,接着找到密文所在文件的偏移然后patch即可,给出解题代码。

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
//dllmain.cpp
#include "pch.h"
#include <windows.h>
#include <shellapi.h>
#include <detours.h>
#include <tlhelp32.h>
#include <stdlib.h>
#include <stdio.h>
#pragma comment(lib,"detours.lib")
#define _KDEBUG
#define DBGMGEBOX(fmt, ...) \
do { \
/* 假设最大长度为1024,根据需要调整大小 */ \
wsprintfA(out, fmt, __VA_ARGS__); \
MessageBoxA(NULL, out, "提示", MB_OK); \
} while(0)
char out[100];
typedef BOOL(WINAPI* WriteProcessMemory_t)(
_In_ HANDLE hProcess,
_In_ LPVOID lpBaseAddress,
_In_reads_bytes_(nSize) LPCVOID lpBuffer,
_In_ SIZE_T nSize,
_Out_opt_ SIZE_T* lpNumberOfBytesWritten
);
WriteProcessMemory_t TrueWriteProcessMemory = NULL;
BOOL
WINAPI
HookWriteProcessMemory(
_In_ HANDLE hProcess,
_In_ LPVOID lpBaseAddress,
_In_reads_bytes_(nSize) LPCVOID lpBuffer,
_In_ SIZE_T nSize,
_Out_opt_ SIZE_T* lpNumberOfBytesWritten
)
{
if (nSize == 4506624 && *((PUCHAR)lpBuffer + 0x7171) == 0x3)
{
*((PUCHAR)lpBuffer + 0x7171) = 0x4;
*(PULONG64)((ULONG64)lpBuffer + 0x7082) = 0x94e7c2136acc0e5c;//
*(PULONG64)((ULONG64)lpBuffer + 0x7093) = 0x8207c5d1af9f3860;
*(PULONG64)((ULONG64)lpBuffer + 0x70F1) = 0xa905cca9edcb138c;
*(PULONG64)((ULONG64)lpBuffer + 0x7108) = 0xa753b8236c56809a;
//C:\Users\15386\Desktop\flag.txt
DBGMGEBOX("Hook Success!\n");
}
return TrueWriteProcessMemory(hProcess, lpBaseAddress, lpBuffer, nSize, lpNumberOfBytesWritten);
}


BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) {
switch (ul_reason_for_call) {
case DLL_PROCESS_ATTACH:

DetourTransactionBegin();
DetourUpdateThread(GetCurrentThread());

TrueWriteProcessMemory = (WriteProcessMemory_t)DetourFindFunction("kernel32.dll", "WriteProcessMemory");
DetourAttach(&(PVOID&)TrueWriteProcessMemory, HookWriteProcessMemory);

DetourTransactionCommit();
break;
case DLL_PROCESS_DETACH:
DetourTransactionBegin();
DetourUpdateThread(GetCurrentThread());
DetourDetach(&(PVOID&)TrueWriteProcessMemory, HookWriteProcessMemory);

DetourTransactionCommit();
break;
}
return TRUE;
}

注入hook成功后,成功在桌面的flag.txt输出token1

{87F08EF1-EECF-4DBC-A7D0-954E04FFA9CD}