跳至主要内容

0xGame 2025 Week3 Reverse WP

引言

  • 我希望能够帮助像我一样的新手,如果大佬发现WP有问题可以评论,我会改进!

ezVBS

  • 我不得不说,要听劝。原题都告诉我了,不让我打开,我还就偏打开了,然后去网上搜VBS逆向,最后在赛博厨子那里逆向出了
1
I have already warned you not to run VBS directly, your computer is likely to be attacked because of this, just as your flag has been overwritten
  • 当时没怎么注意文字,就去看官方WP了,结果打开记事本看了看,和WP上的不一样啊,我还以为题出错了,回来又下了一遍,没运行,直接记事本编译,我恍然大悟,给我自己气笑了
  • 把execute换成wscript.echo,在根目录下运行cmd,输入cscript 1.vbs
  • 复制回1.vbs
  • 继续替换,直到出现正常的代码
  • 这里再运行一遍,还需要替换一遍再运行,才能得到
  • 然后交给赛博厨子

Q(≧▽≦)T

  • 先运行一下exe文件,发现是输入账户密码类型的,我先用exeinfope看了下有没有壳,发现没有,就拖进了dbg,然后搜索了下字符串
  • 中文乱码的话需要安装插件,x64dbg_tol
  • 很明显有两个加密后的字符串以及各种提醒文字
  • 如果运行了或者看到这个提醒就知道,用户名是四位数字的,我们输出1234试一试,在这之前先打个断点
  • 运行后发现这个不是判定用户名是否正确的,而是下面的代码,到达断点后用f7到下面(记得先f9运行一下)
  • 看到这里应该可以猜出是判断是否相等,看了之前的字符串,就知道03ac674216f3e15c761ee1a5e255f067953623c8b388b4459e13f978d7c846f4是我们输入的1234加密后的字符串
  • 我们去赛博厨子跑一下
  • 发现是SHA-256,我们就可以找一个在线平台破解一下之前的SHA-256
  • 就得到了用户名
  • 然后就去找关于密码的字符串继续看相应内容
  • 转到相应的反汇编之后,会发现只有上面的函数和密码加密相关
  • 然后就可以把exe丢到IDA分析一下了
  • 最后就会发现是这个函数在进行加密
  • 联系之前搜索到的字符串,去赛博厨子破解一下
  • 就得到了flag
  • 这道题我不看官方WP根本没头绪啊:(

Minesweeper

  • 打开ms_obfus.js,然后把expert模式下的mines改为0x1,打开hmtl
  • 剩余地雷为1就成功了,随便点一下
  • 用chatgpt也可以直接出

easyApp

  • 下个手机模拟器安装一下,就会发现是个输入+校验的程序
  • 可以用jadx进行分析
  • 接下来先看AndroidManifest.xml这个文件,它实际上是⼀个apk的配置文件,里面存储了apk的很多信息,包括程序入口点,我们要分析程序的执行逻辑,就得先找到程序的入口点
  • 这个就是入口了,双击可以跳转
  • onCreat相当于c语言的main函数,就是入口函数
  • 直接把代码丢给chatgpt :)
  • 直接解压apk,会发现目录和这个反编译目录一样
  • 然后把解压出来的bin文件继续拖到jadx
  • 用一下官方WP的脚本
1
2
3
4
5
6
7
8
9
10
from z3 import *  
a,b,c = Ints("a b c")
s = Solver()
s.add(a*3 + b - 27454419028250566601 == 0)
s.add(c*2 - 5*b + 20616666104378640363 == 0)
s.add(a + 4*c - 0x1dce62be9f0fa2f6c == 0)
if s.check() == sat:
result = s.model()
for var in (a,c):
print(hex(result[var].as_long())[2:],end="")

