WARMUP

VBS逆向,用VScode等文本编辑器打开.vbs文件,将开头处的Execute修改为wscript.echo再运行即可得到源码如下:

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
MsgBox "Dear CTFER. Have fun in XYCTF 2025!"
flag = InputBox("Enter the FLAG:", "XYCTF")
wefbuwiue = "90df4407ee093d309098d85a42be57a2979f1e51463a31e8d15e2fac4e84ea0df622a55c4ddfb535ef3e51e8b2528b826d5347e165912e99118333151273cc3fa8b2b3b413cf2bdb1e8c9c52865efc095a8dd89b3b3cfbb200bbadbf4a6cd4" ' 预设的RC4加密结果(十六进制格式)
qwfe = "rc4key"

' 修复后的RC4加密函数
Function RunRC(sMessage, strKey)
Dim kLen, i, j, temp, pos, outHex
Dim s(255), k(255)

' 初始化密�?
kLen = Len(strKey)
For i = 0 To 255
s(i) = i
k(i) = Asc(Mid(strKey, (i Mod kLen) + 1, 1)) ' 密钥使用ASCII编码
Next

' KSA密钥调度
j = 0
For i = 0 To 255
j = (j + s(i) + k(i)) Mod 256
temp = s(i)
s(i) = s(j)
s(j) = temp
Next

' PRGA加密流程
i = 0 : j = 0 : outHex = ""
For pos = 1 To Len(sMessage)
i = (i + 1) Mod 256
j = (j + s(i)) Mod 256
temp = s(i)
s(i) = s(j)
s(j) = temp

' 加密并转为十六进�?
Dim plainChar, cipherByte
plainChar = Asc(Mid(sMessage, pos, 1)) ' 明文按ASCII处理
cipherByte = s((s(i) + s(j)) Mod 256) Xor plainChar
outHex = outHex & Right("0" & Hex(cipherByte), 2)
Next

RunRC = outHex
End Function

' 主验证逻辑
If LCase(RunRC(flag, qwfe)) = LCase(wefbuwiue) Then
MsgBox "Congratulations! Correct FLAG!"
Else
MsgBox "Wrong flag."
End If

分析可知该程序进行的是RE4加密,用密码学工具箱跑一下即得flag:flag{We1c0me_t0_XYCTF_2025_reverse_ch@lleng3_by_th3_w@y_p3cd0wn‘s_chall_is_r3@lly_gr3@t_&_fuN!}

Dragon

.bc文件,先放入有clang编译器的环境,尝试编译

1
clang Dragon.bc -o Dragon

但是报错,尝试将其先编译为汇编代码

1
llc -o Dragon.s Dragon.bc

得到.s的汇编代码尝试编译仍然报错。喂给AI帮忙分析为C语言得到如下内容

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

#define CRC64_POLY 0x42F0E1EBA9EA3693ULL

uint64_t calculate_crc64_direct(const unsigned char *data, size_t length) {
uint64_t crc = ~0ULL;

for (size_t i = 0; i < length; i++) {
crc ^= (uint64_t)data[i] << 56;

for (int j = 0; j < 8; j++) {
if (crc & 0x8000000000000000ULL) {
crc = (crc << 1) ^ CRC64_POLY;
} else {
crc <<= 1;
}
}
}
return crc ^ ~0ULL;
}

int main() {
const uint64_t enc[] = {
0xdc63e34e419f7b47, 0x031ef8d4e7b2bfc6, 0x12d62fbc625fd89e,
0x83e8b6e1cc5755e8, 0xfc7bb1eb2ab665cc, 0x9382ca1b2a62d96b,
0xb1fff8a07673c387, 0x0da81627388e05e1, 0x9ef1e61ae8d0aab7,
0x92783fd2e7f26145, 0x63c97ca1f56fe60b, 0x9bd3a8b043b73aab
};

char input[100] = {0};
printf("Input U flag:");
scanf("%s", input);

size_t enc_index = 0;
int valid = 1;

for (size_t i = 0; i < strlen(input); i += 2) {
uint64_t crc = calculate_crc64_direct((unsigned char*)input + i, 2);

if (crc != enc[enc_index++]) {
printf("Error!");
valid = 0;
break;
}
}

if (valid) {
printf("Success");
}
return 0;
}

