MiniVNCTF VSnake

这次来给招新赛出题,想着得有点难度梯度以及区分度,基于其他已经出题的师傅的题目难度,出了这么一道游戏题,难度设为困难。

主要考验选手的综合逆向分析能力,怎么在一个复杂的游戏业务逻辑中分析找到关键函数。由于且只打算考选手对排列爆破的思想,所以三个解密算法均采用标准代码,xxtea只修改了Delta,RC4只修改了Sbox,而AES是完全标准的算法。

分析

游戏业务逻辑大概如下:

alt text

最开始初始化了各种游戏全局数值,比如起始方向、坐标、以及三种食物的数据,这边是随机生成三种食物的坐标然后赋值给三种食物的结构体中。

alt text

根据代码分析,可以知道食物的结构体大概如下:

1
2
3
4
5
6
struct Food
{
int x;
int y;
int ICON;
};

alt text

我们可以在LocalType界面,添加一个结构体,然后将这个全局的食物变量类型进行修改。

alt text

alt text

最后伪代码就会变得非常清晰。

alt text

alt text

接下来分析游戏主循环,第一个Draw是单纯的绘制地图和Snake,不做分析。

alt text

第二个函数是获取键盘输入,wasd来赋值方向。

alt text

第三个函数中是核心逻辑,这边通过刚刚获取的方向,来对全局的坐标进行修改,如果当前坐标超出地图范围则游戏结束,若碰到身体也游戏结束。

alt text

这下面是最核心的代码,判断当前蛇头位置是否在其中一个食物的坐标上,则判定吃到食物,进行一次Flag解密,如果返回值不为0则游戏结束,成功,否则重新生成被吃掉的食物。

alt text

DecFlag如下,通过吃到的食物编号去对全局的一个数据进行复杂的运算修改,后续进行MD5得到密钥,用于XXTea和AES的解密密钥。

alt text

alt text

MD5算法可以根据开头的魔数认出。

alt text

RC4使用了固定Sbox和多异或了0x12。

alt text

XXTea是标准解密算法,仅修改了Delta数值。

alt text

AES特征十分好认,并且是完全标准的。

alt text

最后解密判断是否为VNCT开头的字符串,判定解密成功。

alt text

由于该函数是根据当前吃的食物编号对全局的那个数据进行修改,不同编号的计算方式不同,所以吃的食物编号顺序不同会导致计算的结果不同,也就导致后续进行MD5生成的密钥也不一样,会导致解密失败。

alt text

解密思路

复制上述那个根据食物编号计算的函数,实现RC4魔改解密、XXTea解密、AES标准解密代码,其实都可以直接从伪代码中复制出来使用,AES直接引用标准128解密即可。

然后爆破123三种食物的排列顺序,题目描述说明一共需要吃十个食物,也就是解密十次,所以是在限定范围的3种数值的排列组合爆破。

由于是只有三种食物,这边提供的一个简单所有排列的思路是数组实现三进制。

三进制数组爆破

限定一个数组10长度,初始全都是1,代表当前吃的食物全都是第一种食物。

alt text

然后每轮循环都对数组第一个数+1,若大于3了,则逐个向后进位。

以下是从最开始到第九轮循环的排列情况,可以参考。

alt text

实际就是模仿三进制,来对1-3进行全排列爆破,这样循环到最后全都是3为止就会包含所有的排列情况,然后每轮根据该数组情况进行十次解密,尝试判断解密结果是否为VNCTF开头。

EXP

alt text

AES128引用库:AES-128

MD5引用库:MD5

exp.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
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
#include <iostream>
#include <Windows.h>
#include "AES.h"
#include "md5.h"

__int64 __fastcall sub_140002600(DWORD* a1, unsigned int a2)
{
__int64 result; // rax
int v3; // [rsp+0h] [rbp-18h]
int v4; // [rsp+4h] [rbp-14h]

result = a2;
switch (a2)
{
case 1u:
v3 = 0x17FBDD80;
v4 = 0x97;
LABEL_8:
a1[1] += v3;
++*a1;
a1[2] = v4 + ((a1[2] >> 2) ^ (32 * a1[2]));
result = (unsigned int)(a1[2] * v3) ^ a1[2];
a1[2] = result;
return result;
case 2u:
v3 = 0x97223FFC;
v4 = 0x1B;
goto LABEL_8;
case 3u:
v3 = 0xBE95BDCE;
v4 = 0xC2;
goto LABEL_8;
}
return result;
}