World’s_end_BlackBox

  • 先用exeinfope看看有没有壳,发现没壳,拖进ida发现也看不懂
  • 直接拖进dbg搜索一下字符串
  • 发现是输入两段字符串
  • 把两段Length Error处上面的代码打上断点
  • 然后双击到第一个length error处
  • 发现他是在对比我们第一个输入的字符串长度,f9运行到这里
  • 按空格将jz改为jmp
  • 这里其实可以看到应该是长度为12才能过(0xC=12)
  • 往下翻一翻会发现下面有个密码错误,在它上方打个断点
  • 那很明显这是在对比密码正确与否,这时候按f9就能看到正确密码了
  • 这里也需要改下汇编和上面一致
  • 这里就显示了正确密码,如果你想要知道这个密码哪里来的
  • 也很简单,看上方的每个函数,应该调用了两个函数,我们会发现第一个函数就是加密函数,采用的是xxtea
  • 接着往下走会发现还有个长度错误
  • 接着断点,改汇编,f9
  • 接着往下看会发现有个all perfect的字样
  • 在它上方打上断点
  • 你会发现这两个断点之前有很多内容,但是真正重要的只有
  • 这个函数,以及它上方的存储的东西
  • 这里f9运行到刚打的断点,在这之前现在cmd框里输入点东西,因为之前那一大段内容现在用不到,这一部分是加密的
  • 接下来就是看这个406D80的函数,看上面的汇编应该可以知道是传入了两个参数,一个是读取你的,一个是它自带的
  • 双击这个40B0A0可以在内存框中看他存储了什么东西
  • 两个值
  • 然后进入406D80后,可以知道那里面就是对比长度和内容的。也可以得知这两个值相当于指针,从开始到结束
  • 第一个是开始,第二个是结束,CTRL+g进行跳转
  • 就找到了这段加密字符
  • ok,返回到之前那两个断点之前一大段的汇编那里
  • 会发现401fcc是加密函数
  • 这里给出官方的WP脚本
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
flag = [
0xFC, 0xEA, 0x15, 0x2C, 0x86, 0x38, 0x3F, 0xF3, 0x92, 0xCE, 0xDA, 0x8E, 0x48, 0xD3, 0x7, 0x9F,
0xD9, 0x57, 0xB1, 0xEE, 0x41, 0x9A, 0x4D, 0xC5, 0x65, 0x6A, 0xFF, 0xC9, 0x5D, 0x34, 0xAD, 0xEA,
0xB1, 0x20, 0x4B, 0xDC, 0xBD, 0xD2, 0x35, 0x2, 0x84, 0x35, 0x71, 0xEC, 0xE0, 0x48, 0x8E, 0xEA,
0x7B, 0xAA, 0xCF
]

key = "XaleidscopiX"
s = []
t = []

# KSA Initialization
for i in range(256):
s.append(i)
t.append(ord(key[i % len(key)]))

# KSA Scrambling
j = 0
for i in range(256):
j = (j + s[i] + t[i]) % 256
s[i], s[j] = s[j], s[i]

k = []
i = 0
j = 0

# PRGA Keystream Generation
for r in range(len(flag)):
i = (i + 1) % 256
j = (j + s[i]) % 256
s[i], s[j] = s[j], s[i]
t_val = (s[i] + s[j]) % 256 # 使用 t_val 避免与全局列表 t 混淆
k.append(s[t_val])

# Decryption (Final XOR operation)
for idx in range(len(flag)):
# Decryption formula: (Ciphertext XOR Keystream XOR 7)
decrypted_byte = (flag[idx] ^ k[idx] ^ 7)
print(chr(decrypted_byte), end='')

