腾讯游戏安全竞赛2023-PC初赛

静态分析

DIE查壳,vmp,删题跑路

动态调试

定位main

VMP反调直接拿ScyllaHide过掉,运行一段时间后暂停下来看调用栈

定位到main

main被虚拟化了

重点看call,找到sleep返回的地方

这一段两个call,下断点看程序反应,第一个call断下,把程序输出的文件删掉,单步跳过之后生成了文件,说明主要逻辑在第一个call里面,第二个call单纯的sleep

定位API操作

对CreateFile、WriteFile下断点,发现程序断不下来,说明文件操作绕过了系统API,拿火绒剑监控一下,能监控到文件操作

FILE_touch

FILE_write

FILE_modified

直接由syscall实现了,难怪下断点抓不到,对FILE_touch和FILE_write的syscall下断点

通过调用栈没法回溯,还是得回到程序里写文件的部分

看写文件的函数,还是虚拟化

对含call的块下断点

然后F9,看CreateFile、WriteFile、Close分别在哪个块里面被call,再对块里面的call下断点,定位具体调用位置

CreateFile,参数在栈里面

WriteFile跟CloseHandle在一个块里面,WriteFile的参数也在栈里面

手动改一下CreateFile与WriteFile栈里面的内容,看看有没有效果

看样子CreateFile、WriteFile都找到了

定位文件名

回到调用CreateFile的地方,看栈里面的文件名从哪来的

lea rcx, ds:[0x00007FF79B3A72FA]

找到文件名了,但这玩意在变,下个硬件断点看哪在改它

xor了一个@,文件名前一个字符,整个文件名都做了异或

定位flag

在文件名前面有这么一串东西

catchmeifyoucan,这应该就是明文flag

从WriteFile处的密文逆一下

密文在[rbp-38]里面,函数局部变量,运行了几次都在变,那直接下硬件断点看哪在写这个局部变量

分析一下这里是分配内存,大小0x41,再对分配的内存下断点,看哪写了分配的内存

这里在做写零操作,应该是把内存初始化为0,继续看还有哪在写

不再之前的函数里面了,回溯一下

rcx是之前看到的flag,edx=0xF,刚好是flag长度,r8是之前分配的内存

简单看一下,这里调用的是base64加密

破案了,flag确实是catchmeifyoucan

内存里除了加密的flag,还有下面一串怪东西

断点分析一下,其实和文件名一样,flag被拿前面的E做了下异或

PATCH

filename patch

前面分析了文件名的位置

rcx就是文件名,文件名偏移0x772F9,为了避免异或的影响,文件名前面的字节改成0

但是原文件名长度有限,想任意文件名需要把文件名写到其它内存中,再把文件名地址传给rcx,为了不过多修改代码,考虑将新文件名的地址存在原文件名的地方,把lea指令patch成mov指令

1
2
3
4
5
6
7
8
9
// 修改文件名
#define FILENAME_OFF 0x772F9
BYTE filenameOriBytes[13] = {"@contest.txt"};
BYTE filenameBytes[13] = {0, };
*(UINT64*)(filenameBytes + 1) = (UINT64)remoteBuffer;
// 将lea指令替换为mov指令
#define PATCH_FILENAME_OFF 0xCB3C
BYTE patchFilenameOriBytes[1] = { 0x8D };
BYTE patchFilenameBytes[1] = { 0x8B };

flag patch

和文件名一样,为了避免flag被异或,把flag前一个字节改成0

flag的base64加密需要patch掉,把rcx里面的flag直接复制到r8里面,长队为rdx

这一部分有16个字节,直接将这段patch成简单的memcpy,刚好16字节(省略push rdx和pop rdx还可以节约两个字节)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// flag
#define FLAG_OFF 0x772E8
BYTE flagOriBytes[17] = { "Ecatchmeifyoucan" };
BYTE flagBytes[17] = { "\0catchmeifyoucan" };
// patch掉base64入口,替换为memcpy
#define PATCH_FLAG_OFF 0xD6D0
BYTE patchFlagOriBytes[16] = {0x48, 0x8D, 0x05, 0x99, 0xE2, 0xFF, 0xFF, 0xFF, 0xE0, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC};
/**
push rdx
mov al, [rcx+rdx-1]
mov [r8+rdx-1], al
dec edx
jnz $-0xD
pop rdx
ret
*/
BYTE patchFlagBytes[16] = {0x52, 0x8A, 0x44, 0x11, 0xFF, 0x41, 0x88, 0x44, 0x10, 0xFF, 0xFF, 0xCA, 0x75, 0xF3, 0x5A, 0xC3};

code

远程修改上述内存即可,编写程序测试

flag成功写入到指定文件中

完整代码如下

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

#include <Windows.h>
#include <processthreadsapi.h>
#include <WinUser.h>
#include <TlHelp32.h>
#include <Psapi.h>

#define MAX_LEN 256
#define PROC_NAME L"contest.exe"

