Straw's B1og.

浅谈ctf中的游戏逆向

字数统计: 4.7k阅读时长: 25 min
2024/11/11

浅谈ctf中的游戏逆向

前言

​ 最近打了好几场大比赛,感觉每场都会有奇怪的游戏逆向出来,有c++写的,有python写的,有c#写的,藏flag的方式也是多种多样啊,感觉挺有意思的,来个总结也当复现赛题了。想把此篇当为游戏逆向的总章。

奶龙

2024强网拟态-challenge

​ 一个吃豆人的游戏,github上找到了源码NJU-TJL/PacManX: 基于C++控制台(Windows平台)的一个吃豆人小游戏。很精妙的设计,去了符号表,增加整个游戏逆向的难度,该题不是正常的通关游戏给flag的那种,将关键点藏到了退出函数那,也是我玩着玩着发现的。

1

直接对exit函数进行索引找关键函数

2

刚好对应的是读入esc键然后执行sub_140014EB5();然后退出

4

这里面有个明显的字符串混淆,防止被F12直接索引到

5

整体重新分析一下就是

6

​ 很清晰了,在你的注册表里读flag,然后对.data文件进行解密成为一个ps1,执行后删除

​ 那我先把flag写注册表里,然后创个game.ps1文件,设置权限不可执行不可读,只能写入,这样运行在删除前就会报错,留下一个game.tmp

7

用powerdecode去混淆

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
Layer 4 - Plainscript


function enenenenene {
param(
$plaintextBytes,
$keyBytes
)
# Initialize S and KSA
$S = 0..255
$j = 0
for ($i = 0; $i -lt 256; $i++) {
$j = ($j + $S[$i] + $keyBytes[$i % $keyBytes.Length]) % 256
$temp = $S[$i]
$S[$i] = $S[$j]
$S[$j] = $temp
}

# PRGA and encryption
$i = 0
$j = 0
$ciphertextBytes = @()
for ($k = 0; $k -lt $plaintextBytes.Length; $k++) {
$i = ($i + 1) % 256
$j = ($j + $S[$i]) % 256
$temp = $S[$i]
$S[$i] = $S[$j]
$S[$j] = $temp
$t = ($S[$i] + $S[$j]) % 256
$ciphertextBytes += ($plaintextBytes[$k] -bxor $S[$t])
}

# Return ciphertext as a string
return $ciphertextBytes
}
function enenenenene1 {
param(
$inputbyte
)
$key = @(0x70, 0x6f, 0x77, 0x65, 0x72)
$encryptedText = @();
for ($k = 0; $k -lt $inputbyte.Length; $k++) {
$encryptedText = enenenenene -plaintextBytes $inputbyte -keyBytes $key;
$key = enenenenene -plaintextBytes $key -keyBytes $encryptedText;
}
return $encryptedText + $key;
}
function enenenenene2 {
param(
$inputbyte
)
$key = @(0x70, 0x30, 0x77, 0x65, 0x72)
for ($k = 0; $k -lt $inputbyte.Length; $k++) {
$inputbyte[$k] = $inputbyte[$k] + $key[$k % $key.Length]
}
return $inputbyte;
}
function enenenenene3 {
param(
$inputbyte
)
$key = @(0x70, 0x30, 0x77, 0x33, 0x72)
for ($k = 0; $k -lt $inputbyte.Length; $k++) {
$inputbyte[$k] = $inputbyte[$k] * $key[$k % $key.Length]
}
return $inputbyte;
}
$registryPath = 'HKCU:\Software\PacManX'

$valueName = 'MYFLAG'
$value = Get-ItemPropertyValue $registryPath $valueName
$plaintext = @($value) | ForEach-Object {
$input = $_
$plaintext = @()
for ($i = 0; $i -lt $input.Length; $i++) {
$plaintext += [int][char]$input[$i]
}
$plaintext
}
if ($plaintext.Length -ne 36) {
Set-Content -Path "log.txt" -Value "ERROR"
exit
}
$encrypted1Text = enENenenene2 -inputbyte (enenenENene2 -inputbyte (enenenenene3 -inputbyte (Enenenenene2 -inputbyte (enenenenene2 -inputbyte (enenenenene2 -inputbyte (enenenenene1 -input $plaintext))))))
$result = @(38304, 8928, 43673, 25957 , 67260, 47152, 16656, 62832 , 19480 , 66690, 40432, 15072 , 63427 , 28558 , 54606, 47712 , 18240 , 68187 , 18256, 63954 , 48384, 14784, 60690 , 21724 , 53238 , 64176 , 9888 , 54859 , 23050 , 58368 , 46032 , 15648 , 64260 , 17899 , 52782 , 51968 , 12336 , 69377 , 27844 , 43206 , 63616)
for ($k = 0; $k -lt $result.Length; $k++) {
if ($encrypted1Text[$k] -ne $result[$k]) {
Set-Content -Path "log.txt" -Value "ERROR"
exit

}

}
Set-Content -Path "log.txt" -Value "RIGHT"

