复盘一下2022的腾讯游戏安全比赛。

初赛

题目说明

这里有一个画了flag的小程序,可好像出了点问题,flag丢失了,需要把它找回来。

题目

找回flag样例:

要求:

  1. 不得直接patch系统组件实现绘制(如:直接编写D3D代码绘制flag),只能对题目自身代码进行修改或调用。
  2. 找回的flag需要和预期图案(包括颜色)一致,如果绘制结果存在偏差会扣除一定分数。
  3. 赛后需要提交找回flag的截图解题代码或文档进行评分。

评分标准:

根据提交截图和代码文档的时间作为评分依据。

解题过程

先打开所给的程序,发现输出 ACE 的 LOGO 过一会后会消失。

IDA,打开找到 WinMain 函数,找到消息循环函数,分析主体逻辑

看到主体的函数

有一个初始化的操作,会分配一片可读可写可执行(#define PAGE_EXECUTE_READWRITE 0x40)内存并将代码拷贝过去,主要有 140005040140006350 两个地址的代码。

第一段拷贝完成之后可以发现它 PATCH 了函数开头的几个字节,应该是防静态分析的,而且看字节大概是 PUSH,POP 指令。

下面可以看到记录了当前的时刻,如果发现起始时刻与当前时刻超过了 4000(4000MS)那么执行下面的指令,这里根据开始的运行大概能猜测出来应该就是停止绘制的代码了。

最后调用 shellcode+0x650 的代码作为入口,下面可以尝试跟一下这个函数,这里可以采用静态修改代码为真实代码,也可以动调执行到这里的时候反编译。

直接跟到入口,可以很明显地发现函数入口往后有一段对自身的调用

看地址是 shellcode+0x420,跟过去,重建函数,是一个很标准的虚拟机流程

虚拟机的代码存在于 shellcode+0x1301 的地址,也就是第二次拷贝得到的代码。

根据自己的理解还原了一下虚拟机的流程:

  • op0:Stack[0]+=Stack[1]
  • op1:Stack[0]-=Stack[1]
  • op2 num1,num2:Stack[num2]=Stack[num1]
  • op3 num1,num2:Stack[num2]=num1
  • op4 num:这里的操作很神奇,会把栈中第一个值赋值为 num^'ACE',第二个值赋值为一个很复杂的运算。
  • op5:调用 shellcode 头部的函数。
  • op6:调用 shellcode 头部的函数,与上一个唯一的区别是第五个参数。
  • op7:退出

而这里的 v16-v18 大概率也是 op2 和 op3 会操作到的,也算栈中的值。

这里很容易猜测 op5 和 op6 应该是绘制函数的代码。

FFFF00 刚好是黄色的代码,将取色器放置在程序上也能发现蓝色的代码是 2DDBE7,和这里的颜色代码差了一点,但是可以尝试修改一下。

CE 找到这个位置,将代码修改一下

这里我直接把它改成 000000 也就是黑色,直接跳出来。

然后执行完下面的代码,发现输出变成黑色了

那么主要肯定是要分析 shellcode+0 处的函数代码了(看不懂直接放弃)。

虽然看不懂,但是已经知道里面传的一个值是颜色了,去分析分析其他参数的含义就可以了。这里最好的一个办法应该是 hook,去打印它的参数,为了方便可以把它限时输出这点 PATCH 了。

这个直接去找到它的跳转让它永远跳转或者永远不跳转就行了,这里是改成永远跳转,90 加前面,偏移可以不用动。

写一个 DLL 去做 HOOK,主要去 HOOK shellcode,这个地址通过全局变量可以获得(2022游戏安全技术竞赛初赛.exe+0x8308)。

64 位的程序hook一般直接用 inline 或者 hotfix 或者无痕,个人感觉 hotfix 实现起来简单,但是个人更喜欢 inline hook,因为它 windows 消息的机制,会不停地打印数据,因此加全局变量限制输出前 100 次调用的结果。

注入器(基本通用的):

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
#include<windows.h>
#include<iostream>
#include<time.h>
#include<stdlib.h>
#include<TlHelp32.h>
#define EXEFILEW L"2022游戏安全技术竞赛初赛.exe"
#define EXEFILE "2022游戏安全技术竞赛初赛.exe"
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, EXEFILEW, lstrlen(EXEFILEW))) {
printf("找到程序 %s ,PID=%d\n", EXEFILE, 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);
}
int main() {
DWORD ProcessId = FindProcess();
while (!ProcessId) {
printf("未找到%s程序,等待两秒中再试\n",EXEFILE);
Sleep(2000);
ProcessId = FindProcess();
}
InjectModule(ProcessId, "C:\\Users\\xia0ji233\\source\\repos\\T2022Pre\\x64\\Debug\\hack.dll");
}