DWORD GetDwPidByName(LPCWSTR procName)
{
// 使用tlhelp32获取进程PID
HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, (DWORD)0);
if (hSnapshot == INVALID_HANDLE_VALUE)
{
return 0;
}
LPWSTR fileNameBuf = (LPWSTR)malloc(sizeof(WCHAR) * MAX_LEN);
PROCESSENTRY32 pe32;
pe32.dwSize = sizeof(PROCESSENTRY32);
Process32First(hSnapshot, &pe32);
do
{
DWORD dwPID = pe32.th32ProcessID;
HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, dwPID);
if (hProcess == NULL)
{
continue;
}
GetProcessImageFileName(hProcess, fileNameBuf, (DWORD)MAX_LEN);
// 提取进程名
WCHAR* pFileName = wcsrchr(fileNameBuf, L'\\');
pFileName++;
if (!wcscmp(procName, pFileName))
{
free((void*)fileNameBuf);
return dwPID;
}
} while (Process32Next(hSnapshot, &pe32));
free((void*)fileNameBuf);
return 0;
}


DWORD GetMainThreadId(DWORD processId) {
HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
if (hSnapshot == INVALID_HANDLE_VALUE) {
return 0;
}

THREADENTRY32 te32;
te32.dwSize = sizeof(THREADENTRY32);

DWORD threadId = 0;

if (Thread32First(hSnapshot, &te32)) {
do {
if (te32.th32OwnerProcessID == processId) {
threadId = te32.th32ThreadID;
break;
}
} while (Thread32Next(hSnapshot, &te32));
}

CloseHandle(hSnapshot);
return threadId;
}


HMODULE GetProcBase(HANDLE hProcess)
{
HMODULE hModules[1024];
DWORD cbNeeded;

if (EnumProcessModules(hProcess, hModules, sizeof(hModules), &cbNeeded)) {
return hModules[0]; // 返回第一个模块的基址
}
return NULL;
}


// 修改文件名
#define FILENAME_OFF 0x772F9
BYTE filenameOriBytes[13] = {"@contest.txt"};
BYTE filenameBytes[13] = {0, };
// 将lea指令替换为mov指令
#define PATCH_FILENAME_OFF 0xCB3C
BYTE patchFilenameOriBytes[1] = { 0x8D };
BYTE patchFilenameBytes[1] = { 0x8B };

// flag
#define FLAG_OFF 0x772E8
BYTE flagOriBytes[17] = { "Ecatchmeifyoucan" };
BYTE flagBytes[17] = { "\0catchmeifyoucan" };
// patch掉base64入口,替换为memcpy
#define PATCH_FLAG_OFF 0xD6D0
BYTE patchFlagOriBytes[16] = {0x48, 0x8D, 0x05, 0x99, 0xE2, 0xFF, 0xFF, 0xFF, 0xE0, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC};
/**
push rdx
mov al, [rcx+rdx-1]
mov [r8+rdx-1], al
dec edx
jnz $-0xD
pop rdx
ret
*/
BYTE patchFlagBytes[16] = {0x52, 0x8A, 0x44, 0x11, 0xFF, 0x41, 0x88, 0x44, 0x10, 0xFF, 0xFF, 0xCA, 0x75, 0xF3, 0x5A, 0xC3};

