终于如愿参加一次腾讯游戏安全大赛,初赛也算是把所有题目都做出来了,进入决赛,但是自己太菜了,只会暴力NOP。
决赛题目就是一个占用率很高的服务进程,让我们分析解决,前几天都有事,最后一天 all in 写完的。
略
1.在64位Windows10系统上运行contest.exe, 找到明文的信息,作为答案提交(1分)。
答案:catchmeifyoucan
正常运行会在当前目录下的 contest.txt 去写入一个 ImVkImx9JG12OGtlImV+
。
CE打开,先通过CE的memory view找到一串一直在变化的内存。
虽然不确定,找到了 contest.txt 这个明文,但是 catchmeifyoucan
不确定它是不是flag,还得进一步确定,于是查找是谁访问了这个地址。
在第三项中,发现后面有一个读取 r10
所指示的内存的操作。
同样memory view查找 7FF713657190
地址,结果找到一串 base 表:
QRSTUVWXYZabcdefABCDEFGHIJKLMNOPwxyz0123456789+/ghijklmnopqrstuv
于是果断拿密文解密,发现得到了正确答案。
2.编写程序,运行时修改尽量少的contest.exe内存,让contest.exe 由写入密文信息变为写入明文信息成功。(满分2分)
Base64编码的一组中,三个原文对应四个密文。第一个密文和第四个密文只会被原文第一个字符和第三个字符影响。第二个字符会被第一个原文和第二个原文字符影响,第三个字符会被第二个原文和第三个原文字符影响。
对 base 表进行访问查询,找到三个地址,对三个地址分析。
查看第一项访问地址
右移两位进行查表,很明显就是对第一个密文的操作,因此把中间的操作全部NOP,只剩读取和写入操作。
再对第二个项目分析,很明显看到了对第四个密文的操作:
这里的 and 0x3f
就是取得最低的六位,很明显是最后一位,同样也把中间的指令全部NOP掉。
不仅如此,同时注意到赋值的时候,对 r14+1了,并且r14的值在后面+1。这里有一个很头疼的点就是明文密文长度不一致,三个原文对应四个密文,因此这里可以为这个点考虑起来了。如果我 r14 的值不给他 +1,直接给 r14 的值赋值,结果会怎样呢?也就是对应指令修改为:
1
2
|
mov [r14],al
add r14,
0
|
发现输出文件的内容果然变短了。
并且此时,我们再处理中间的一位,就能得到明文输出了。
我们来看第三个项目:
这里同样把 BA5D-BA68的代码段NOP掉,做完之后,发现contest输出明文了。
总结一下:
1
2
|
mov [r14],al
add r14,
0
|
编写dll进行一次性修改:
但是在写内存的时候,发现 VirtualProtect
一直调用失败,拿火绒剑扫了一下,发现 VirtualProtect
被下了钩子。
尝试修复一下这个钩子,写一个注入器和dll
DLL不能直接使用 VirtualProtect 去修改内存属性,所以我们需要在注入之前,使用 VirtualProtectEx
先修改内存权限,再通过 WriteProcessMemory
函数修复程序在 API
处下的一个钩子,这里是 inline hook
,因此直接遍历模块寻找 ZwProtectVirtualMemory
函数把钩子取消。
取消之后,即可使用远程线程注入的方式去注入dll,远程注入的思路就是开辟一块远程内存,写入dll路径,创建远程线程回调 LoadLibraryA
函数去加载 DLL。
DLL主要就是作上面分析的一些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
|
#include<windows.h>
#include<iostream>
#include<time.h>
#include<stdlib.h>
#include<TlHelp32.h>
DWORD old;
SIZE_T written;
DWORD FindProcess() {
HANDLE hSnap
=
CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS,
0
);
PROCESSENTRY32 pe32;
pe32
=
{ sizeof(pe32) };
BOOL
ret
=
Process32First(hSnap, &pe32);
while
(ret)
{
if
(!wcsncmp(pe32.szExeFile, L
"contest.exe"
,
11
)) {
printf(
"Find contest.exe Process %d\n"
, pe32.th32ProcessID);
return
pe32.th32ProcessID;
}
ret
=
Process32Next(hSnap, &pe32);
}
return
0
;
}
void InjectModule(DWORD ProcessId, const char
*
szPath)
{
HANDLE hProcess
=
OpenProcess(PROCESS_ALL_ACCESS, FALSE, ProcessId);
printf(
"进程句柄:%p\n"
, hProcess);
LPVOID lpAddress
=
VirtualAllocEx(hProcess, NULL,
0x100
, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
SIZE_T dwWriteLength
=
0
;
WriteProcessMemory(hProcess, lpAddress, szPath, strlen(szPath), &dwWriteLength);
HANDLE hThread
=
CreateRemoteThread(hProcess, NULL, NULL, (LPTHREAD_START_ROUTINE)LoadLibraryA, lpAddress, NULL, NULL);
WaitForSingleObject(hThread,
-
1
);
VirtualFreeEx(hProcess, lpAddress,
0
, MEM_RELEASE);
CloseHandle(hProcess);
CloseHandle(hThread);
}
void UNHOOK(DWORD ProcessId) {
BYTE INS[]
=
{
0x4C
,
0x8B
,
0xD1
,
0xB8
,
0x50
};
HANDLE ths
=
CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, ProcessId);
MODULEENTRY32 me;
me.dwSize
=
sizeof(me);
UINT64 addr
=
0
;
if
(Module32First(ths, &me))
{
do
{
if
(addr
=
(UINT64)GetProcAddress(me.hModule,
"ZwProtectVirtualMemory"
))
{
printf(
"addr:%p\n"
, addr);
break
;
}
}
while
(Module32Next(ths, &me));
}
CloseHandle(ths);
HANDLE hProcess
=
OpenProcess(PROCESS_ALL_ACCESS, FALSE, ProcessId);
VirtualProtectEx(hProcess, (void
*
)addr,
0x5
, PAGE_EXECUTE_READWRITE, &old);
WriteProcessMemory(hProcess, (void
*
)addr, INS,
0x5
, &written);
printf(
"written:%d\n"
, written);
VirtualProtectEx(hProcess, (void
*
)addr,
0x5
, old, &old);
CloseHandle(hProcess);
}
int
main() {
DWORD ProcessId
=
FindProcess();
while
(!ProcessId) {
printf(
"未找到contest程序,等待两秒中再试\n"
);
Sleep(
2000
);
ProcessId
=
FindProcess();
}
printf(
"尝试去除钩子...\n"
);
UNHOOK(ProcessId);
/
/
去除钩子
printf(
"开始注入进程...\n"
);
InjectModule(ProcessId,
"C:\\Users\\xia0ji233\\source\\repos\\T-contest\\x64\\Debug\\T-contest.dll"
);
printf(
"注入完毕\n"
);
}
|
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
|
#include<Windows.h>
#include<time.h>
#include<stdio.h>
DWORD oldprot,ret;
PROC HookedFunction;
UINT64 Offset[
3
]
=
{
0xBA39
,
0xB9FD
,
0xBA5D
},
Len
[
3
]
=
{
9
,
8
,
12
};
/
/
PATCH偏移和PATCH长度,这里皆patch为
0x90
(NOP)
BYTE Ins[]
=
{
0x41
,
0x88
,
0x06
,
/
/
mov [r14],al
0x90
,
/
/
nop
0x49
,
0x83
,
0xC6
,
0x00
/
/
add r14,
0
};
UINT64 InsOffset
=
0xBA05
,InsLen
=
sizeof(Ins);
SIZE_T num;
BYTE buf
=
0x90
;
BYTE NOP[]
=
{
0x90
,
0x90
,
0x90
,
0x90
,
0x90
,
0x90
,
0x90
,
0x90
,
0x90
,
0x90
,
0x90
,
0x90
,
0x90
,
0x90
,
0x90
};
void patch() {
UINT64 Base
=
(UINT64)GetModuleHandle(nullptr);
for
(
int
i
=
0
; i <
3
; i
+
+
) {
UINT64 addr
=
Base
+
Offset[i];
VirtualProtect((void
*
)addr,
Len
[i], PAGE_EXECUTE_READWRITE, &oldprot);
memcpy((void
*
)addr, NOP,
Len
[i]);
VirtualProtect((void
*
)addr,
Len
[i], oldprot, &oldprot);
}
printf(
"NOP done\n"
);
VirtualProtect((void
*
)(Base
+
InsOffset), InsLen,PAGE_EXECUTE_READWRITE, &oldprot);
memcpy((void
*
)(Base
+
InsOffset), Ins, InsLen);
VirtualProtect((void
*
)(Base
+
InsOffset), InsLen, oldprot, &oldprot);
printf(
"Instruction Patch Done!\n"
);
}
BOOL
APIENTRY DllMain(HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
patch();
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break
;
}
return
TRUE;
}
|
在一开始的时候我们在内存看到了 contest.txt
,于是尝试直接修改,发现可以成功更改写入的文件,因此为了达到目的我们可以直接修改这里的内存,但是因为它一直在变化,因此可以查看什么写了这个内存。
主要是做了一个异或运算,直接 NOP即可。
需要注意在把指令patch掉之后,一定要写一下这里存储 Name 的地方,把 Name 替换为自己想要的名字即可。
注入器不变,DLL 需要稍微改变一下。
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
|
#include<Windows.h>
#include<time.h>
#include<stdio.h>
DWORD oldprot,ret;
PROC HookedFunction;
UINT64 Offset[
3
]
=
{
0xBA39
,
0xB9FD
,
0xBA5D
},
Len
[
3
]
=
{
9
,
8
,
12
};
/
/
PATCH偏移和PATCH长度,这里皆patch为
0x90
(NOP)
BYTE Ins[]
=
{
0x41
,
0x88
,
0x06
,
/
/
mov [r14],al
0x90
,
/
/
nop
0x49
,
0x83
,
0xC6
,
0x00
/
/
add r14,
0
};
UINT64 InsOffset
=
0xBA05
,InsLen
=
sizeof(Ins);
SIZE_T num;
BYTE buf
=
0x90
;
BYTE NOP[]
=
{
0x90
,
0x90
,
0x90
,
0x90
,
0x90
,
0x90
,
0x90
,
0x90
,
0x90
,
0x90
,
0x90
,
0x90
,
0x90
,
0x90
,
0x90
};
void patch() {
UINT64 Base
=
(UINT64)GetModuleHandle(nullptr);
for
(
int
i
=
0
; i <
3
; i
+
+
) {
/
/
把三个点位的指令NOP掉
UINT64 addr
=
Base
+
Offset[i];
VirtualProtect((void
*
)addr,
Len
[i], PAGE_EXECUTE_READWRITE, &oldprot);
memcpy((void
*
)addr, NOP,
Len
[i]);
VirtualProtect((void
*
)addr,
Len
[i], oldprot, &oldprot);
}
printf(
"NOP done\n"
);
VirtualProtect((void
*
)(Base
+
InsOffset), InsLen,PAGE_EXECUTE_READWRITE, &oldprot);
memcpy((void
*
)(Base
+
InsOffset), Ins, InsLen);
/
/
替换对应的指令
VirtualProtect((void
*
)(Base
+
InsOffset), InsLen, oldprot, &oldprot);
printf(
"Instruction Patch Done!\n"
);
}
void patchname() {
UINT64 Base
=
(UINT64)GetModuleHandle(nullptr);
UINT64 Offset1
=
0xC8F3
,Offset2
=
0xC5C6
,NameOffset
=
0x772FA
,Len1
=
4
,Len2
=
5
,flagOffset
=
0x772E9
;
char NewName[]
=
"test.txt"
;
/
/
新文件名
char flag[]
=
"catchmeifyoucan"
;
VirtualProtect((void
*
)(Base
+
Offset1), Len1, PAGE_EXECUTE_READWRITE, &oldprot);
memcpy((void
*
)(Base
+
Offset1), NOP, Len1);
/
/
指令Nop掉防止写的时机不对发生变化
VirtualProtect((void
*
)(Base
+
Offset1), Len1, oldprot, &oldprot);
VirtualProtect((void
*
)(Base
+
Offset2), Len2, PAGE_EXECUTE_READWRITE, &oldprot);
memcpy((void
*
)(Base
+
Offset2), NOP, Len2);
/
/
指令Nop掉防止写的时机不对发生变化
VirtualProtect((void
*
)(Base
+
Offset2), Len2, oldprot, &oldprot);
VirtualProtect((void
*
)(Base
+
NameOffset), sizeof(NewName), PAGE_EXECUTE_READWRITE, &oldprot);
memcpy((void
*
)(Base
+
NameOffset), NewName, sizeof(NewName));
/
/
把名字写到内存中
VirtualProtect((void
*
)(Base
+
NameOffset), sizeof(NewName), oldprot, &oldprot);
VirtualProtect((void
*
)(Base
+
flagOffset), sizeof(flag), PAGE_EXECUTE_READWRITE, &oldprot);
memcpy((void
*
)(Base
+
flagOffset), flag, sizeof(flag));
/
/
把flag写到内存中
VirtualProtect((void
*
)(Base
+
flagOffset), sizeof(flag), oldprot, &oldprot);
printf(
"Change Name Success\n"
);
}
BOOL
APIENTRY DllMain(HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
patch();
patchname();
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break
;
}
return
TRUE;
}
|
常规思路就是找进程匹配名字直接杀,但是发现会再生,用火绒剑分析了一下,发现有两个进程,应该是相互守护关系。并且 exploror.exe 进程一直在产生新的 working service 进程。猜测应该是运行了程序之后对这个进程下了钩子,于是火绒剑扫描一下钩子:
上下图对比下图是没运行之前扫描的结果,可以发现对 ZwProtectVirtualMemory 下了钩子,和之前一样,先把这个钩子修复,然后再杀掉所有进程名为 WorkingService 的进程即可。
但是之后又发现一些很奇怪的事情,有时候可以完全杀掉,有时候杀不掉,究其原因,原来它对很多进程都进行了 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
|
#include<windows.h>
#include<iostream>
#include<time.h>
#include<stdlib.h>
#include<TlHelp32.h>
#define EXP L"explorer"
#define PROCESSNAME L"WorkingService"
DWORD old;
SIZE_T written;
void UNHOOK() {
DWORD ProcessId
=
0
;
BYTE INS[]
=
{
0x4C
,
0x8B
,
0xD1
,
0xB8
,
0x50
},buf;
HANDLE hSnap
=
CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS,
0
);
PROCESSENTRY32 pe32;
pe32
=
{ sizeof(pe32) };
BOOL
ret
=
Process32First(hSnap, &pe32);
while
(ret)
{
if
(TRUE) {
ProcessId
=
pe32.th32ProcessID;
HANDLE ths
=
CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, ProcessId);
if
(!ths) {
ret
=
Process32Next(hSnap, &pe32);
continue
;
}
MODULEENTRY32 me;
me.dwSize
=
sizeof(me);
UINT64 addr
=
0
;
if
(Module32First(ths, &me))
{
do
{
if
(addr
=
(UINT64)GetProcAddress(me.hModule,
"ZwProtectVirtualMemory"
))
{
break
;
}
}
while
(Module32Next(ths, &me));
}
CloseHandle(ths);
HANDLE hProcess
=
OpenProcess(PROCESS_ALL_ACCESS, FALSE, ProcessId);
if
(!hProcess||addr
=
=
0
) {
ret
=
Process32Next(hSnap, &pe32);
continue
;
}
ReadProcessMemory(hProcess, (void
*
)addr, &buf,
1
, &written);
if
(buf
=
=
0xE9
) {
/
/
识别到inline hook的标志
VirtualProtectEx(hProcess, (void
*
)addr,
0x5
, PAGE_EXECUTE_READWRITE, &old);
WriteProcessMemory(hProcess, (void
*
)addr, INS,
0x5
, &written);
printf(
"Process %d,hook addr:%p\n"
,ProcessId, addr);
printf(
"written:%d\n"
, written);
VirtualProtectEx(hProcess, (void
*
)addr,
0x5
, old, &old);
}
CloseHandle(hProcess);
}
ret
=
Process32Next(hSnap, &pe32);
}
}
DWORD FindProcess() {
HANDLE hSnap
=
CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS,
0
);
PROCESSENTRY32 pe32;
pe32
=
{ sizeof(pe32) };
BOOL
ret
=
Process32First(hSnap, &pe32);
while
(ret)
{
if
(!wcsncmp(pe32.szExeFile, PROCESSNAME, lstrlenW(PROCESSNAME))) {
printf(
"Find WorkingService.exe Process %d\n"
, pe32.th32ProcessID);
return
pe32.th32ProcessID;
}
ret
=
Process32Next(hSnap, &pe32);
}
return
0
;
}
int
main() {
DWORD ProcessId
=
0
;
UNHOOK();
do {
ProcessId
=
FindProcess();
printf(
"pid:%p\n"
, ProcessId);
if
(!ProcessId) {
break
;
}
HANDLE hProcess
=
OpenProcess(PROCESS_ALL_ACCESS, FALSE, ProcessId);
printf(
"hProcess:%p\n"
, hProcess);
if
(!hProcess) {
break
;
}
TerminateProcess(hProcess,
0
);
}
while
(
1
);
printf(
"Terminate OK\n"
);
system(
"pause"
);
}
|
运行结果:
直接使用main1.cpp编译出来的代码运行即可结束。
但是有一定概率失败,不知道是什么问题,但是有时候可以完美运行,因此这里我使用了另一种方法:检测到被注入的进程,然后直接结束它们,之后马上杀掉服务进程。
实现起来也非常简单:
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
|
#include<windows.h>
#include<iostream>
#include<time.h>
#include<stdlib.h>
#include<TlHelp32.h>
#define EXP L"explorer"
#define PROCESSNAME L"WorkingService"
#define DLLNAME L"WorkingServiceDll.dll"
DWORD old;
SIZE_T written;
void UNHOOK() {
DWORD ProcessId
=
0
;
BYTE INS[]
=
{
0x4C
,
0x8B
,
0xD1
,
0xB8
,
0x50
}, buf;
HANDLE hSnap
=
CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS,
0
);
PROCESSENTRY32 pe32;
pe32
=
{ sizeof(pe32) };
BOOL
ret
=
Process32First(hSnap, &pe32);
while
(ret)
{
if
(TRUE) {
ProcessId
=
pe32.th32ProcessID;
HANDLE ths
=
CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, ProcessId);
if
(!ths) {
ret
=
Process32Next(hSnap, &pe32);
continue
;
}
MODULEENTRY32 me;
me.dwSize
=
sizeof(me);
UINT64 addr
=
0
;
if
(Module32First(ths, &me))
{
do
{
if
(!memcmp(me.szModule,DLLNAME,sizeof(DLLNAME)))
{
printf(
"terminate pid=%d\n"
, ProcessId);
HANDLE hProcess
=
OpenProcess(PROCESS_ALL_ACCESS, FALSE, ProcessId);
TerminateProcess(hProcess,
0
);
CloseHandle(hProcess);
}
}
while
(Module32Next(ths, &me));
}
CloseHandle(ths);
}
ret
=
Process32Next(hSnap, &pe32);
}
}
DWORD FindProcess() {
HANDLE hSnap
=
CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS,
0
);
PROCESSENTRY32 pe32;
pe32
=
{ sizeof(pe32) };
BOOL
ret
=
Process32First(hSnap, &pe32);
while
(ret)
{
if
(!wcsncmp(pe32.szExeFile, PROCESSNAME, lstrlenW(PROCESSNAME))) {
printf(
"Find WorkingService.exe Process %d\n"
, pe32.th32ProcessID);
return
pe32.th32ProcessID;
}
ret
=
Process32Next(hSnap, &pe32);
}
return
0
;
}
int
main() {
DWORD ProcessId
=
0
;
UNHOOK();
system(
"taskkill /f /im WorkingService.exe"
);
printf(
"Terminate OK\n"
);
system(
"pause"
);
}
|
此方式编译main1-2.cpp文件的代码运行即可。
通过火绒剑分析行为,发现一秒内就会有大量的创建文件请求。
因此想到使用 CE 去查找到 contest 字符串,找到创建文件的指令,hook出来每次创建文件之后sleep即可。
但是由于pid一直变化,因此尝试先卸载钩子,保证pid不变化,使用 CE 调试,找到内存访问的指令。
注入的时候同样发现进程存在钩子,因此也先在注入器当中手动把钩子去掉。
找到读 contest 内存的代码段,把代码段拷贝到一个自己分配的内存上面,赋予可执行权限,不过在指令中间可以加入一个 sleep函数,让它得以休息。不过这里遇到一个问题,就是32位偏移貌似跳转不了,因此使用
1
2
|
mov rax,
0x0000000000000000
call rax
|
的方式去调用休眠函数,这里的八字节整数直接填sleep函数的虚拟地址即可。
注入器代码:
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
|
#include<windows.h>
#include<iostream>
#include<time.h>
#include<stdlib.h>
#include<TlHelp32.h>
DWORD old;
SIZE_T written;
void InjectModule(DWORD ProcessId, const char
*
szPath)
{
HANDLE hProcess
=
OpenProcess(PROCESS_ALL_ACCESS, FALSE, ProcessId);
printf(
"进程句柄:%p\n"
, hProcess);
LPVOID lpAddress
=
VirtualAllocEx(hProcess, NULL,
0x100
, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
SIZE_T dwWriteLength
=
0
;
WriteProcessMemory(hProcess, lpAddress, szPath, strlen(szPath), &dwWriteLength);
HANDLE hThread
=
CreateRemoteThread(hProcess, NULL, NULL, (LPTHREAD_START_ROUTINE)LoadLibraryA, lpAddress, NULL, NULL);
WaitForSingleObject(hThread,
-
1
);
VirtualFreeEx(hProcess, lpAddress,
0
, MEM_RELEASE);
CloseHandle(hProcess);
CloseHandle(hThread);
}
void UNHOOK(DWORD ProcessId) {
BYTE INS[]
=
{
0x4C
,
0x8B
,
0xD1
,
0xB8
,
0x50
};
HANDLE ths
=
CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, ProcessId);
MODULEENTRY32 me;
me.dwSize
=
sizeof(me);
UINT64 addr
=
0
;
if
(Module32First(ths, &me))
{
do
{
if
(addr
=
(UINT64)GetProcAddress(me.hModule,
"ZwProtectVirtualMemory"
))
{
printf(
"addr:%p\n"
, addr);
break
;
}
}
while
(Module32Next(ths, &me));
}
CloseHandle(ths);
HANDLE hProcess
=
OpenProcess(PROCESS_ALL_ACCESS, FALSE, ProcessId);
VirtualProtectEx(hProcess, (void
*
)addr,
0x5
, PAGE_EXECUTE_READWRITE, &old);
WriteProcessMemory(hProcess, (void
*
)addr, INS,
0x5
, &written);
printf(
"written:%d\n"
, written);
VirtualProtectEx(hProcess, (void
*
)addr,
0x5
, old, &old);
CloseHandle(hProcess);
}
void FindProcess() {
HANDLE hSnap
=
CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS,
0
);
PROCESSENTRY32 pe32;
pe32
=
{ sizeof(pe32) };
BOOL
ret
=
Process32First(hSnap, &pe32);
while
(ret)
{
if
(!wcsncmp(pe32.szExeFile, L
"WorkingService"
,
14
)) {
printf(
"Find WorkingService.exe Process %d\n"
, pe32.th32ProcessID);
DWORD PID
=
pe32.th32ProcessID;
printf(
"尝试去除钩子...\n"
);
UNHOOK(PID);
/
/
去除钩子
printf(
"开始注入进程...\n"
);
InjectModule(PID,
"T-Contest-final-2-dll.dll"
);
printf(
"注入完毕\n"
);
system(
"pause"
);
}
ret
=
Process32Next(hSnap, &pe32);
}
}
int
main() {
FindProcess();
}
|
DLL代码:
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
|
/
/
dllmain.cpp : 定义 DLL 应用程序的入口点。
#include<Windows.h>
#include<stdio.h>
DWORD oldprot;
BYTE code[]
=
{
0x8B
,
0x0A
,
/
/
+
0
mov ecx,[rdx]
0x44
,
0x0F
,
0xB7
,
0x42
,
0x04
,
/
/
+
2
movzx r8d,word ptr [rdx
+
04
]
0x44
,
0x0F
,
0xB6
,
0x4A
,
0x06
,
/
/
+
7
movzx r9d,byte ptr [rdx
+
06
]
0x89
,
0x08
,
/
/
+
C mov [rax],ecx
0x66
,
0x44
,
0x89
,
0x40
,
0x04
,
/
/
+
E mov [rax
+
04
],r8w
0x44
,
0x88
,
0x48
,
0x06
,
/
/
+
13
mov [rax
+
06
],r9b
0x48
,
0xB8
,
0x00
,
0x00
,
0x00
,
0x00
,
0x00
,
0x00
,
0x00
,
0x00
,
/
/
+
17
call sleep
0xFF
,
0xD0
,
/
/
+
22
call rax
0xC3
/
/
+
24
ret
};
void SSLEEP() {
Sleep(
500
);
}
void HOOK() {
UINT64 offset
=
0x7C70
,Base
=
(UINT64)GetModuleHandle(nullptr),offset2
=
0x19
,InsLen
=
5
;
BYTE INS[]
=
{
0xe9
,
0x00
,
0x00
,
0x00
,
0x00
};
UINT64 PAGEADDR
=
(UINT64)VirtualAlloc((LPVOID)
0x7FF6F8D90000
,
0x200
, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
printf(
"%p\n"
, PAGEADDR);
*
(DWORD
*
)(INS
+
1
)
=
PAGEADDR
-
(Base
+
offset)
-
5
;
*
(UINT64
*
)(code
+
offset2)
=
(UINT64)SSLEEP;
memcpy((void
*
)PAGEADDR, code, sizeof(code));
VirtualProtect((void
*
)(Base
+
offset), InsLen, PAGE_EXECUTE_READWRITE, &oldprot);
memcpy((void
*
)(Base
+
offset), INS, InsLen);
/
/
替换对应的指令
VirtualProtect((void
*
)(Base
+
offset), InsLen, oldprot, &oldprot);
}
BOOL
APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
AllocConsole();
freopen(
"CONOUT$"
,
"w"
, stdout);
HOOK();
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break
;
}
return
TRUE;
}
|
内存利用率下降了很多。
使用main2.cpp和injector.cpp编译出exe文件,先执行main2取消被钩住的进程,再使用injector注入dllmain.cpp编译出的动态链接库,达到效果。
同样做的时候发现了不稳定的因素,有时候可以使用,有时候不能使用,因此采取第二种方式来解决。
同样要先把所有钩子卸掉,最好的办法还是重启那些已经被钩住的程序。
这部分程序与之前 main1-2.cpp 中的区别仅仅是少了 system("taskkill /f /im WorkingService.exe");
而已。
然后把上面的dll注入到两个进程中去,这一段代码没有变化。
通过火绒剑的事件分析可以发现,文件创建的频率明显下降很多(因为每次强制休息了500MS):
并且没有影响进程功能,CPU占用率也大大降低了。
首先就要分析它写入了哪四个文件了,从火绒剑的分析来看,应该是有四个线程在不同的写,因为很容易注意到,四个文件几乎是同时打开的。
对进程进行内存dump,拖入ida分析,发现一串base64的表,于是在CE中找到主要编码的函数。
通过参数分析找到了内存的位置,通过仔细观察,发现和初赛的flag:catchmeifyoucan
十分相似。
经过分析,发现它们存在互补关系
通过某次虚拟机炸了之后,意外得到了两份落地的文件
经过base64解码发现,其中一个是 catchmeifyoucan
+ id,一个是不可见的字符,而且刚好等于上面不可见字符的补集。
本来并没有什么头绪,但是发现了一个细节,在火绒剑当中
可以发现 contestxxxx.txt 的 xxxx 就是线程的 id,
大概分析了一下,应该是有两串content,根据线程id,选择到底是 ASCII 字符还是非ASCII 字符,然后base64后+TID。
经过多次测试之后发现,只会出现两种:两种互补,就像这样子:
根据逻辑分析,是计算TID的总和,然后根据位数,选择这一位到底是选择 ASCII 还是非ASCII字符。
根据之前进程的行为写出了以下的程序,这里需要CREATE_ALWAYS和FILE_FLAG_DELETE_ON_CLOSE两个标志位,以便于达到和之前进程一样的行为。
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
|
#include <stdio.h>
#include <windows.h>
char flag1[]
=
"catchmeifyoucan"
;
char flag2[]
=
"\x15\x17\x02\x15\x1e\x1b\x13\x1f\x10\x0f\x19\x03\x15\x17\x18"
;
DWORD written;
const char
*
BASE64_CHARS
=
"ABCDEFGHIJKLMNOPwxyz0123456789+/ghijklmnopqrstuvQRSTUVWXYZabcdef"
;
DWORD
all
=
0
;
void base64_encode(const char
*
input
, char
*
output) {
int
i, j;
unsigned char buf[
3
];
int
num_bits
=
0
, bits;
size_t
len
=
strlen(
input
);
for
(i
=
0
, j
=
0
; i <
len
; i
+
+
) {
buf[num_bits
+
+
]
=
input
[i];
if
(num_bits
=
=
3
) {
bits
=
(buf[
0
] <<
16
) | (buf[
1
] <<
8
) | buf[
2
];
output[j
+
+
]
=
BASE64_CHARS[(bits >>
18
) &
0x3F
];
output[j
+
+
]
=
BASE64_CHARS[(bits >>
12
) &
0x3F
];
output[j
+
+
]
=
BASE64_CHARS[(bits >>
6
) &
0x3F
];
output[j
+
+
]
=
BASE64_CHARS[bits &
0x3F
];
num_bits
=
0
;
}
}
if
(num_bits >
0
) {
bits
=
(buf[
0
] <<
16
) | ((num_bits
=
=
2
) ? (buf[
1
] <<
8
) :
0
);
output[j
+
+
]
=
BASE64_CHARS[(bits >>
18
) &
0x3F
];
output[j
+
+
]
=
BASE64_CHARS[(bits >>
12
) &
0x3F
];
output[j
+
+
]
=
(num_bits
=
=
1
) ?
'='
: BASE64_CHARS[(bits >>
6
) &
0x3F
];
output[j
+
+
]
=
'='
;
}
output[j]
=
'\0'
;
}
void process(LPVOID f) {
DWORD TID
=
GetCurrentThreadId();
HANDLE hFile
=
NULL;
char filename[
20
];
char content[
50
];
char c[
50
];
char tmp[
50
];
sprintf(filename,
"contest%d.txt"
, TID);
while
(!
all
);
DWORD x
=
all
;
printf(
"%d\n"
,
*
(
int
*
)f);
for
(
int
i
=
0
; i <
15
; i
+
+
) {
if
((
*
(
int
*
)f
%
2
)^(x&
1
)) {
c[i]
=
flag1[i];
}
else
{
c[i]
=
flag2[i];
}
x >>
=
1
;
}
c[
15
]
=
0
;
base64_encode(c, tmp);
sprintf(content,
"%s%d"
, tmp, TID);
/
/
printf(content);
do {
hFile
=
CreateFileA(filename, GENERIC_WRITE,
0
, NULL, CREATE_ALWAYS, FILE_FLAG_DELETE_ON_CLOSE, NULL);
if
(hFile !
=
(HANDLE)
-
1
)WriteFile(hFile, content, strlen(content), &written,
0
);
Sleep(
1000
);
}
while
(
1
);
CloseHandle(hFile);
}
int
main()
{
HANDLE hThread[
4
];
DWORD ans
=
0
;
int
k[]
=
{
0
,
1
,
2
,
3
};
for
(
int
i
=
0
; i <
4
; i
+
+
) {
DWORD dwThreadId;
hThread[i]
=
CreateThread(
NULL,
/
/
lpThreadAttributes 参数,一般传递 NULL
0
,
/
/
dwStackSize 参数,线程堆栈大小,一般为
0
(LPTHREAD_START_ROUTINE)process,
/
/
lpStartAddress 参数,线程函数
&k[i],
/
/
lpParameter 参数,线程函数的参数,这里不需要传递参数
0
,
/
/
dwCreationFlags 参数,线程的创建标志,一般为
0
&dwThreadId
/
/
lpThreadId 参数,线程
ID
的输出参数
);
ans
+
=
dwThreadId;
}
all
=
ans&
0xffff
;
for
(
int
i
=
0
; i <
4
; i
+
+
) {
WaitForSingleObject(hThread[i],
-
1
);
}
return
0
;
}
|
在附件中,编译 main3.cpp,运行可以直接达到效果,并且拥有很低的CPU占用率。
更多【腾讯游戏安全大赛题解】相关视频教程:www.yxfzedu.com