unsigned char byte_140007280[256] = {
0x3F, 0x1E, 0xFC, 0x3D, 0x68, 0x51, 0xF0, 0x20, 0x92, 0x02, 0x9D, 0xAC, 0x54, 0x6E, 0xFB, 0x42,
0x29, 0xE8, 0x23, 0x2A, 0xD5, 0xA2, 0x3A, 0xC0, 0xD4, 0xBA, 0xB5, 0x84, 0xA6, 0x74, 0x5C, 0x08,
0xF2, 0x22, 0xA7, 0x41, 0x8B, 0xEB, 0x32, 0x0D, 0x8A, 0x89, 0x46, 0x70, 0x5E, 0xA0, 0xEF, 0x67,
0x49, 0xD3, 0x18, 0x76, 0xBD, 0xD0, 0x8D, 0x2F, 0xB4, 0x55, 0xC5, 0xC8, 0x36, 0x37, 0x66, 0x04,
0xD8, 0x01, 0x5A, 0x2E, 0xED, 0x91, 0xFF, 0x15, 0x6C, 0x64, 0x5D, 0x24, 0x72, 0x1A, 0x75, 0x30,
0x56, 0xBF, 0xB1, 0x73, 0xC7, 0x95, 0x05, 0xB6, 0x52, 0x31, 0xB3, 0x10, 0x2B, 0x6F, 0x43, 0xBB,
0x62, 0x7C, 0x7B, 0xA3, 0xBE, 0xD9, 0xBC, 0xDC, 0xC9, 0x8F, 0xA4, 0xE3, 0xE7, 0x17, 0x5F, 0xE9,
0xCA, 0x6D, 0x4E, 0xAE, 0x83, 0x63, 0x82, 0x27, 0x4A, 0x21, 0x71, 0x2C, 0x57, 0x7D, 0xAF, 0x44,
0x85, 0xC1, 0x47, 0x4B, 0x48, 0xF4, 0xFD, 0x3C, 0xF1, 0x45, 0x1F, 0x5B, 0xB8, 0xA1, 0xC4, 0x79,
0x53, 0x09, 0xEA, 0xEE, 0x0C, 0xD6, 0x61, 0xC6, 0xAA, 0xB0, 0x69, 0x81, 0xB9, 0x7F, 0xEC, 0x94,
0xCE, 0xA9, 0x97, 0x3B, 0xDA, 0x8E, 0xE5, 0x86, 0x16, 0x11, 0xAD, 0xD1, 0xD7, 0x40, 0xB2, 0x65,
0xCB, 0xB7, 0x1C, 0x7A, 0xF6, 0x87, 0xCD, 0x4F, 0x9F, 0xAB, 0x4D, 0x0F, 0x6A, 0xA8, 0xDE, 0xC3,
0x39, 0x50, 0xFA, 0x35, 0x33, 0x90, 0xDF, 0xF8, 0x25, 0x8C, 0x9C, 0xE0, 0xF7, 0x07, 0xE2, 0x99,
0x77, 0x00, 0x26, 0x6B, 0x0B, 0x3E, 0x1D, 0xE1, 0x58, 0x38, 0xC2, 0x78, 0x0E, 0x59, 0x93, 0x1B,
0x88, 0xDD, 0x9B, 0xD2, 0x19, 0x7E, 0xF9, 0xDB, 0xFE, 0x60, 0x13, 0x4C, 0xCC, 0xF3, 0xA5, 0x14,
0x34, 0x96, 0x80, 0xE6, 0x9A, 0xF5, 0x9E, 0xE4, 0x2D, 0x03, 0x12, 0x0A, 0xCF, 0x98, 0x28, 0x06
};

BYTE* __fastcall Swap(BYTE* a1, BYTE* a2)
{
BYTE v3; // [rsp+0h] [rbp-18h]

v3 = *a1;
*a1 = *a2;
*a2 = v3;
return a2;
}