Calamity_Fortune

  • 拖进ida分析一下
  • 同步分析在反汇编中的跳跃中打断点
  • 调试中,在寄存器窗口,按e将ZF改为1,来跳跃判断
  • 持续步进会发现别有洞天,MessageBoxA是个假函数,被调到了另一个函数中
  • 继续步入到正确的函数,f5看伪代码
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
__int64 sub_402B50()
{
char n79; // dl
char *Buffer_1; // rax
char n87; // dl
char *i; // rax
int v4; // eax
unsigned __int8 *v5; // r9
_BYTE *v6; // rdx
unsigned int n44; // esi
char n75; // dl
char *j; // rax
unsigned int *v10; // r10
unsigned __int8 *v11; // rdx
int n32; // ecx
unsigned int v13; // r8d
int v14; // eax
unsigned int v15; // eax
unsigned int v16; // r11d
int n1634492739; // ebp
unsigned int v18; // r9d
unsigned int *v19; // r8
int k; // ecx
unsigned int v21; // edx
unsigned int v22; // ecx
unsigned __int8 *v23; // r10
unsigned int *v24; // rdi
_DWORD *v25; // r8
_DWORD *v26; // rax
int m; // ecx
unsigned int v28; // edx
_BYTE *v29; // r9
unsigned int v30; // r8d
unsigned __int64 n; // rax
char n61; // dl
_BYTE *v33; // rbx
int v34; // eax
char v35; // r8
_BYTE *v36; // rdx
unsigned __int8 *v37; // rdx
unsigned __int8 *v38; // rbx
unsigned __int8 *v39; // rax
char *v40; // rcx
int ii; // eax
char *Kbi_so_Buuhu&_1; // rax
char jj; // dl
char n65; // dl
char *kk; // rax
_QWORD Calamity_Fortune[2]; // [rsp+20h] [rbp-218h] BYREF
char Wkbftb_Niwrs__hru_akf_[22]; // [rsp+30h] [rbp-208h] BYREF
__int16 v49; // [rsp+46h] [rbp-1F2h] BYREF
unsigned int v50; // [rsp+50h] [rbp-1E8h] BYREF
int v51; // [rsp+54h] [rbp-1E4h] BYREF
unsigned int v52; // [rsp+78h] [rbp-1C0h]
_BYTE v53[44]; // [rsp+80h] [rbp-1B8h] BYREF
int v54; // [rsp+ACh] [rbp-18Ch] BYREF
_BYTE v55[44]; // [rsp+B0h] [rbp-188h] BYREF
char v56; // [rsp+DCh] [rbp-15Ch] BYREF
_QWORD v57[7]; // [rsp+E0h] [rbp-158h] BYREF
int n925053188; // [rsp+118h] [rbp-120h]
_BYTE v59[59]; // [rsp+120h] [rbp-118h] BYREF
_BYTE v60[5]; // [rsp+15Bh] [rbp-DDh] BYREF
char Kbi_so_Buuhu&[64]; // [rsp+160h] [rbp-D8h] BYREF
char Buffer[85]; // [rsp+1A0h] [rbp-98h] BYREF
_BYTE Buffer_2[3]; // [rsp+1F5h] [rbp-43h] BYREF

n79 = 79;
v57[0] = 0x280D30732B077874LL;
v57[1] = 0x242D00103573060BLL;
v57[2] = 0x141C3406727D2F73LL;
v57[3] = 0xA71137676362833LL;
v57[4] = 0xE232B242F04742ALL;
v57[5] = 0x2F373F03033D7310LL;
v57[6] = 0x77067C3612772D7DLL;
qmemcpy(Calamity_Fortune, "Calamity_Fortune", sizeof(Calamity_Fortune));
qmemcpy(
Buffer,
"Ofqni`'jfcb'ns'sont'afu+'sob'tretbvrbis'bidu~wsnhi'tohrkc'eb'f'wnbdb'ha'dflb'ahu'~hry",
sizeof(Buffer));
Buffer_1 = Buffer;
n925053188 = 925053188;
while ( 1 )
{
*Buffer_1++ = n79 ^ 7;
if ( Buffer_2 == Buffer_1 )
break;
n79 = *Buffer_1;
}
puts(Buffer);
n87 = 87;
qmemcpy(Wkbftb_Niwrs__hru_akf_, "Wkbftb'Niwrs'~hru'akf`", sizeof(Wkbftb_Niwrs__hru_akf_));
for ( i = Wkbftb_Niwrs__hru_akf_; ; n87 = *i )
{
*i++ = n87 ^ 7;
if ( i == (char *)&v49 )
break;
}
puts(Wkbftb_Niwrs__hru_akf_);
scanf("%44s", v55);
v4 = v55[0];
v56 = 0;
if ( !v55[0] )
goto LABEL_11;
v5 = v55;
v6 = v55;
do
n44 = 1 - (unsigned int)v55 + (_DWORD)v6++;
while ( *v6 );
if ( n44 != 44 )
{
LABEL_11:
n75 = 75;
qmemcpy(Kbi_so_Buuhu&, "Kbi`so'Buuhu&", 13);
for ( j = Kbi_so_Buuhu&; ; n75 = *j )
{
*j++ = n75 ^ 7;
if ( &Kbi_so_Buuhu&[13] == j )
break;
}
puts(Kbi_so_Buuhu&);
exit(0);
}
v10 = &v50;
while ( 1 )
{
v11 = v5;
n32 = 0;
v13 = 0;
while ( 1 )
{
v14 = v4 << n32;
n32 += 8;
++v11;
v13 |= v14;
if ( n32 == 32 )
break;
v4 = *v11;
}
v5 += 4;
*v10++ = v13;
if ( &v56 == (char *)v5 )
break;
v4 = *v5;
}
v15 = v52;
v16 = -1640531527;
n1634492739 = 1634492739;
v18 = v50;
while ( 1 )
{
v19 = &v50;
for ( k = 0; ; ++k )
{
v21 = v19[1];
++v19;
v15 = v18
+ (((v15 ^ *((_DWORD *)Calamity_Fortune + (((unsigned __int8)(v16 >> 2) ^ (unsigned __int8)k) & 3)))
+ (v21 ^ v16))
^ (((4 * v21) ^ (v15 >> 5)) + ((16 * v15) ^ (v21 >> 3))));
*(v19 - 1) = v15;
if ( k == 9 )
break;
v18 = *v19;
}
v18 = v50;
v22 = v16;
v16 -= 1640531527;
v15 = v52 + ((((v15 >> 5) ^ (4 * v50)) + ((16 * v15) ^ (v50 >> 3))) ^ ((v50 ^ v22) + (n1634492739 ^ v15)));
v52 = v15;
if ( v16 == -865977613 )
break;
n1634492739 = *((_DWORD *)Calamity_Fortune + (((unsigned __int8)(v16 >> 2) ^ 0xA) & 3));
}
v23 = v53;
v24 = (unsigned int *)&v51;
v25 = v53;
while ( 1 )
{
v26 = v25;
for ( m = 0; m != 32; m += 8 )
{
v26 = (_DWORD *)((char *)v26 + 1);
v28 = v18 >> m;
*((_BYTE *)v26 - 1) = v28;
}
if ( &v54 == ++v25 )
break;
v18 = *v24++;
}
qmemcpy(Kbi_so_Buuhu&, "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/", sizeof(Kbi_so_Buuhu&));
v29 = v59;
do
{
v30 = *v23 << 16;
if ( n44 != 1 )
{
v30 |= v23[1] << 8;
if ( n44 > 2 )
v30 |= v23[2];
}
for ( n = 0; n != 4; ++n )
{
n61 = 61;
if ( n <= n44 )
n61 = Kbi_so_Buuhu&[(v30 >> (-6 * n + 18)) & 0x3F];
v29[n] = n61;
}
v23 += 3;
v29 += 4;
n44 -= 3;
}
while ( (unsigned __int8 *)((char *)&v54 + 1) != v23 );
v33 = v60;
v60[1] = 0;
srand(0x65u);
while ( 1 )
{
v34 = rand();
v35 = *v33;
v36 = &v59[v34 % (int)(60 - (unsigned int)v60 + (_DWORD)v33)];
*v33 = *v36;
*v36 = v35;
v37 = v33 - 1;
if ( v59 == v33 - 1 )
break;
--v33;
}
v38 = v33 + 59;
v39 = v37;
do
*v39++ ^= 0x45u;
while ( v38 != v39 );
v40 = (char *)v57 + 1;
for ( ii = 116; ; ii = *v40++ )
{
if ( *v37 != ii )
{
qmemcpy(Kbi_so_Buuhu&, "Dfkfjns~&'wkbftb'su~'f`fni", 26);
Kbi_so_Buuhu&_1 = Kbi_so_Buuhu&;
for ( jj = 68; ; jj = *Kbi_so_Buuhu&_1 )
{
*Kbi_so_Buuhu&_1++ = jj ^ 7;
if ( &Kbi_so_Buuhu&[26] == Kbi_so_Buuhu&_1 )
break;
}
puts(Kbi_so_Buuhu&);
exit(0);
}
if ( v38 == ++v37 )
break;
}
n65 = 65;
qmemcpy(Kbi_so_Buuhu&, "Ahusrib&@hhc'Krdl&", 18);
for ( kk = Kbi_so_Buuhu&; ; n65 = *kk )
{
*kk++ = n65 ^ 7;
if ( &Kbi_so_Buuhu&[18] == kk )
break;
}
puts(Kbi_so_Buuhu&);
return 1;
}
  • 会发现主题流程为
  • 读入 input(44 字节)
  • input → 按 4 字节打包成 11 个 uint32
  • 对这 11 个 uint32 做一轮 XXTEA 变体加密(10 轮)
  • 加密后的 44 字节 → Base64 编码 → 60 字符
  • 60 字符做一个固定 seed 的 伪随机洗牌srand(0x65) + rand()
  • 洗牌后每个字节再 XOR 0x45
  • 把结果与程序里的 60 字节常量比较:相等则 success,否则 fail
  • 脚本如下
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
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import base64
import struct
from typing import List, Tuple