void crack(HANDLE hProcess, HANDLE hThread, UINT64 procBase)
{
PVOID remoteBuffer = NULL;
DWORD newProtect;
DWORD oldProtect;
while (TRUE)
{
char op = '\n';
puts("[m] modify file path");
puts("[r] reset");
puts("[q] quit");
printf("input: ");
while (op == '\n') op = getchar();
switch (op)
{
case 'm':
if (remoteBuffer == NULL)
{
remoteBuffer = VirtualAllocEx(hProcess, NULL, MAX_LEN, MEM_COMMIT, PAGE_READWRITE);
if (remoteBuffer == NULL)
{
puts("failed!");
break;
}
}
puts("file path:");
char path[MAX_LEN];
scanf_s("%255s", path);
WriteProcessMemory(hProcess, remoteBuffer, path, MAX_LEN, NULL);
SuspendThread(hThread);
// 修改文件名
newProtect = PAGE_READWRITE;
VirtualProtectEx(hProcess, (LPVOID)(procBase + FILENAME_OFF & 0x1000), 0x1000, newProtect, &oldProtect);
*(UINT64*)(filenameBytes + 1) = (UINT64)remoteBuffer;
WriteProcessMemory(hProcess, (LPVOID)(procBase + FILENAME_OFF), filenameBytes, sizeof(filenameBytes), NULL);
VirtualProtectEx(hProcess, (LPVOID)(procBase + FILENAME_OFF & 0x1000), 0x1000, oldProtect, &newProtect);
// path CreateFile处lea
newProtect = PAGE_READWRITE;
VirtualProtectEx(hProcess, (LPVOID)(procBase + PATCH_FILENAME_OFF & 0x1000), 0x1000, newProtect, &oldProtect);
WriteProcessMemory(hProcess, (LPVOID)(procBase + PATCH_FILENAME_OFF), patchFilenameBytes, sizeof(patchFilenameBytes), NULL);
VirtualProtectEx(hProcess, (LPVOID)(procBase + PATCH_FILENAME_OFF & 0x1000), 0x1000, oldProtect, &newProtect);
// 修改flag
newProtect = PAGE_READWRITE;
VirtualProtectEx(hProcess, (LPVOID)(procBase + FLAG_OFF & 0x1000), 0x1000, newProtect, &oldProtect);
WriteProcessMemory(hProcess, (LPVOID)(procBase + FLAG_OFF), flagBytes, sizeof(flagBytes), NULL);
VirtualProtectEx(hProcess, (LPVOID)(procBase + FLAG_OFF & 0x1000), 0x1000, oldProtect, &newProtect);
// patch base64
newProtect = PAGE_READWRITE;
VirtualProtectEx(hProcess, (LPVOID)(procBase + PATCH_FLAG_OFF & 0x1000), 0x1000, newProtect, &oldProtect);
WriteProcessMemory(hProcess, (LPVOID)(procBase + PATCH_FLAG_OFF), patchFlagBytes, sizeof(patchFlagBytes), NULL);
VirtualProtectEx(hProcess, (LPVOID)(procBase + PATCH_FLAG_OFF & 0x1000), 0x1000, oldProtect, &newProtect);
ResumeThread(hThread);
break;
case 'r':
if (remoteBuffer == NULL)
{
break;
}
SuspendThread(hThread);
// 修改文件名
newProtect = PAGE_READWRITE;
VirtualProtectEx(hProcess, (LPVOID)(procBase + FILENAME_OFF & 0x1000), 0x1000, newProtect, &oldProtect);
WriteProcessMemory(hProcess, (LPVOID)(procBase + FILENAME_OFF), filenameOriBytes, sizeof(filenameOriBytes), NULL);
VirtualProtectEx(hProcess, (LPVOID)(procBase + FILENAME_OFF & 0x1000), 0x1000, oldProtect, &newProtect);
// path CreateFile处lea
newProtect = PAGE_READWRITE;
VirtualProtectEx(hProcess, (LPVOID)(procBase + PATCH_FILENAME_OFF & 0x1000), 0x1000, newProtect, &oldProtect);
WriteProcessMemory(hProcess, (LPVOID)(procBase + PATCH_FILENAME_OFF), patchFilenameOriBytes, sizeof(patchFilenameOriBytes), NULL);
VirtualProtectEx(hProcess, (LPVOID)(procBase + PATCH_FILENAME_OFF & 0x1000), 0x1000, oldProtect, &newProtect);
// 修改flag
newProtect = PAGE_READWRITE;
VirtualProtectEx(hProcess, (LPVOID)(procBase + FLAG_OFF & 0x1000), 0x1000, newProtect, &oldProtect);
WriteProcessMemory(hProcess, (LPVOID)(procBase + FLAG_OFF), flagOriBytes, sizeof(flagOriBytes), NULL);
VirtualProtectEx(hProcess, (LPVOID)(procBase + FLAG_OFF & 0x1000), 0x1000, oldProtect, &newProtect);
// patch base64
newProtect = PAGE_READWRITE;
VirtualProtectEx(hProcess, (LPVOID)(procBase + PATCH_FLAG_OFF & 0x1000), 0x1000, newProtect, &oldProtect);
WriteProcessMemory(hProcess, (LPVOID)(procBase + PATCH_FLAG_OFF), patchFlagOriBytes, sizeof(patchFlagOriBytes), NULL);
VirtualProtectEx(hProcess, (LPVOID)(procBase + PATCH_FLAG_OFF & 0x1000), 0x1000, oldProtect, &newProtect);
VirtualFreeEx(hProcess, remoteBuffer, 0, MEM_RELEASE);
remoteBuffer = NULL;
ResumeThread(hThread);
break;
case 'q':
return;
break;
}
}
}

int main()
{
DWORD dwPID = 0;
DWORD dwTID = 0;
HANDLE hProcess = NULL;
HANDLE hThread = NULL;
HMODULE hModule = NULL;

dwPID = GetDwPidByName(PROC_NAME);
if (dwPID == 0) return -1;

hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPID);
if (hProcess == NULL) return -1;

dwTID = GetMainThreadId(dwPID);
if (dwTID == 0)
{
CloseHandle(hProcess);
return -1;
}

hThread = OpenThread(THREAD_ALL_ACCESS, FALSE, dwTID);
if (hThread == NULL)
{
CloseHandle(hProcess);
return -1;
}

hModule = GetProcBase(hProcess);
if (hModule == NULL)
{
CloseHandle(hProcess);
CloseHandle(hThread);
return -1;
}

crack(hProcess, hThread, (UINT64)hModule);

system("pause");
CloseHandle(hProcess);
CloseHandle(hThread);
return 0;
}

腾讯游戏安全竞赛2023-PC初赛
https://blog.noxke.icu/2024/02/14/ctf_wp/腾讯游戏安全竞赛2023-PC初赛/
作者
noxke
发布于
2024年2月14日
许可协议