__int64 __fastcall RC4(uint8_t* a1, unsigned int a2)
{
__int64 result; // rax
unsigned __int8 v3; // [rsp+20h] [rbp-138h]
unsigned __int8 v4; // [rsp+21h] [rbp-137h]
unsigned int i; // [rsp+24h] [rbp-134h]
BYTE v6[256]; // [rsp+30h] [rbp-128h] BYREF

v3 = 0;
v4 = 0;
memcpy(v6, &byte_140007280, 256);
for (i = 0; ; ++i)
{
result = a2;
if (i >= a2)
break;
v4 = ((unsigned __int8)v6[++v3] + v4) % 256;
Swap(&v6[v3], &v6[v4]);
a1[i] ^= v6[(unsigned __int8)(v6[v4] + v6[v3])] ^ 0x12;
}
return result;
}

__int64 __fastcall XXTea(int* a1, int a2, int* a3)
{
int v3; // eax
__int64 result; // rax
int i; // [rsp+0h] [rbp-28h]
unsigned int v6; // [rsp+4h] [rbp-24h]
unsigned int v7; // [rsp+8h] [rbp-20h]
unsigned int v8; // [rsp+8h] [rbp-20h]
unsigned int v9; // [rsp+Ch] [rbp-1Ch]
int v10; // [rsp+10h] [rbp-18h]
int v11; // [rsp+18h] [rbp-10h]
int v12; // [rsp+1Ch] [rbp-Ch]
int v13; // [rsp+38h] [rbp+10h]

v13 = -a2;
v10 = 52 / -a2 + 6;
v9 = 0x7CB893DC * v10;
v6 = *a1;
do
{
v11 = (v9 >> 2) & 3;
for (i = v13 - 1; i; --i)
{
v7 = a1[i - 1];
v3 = a1[i] - (((v7 ^ a3[v11 ^ i & 3]) + (v6 ^ v9)) ^ (((16 * v7) ^ (v6 >> 3)) + ((4 * v6) ^ (v7 >> 5))));
a1[i] = v3;
v6 = v3;
}
v8 = a1[v13 - 1];
v12 = *a1 - (((v8 ^ a3[v11]) + (v6 ^ v9)) ^ (((16 * v8) ^ (v6 >> 3)) + ((4 * v6) ^ (v8 >> 5))));
*a1 = v12;
v6 = v12;
v9 -= 0x7CB893DC;
result = (unsigned int)--v10;
} while (v10);
return result;
}

bool Check(int* EatSequence)
{
uint8_t Enc[]
{
0xFE, 0x67, 0x90, 0x7F, 0x30, 0x3F, 0xAC, 0x1D, 0x4C, 0x95, 0x6C, 0x8F, 0x30, 0xA7, 0x5B, 0x17,
0x1A, 0x96, 0x91, 0x2F, 0x2F, 0xDD, 0x30, 0x6F, 0xB3, 0xED, 0x10, 0x84, 0x86, 0x39, 0x82, 0xFD
};

DWORD Data_[10]{};

for (int i = 0; i < 10; i++)
{
// 生成Key
uint8_t Key[16]{};
sub_140002600(Data_, EatSequence[i]);
md5((uint8_t*)(&Data_), 12, Key);

// 三种解密
if (EatSequence[i] == 1)
{
RC4((uint8_t*)Enc, 32);
}
if (EatSequence[i] == 2)
{
XXTea((int*)Enc, -8, (int*)Key);
}
if (EatSequence[i] == 3)
{
aesDecrypt(Key, 16, (uint8_t*)Enc, (uint8_t*)Enc, 32);
}
}

// 判断是否解密成功
if (memcmp(Enc, "VNCTF", 5) == 0)
{
printf("Found: %.32s\n", Enc);
printf("Eat Sequence: ");
for (int i = 0; i < 16; i++)
{
printf("%d,", EatSequence[i]);
}
printf("\n");
return true;
}
return false;
}

int main()
{
// 初始化全都为1
int EatSequence[16]{};

for (int i = 0; i < 16; i++)
{
EatSequence[i] = 1;
}

// 总次数是3^10
uint64_t total = pow(3, 10);

for (uint64_t i = 0; i < total; i++)
{
if (Check(EatSequence))
{
printf("Finished\n");
break;
}

// 三进制数组加法
int pos = 0;
while (pos < 10)
{
EatSequence[pos]++;
if (EatSequence[pos] > 3)
{
EatSequence[pos] = 1;
pos++;
}
else
{
break;
}
}
}
return 0;
}