# -----------------------------------------------------------
# 辅助函数:模拟 C 语言的 32 位无符号整数溢出
# 因为 Python 的整数是无限精度的,而 C 语言中 uint32 超过 0xFFFFFFFF 会截断
# -----------------------------------------------------------
def u32(x: int) -> int:
return x & 0xFFFFFFFF


# -----------------------------------------------------------
# 模拟 Windows C 运行时库 (MSVCRT) 的随机数生成器
# 题目通常是 Windows 下的 exe,其 rand() 实现是固定的线性同余生成器 (LCG)
# -----------------------------------------------------------
class MSVCRTRand:
def __init__(self, seed: int):
self.state = seed & 0xFFFFFFFF

def rand(self) -> int:
# 经典的 MSVCRT 参数:乘 214013,加 2531011
self.state = (self.state * 214013 + 2531011) & 0xFFFFFFFF
# 返回高位部分 (15 bits)
return (self.state >> 16) & 0x7FFF


# -----------------------------------------------------------
# 第一步:重建最终的密文数据
# 逆向时发现密文在内存中是以 QWORD (8字节) 数组形式存在的,需要拼起来
# -----------------------------------------------------------
def build_expected_60() -> bytes:
# 从 IDA Pro 等工具中提取的硬编码数据
v57_qwords = [
0x280D30732B077874,
0x242D00103573060B,
0x141C3406727D2F73,
0x0A71137676362833,
0x0E232B242F04742A,
0x2F373F03033D7310,
0x77067C3612772D7D,
]
n925 = 925053188 # 尾部的 4 字节数据