得知是CRC64,这是一种64位长度的循环冗余校验算法,用于传输数据时完整性检测,类似于哈希算法(但不是),使用爆破得出答案:

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
def calculate_crc64_direct(data):
#该算法是CRC64算法的python代码实现
crc = 0xFFFFFFFFFFFFFFFF
for byte in data:
crc ^= (byte << 56)
crc &= 0xFFFFFFFFFFFFFFFF
for _ in range(8):
msb = crc & (1 << 63)
crc = (crc << 1) & 0xFFFFFFFFFFFFFFFF
if msb:
crc ^= 0x42F0E1EBA9EA3693
return crc ^ 0xFFFFFFFFFFFFFFFF

enc = [
0xdc63e34e419f7b47, 0x031ef8d4e7b2bfc6, 0x12d62fbc625fd89e,
0x83e8b6e1cc5755e8, 0xfc7bb1eb2ab665cc, 0x9382ca1b2a62d96b,
0xb1fff8a07673c387, 0x0da81627388e05e1, 0x9ef1e61ae8d0aab7,
0x92783fd2e7f26145, 0x63c97ca1f56fe60b, 0x9bd3a8b043b73aab
]

flag = []
for target in enc:
found = False
for c1 in range(32, 127):
for c2 in range(32, 127):
if calculate_crc64_direct([c1, c2]) == target:
flag.append(chr(c1) + chr(c2))
found = True
break
if found:
break
if not found:
print("Failed to find a valid pair")
exit()

print("Flag:", ''.join(flag))
#flag{LLVM_1s_Fun_Ri9h7?}

补充:CRC64算法流程介绍(以”UC”为例):

​初始化:

1
crc = 0xFFFFFFFFFFFFFFFF  # 64个1的二进制

​处理第一个字节U(0x55)​:

1
crc ^= (0x55 << 56)  # 把字节移到最高8位异或

此时最高8位被修改,例如:0x55… → 10101011…

​处理8个bit(每个bit左移1次)​:

1
2
3
4
5
for _ in range(8):
msb = crc & 0x8000000000000000 # 取最高位
crc = (crc << 1) & 0xFFFFFFFFFFFFFFFF # 左移1位
if msb:
crc ^= 0x42F0E1EBA9EA3693 # 异或多项式

​关键操作:如果移出的最高位是1,就异或这个魔法数

​处理第二个字节C(0x43)​:

重复步骤2-3,继续处理8个bit

​最终处理:

1
crc ^= 0xFFFFFFFFFFFFFFFF  # 取反得到结果

Moon

打开main.py,发现核心加密函数moon.check_flag()在导入的moon库中,而moon库以pyd文件形式给出,使用python的help函数查看该模块的详细文档,得到以下内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
PS D:\桌面\moon> python main.py
Help on module moon:

NAME
moon

FUNCTIONS
check_flag(input_str)
返回验证结果的元组:(是否通过, 错误类型)

xor_crypt(seed_value, data_bytes)

DATA
SEED = 1131796
TARGET_HEX = '426b87abd0ceaa3c58761bbb0172606dd8ab064491a2a76af9a93e1a...'
__test__ = {}

FILE
d:\桌面\moon\moon.pyd

注:help函数的使用

1
help(moon)

由此得知该moon库中主要设置的两个函数为check_flag(input_str)xor_crypt(seed_value, data_bytes),后者接受两个参数,一个seed,一个data_bytes,而DATA部分正好包含SEED的值,以及一段类似密文的数据

用IDA分析moon.pyd,搜索字符串找到moon.xor_crypt,定位到对应函数sub_180001130,AI协助分析得知:该函数是一个Python C扩展模块的导出函数,用于实现名为xor_crypt的加密/解密功能。主要职责是解析Python调用参数,验证参数合法性,并调用底层加密逻辑,sub_180001370是实际执行异或加密/解密的函数

进一步跟进sub_180001370函数,其是Python C扩展模块中的异或加密/解密核心逻辑,负责将输入数据(字节流或列表)与密钥逐元素进行异或运算,并返回处理后的字节流。

因为check的逻辑不复杂,而且异或可逆,所以可以黑盒调用xor_crypt,传入密文得到明文flag