用于 HOOK 的 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
// dllmain.cpp : 定义 DLL 应用程序的入口点。
#include "pch.h"
#include <Windows.h>
#include <stdio.h>
#include <math.h>
typedef __int64 (*Func)(int a1, int a2, int a3, int a4, int a5, __int64 a6, __int64 a7, __int64 a8, __int64 a9, __int64 a10);
__int64 GetBaseAddr() {
HMODULE hMode = GetModuleHandle(nullptr);
return (__int64)hMode;
}
void* shellcode = 0;
BYTE HookCode[] = {
0x48,0xB8,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, //mov rax,xxx
0xFF,0xE0 //jmp rax
};
BYTE OriginCode[0x50];
size_t HookLen = 12;
__int64 times = 100;
__int64 HackShellcode(int a1, int a2, int a3, int a4, int a5, __int64 a6, __int64 a7, __int64 a8, __int64 a9, __int64 a10) {
memcpy(shellcode, OriginCode,HookLen); //unhook
//
int x = a1, y = a2;
__int64 ret=(*(Func)shellcode)(x, y, a3, a4, 0xFFFF0000, a6, a7, a8, a9, a10);
times--;
if (times>0) {
printf("call shellcode(%d,%d,%d,%d,%d,%p,%p,%p,%p,%p)\n",x, y, a3, a4, a5, a6, a7, a8, a9, a10);

}
memcpy(shellcode, HookCode, HookLen); //rehook
return ret;
}


void HookShellcode() {
__int64 base = GetBaseAddr();
__int64 Ptr = base + 0x8308;

shellcode = (void*)(*(__int64*)Ptr);
while (!shellcode) {
shellcode = (void*)(*(__int64*)Ptr);
printf("Find shellcode Fail\n");
Sleep(200);
}
printf("shellcode addr=%p\n", shellcode);
memcpy(OriginCode, shellcode,HookLen); //saved
Func FuncPtr = HackShellcode;
*(__int64*)(HookCode + 2) = (__int64)FuncPtr; //construct
memcpy(shellcode, HookCode, HookLen); //hook

}


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);
HookShellcode();
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}

首先是为了输出方便创建一个终端定向标准输出,然后 IO 函数就能往终端打印了。

hook 其实就是覆盖函数头,劫持到自己的函数里面,打印出参数之后把钩子去掉,恢复回原来的样子,再去正常调用,调用结束之后重新挂回钩子,要实现输出前100条的话最后不要重新挂钩子就行。

结果(PS:输出是正常的,但是我强制都改成黄色绘制了):