# struct.pack("<Q") 表示以小端序 (Little-Endian) 将整数打包成 bytes
v57_bytes = b"".join(struct.pack("<Q", q) for q in v57_qwords) # 共 7*8 = 56 字节
tail4 = struct.pack("<I", n925) # 4 字节

# 逻辑还原:
# 原始比较逻辑可能是:先比较第0个字节是否为 0x74,然后比较后面的一串
# 这里把它们拼接成完整的 60 字节目标密文
expected = bytes([0x74]) + (v57_bytes[1:] + tail4) # 60 bytes
assert len(expected) == 60
return expected


# -----------------------------------------------------------
# 第二步:逆向 Fisher–Yates 乱序算法
# 正向加密时:从后往前遍历,每次生成一个随机位置并交换
# 逆向解密时:必须重现完全一样的随机数序列,然后倒着交换回去
# -----------------------------------------------------------
def unshuffle_msvcrt(shuffled: bytes, seed: int = 0x65) -> bytes:
# 初始化随机数生成器,种子为 0x65 (十进制 101)
r = MSVCRTRand(seed)

# 1. 先把正向加密时产生的所有 swap 操作记录下来
# 注意:必须按正向的顺序调用 r.rand(),才能保证随机数序列一致
swaps: List[Tuple[int, int]] = []
for i in range(59, -1, -1):
j = r.rand() % (i + 1)
swaps.append((i, j))

# 2. 开始执行逆向交换
a = bytearray(shuffled)
# reversed(swaps) -> 后发生的交换先还原
for i, j in reversed(swaps):
a[i], a[j] = a[j], a[i] # 交换回原位
return bytes(a)


# -----------------------------------------------------------
# 第三步:XXTEA 变种算法解密
# 这是一个典型的“魔改”TEA算法,通常涉及 DELTA 常量和复杂的移位异或
# -----------------------------------------------------------
DELTA_SUB = 1640531527 # 0x61C88647 (常见的 TEA 魔数 0x9E3779B9 的变体或计算结果)
DELTA_ADD = 0x9E3779B9

# 逆向工程得到的初始 SUM 和结束 SUM
SUM_START = u32(-1640531527) # 0x9E3779B9
SUM_STOP = u32(-865977613) # 0xCC623AF3
KEY2_FIRST = u32(1634492739) # 初始的 Key2 变量值

# 核心混合函数1 (处理数组中间元素的)
def mx_inner(z: int, y: int, sum_: int, k: int, key4: List[int]) -> int:
"""
对应加密代码中的:
e = ((sum >> 2) & 3)
z = v[n-1], y = v[i+1]
mx = ((z>>5^y<<2) + (y>>3^z<<4)) ^ ((sum^y) + (key[(i&3)^e]^z))
"""
# 这里计算 key 的索引: ((sum>>2) ^ k) & 3
idx = (((sum_ >> 2) & 0xFF) ^ (k & 0xFF)) & 3
return u32(((z ^ key4[idx]) + (y ^ sum_)) ^ (((4 * y) ^ (z >> 5)) + ((16 * z) ^ (y >> 3))))