1
2
3
4
5
import moon 
seed_val = 0x114514
data = [0x42,0x6B,0x87,0xAB,0xD0,0xCE,0xAA,0x3C,0x58,0x76,0x1B,0xBB,0x01,0x72,0x60,0x6D,0xD8,0xAB,0x06,0x44,0x91,0xA2,0xA7,0x6A,0xF9,0xA9,0x3E,0x1A,0xE5,0x6F,0xA8,0x42,0x06,0xA2,0xF7]
print(moon.xor_crypt(seed_val,data))
#flag{but_y0u_l00k3d_up_@t_th3_mOOn}

补充:之前不会使用help指令,曾尝试动态调试,首先搜索字符串时顺便发现了python311的信息,配置好环境以及IDA后尝试调试,能成功运行,但是断点不能命中,故后来放弃

附IDA调试pyd文件配置方法:

Lake

找不到核心算法代码,先在start函数里下断点然后步进/步过跟踪,对比软件调试时显示对应文本的时间,结合静态分析排除部分非关键函数,最后找到主函数sub_100001B70。

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
int __fastcall __noreturn main(int argc, const char **argv, const char **envp)
{
__int64 v3; // rbx
void *v4; // rcx
__int64 v5; // rbx
__int64 v6; // rbx
void *v7; // rcx
__int64 v8; // rbx
__int64 v9; // rbx
void *v10; // rcx
__int64 v11; // rbx
__int64 v12; // rbx
void *v13; // rcx
__int64 v14; // rax
__int64 v15; // rbx
__int16 v16; // ax
__int64 v17; // rbx
void *v18; // rcx
__int64 v19; // rbx
void *v20; // rcx
__int64 v21; // rbx

sub_100008570();
i = -1;
do
{
aJF[(unsigned __int16)++i] ^= 0x33u;
v3 = sub_10000C190();
sub_10000C760(0i64, v3, LOBYTE(aJF[(unsigned __int16)i]));
sub_100008510();
sub_10000C2F0(v3);
sub_100008510();
if ( qword_100021B50 )
v4 = (void *)qword_100021B50((unsigned int)dword_100020580);
else
v4 = &unk_100020588;
output((__int64)v4);
sub_100008510();
sub_1000130D0(0x64u);
}
while ( i < 94 ); // You must live in the present, launch yourself on every wave, find your eternity in each moment.
v5 = sub_10000C190();
sub_10000C310(v5);
sub_100008510();
i = -1;
do
{
*(_WORD *)&byte_100015210[2 * (unsigned __int16)++i] -= 37;
v6 = sub_10000C190();
sub_10000C760(0i64, v6, byte_100015210[2 * (unsigned __int16)i]);
sub_100008510();
sub_10000C2F0(v6);
sub_100008510();
if ( qword_100021B50 )
v7 = (void *)qword_100021B50((unsigned int)dword_100020580);
else
v7 = &unk_100020588;
output((__int64)v7);
sub_100008510();
sub_1000130D0(0x64u);
}
while ( i < 73 ); // Fools stand on their island of opportunities and look toward another land.
v8 = sub_10000C190();
sub_10000C310(v8);
sub_100008510();
i = -1;
do
{
*(_WORD *)&byte_1000152B0[2 * (unsigned __int16)++i] ^= 0x77u;
v9 = sub_10000C190();
sub_10000C760(0i64, v9, byte_1000152B0[2 * (unsigned __int16)i]);
sub_100008510();
sub_10000C2F0(v9);
sub_100008510();
if ( qword_100021B50 )
v10 = (void *)qword_100021B50((unsigned int)dword_100020580);
else
v10 = &unk_100020588;
output((__int64)v10);
sub_100008510();
sub_1000130D0(0x64u);
}
while ( i < 55 ); // There is no other land; there is no other life but this.
v11 = sub_10000C190();
sub_10000C310(v11);
sub_100008510();
sub_1000130D0(0x1F4u);
i = -1;
do
{
aFgo[(unsigned __int16)++i] += 8;
v12 = sub_10000C190();
sub_10000C760(0i64, v12, LOBYTE(aFgo[(unsigned __int16)i]));
sub_100008510();
sub_10000C2F0(v12);
sub_100008510();
if ( qword_100021B50 )
v13 = (void *)qword_100021B50((unsigned int)dword_100020580);
else
v13 = &unk_100020588;
output((__int64)v13);
sub_100008510();
sub_1000130D0(0xC8u);
}
while ( i < 49 ); // Now please input your understanding of the Walden:
v14 = sub_10000C190();
sub_10000C310(v14);
sub_100008510();
v15 = sub_10000C160();
scanf(v15, input, 255i64);
sub_100008510();
sub_10000C940(v15);
sub_100008510();
sub_1000026E0(byte_100020010, 40i64, 0i64);
v16 = (unsigned __int8)input[0];
if ( input[0] )
{
i = 0;
do // important1
{
if ( i++ <= 39i64 )
byte_100020010[i - 1] = input[(unsigned __int8)i];
}
while ( v16 > i );
}
i = 0;
while ( i < 123i64 ) // VM
{ // Read code
word_100020060 = word_100015470[(unsigned __int16)i];// 指令
word_100020070 = word_100015470[i + 1]; // 操作对象(数组下标)
word_100020080 = word_100015470[i + 2]; // 操作数
i += 3;
if ( word_100020060 >= 1 )
{
switch ( word_100020060 )
{
case 1:
fuc_add(word_100020070, word_100020080);// 加
break;
case 2:
fuc_sub(word_100020070, word_100020080);// 减
break;
case 3:
fuc_mul(word_100020070, word_100020080);// 乘
break;
case 4:
fuc_div(word_100020070, word_100020080);// 除
break;
case 5:
fuc_rem(word_100020070, word_100020080);// 取余
break;
case 6:
fuc_and(word_100020070, word_100020080);// 且
break;
case 7:
fuc_or(word_100020070, word_100020080);// 或
break;
case 8:
fuc_xor(word_100020070, word_100020080);// 异或
break;
}
}
}
sub_1000019B0(byte_100020010, 39i64); // enc2
word_100015570 = 0;
word_100020050 = -1;
while ( 1 ) // check
{
++word_100020050;
if ( byte_100020010[(unsigned __int16)word_100020050] != byte_100015440[(unsigned __int16)word_100020050] )
word_100015570 = 1;
if ( word_100020050 >= 39 )
{
if ( word_100015570 )
{
i = -1;
do
{
aEiqjmQgGDlLgEg[(unsigned __int16)++i] ^= 0x28u;
v19 = sub_10000C190();
sub_10000C760(0i64, v19, LOBYTE(aEiqjmQgGDlLgEg[(unsigned __int16)i]));
sub_100008510();
sub_10000C2F0(v19);
sub_100008510();
if ( qword_100021B50 )
v20 = (void *)qword_100021B50((unsigned int)dword_100020580);
else
v20 = &unk_100020588;
output((__int64)v20);
sub_100008510();
sub_1000130D0(0x64u);
}
while ( i < 35 ); // Maybe you should do more to find it.
}
else
{
i = -1;
do
{
aKgfoziDiAgf[(unsigned __int16)++i] ^= 0x28u;
v17 = sub_10000C190();
sub_10000C760(0i64, v17, LOBYTE(aKgfoziDiAgf[(unsigned __int16)i]));
sub_100008510();
sub_10000C2F0(v17);
sub_100008510();
if ( qword_100021B50 )
v18 = (void *)qword_100021B50((unsigned int)dword_100020580);
else
v18 = &unk_100020588;
output((__int64)v18);
sub_100008510();
sub_1000130D0(0x64u);
}
while ( i < 43 );
} // win
v21 = sub_10000C190();
sub_10000C310(v21);
sub_100008510();
sub_100008980();
}
}
}