rc4+简单乘加

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
pw=[38304, 8928, 43673, 25957 , 67260, 47152, 16656, 62832 , 19480 , 66690, 40432, 15072 , 63427 , 28558 , 54606, 47712 , 18240 , 68187 , 18256, 63954 , 48384, 14784, 60690 , 21724 , 53238 , 64176 , 9888 , 54859 , 23050 , 58368 , 46032 , 15648 , 64260 , 17899 , 52782 , 51968 , 12336 , 69377 , 27844 , 43206 , 63616]
key=[0x70, 0x6f, 0x77, 0x65, 0x72]
key1=[0x70, 0x30, 0x77, 0x65, 0x72]
key2=[0x70, 0x30, 0x77, 0x33, 0x72]
for i in range(len(pw)):
print(hex(int((pw[i]-2*key1[i%5])/key2[i%5])-3*key1[i%5]),end=',')


pw2=[0x4,0x28,0x8,0xca,0xf6,0x53,0xc9,0xa9,0x4b,0xf1,0x17,0xa8,0xae,0xfd,0x87,0x58,0xea,0xd6,0x33,0xd9,0x5e,0xa2,0x97,0x77,0x7b,0xeb,0x3c,0x66,0x91,0xa8,0x49,0xb4,0xb5,0x2c,0x77,0x7e]
key3=[0x6f,0xe0,0xef,0x23,0xe6]

def rc4(key, data):
S = list(range(256))
j = 0
key_length = len(key)

for i in range(256):
j = (j + S[i] + key[i % key_length]) % 256
S[i], S[j] = S[j], S[i]

i = j = 0
output = []

for byte in data:
i = (i + 1) % 256
j = (j + S[i]) % 256
S[i], S[j] = S[j], S[i]
t = (S[i] + S[j]) % 256
output.append(byte ^ S[t])

return output

print(len(pw2))
for _ in range(1):
key3=rc4(pw2,key3)
pw2=rc4(key3,pw2)
for i in key3:
print(chr(i),end='')
print()
for i in pw2:
print(chr(i),end='')
#73412036-7d8c-437b-9026-0c2ca1b7f79d

2024强网杯-斯内克

一道用c语言实现的简单的贪吃蛇,没找到源码,但是很简单,感觉是出题人自己写的。其实到头来是个smc。

1.1

简单分析完主函数流程,我们主要看 sub_1400111FE() 和sub_14001112C();

1.2

最上面会判断你的键盘输入有没有变,来决定是否进行下面操作,然后下面会根据你键盘不同的输入,进行smc段不同的操作。

1.3

然后看另一个函数的另外一段

这边会对smc的那段代码进行校验,每次吃到果子对比md5的值,当还原成正确的代码段后,作为一个函数对最开始你的输入进行操作。但是md5不可逆,作者也给出了要我们求最优路径,操作数最少,果子的位置出现也是个伪随机,所以就是一道算法题。算法不会一点,cp一下carbo的代码,虽然他也是chat的。[捂嘴笑]

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
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
#include "bits/stdc++.h"
#include "md5.h"
#include "defs.h"
using namespace std;

enum Direction {
UP, // W
DOWN, // S
LEFT, // A
RIGHT // D
};

struct State {
int x, y; // 蛇头位置
Direction dir; // 当前方向
string path; // 路径
int fruitIndex; // 当前要吃的果子索引
int steps; // 步数

State(int x, int y, Direction d, string p, int f, int s)
: x(x), y(y), dir(d), path(p), fruitIndex(f), steps(s) {}

bool operator<(const State& other) const {
if (x != other.x) return x < other.x;
if (y != other.y) return y < other.y;
if (dir != other.dir) return dir < other.dir;
return fruitIndex < other.fruitIndex;
}
};