因为前几个参数是 __int32 类型的,所以直接换 %d 打印一下,这里放一下部分的数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
shellcode addr=000001C5654B0000
call shellcode(-950,50,-14703700,1248208,ffffff00,000001C56640AC28,000001C5701C78B0,000001C5665CA4D8,000001C5704025C8,000001C570402D88)
call shellcode(50,-390,1822057,-1524539,ffffff00,000001C56640AC28,000001C5701C78B0,000001C5665CA4D8,000001C5704025C8,000001C570402D88)
call shellcode(-950,170,-7425437,14227863,ffffff00,000001C56640AC28,000001C5701C78B0,000001C5665CA4D8,000001C5704025C8,000001C570402D88)
call shellcode(50,230,1897472,15215384,ffffff00,000001C56640AC28,000001C5701C78B0,000001C5665CA4D8,000001C5704025C8,000001C570402D88)
call shellcode(-950,-210,3743658,7267794,ffffff00,000001C56640AC28,000001C5701C78B0,000001C5665CA4D8,000001C5704025C8,000001C570402D88)
call shellcode(50,350,966258,14122466,ffffff00,000001C56640AC28,000001C5701C78B0,000001C5665CA4D8,000001C5704025C8,000001C570402D88)
call shellcode(-890,-270,463184,7666472,ffffff00,000001C56640AC28,000001C5701C78B0,000001C5665CA4D8,000001C5704025C8,000001C570402D88)
call shellcode(170,-270,-2474473,12856971,ffffff00,000001C56640AC28,000001C5701C78B0,000001C5665CA4D8,000001C5704025C8,000001C570402D88)
call shellcode(-770,230,3460907,-13492529,ffffff00,000001C56640AC28,000001C5701C78B0,000001C5665CA4D8,000001C5704025C8,000001C570402D88)
call shellcode(110,-390,-5989351,14280177,ffffff00,000001C56640AC28,000001C5701C78B0,000001C5665CA4D8,000001C5704025C8,000001C570402D88)
call shellcode(-830,170,-3514649,12856715,ffffff00,000001C56640AC28,000001C5701C78B0,000001C5665CA4D8,000001C5704025C8,000001C570402D88)
call shellcode(650,50,15384343,11002795,ff2ddbe7,000001C56640AC28,000001C5701C78B0,000001C5665CA4D8,000001C5704025C8,000001C570402D88)
call shellcode(590,110,12856715,13307703,ff2ddbe7,000001C56640AC28,000001C5701C78B0,000001C5665CA4D8,000001C5704025C8,000001C570402D88)
call shellcode(530,170,14096303,2054931,ff2ddbe7,000001C56640AC28,000001C5701C78B0,000001C5665CA4D8,000001C5704025C8,000001C570402D88)
call shellcode(470,230,12869865,15314261,ff2ddbe7,000001C56640AC28,000001C5701C78B0,000001C5665CA4D8,000001C5704025C8,000001C570402D88)
call shellcode(410,290,13085065,12188981,ff2ddbe7,000001C56640AC28,000001C5701C78B0,000001C5665CA4D8,000001C5704025C8,000001C570402D88)
call shellcode(470,290,11235584,6581496,ff2ddbe7,000001C56640AC28,000001C5701C78B0,000001C5665CA4D8,000001C5704025C8,000001C570402D88)
call shellcode(530,290,12847529,3263901,ff2ddbe7,000001C56640AC28,000001C5701C78B0,000001C5665CA4D8,000001C5704025C8,000001C570402D88)
call shellcode(710,50,14122635,3090291,ff2ddbe7,000001C56640AC28,000001C5701C78B0,000001C5665CA4D8,000001C5704025C8,000001C570402D88)
call shellcode(770,50,14265601,10052917,ff2ddbe7,000001C56640AC28,000001C5701C78B0,000001C5665CA4D8,000001C5704025C8,000001C570402D88)
call shellcode(830,50,3793238,14305830,ff2ddbe7,000001C56640AC28,000001C5701C78B0,000001C5665CA4D8,000001C5704025C8,000001C570402D88)
call shellcode(710,110,1253500,3434568,ff2ddbe7,000001C56640AC28,000001C5701C78B0,000001C5665CA4D8,000001C5704025C8,000001C570402D88)
call shellcode(770,170,13177867,745383,ff2ddbe7,000001C56640AC28,000001C5701C78B0,000001C5665CA4D8,000001C5704025C8,000001C570402D88)

可以发现,前两个参数应该是坐标(我猜的),但是出现了负值,也就是打印到了屏幕外面,而且都是黄色的点会出现这种情况,导致了 FLAG 打印不出来,因此尝试在 hook 层面修复这个 bug,把所有的负值翻转,但是发现并没什么用,说明bug应该不止那么简单,还得再分析分析。

首先就是想看看它一轮有多少个点,直接建个 set 去输出就行,把所有点保存下来。

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
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
// dllmain.cpp : 定义 DLL 应用程序的入口点。
#include "pch.h"
#include <Windows.h>
#include <set>
#include <stdio.h>
#include <math.h>
std::set<std::pair<int, int>>s;