经分析可知前面几个循环是在实现逐字节输出的动画,对应的内容已用注释标记。

important1部分把输入字符串复制到另一个数组,然后进行第一次加密。第一次加密的流程是VM虚拟机,先是读取指令、操作对象和操作数,然后跳转对应指令进行计算(分别定义了加减乘除取余或异或等指令)

接着由sub_1000019B0执行第二次加密,流程如下:

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
char __fastcall sub_1000019B0(__int64 a1, __int64 a2)
{
char result; // al
__int16 v3; // [rsp+4h] [rbp-3Ch]
__int16 v4; // [rsp+4h] [rbp-3Ch]
char v5[40]; // [rsp+8h] [rbp-38h]
__int64 v6; // [rsp+30h] [rbp-10h]
__int64 v7; // [rsp+38h] [rbp-8h]

v7 = a1;
v6 = a2;
v3 = -1;
do
{
if ( 4i64 * ++v3 + 2 <= 39 )
v5[4 * v3] = (8 * *(_BYTE *)(v7 + 4i64 * v3 + 1)) | (*(_BYTE *)(v7 + 4i64 * v3 + 2) >> 5);
if ( 4i64 * v3 + 3 <= 39 )
v5[4 * v3 + 1] = (8 * *(_BYTE *)(v7 + 4i64 * v3 + 2)) | (*(_BYTE *)(v7 + 4i64 * v3 + 3) >> 5);
if ( 4i64 * v3 <= 39 )
v5[4 * v3 + 2] = (*(_BYTE *)(v7 + 4i64 * v3) >> 5) | (8 * *(_BYTE *)(v7 + 4i64 * v3 + 3));
if ( 4i64 * v3 + 1 <= 39 )
v5[4 * v3 + 3] = (8 * *(_BYTE *)(v7 + 4i64 * v3)) | (*(_BYTE *)(v7 + 4i64 * v3 + 1) >> 5);
}
while ( v3 < 9 );
v4 = -1;
do
{
result = v5[(unsigned __int16)++v4];
*(_BYTE *)(v7 + v4) = result;
}
while ( v4 < 39 );
return result;
}