class SnakePathFinder {
private:
const int SIZE = 20;
vector<pair<int, int>> fruits;

bool isValid(int x, int y) {
return x >= 0 && x < SIZE && y >= 0 && y < SIZE;
}

Direction getOppositeDirection(Direction dir) {
switch(dir) {
case UP: return DOWN;
case DOWN: return UP;
case LEFT: return RIGHT;
case RIGHT: return LEFT;
}
return UP;
}

char getDirectionChar(Direction dir) {
switch(dir) {
case UP: return 'W';
case DOWN: return 'S';
case LEFT: return 'A';
case RIGHT: return 'D';
}
return 'W';
}

pair<int, int> moveInDirection(int x, int y, Direction dir) {
switch(dir) {
case UP: return {x-1, y};
case DOWN: return {x+1, y};
case LEFT: return {x, y-1};
case RIGHT: return {x, y+1};
}
return {x, y};
}

// 优化后的启发式估计函数
int estimateRemainingSteps(const State& state) {
if (state.fruitIndex >= fruits.size()) return 0;

int totalEstimate = 0;
int currentX = state.x;
int currentY = state.y;

// 计算到所有剩余果子的最小距离之和
for (int i = state.fruitIndex; i < fruits.size(); i++) {
int dx = abs(currentX - fruits[i].first);
int dy = abs(currentY - fruits[i].second);

// 考虑转向的代价
if (i == state.fruitIndex) {
bool needHorizontalMove = (currentY != fruits[i].second);
bool needVerticalMove = (currentX != fruits[i].first);

if (needHorizontalMove && needVerticalMove) {
// 如果需要同时在水平和垂直方向移动,至少需要一次转向
if ((state.dir == LEFT || state.dir == RIGHT) && needVerticalMove) {
totalEstimate += 1; // 考虑转向的代价
}
if ((state.dir == UP || state.dir == DOWN) && needHorizontalMove) {
totalEstimate += 1; // 考虑转向的代价
}
}
}

totalEstimate += dx + dy; // 曼哈顿距离
currentX = fruits[i].first;
currentY = fruits[i].second;
}

return totalEstimate;
}

public:
SnakePathFinder(const vector<pair<int, int>>& fruitSequence) {
fruits = fruitSequence;
}

string findOptimalPath(int startX, int startY, Direction startDir) {
// 使用优先队列,按照估计的总步数排序
auto cmp = [](const State& a, const State& b) {
return (a.steps + a.path.length()) > (b.steps + b.path.length());
};
priority_queue<State, vector<State>, decltype(cmp)> pq(cmp);

set<State> visited;
State initial(startX, startY, startDir, "", 0, 0);
pq.push(initial);
visited.insert(initial);

while(!pq.empty()) {
State current = pq.top();
pq.pop();

// 如果所有果子都被吃掉
if(current.fruitIndex >= fruits.size()) {
return current.path;
}

// 如果到达当前目标果子
if(current.x == fruits[current.fruitIndex].first &&
current.y == fruits[current.fruitIndex].second) {
State nextState = current;
nextState.fruitIndex++;

if(visited.find(nextState) == visited.end()) {
pq.push(nextState);
visited.insert(nextState);
}
continue;
}

// 尝试所有可能的方向
for(int i = 0; i < 4; i++) {
Direction newDir = static_cast<Direction>(i);

// 不能直接调头
if(newDir == getOppositeDirection(current.dir)) {
continue;
}

auto [newX, newY] = moveInDirection(current.x, current.y, newDir);

if(!isValid(newX, newY)) {
continue;
}

string newPath = current.path;
if(newDir != current.dir) {
newPath += getDirectionChar(newDir);
}

State newState(newX, newY, newDir, newPath,
current.fruitIndex, current.steps + 1);

if(visited.find(newState) == visited.end()) {
pq.push(newState);
visited.insert(newState);
}
}
}

return "No path found";
}
};