typedef __int64 (*Func)(int a1, int a2, int a3, int a4, int a5, __int64 a6, __int64 a7, __int64 a8, __int64 a9, __int64 a10);
__int64 GetBaseAddr() {
HMODULE hMode = GetModuleHandle(nullptr);
return (__int64)hMode;
}
void* shellcode = 0;
BYTE HookCode[] = {
0x48,0xB8,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, //mov rax,xxx
0xFF,0xE0 //jmp rax
};
BYTE OriginCode[0x50];
size_t HookLen = 12;
__int64 times = 100;
void printset() {
for (auto k : s) {
printf("(%d,%d)\n", k.first, k.second);
}
}
__int64 HackShellcode(int a1, int a2, int a3, int a4, int a5, __int64 a6, __int64 a7, __int64 a8, __int64 a9, __int64 a10) {
static int flag = 1;
memcpy(shellcode, OriginCode,HookLen); //unhook
//
int x = a1, y = a2;
__int64 ret=(*(Func)shellcode)(x, y, a3, a4, a5, a6, a7, a8, a9, a10);
times--;
if (times>0) {
printf("call shellcode(%d,%d,%d,%d,%x,%p,%p,%p,%p,%p)\n",x, y, a3, a4, a5, a6, a7, a8, a9, a10);
}
int presize = s.size();
s.insert({ x,y });
if (s.size() == presize) {
if (flag) {
printset();
flag = 0;
}
}
memcpy(shellcode, HookCode, HookLen); //rehook
return ret;
}


void HookShellcode() {
__int64 base = GetBaseAddr();
__int64 Ptr = base + 0x8308;

shellcode = (void*)(*(__int64*)Ptr);
while (!shellcode) {
shellcode = (void*)(*(__int64*)Ptr);
printf("Find shellcode Fail\n");
Sleep(200);
}
printf("shellcode addr=%p\n", shellcode);
memcpy(OriginCode, shellcode,HookLen); //saved
Func FuncPtr = HackShellcode;
*(__int64*)(HookCode + 2) = (__int64)FuncPtr; //construct
memcpy(shellcode, HookCode, HookLen); //hook

}


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);
HookShellcode();
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}


输出:

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
(-950,-210)
(-950,50)
(-950,170)
(-890,-270)
(-830,170)
(-770,230)
(50,-390)
(50,230)
(50,350)
(110,-390)
(170,-270)
(410,290)
(470,230)
(470,290)
(530,170)
(530,290)
(590,110)
(650,50)
(710,50)
(710,110)
(770,50)
(770,170)
(830,50)
(830,230)
(890,290)
(950,50)
(950,290)
(1010,50)
(1010,110)
(1010,290)
(1070,50)
(1070,170)
(1070,290)
(1130,50)
(1130,170)
(1130,230)
(1190,170)
(1190,290)
(1250,170)
(1250,290)
(1310,290)
(1370,290)

一共是 42 个点,而数了一下它题目给的正确的点数刚好也是42个,黄点是有 11 个, 蓝点有 31 个。

但是稍微改了一下第一个第二个参数发现点会直接消失,看样子是跟第三个第四个参数会有关系。

通过左右参数的比对观察一下,正确调用时的这些局部变量分别是多少,看看能不能找到点关系(放弃)。

还是选择自己写一个虚拟机去跑。

代码太长了不放了,可以自己去 dump,我写一下我自己的虚拟机调试流程:

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
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
int code[1855]; //...
int main(){
int RIP=0;
int reg;
int Stack[50];
int v13,v14;
printf("start\n");
memset(Stack,0,sizeof(Stack));
Stack[8]=Stack[9]=50;//截图中忘了这一句,不要忘了加上
while(RIP<=0x1301){
//printf("execute opcode=%d RIP=%d\n",code[RIP],RIP);
int opcode=code[RIP];
RIP++;
switch (opcode) {

case 0:
Stack[0]+=Stack[1];
printf("stack[0]=%d+%d\n",Stack[0],Stack[1]);
break;
case 1:
Stack[0]-=Stack[1];
printf("stack[0]=%d-%d\n",Stack[0],Stack[1]);
break;
case 2:
Stack[code[RIP+1]]=Stack[code[RIP]];
printf("Stack[%d]=Stack[%d]=%d\n",code[RIP+1],code[RIP],Stack[code[RIP]]);
RIP+=2;
break;
case 3:
Stack[code[RIP+1]]=code[RIP];
printf("Stack[%d]=%d\n",code[RIP+1],code[RIP]);
RIP+=2;
break;
case 4:
v13=Stack[0];
v14=Stack[0]*(Stack[1]+1);
printf("Origin: Stack[0]=%d Stack[1]=%d ",Stack[0],Stack[1]);
Stack[0]=code[RIP]^0x414345;
Stack[1]=((Stack[0] ^ (Stack[1] + v13)) % 256+ (((Stack[0] ^ (v13 * Stack[1])) % 256 + (((Stack[0] ^ (Stack[1] + v14)) % 256) << 8)) << 8));
printf("Target: Stack[0]=%d Stack[1]=%d\n",Stack[0],Stack[1]);
RIP+=1;
break;
case 5:
printf("paint(%d,%d,%d,%d,0xFFFFFF00);\n",Stack[4],Stack[5],Stack[6],Stack[7]);
break;
case 6:
printf("paint(%d,%d,%d,%d,0xFF2DDBE7);\n",Stack[4],Stack[5],Stack[6],Stack[7]);
break;
case 7:
printf("exit\n");
exit(0);
default:
exit(0);
break;

}
}
}