# 核心混合函数2 (处理数组最后一个元素的)
def mx_last(z: int, v0: int, sum_old: int, key2: int) -> int:
"""
对应加密循环中每一轮结尾对 v[n] 的特殊处理
"""
return u32((((z >> 5) ^ (4 * v0)) + ((16 * z) ^ (v0 >> 3))) ^ ((v0 ^ sum_old) + (key2 ^ z)))


def build_round_params(key4: List[int]) -> Tuple[List[int], List[int]]:
"""
预计算每一轮的参数。
因为 XXTEA 是多轮加密(这里是 10 轮),解密时需要知道每一轮对应的 sum 值和 key2 值。
加密是从 sum = START 加到 STOP,解密我们先算出来存表里,方便倒着用。
"""
sums: List[int] = []
key2s: List[int] = []

sum_ = SUM_START
key2 = KEY2_FIRST

for _ in range(10): # 循环 10 轮
sums.append(sum_)
key2s.append(key2)

# 模拟加密时的更新逻辑
sum_ = u32(sum_ - DELTA_SUB)
if sum_ == SUM_STOP:
break
# 计算下一轮用到的 key2
idx2 = (((sum_ >> 2) & 0xFF) ^ 0x0A) & 3
key2 = key4[idx2]

return sums, key2s


def decrypt_variant(words: List[int], key4: List[int]) -> List[int]:
"""
解密主函数
words: 11 个 32位整数 (密文)
key4: 4 个 32位整数 (密钥)
"""
v = [u32(x) for x in words]

# 获取每一轮的 sum 和 key2
sums, key2s = build_round_params(key4)

# 倒着遍历每一轮 (Round 10 -> Round 1)
for sum_old, key2 in zip(reversed(sums), reversed(key2s)):
# 1. 先逆向处理最后一个元素 v[10]
# 加密时:v[10] += mx_last(...)
# 解密时:v[10] -= mx_last(...)
z_new_v9 = v[9]
v0_new = v[0]
v[10] = u32(v[10] - mx_last(z_new_v9, v0_new, sum_old, key2))

# 2. 逆向处理前面的元素 v[9] 到 v[0]
# 加密时是从 0 到 9 顺序处理,解密时要从 9 到 0 倒序处理
for k in range(9, -1, -1):
y_old = v[k + 1] # 后一个元素 (已经是解密后的旧值了)
z = v[10] if k == 0 else v[k - 1] # 前一个元素
# 执行减法操作逆向
v[k] = u32(v[k] - mx_inner(z, y_old, sum_old, k, key4))

return v


# -----------------------------------------------------------
# 主程序入口
# -----------------------------------------------------------
def main():
# 1. 获取目标字节串 (60 bytes)
expected = build_expected_60()

# 2. 逆向 XOR 操作
# 加密逻辑推测:data[i] ^ 0x45
# 解密逻辑:result[i] ^ 0x45 (异或的逆运算还是异或)
shuffled_b64 = bytes(b ^ 0x45 for b in expected)

# 3. 逆向 Shuffle 操作
# 还原出 Base64 字符串
b64_text = unshuffle_msvcrt(shuffled_b64, seed=0x65)
print(f"[+] Base64 字符串: {b64_text}")

# 4. Base64 解码
# 将 60 字节的 Base64 字符串解码为 44 字节的二进制数据
ct = base64.b64decode(b64_text, validate=False)
if len(ct) != 44:
raise SystemExit(f"[!] base64 len={len(ct)} != 44")

print("[+] Ciphertext (Hex):", ct.hex())

# 5. 准备解密密钥和密文数组
# 将 44 字节解包成 11 个整数 (11 * 4 = 44)
v = list(struct.unpack("<11I", ct))

# 密钥是 "Calamity_Fortune",转成 4 个整数
key4 = list(struct.unpack("<4I", b"Calamity_Fortune"))

# 6. 执行核心算法解密
pt_words = decrypt_variant(v, key4)

# 7. 将解密后的整数数组打包回字符串
pt = struct.pack("<11I", *pt_words)

print("[+] 最终 Flag (Hex):", pt.hex())
try:
print("[+] 最终 Flag (UTF-8):", pt.decode("utf-8"))
except UnicodeDecodeError:
print("[!] 解码失败,可能包含非打印字符:", pt)


if __name__ == "__main__":
main()

关于本文

由 GuQing 撰写,采用 CC BY-NC 4.0 许可协议。

#Reverse_Writeup