unsigned char map_data_orig[1152] = {
0xBD, 0xBD, 0xBD, 0xBD, 0xBD, 0xBD, 0xBD, 0xBD, 0xBD, 0xBD, 0xBD, 0x38, 0x4C, 0xB0, 0x38, 0x6D,
0xEE, 0x3F, 0xC4, 0xB4, 0xB4, 0x09, 0x6A, 0xF0, 0x38, 0x2C, 0x79, 0xF6, 0x34, 0xE9, 0x89, 0x38,
0xAC, 0x7F, 0x35, 0xD4, 0xB4, 0xB4, 0x38, 0x6D, 0x77, 0xF6, 0xB6, 0x38, 0x6D, 0x78, 0xF6, 0xB6,
0x2B, 0x18, 0xB4, 0xB4, 0xB4, 0x3B, 0x81, 0x81, 0x81, 0x81, 0xEF, 0x4E, 0x38, 0x4C, 0x7D, 0xF6,
0x33, 0xD4, 0xB4, 0xB4, 0xB0, 0xE8, 0xF4, 0xB4, 0xB4, 0xB4, 0xB4, 0xB0, 0xE8, 0xF6, 0x2B, 0x27,
0xA3, 0x1D, 0x3B, 0xF4, 0xB4, 0xB4, 0xB4, 0x38, 0x4A, 0xC0, 0xB4, 0xB0, 0xF8, 0x04, 0x38, 0x89,
0xE3, 0xC3, 0xCA, 0x3B, 0xF4, 0xB4, 0xB4, 0xB4, 0x38, 0x4A, 0xC0, 0xC4, 0xB0, 0xF8, 0x04, 0x38,
0xB3, 0x67, 0xE3, 0x16, 0x3B, 0xF4, 0xB4, 0xB4, 0xB4, 0x38, 0x4A, 0xC0, 0xD4, 0xB0, 0xF8, 0x04,
0x38, 0xB6, 0xD3, 0xB6, 0xA9, 0x3B, 0xF4, 0xB4, 0xB4, 0xB4, 0x38, 0x4A, 0xC0, 0xE4, 0xB0, 0xF8,
0x04, 0x38, 0x89, 0xD8, 0xC7, 0x33, 0x3B, 0xF4, 0xB4, 0xB4, 0xB4, 0x38, 0x4A, 0xC0, 0xB4, 0x2B,
0xF4, 0xB4, 0xB4, 0xB4, 0x38, 0x4A, 0x50, 0xB4, 0x38, 0x4C, 0xED, 0xB5, 0xD4, 0xB4, 0xB4, 0x4C,
0xF4, 0xD4, 0x2C, 0xF8, 0x85, 0x37, 0x3B, 0xF4, 0xB4, 0xB4, 0xB4, 0x38, 0x4A, 0xC0, 0xC4, 0x2B,
0xF4, 0xB4, 0xB4, 0xB4, 0x38, 0x4A, 0x50, 0xC4, 0x38, 0x4C, 0xED, 0xB5, 0xD4, 0xB4, 0xB4, 0x4C,
0xF4, 0xD4, 0x2C, 0xF8, 0x85, 0x37, 0x3B, 0xF4, 0xB4, 0xB4, 0xB4, 0x38, 0x4A, 0xC0, 0xD4, 0x2B,
0xF4, 0xB4, 0xB4, 0xB4, 0x38, 0x4A, 0x50, 0xD4, 0x38, 0x4C, 0xED, 0xB5, 0xD4, 0xB4, 0xB4, 0x4C,
0xF4, 0xD4, 0x2C, 0xF8, 0x85, 0x37, 0x3B, 0xF4, 0xB4, 0xB4, 0xB4, 0x38, 0x4A, 0xC0, 0xE4, 0x2B,
0xF4, 0xB4, 0xB4, 0xB4, 0x38, 0x4A, 0x50, 0xE4, 0x38, 0x4C, 0xED, 0xB5, 0xD4, 0xB4, 0xB4, 0x4C,
0xF4, 0xD4, 0x2C, 0xF8, 0x85, 0x37, 0xB0, 0xEC, 0xFE, 0xB4, 0xB4, 0xB4, 0xB4, 0xB4, 0xB4, 0xB4,
0x6F, 0x14, 0x4C, 0xEC, 0xFE, 0xB4, 0xB4, 0xB4, 0x2F, 0xC0, 0x2C, 0xEC, 0xFE, 0xB4, 0xB4, 0xB4,
0xCC, 0x6C, 0xFE, 0xB4, 0xB4, 0xB4, 0xB6, 0x24, 0xCC, 0x72, 0xB4, 0xB4, 0xB4, 0x3B, 0xF4, 0xB4,
0xB4, 0xB4, 0x38, 0x4A, 0xC0, 0xB4, 0x2B, 0xF4, 0xB4, 0xB4, 0xB4, 0x38, 0x4A, 0x50, 0xC4, 0x4C,
0x79, 0x85, 0x37, 0xD0, 0xD2, 0xF4, 0x5B, 0xF4, 0xB4, 0xB4, 0xB4, 0x38, 0x4A, 0xE1, 0xC4, 0x4C,
0xF9, 0x05, 0x37, 0xD0, 0x62, 0x04, 0xE3, 0x60, 0x5B, 0xF4, 0xB4, 0xB4, 0xB4, 0x38, 0x4A, 0xE1,
0xC4, 0xE4, 0x79, 0x05, 0x37, 0x4C, 0xE9, 0xF4, 0xCC, 0xE2, 0xE4, 0x4C, 0xE1, 0x4C, 0xF9, 0xED,
0x38, 0xF8, 0x4C, 0xE8, 0xF4, 0xF8, 0xE4, 0xE0, 0xA8, 0x4C, 0xC1, 0xE3, 0x60, 0xE4, 0x79, 0x04,
0x37, 0x4C, 0xD0, 0x2B, 0xF4, 0xB4, 0xB4, 0xB4, 0x38, 0x4A, 0x50, 0xB4, 0x2C, 0xF8, 0x85, 0x37,
0x4C, 0xE8, 0xF6, 0x4C, 0x69, 0xF4, 0xE4, 0x40, 0x4C, 0xD0, 0x2C, 0xE8, 0xF4, 0x3B, 0xF4, 0xB4,
0xB4, 0xB4, 0x38, 0x4A, 0xC0, 0xC4, 0x2B, 0xF4, 0xB4, 0xB4, 0xB4, 0x38, 0x4A, 0x50, 0xB4, 0x4C,
0x79, 0x85, 0x37, 0xD0, 0xD2, 0xF4, 0x5B, 0xF4, 0xB4, 0xB4, 0xB4, 0x38, 0x4A, 0xE1, 0xB4, 0x4C,
0xF9, 0x05, 0x37, 0xD0, 0x62, 0x04, 0xE3, 0x60, 0x5B, 0xF4, 0xB4, 0xB4, 0xB4, 0x38, 0x4A, 0xE1,
0xB4, 0xE4, 0x79, 0x05, 0x37, 0x4C, 0xE9, 0xF4, 0xD0, 0x62, 0x64, 0xCC, 0xE2, 0xE4, 0x4C, 0xE1,
0x4C, 0xF9, 0xED, 0x38, 0xF8, 0x4C, 0xE8, 0xF4, 0xF8, 0xE4, 0xE0, 0xA8, 0x4C, 0xC1, 0xE3, 0x60,
0xE4, 0x79, 0x04, 0x37, 0x4C, 0xD0, 0x2B, 0xF4, 0xB4, 0xB4, 0xB4, 0x38, 0x4A, 0x50, 0xC4, 0x2C,
0xF8, 0x85, 0x37, 0x52, 0x54, 0x2F, 0x2F, 0x2F, 0xB0, 0xEC, 0x00, 0xB4, 0xB4, 0xB4, 0xB4, 0xB4,
0xB4, 0xB4, 0x6F, 0x14, 0x4C, 0xEC, 0x00, 0xB4, 0xB4, 0xB4, 0x2F, 0xC0, 0x2C, 0xEC, 0x00, 0xB4,
0xB4, 0xB4, 0xCC, 0x6C, 0x00, 0xB4, 0xB4, 0xB4, 0xB6, 0x24, 0xCC, 0x72, 0xB4, 0xB4, 0xB4, 0x3B,
0xF4, 0xB4, 0xB4, 0xB4, 0x38, 0x4A, 0xC0, 0xD4, 0x2B, 0xF4, 0xB4, 0xB4, 0xB4, 0x38, 0x4A, 0x50,
0xE4, 0x4C, 0x79, 0x85, 0x37, 0xD0, 0xD2, 0xF4, 0x5B, 0xF4, 0xB4, 0xB4, 0xB4, 0x38, 0x4A, 0xE1,
0xE4, 0x4C, 0xF9, 0x05, 0x37, 0xD0, 0x62, 0x04, 0xE3, 0x60, 0x5B, 0xF4, 0xB4, 0xB4, 0xB4, 0x38,
0x4A, 0xE1, 0xE4, 0xE4, 0x79, 0x05, 0x37, 0x4C, 0xE9, 0xF4, 0xCC, 0xE2, 0xE4, 0x4C, 0xE1, 0x4C,
0xF9, 0xED, 0x38, 0xF8, 0x4C, 0xE8, 0xF4, 0xF8, 0xE4, 0xE0, 0xA8, 0x4C, 0xC1, 0xE3, 0x60, 0xE4,
0x79, 0x04, 0x37, 0x4C, 0xD0, 0x2B, 0xF4, 0xB4, 0xB4, 0xB4, 0x38, 0x4A, 0x50, 0xD4, 0x2C, 0xF8,
0x85, 0x37, 0x4C, 0xE8, 0xF6, 0x4C, 0x69, 0xF4, 0xE4, 0x40, 0x4C, 0xD0, 0x2C, 0xE8, 0xF4, 0x3B,
0xF4, 0xB4, 0xB4, 0xB4, 0x38, 0x4A, 0xC0, 0xE4, 0x2B, 0xF4, 0xB4, 0xB4, 0xB4, 0x38, 0x4A, 0x50,
0xD4, 0x4C, 0x79, 0x85, 0x37, 0xD0, 0xD2, 0xF4, 0x5B, 0xF4, 0xB4, 0xB4, 0xB4, 0x38, 0x4A, 0xE1,
0xD4, 0x4C, 0xF9, 0x05, 0x37, 0xD0, 0x62, 0x04, 0xE3, 0x60, 0x5B, 0xF4, 0xB4, 0xB4, 0xB4, 0x38,
0x4A, 0xE1, 0xD4, 0xE4, 0x79, 0x05, 0x37, 0x4C, 0xE9, 0xF4, 0xD0, 0x62, 0x64, 0xCC, 0xE2, 0xE4,
0x4C, 0xE1, 0x4C, 0xF9, 0xED, 0x38, 0xF8, 0x4C, 0xE8, 0xF4, 0xF8, 0xE4, 0xE0, 0xA8, 0x4C, 0xC1,
0xE3, 0x60, 0xE4, 0x79, 0x04, 0x37, 0x4C, 0xD0, 0x2B, 0xF4, 0xB4, 0xB4, 0xB4, 0x38, 0x4A, 0x50,
0xE4, 0x2C, 0xF8, 0x85, 0x37, 0x52, 0x54, 0x2F, 0x2F, 0x2F, 0x3B, 0xF4, 0xB4, 0xB4, 0xB4, 0x38,
0x4A, 0xC0, 0xB4, 0x2B, 0xF4, 0xB4, 0xB4, 0xB4, 0x38, 0x4A, 0x50, 0xD4, 0x4C, 0x79, 0x85, 0x37,
0x4C, 0xF8, 0x04, 0x37, 0xE3, 0xD0, 0x2B, 0xF4, 0xB4, 0xB4, 0xB4, 0x38, 0x4A, 0x50, 0xB4, 0x2C,
0xF8, 0x85, 0x37, 0x3B, 0xF4, 0xB4, 0xB4, 0xB4, 0x38, 0x4A, 0xC0, 0xC4, 0x2B, 0xF4, 0xB4, 0xB4,
0xB4, 0x38, 0x4A, 0x50, 0xE4, 0x4C, 0x79, 0x85, 0x37, 0x4C, 0xF8, 0x04, 0x37, 0xE3, 0xD0, 0x2B,
0xF4, 0xB4, 0xB4, 0xB4, 0x38, 0x4A, 0x50, 0xC4, 0x2C, 0xF8, 0x85, 0x37, 0x3B, 0xF4, 0xB4, 0xB4,
0xB4, 0x38, 0x4A, 0xC0, 0xE4, 0x2B, 0xF4, 0xB4, 0xB4, 0xB4, 0x38, 0x4A, 0x50, 0xB4, 0x4C, 0x79,
0x85, 0x37, 0x4C, 0xF8, 0x04, 0x37, 0xE3, 0xD0, 0x2B, 0xF4, 0xB4, 0xB4, 0xB4, 0x38, 0x4A, 0x50,
0xE4, 0x2C, 0xF8, 0x85, 0x37, 0x3B, 0xF4, 0xB4, 0xB4, 0xB4, 0x38, 0x4A, 0xC0, 0xC4, 0x2B, 0xF4,
0xB4, 0xB4, 0xB4, 0x38, 0x4A, 0x50, 0xD4, 0x4C, 0x79, 0x85, 0x37, 0x4C, 0xF8, 0x04, 0x37, 0xE3,
0xD0, 0x2B, 0xF4, 0xB4, 0xB4, 0xB4, 0x38, 0x4A, 0x50, 0xD4, 0x2C, 0xF8, 0x85, 0x37, 0xA0, 0xEC,
0x42, 0xB4, 0xB4, 0xB4, 0x3D, 0xA0, 0xEC, 0x52, 0xB4, 0xB4, 0xB4, 0xBE, 0xA0, 0xEC, 0x62, 0xB4,
0xB4, 0xB4, 0x51, 0xA0, 0xEC, 0x6F, 0xB4, 0xB4, 0xB4, 0x3D, 0xA0, 0xEC, 0x7F, 0xB4, 0xB4, 0xB4,
0x5B, 0xA0, 0xEC, 0x12, 0xB4, 0xB4, 0xB4, 0x8D, 0xA0, 0xEC, 0x22, 0xB4, 0xB4, 0xB4, 0x65, 0xA0,
0xEC, 0x32, 0xB4, 0xB4, 0xB4, 0xA7, 0xA0, 0xEC, 0xBF, 0xB4, 0xB4, 0xB4, 0x4D, 0xA0, 0xEC, 0xCF,
0xB4, 0xB4, 0xB4, 0xAC, 0xA0, 0xEC, 0xDF, 0xB4, 0xB4, 0xB4, 0xF8, 0xA0, 0xEC, 0xEF, 0xB4, 0xB4,
0xB4, 0x06, 0xA0, 0xEC, 0xFF, 0xB4, 0xB4, 0xB4, 0xE9, 0xA0, 0xEC, 0x8F, 0xB4, 0xB4, 0xB4, 0x3B,
0xA0, 0xEC, 0x9F, 0xB4, 0xB4, 0xB4, 0xA3, 0xA0, 0xEC, 0xAF, 0xB4, 0xB4, 0xB4, 0x31, 0xB0, 0xEC,
0xF5, 0xC4, 0xB4, 0xB4, 0xB4, 0xB4, 0xB4, 0xB4, 0x6F, 0x14, 0x4C, 0xEC, 0xF5, 0xC4, 0xB4, 0xB4,
0x2F, 0xC0, 0x2C, 0xEC, 0xF5, 0xC4, 0xB4, 0xB4, 0xCC, 0x6C, 0xF5, 0xC4, 0xB4, 0xB4, 0xB5, 0x68,
0xE6, 0x38, 0xCA, 0xEC, 0xF5, 0xC4, 0xB4, 0xB4, 0x24, 0x1B, 0xF8, 0x04, 0x37, 0x38, 0xCA, 0x6D,
0xF5, 0xC4, 0xB4, 0xB4, 0x24, 0x1B, 0x7D, 0x85, 0x42, 0xB4, 0xB4, 0xB4, 0x63, 0xD0, 0xF7, 0xF4,
0xD3, 0xC0, 0x6F, 0xF4, 0x6F, 0x00, 0xBB, 0xC4, 0x38, 0x4C, 0x3F, 0xBD, 0xBD, 0xBD, 0xBD, 0xBD
};