运行之后发现了一点:

第三个和第四个参数分别为用 opcode==4 时候的 x 和 y 的坐标运算得到的值。

试试看利用 opcode=4 的流程能否让程序任意位置输出色块。

1
2
3
4
v13=x;
v14=x*(y+1);
a3=operand^0x414345;
a4=((x ^ (y + v13)) % 256+ (((x ^ (v13 * y)) % 256 + (((x ^ (y + v14)) % 256) << 8)) << 8));

因为每次的这个操作数都不同,这里选取第一个错误坐标 -950 50 来绘制,利用这里的逻辑去做。

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
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
// dllmain.cpp : 定义 DLL 应用程序的入口点。
#include "pch.h"
#include <Windows.h>
#include <set>
#include <stdio.h>
#include <math.h>
std::set<std::pair<int, int>>s;

typedef __int64 (*Func)(int a1, int a2, int a3, int a4, int a5, __int64 a6, __int64 a7, __int64 a8, __int64 a9, __int64 a10);
__int64 GetBaseAddr() {
HMODULE hMode = GetModuleHandle(nullptr);
return (__int64)hMode;
}
void* shellcode = 0;
BYTE HookCode[] = {
0x48,0xB8,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, //mov rax,xxx
0xFF,0xE0 //jmp rax
};
BYTE OriginCode[0x50];
size_t HookLen = 12;
__int64 times = 100;
void printset() {
for (auto k : s) {
printf("(%d,%d)\n", k.first, k.second);
}
}
__int64 HackShellcode(int a1, int a2, int a3, int a4, int a5, __int64 a6, __int64 a7, __int64 a8, __int64 a9, __int64 a10) {
static int flag = 1;
memcpy(shellcode, OriginCode,HookLen); //unhook
//
int x = a1, y = a2;
int v13, v14;
if (x == -950 && y == 50) {
x = 50;
y = 50;
v13=x;
v14=x*(y+1);
//printf("Origin: Stack[0]=%d Stack[1]=%d ",Stack[0],Stack[1]);
//printf("num=0x%x ",code[RIP]);
a3=0x524895^0x414345;
a4=(unsigned int)((a3 ^ (y + v13)) % 256
+ (((a3 ^ (v13 * y)) % 256 + (((a3 ^ (y + v14)) % 256) << 8)) << 8));
}
__int64 ret=(*(Func)shellcode)(x, y, a3, a4, a5, a6, a7, a8, a9, a10);
times--;
if (times>0) {
printf("call shellcode(%d,%d,%d,%d,%x) retval=%d\n",x, y, a3, a4, a5, ret);
}
int presize = s.size();
s.insert({ x,y });
if (s.size() == presize) {
if (flag) {
printset();
flag = 0;
}
}
memcpy(shellcode, HookCode, HookLen); //rehook
return ret;
}


void HookShellcode() {
__int64 base = GetBaseAddr();
__int64 Ptr = base + 0x8308;

shellcode = (void*)(*(__int64*)Ptr);
while (!shellcode) {
shellcode = (void*)(*(__int64*)Ptr);
printf("Find shellcode Fail\n");
Sleep(200);
}
printf("shellcode addr=%p\n", shellcode);
memcpy(OriginCode, shellcode,HookLen); //saved
Func FuncPtr = HackShellcode;
*(__int64*)(HookCode + 2) = (__int64)FuncPtr; //construct
memcpy(shellcode, HookCode, HookLen); //hook

}


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);
HookShellcode();
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}

注入之后,在指定的位置输出了黄色方块

说明修复思路是没有问题的,接下来可以用虚拟机流程把错误的坐标和对应的该操作数 dump 出来,hook 的时候进行替换。