最后进行了循环比较,可知密文储存在byte_100015440中

动调发现VM部分实际使用的指令只有ADD、SUB和XOR,所以在这三处打断点再次调试,得到每次的操作对象和操作数并记录

然后对sub_1000019B0函数进行逆向并整合即可写出exp

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

void decrypt_func(unsigned char *data, int len)
{
unsigned char temp[40];
memcpy(temp, data, 40);

for (int i = 0; i < 10; i++)
{
int base = 4 * i;
unsigned char block[4];

if (base < len)
{
block[0] = ((temp[base + 2] & 0x1F) << 5) | (temp[base + 3] >> 3);
block[1] = (temp[base] >> 3) | ((temp[base + 3] & 0x07) << 5);
block[2] = ((temp[base] & 0x07) << 5) | (temp[base + 1] >> 3);
block[3] = ((temp[base + 1] & 0x07) << 5) | (temp[base + 2] >> 3);

for (int j = 0; j < 4 && base + j < len; j++)
{
data[base + j] = block[j];
}
}
}
}
int main()
{
unsigned char Input[48] = {
0x4A, 0xAB, 0x9B, 0x1B, 0x61, 0xB1, 0xF3, 0x32, 0xD1, 0x8B, 0x73, 0xEB, 0xE9, 0x73, 0x6B, 0x22,
0x81, 0x83, 0x23, 0x31, 0xCB, 0x1B, 0x22, 0xFB, 0x25, 0xC2, 0x81, 0x81, 0x73, 0x22, 0xFA, 0x03,
0x9C, 0x4B, 0x5B, 0x49, 0x97, 0x87, 0xDB, 0x51};

decrypt_func(Input, 40);
Input[2] += 12;
Input[26] -= 85;
Input[35] -= 12;
Input[14] += 9;
Input[27] -= 6;
Input[6] ^= 5;
Input[1] ^= 5;
Input[27] += 14;
Input[25] += 3;
Input[26] += 4;
Input[4] ^= 8;
Input[3] -= 12;
Input[12] += 10;
Input[37] -= 2;
Input[32] -= 2;
Input[9] -= 12;
Input[26] ^= 5;
Input[4] += 13;
Input[8] ^= 15;
Input[10] += 14;
Input[16] -= 7;
Input[12] -= 7;
Input[34] ^= 8;
Input[21] ^= 10;
Input[39] -= 126;
Input[7] += 2;
Input[15] ^= 3;
Input[10] ^= 10;
Input[34] -= 11;
Input[18] += 8;
Input[25] += 9;
Input[14] ^= 6;
Input[0] ^= 5;
Input[10] -= 8;
Input[27] ^= 7;
Input[13] ^= 6;
Input[13] ^= 4;
Input[23] ^= 12;
Input[34] ^= 14;
Input[18] += 52;
Input[38] -= 119;
printf("%.40s\n", Input);
// flag{L3@rn-ng_1n_0ld_sch00b_@nd_g3x_j0y} -> flag{L3@rn1ng_1n_0ld_sch00l_@nd_g3t_j0y}
return 0;
}

本文部分内容参考了这位师傅的WP:https://tkazer.github.io/2025/04/07/XYCTF2025/index.html#XYCTF2025-%E9%80%86%E5%90%91WP

后续的题目就非我能力范围所及了,想进一步学习的可移步至上文给出链接里Liv师傅的WP