uint8 *map_data;

unsigned char tmp_map[1152]={};

std::string charArrayToHex(uint8* input, size_t size) {
std::stringstream hexStream;
hexStream << std::hex << std::setfill('0');
for (size_t i = 0; i < size; ++i) {
hexStream << std::setw(2) << static_cast<int>(static_cast<unsigned char>(input[i]));
}
return hexStream.str();
}


int main() {
int n = 1152;

map_data = (uint8 *) malloc(n);

int rounds = 0;
while (true) {
rounds++;

srand(0xDEADBEEF);
memcpy(map_data, map_data_orig, n);
// 测试用例
vector<pair<int, int>> fruits;

for (int i = 0; i < rounds; ++i) {
int y = rand() % 20;
int x = rand() % 20;
fruits.push_back({x, y});
// cout << x << " " << y << "\n";
}

SnakePathFinder pathFinder(fruits);

vector<uint8> v;

v.push_back(0x11);
v.push_back(0x12);

string path = pathFinder.findOptimalPath(10, 10, RIGHT);

for (char p : path) {
if(p == 'D')
{
for(int i=0;i<n;i++)
{
map_data[i] += 30;
}

}
else if(p == 'A'){
for(int i=0;i<n;i++)
{
tmp_map[i] = map_data[i];

}
for(int i=0;i<n;i++)
{
map_data[i] = tmp_map[(i+6)%n];
}
}
else if(p == 'S')
{
for(int i=0;i<n;i++){
map_data[i]=(map_data[i]>>5)|(map_data[i]<<3);
}
}
else if(p == 'W'){
for(int i=0;i<n;i++){
map_data[i]-=102;
}

}
}

MD5 md5 = MD5(map_data, n);
string hexd = md5.toStr();

cout << "round:" << rounds << " way: " << path << " md5: " << hexd << " ";

if (hexd == "9c06c08f882d7981e91d663364ce5e2e") {
cout << "found it" << "\n";
cout << charArrayToHex(map_data, n);
exit(0);
} else {
cout << "nop\n";
}

if (rounds > 1000) break;
}
return 0;
}