后面用截图工具比了一下,发现它们水平距离都一样的,所以可以用已有的正确坐标参考,从上到下坐标分别为 50,110,170,230... 就是每隔一个查了 60 的距离,水平距离也同样是 60,那么最靠左的正确的方块是 (410,290),肉眼分析下来,最左边的坐标是 50,y 坐标因为对对齐的也是 50,所以第一个色块是完美还原的。

那么最左边 6 个就是

1
2
3
4
5
6
50,50
50,110
50,170
50,230
50,290
50,350

对角线延伸出去三个就是

1
2
3
110,110
170,170
230,230

最后两个补齐 FLAG是

1
2
110,230
170,230

最后的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
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
// dllmain.cpp : 定义 DLL 应用程序的入口点。
#include "pch.h"
#include <Windows.h>
#include <set>
#include <stdio.h>
#include <math.h>
std::set<std::pair<int, int>>s;

typedef __int64 (*Func)(int a1, int a2, int a3, int a4, int a5, __int64 a6, __int64 a7, __int64 a8, __int64 a9, __int64 a10);
__int64 GetBaseAddr() {
HMODULE hMode = GetModuleHandle(nullptr);
return (__int64)hMode;
}
void* shellcode = 0;
BYTE HookCode[] = {
0x48,0xB8,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, //mov rax,xxx
0xFF,0xE0 //jmp rax
};
BYTE OriginCode[0x50];
size_t HookLen = 12;
__int64 times = 100;
void printset() {
for (auto k : s) {
printf("(%d,%d)\n", k.first, k.second);
}
}
int val[] = {5392533,5934636,9984722,11102301,7888111,9846439,4608533,8744398,7703662,10004148,8744654};
std::pair<int, int>WrongPos[] = {
{-950,50},
{50,-390},
{-950,170},
{50,230},
{-950,-210},
{50,350},
{-890,-270},
{170,-270},
{-770,230},
{110,-390},
{-830,170},
};
std::pair<int, int>TargetPos[] = {
{50,50},
{50,110},
{50,170},
{50,230},
{50,290},
{50,350},
{110,110},
{170,170},
{230,230},
{110,230},
{170,230},
};
int CountOfWrongPos=11;
__int64 HackShellcode(int a1, int a2, int a3, int a4, int a5, __int64 a6, __int64 a7, __int64 a8, __int64 a9, __int64 a10) {
static int flag = 1;
memcpy(shellcode, OriginCode,HookLen); //unhook
//
int x = a1, y = a2;
int v13, v14;
for (int i = 0; i < CountOfWrongPos; i++) {
if (std::pair<int,int>{ x,y } == WrongPos[i]) {
x = TargetPos[i].first;
y = TargetPos[i].second;
v13=x;
v14=x*(y+1);
a3=val[i]^0x414345;
a4=(unsigned int)((a3 ^ (y + v13)) % 256
+ (((a3 ^ (v13 * y)) % 256 + (((a3 ^ (y + v14)) % 256) << 8)) << 8));
printf("Hook Value Success from position (%d,%d) to (%d,%d)\n",a1,a2,x,y);
break;
}
}
__int64 ret=(*(Func)shellcode)(x, y, a3, a4, a5, a6, a7, a8, a9, a10);
times--;
if (times>0) {
printf("call shellcode(%d,%d,%d,%d,%x) retval=%d\n",x, y, a3, a4, a5, ret);
}
int presize = s.size();
s.insert({ x,y });
if (s.size() == presize) {
if (flag) {
printset();
flag = 0;
}
}
memcpy(shellcode, HookCode, HookLen); //rehook
return ret;
}


void HookShellcode() {
__int64 base = GetBaseAddr();
__int64 Ptr = base + 0x8308;

shellcode = (void*)(*(__int64*)Ptr);
while (!shellcode) {
shellcode = (void*)(*(__int64*)Ptr);
printf("Find shellcode Fail\n");
Sleep(200);
}
printf("shellcode addr=%p\n", shellcode);
memcpy(OriginCode, shellcode,HookLen); //saved
Func FuncPtr = HackShellcode;
*(__int64*)(HookCode + 2) = (__int64)FuncPtr; //construct
memcpy(shellcode, HookCode, HookLen); //hook

}


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);
HookShellcode();
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}

最终结果也是完美实现了


To be continue For Final in 2022.