当吃到第十个果子的时候就出了,得到正确的代码片段,就是一个魔改xtea

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

int main() {
unsigned int enc[4] = {0x98d9a098,
0x711b97ba,
0x2f44819b,
0xdf37b855};
int n = 4;
unsigned int key[4] = {0x63313357,
0x2e336d30,
0x51203220,
0x38734257};
int i, j;
long sum = 0, delta = 0x9E3779B9;
enc[2] ^= enc[1];
enc[3] ^= enc[0];
enc[1] ^= enc[3];
enc[0] ^= enc[2];
for (i = 0; i < n; i += 2) {
sum = (32 * delta * (i / 2 + 1));
for (j = 0; j < 32; j++) {
enc[i + 1] -= (((enc[i] >> 5) ^ (16 * enc[i])) + enc[i]) ^ (key[((sum >> 11) & 3)] + sum);//容易魔改
sum -= delta;
enc[i] -= (((enc[i + 1] >> 5) ^ (16 * enc[i + 1])) + enc[i + 1]) ^ (key[(sum & 3)] + sum);//容易魔改
}
}
for (i = 0; i < n; i++) {
for (j = 0; j <= 3; j++) {
printf("%c", (enc[i] >> (j * 8)) & 0xFF);
}
}
return 0;
}

// flag{G0@d_Snake}

DLS2XLS

​ 一个罗总正在玩的一个很老的游戏,他给了我几个游戏数据要我还原数据,还给了我一个老外写的一个数据转换的exe,但是是用c#写的,直接逆c#了。

​ 程序要先读入一个cfg的格式文件,然后再还原dfs,逆向这个程序找到关键代码段。

2.1

可以发现dfs文件具有一个0x89字节的文件头,然后后面12字节都是一段总的数据,用来和cfg进行对比匹配。

2.2

然后根据cfg文件里的数据类型,分成一个m*n的表格,进行数据的转换。

由此可以推出游戏数据格式

2.3

数据都是以int小端序输入的

红色:每段数据的大小

蓝色:每段数据有几个类型

绿色:一共有多少这样的数据

于是可以模仿那个程序写个脚本

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
import os
import fnmatch

def find_cfg_file(directory, filename):
filename = filename.lower()
for root, dirs, files in os.walk(directory):
for file in files:
if fnmatch.fnmatch(file.lower(), f"{filename}.cfg"):
return os.path.join(root, file)

return None

def find_dfs_file(directory, filename):
filename = filename.lower()
for root, dirs, files in os.walk(directory):
for file in files:
if fnmatch.fnmatch(file.lower(), f"{filename}.dfs"):
return os.path.join(root, file)


def read_file_line(file_path,i):
try:
with open(file_path, 'r', encoding='utf-8') as file:
content = file.readlines()
return content[i]
except Exception as e:
return f"读取cfg文件时发生错误: {e}"


def read_dfs_file(file_path):
try:
with open(file_path, 'rb') as file:
content = file.read()
return content
except Exception as e:
return f"读取dfs文件时发生错误: {e}"


def an_cfg(text):
result = []

types = text.split(';')

for data_type in types:
if data_type.startswith('int32'):
result.append(4)
elif data_type.startswith('int16'):
result.append(2)
elif data_type.startswith('int8'):
result.append(1)
elif data_type.startswith('wstring'):
# 解析出 wstring 后的数字部分
length = int(data_type.split(':')[1])
result.append(length)
elif data_type.startswith('string'):
# 解析出 string 后的数字部分
length = int(data_type.split(':')[1])
result.append(length)

return result

filename = "Text_SkillTable"#文件名
current_directory = os.path.dirname(os.path.abspath(__file__))



cfgdirectory = current_directory+'\cfg'
cfg_file_path = find_cfg_file(cfgdirectory, filename)

if cfg_file_path:
print(f"找到cfg文件:{cfg_file_path}")
cfg_content = read_file_line(cfg_file_path,-1)#读入数据类型
cfg_top =read_file_line(cfg_file_path,-2)
print(cfg_content,end='')
type=an_cfg(cfg_content)
print(type)
print("size =",sum(type))
else:
print("未找到对应的.cfg 文件")
exit(0)

dfsdirectory = current_directory+'\dfs'
dfs_file_path = find_dfs_file(dfsdirectory, filename)

if dfs_file_path:
print(f"找到dfs文件:{dfs_file_path}")
dfs_content = read_dfs_file(dfs_file_path)
d_size=int.from_bytes(dfs_content[0x89:0x8c],"little")
col=int.from_bytes(dfs_content[0x8d:0x90],"little")
n=int.from_bytes(dfs_content[0x91:0x94],"little")
print("size =",d_size)
print("col =",col)
print("n =",n)
else:
print("未找到对应的.dfs 文件")
exit(0)


if(d_size!=sum(type)):
print("dfs文件与cfg文件不匹配")
if(len(type)==col):
print("尝试解密...")
print("一共{}列".format(col))
try:
print(cfg_top)
for o in range(n):
text=dfs_content[0x95+o*d_size:0x95+(o+1)*d_size]
# print(text)
tmp=0
for t in type:
if t<=8:
print(int.from_bytes(text[tmp:tmp+t],"little"),end=" ")
else:
t=d_size-tmp
for i in range(0, t-2, 2):
print(chr(int.from_bytes(text[tmp+i:tmp+i+2], "little")),end="")
print()
tmp=tmp+t
except:
exit(0)
exit(0)
else:
print("成功匹配开始解密...")

print(cfg_top)
for o in range(n):
text=dfs_content[0x95+o*d_size:0x95+(o+1)*d_size]
# print(text)
tmp=0
for t in type:
if t<=8:
print(int.from_bytes(text[tmp:tmp+t],"little"),end=" ")
else:
for i in range(0, t-2, 2):
print(chr(int.from_bytes(text[tmp+i:tmp+i+2], "little")),end="")
print("",end=" ")
tmp=tmp+t
print()

2.4

​ 用python简单还原了一下,感觉还是有许多数据匹配不上,做了个尝试解密的,感觉肯定是老罗给的数据结构文件有问题

总结

​ 游戏逆向也是逆向的一大类,还未总结的python系列与unity系列都是逆向的大头,成型的结构和开发系统,会让逆向更难啊。

CATALOG
  1. 1. 浅谈ctf中的游戏逆向
    1. 1.1. 前言
    2. 1.2. 2024强网拟态-challenge
    3. 1.3. 2024强网杯-斯内克
    4. 1.4. DLS2XLS
    5. 1.5. 总结