XYCTF2024 Reverse 方向复现

你是真的大学生吗?

附件是MS-DOS executable (EXE)文件,ida 不能反编译,硬看汇编

解密脚本

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
'''
T[20]=C[20]^C[0]
al=T[20]
T[19]=C[19]^al即T[19]=C[19]^T[20]
al=T[19]
T[18]=C[18]^al即T[18]=C[18]^T[19]
...
T[0]=C[0]^al即T[0]=C[0]^T[1]
所以对于i=0~19
T[i]=C[i]^T[i+1]
求C[i]就用C[i]=T[i]^T[i+1]
对于i=20
C[20]=T[20]^C[0]
C[20]=T[20]^(T[0]^T[1])
'''

enc=[0x76, 0x0E, 0x77, 0x14, 0x60, 0x06, 0x7D, 0x04, 0x6B, 0x1E,
0x41, 0x2A, 0x44, 0x2B, 0x5C, 0x03, 0x3B, 0x0B, 0x33, 0x05]

flag=[]
for i in range(len(enc)-1):
flag.append(enc[i]^enc[i+1])
flag.append(enc[-1]^enc[0]^enc[1])

print(''.join([chr(c) for c in flag]))

#xyctf{you_know_8086}

flag 为yctf{you_know_8086}

给阿姨倒一杯卡布奇诺

经典的 TEA 了

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
#include <stdio.h>
#define uint32_t unsigned int

void decrypt(uint32_t *v, uint32_t *key)
{
static uint32_t data1 = 0x5F797274;
static uint32_t data2 = 0x64726168;
int i; // [rsp+20h] [rbp-10h]
uint32_t sum; // [rsp+24h] [rbp-Ch]
uint32_t v1; // [rsp+28h] [rbp-8h]
uint32_t v0; // [rsp+2Ch] [rbp-4h]

sum = 0x6E75316C * 32;


//保存当前密文
uint32_t data1_tmp = v[0];
uint32_t data2_tmp = v[1];
v0 = v[0];
v1 = v[1];
for (i = 31; i >= 0; i--)//i参与加密,for循环中的i也要倒着写
{
v1 -= ((v0 >> 5) + key[3]) ^ (v0 + sum) ^ (key[2] + 16 * v0) ^ (sum + i);
v0 -= ((v1 >> 5) + key[1]) ^ (v1 + sum) ^ (key[0] + 16 * v1) ^ (sum + i);
sum -= 0x6E75316C;
}

v[0] = v0 ^ data1;
v[1] = v1 ^ data2;
data1 = data1_tmp;
data2 = data2_tmp;
}

int main()
{
uint32_t key[4]; // [rsp+60h] [rbp-40h] BYREF
uint32_t array[8]; // [rsp+70h] [rbp-30h]
array[0] = 0x9B28ED45;
array[1] = 0x145EC6E9;
array[2] = 0x5B27A6C3;
array[3] = 0xE59E75D5;
array[4] = 0xE82C2500;
array[5] = 0xA4211D92;
array[6] = 0xCD8A4B62;
array[7] = 0xA668F440;
key[0] = 0x65766967;
key[1] = 0x756F795F;
key[2] = 0x7075635F;
key[3] = 0x6165745F;
for (int i = 0; i <= 7; i += 2)
{
decrypt(array + i, key);
}
for(int i=0; i<32; i++)
{
printf("%c", ((char*)array)[i]);
}
return 0;
}

flag 为XTCTF{133bffe401d223a02385d90c5f1ca377}

DebugMe

jadx 打开有混淆,用 jeb 打开能正常反编译,安卓 java 层动调,Frida 一把梭,hook What类

1
2
3
4
5
6
7
8
9
10
11
12
13
function hookwhat() {
var what = Java.use("com.xyctf.ezapk.What");
var enc = "WikFhRxyYjoSJ8mMbM3fRwty/74bc7Ip7ojqenHaSqc9wDv3JDG9XfV6xEiC7Eg1RWTUa4LaM%2BD0W%2BPKanSA5w==";
var flag = what.x(enc);
console.log("\nFlag: " + flag);
}

function main() {
Java.perform(function () {
hookwhat();
})
};
setImmediate(main);

直接出 flag

flag 为XYCTF{d3bugg3r_15_v3ry_u53ful}

何须相思煮余年

附件是 txt,将 16 进制数据放进 CyberChef 里,选择From Hex,然后在 Output 保存文件

将保存的文件用 32 位的 ida 打开,发现有函数序言和函数尾声

1
2
3
push    ebp         ; 保存旧的栈基址
mov ebp, esp ; 建立新的栈基址
sub esp, 0x20 ; 为局部变量分配空间
1
2
3
leave               ; 恢复栈指针和基址
(mov esp, ebp; pop ebp)
ret ; 返回到调用者

P 创建函数后 F5能反编译了

逆向写出解题脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
enc=[88,88,134,87,74,118,318,101,59,92,480,60,65,41,770,110,73,31,918,
39,120,27,1188,47,77,24,1352,44,81,23,1680,46,85,15,1870,66,91,16,4750]
# length=len(enc)
# print(length)
result=enc.copy()
for i in range(len(enc)):
if i%4==0:
result[i]-=i
elif i%4==1:
result[i]+=i
elif i%4==2:
if i!=0:
result[i]//=i
elif i%4==3:
result[i]^=i

print(''.join([chr(c) for c in result]))

flag 为XYCTF{5b3e07567a9034d06851475481507a75}

what’s this

exeinfope打开看发现 Lua bytecode (generic),用 luadec 反编译

有很大一部分没用的代码直接跳过,看后面关键逻辑

把每个字符先异或 8,再 +3

base64,逆序,字符替换
解题脚本

1
2
3
4
5
6
7
8
9
10
import base64
enc="==AeuFEcwxGPuJ0PBNzbC16ctFnPB5DPzI0bwx6bu9GQ2F1XOR1U"
#替换
new_enc=enc.replace("6","W")
#逆序 [start:stop:step] step=-1,从后往前遍历
enc=new_enc[::-1]
dec=base64.b64decode(enc)
for i in dec.decode():
i =(ord(i)-3)^8
print(chr(i),end="")

flag 为XYCTF{5dcbaed781363fbfb7d8647c1aee6c}

ez_enc

main 函数中加密,byte_14001E008 里是密文,z3求解

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
from z3 import *

enc=[0x27, 0x24, 0x17, 0x0B, 0x50, 0x03, 0xC8, 0x0C, 0x1F, 0x17, 0x36, 0x55, 0xCB,
0x2D, 0xE9, 0x32, 0x0E, 0x11, 0x26, 0x02, 0x0C, 0x07, 0xFC, 0x27, 0x3D, 0x2D,
0xED, 0x35, 0x59, 0xEB, 0x3C, 0x3E, 0xE4, 0x7D]
# print(len(enc))
# 34
s=Solver()
#创建一个名字为 'flag[i]' 的 8 位的 位向量
item=[BitVec('flag[%d]'%i,8) for i in range(34)]
#copy item to dec
dec=item[:]

#限制flag中字符在常见可打印字符范围内
for i in range(34):
s.add(item[i]<127)
s.add(item[i]>31)

for i in range(33):
item[i]=b'IMouto'[i%6]^(item[i+1]+item[i]%20)&0xff

for i in range(34):
s.add(item[i]==enc[i])

if s.check()==sat:
m=s.model()
print(''.join(chr(m[dec[i]].as_long()) for i in range(34)))

flag 为flag{!_r3ea11y_w4nt_@_cu7e_s1$ter}

ezmath

python 逆向,先用 pyinstxtractor 解包,找到ezmath.pyc,用 https://tool.lu/pyc 反编译得到代码

1
2
3
4
5
6
7
#!/usr/bin/env python
# visit https://tool.lu/pyc/ for more information
# Version: Python 3.8

flag = (lambda .0: [ ord(i) for i in .0 ])(input('flag:'))
if len(flag) == 32 and (((((((((((((((((((((sum((lambda .0: [ flag[23] for _ in .0 ])(range(flag[23]))) + sum((lambda .0: [ flag[12] for _ in .0 ])(range(flag[12]))) + sum((lambda .0: [ flag[1] for _ in .0 ])(range(flag[1]))) - sum((lambda .0: [ flag[24] for _ in .0 ])(range(222)))) + sum((lambda .0: [ flag[22] for _ in .0 ])(range(flag[22]))) + sum((lambda .0: [ flag[31] for _ in .0 ])(range(flag[31]))) + sum((lambda .0: [ flag[26] for _ in .0 ])(range(flag[26]))) - sum((lambda .0: [ flag[9] for _ in .0 ])(range(178))) - sum((lambda .0: [ flag[29] for _ in .0 ])(range(232)))) + sum((lambda .0: [ flag[17] for _ in .0 ])(range(flag[17]))) - sum((lambda .0: [ flag[23] for _ in .0 ])(range(150))) - sum((lambda .0: [ flag[6] for _ in .0 ])(range(226))) - sum((lambda .0: [ flag[7] for _ in .0 ])(range(110)))) + sum((lambda .0: [ flag[19] for _ in .0 ])(range(flag[19]))) + sum((lambda .0: [ flag[2] for _ in .0 ])(range(flag[2]))) - sum((lambda .0: [ flag[0] for _ in .0 ])(range(176)))) + sum((lambda .0: [ flag[10] for _ in .0 ])(range(flag[10]))) - sum((lambda .0: [ flag[12] for _ in .0 ])(range(198)))) + sum((lambda .0: [ flag[24] for _ in .0 ])(range(flag[24]))) + sum((lambda .0: [ flag[9] for _ in .0 ])(range(flag[9]))) - sum((lambda .0: [ flag[3] for _ in .0 ])(range(168)))) + sum((lambda .0: [ flag[8] for _ in .0 ])(range(flag[8]))) - sum((lambda .0: [ flag[2] for _ in .0 ])(range(134)))) + sum((lambda .0: [ flag[14] for _ in .0 ])(range(flag[14]))) - sum((lambda .0: [ flag[13] for _ in .0 ])(range(170)))) + sum((lambda .0: [ flag[4] for _ in .0 ])(range(flag[4]))) - sum((lambda .0: [ flag[10] for _ in .0 ])(range(142)))) + sum((lambda .0: [ flag[27] for _ in .0 ])(range(flag[27]))) + sum((lambda .0: [ flag[15] for _ in .0 ])(range(flag[15]))) - sum((lambda .0: [ flag[15] for _ in .0 ])(range(224)))) + sum((lambda .0: [ flag[16] for _ in .0 ])(range(flag[16]))) - sum((lambda .0: [ flag[11] for _ in .0 ])(range(230))) - sum((lambda .0: [ flag[1] for _ in .0 ])(range(178)))) + sum((lambda .0: [ flag[28] for _ in .0 ])(range(flag[28]))) - sum((lambda .0: [ flag[5] for _ in .0 ])(range(246))) - sum((lambda .0: [ flag[17] for _ in .0 ])(range(168)))) + sum((lambda .0: [ flag[30] for _ in .0 ])(range(flag[30]))) - sum((lambda .0: [ flag[21] for _ in .0 ])(range(220))) - sum((lambda .0: [ flag[22] for _ in .0 ])(range(212))) - sum((lambda .0: [ flag[16] for _ in .0 ])(range(232)))) + sum((lambda .0: [ flag[25] for _ in .0 ])(range(flag[25]))) - sum((lambda .0: [ flag[4] for _ in .0 ])(range(140))) - sum((lambda .0: [ flag[31] for _ in .0 ])(range(250))) - sum((lambda .0: [ flag[28] for _ in .0 ])(range(150)))) + sum((lambda .0: [ flag[11] for _ in .0 ])(range(flag[11]))) + sum((lambda .0: [ flag[13] for _ in .0 ])(range(flag[13]))) - sum((lambda .0: [ flag[14] for _ in .0 ])(range(234)))) + sum((lambda .0: [ flag[7] for _ in .0 ])(range(flag[7]))) - sum((lambda .0: [ flag[8] for _ in .0 ])(range(174)))) + sum((lambda .0: [ flag[3] for _ in .0 ])(range(flag[3]))) - sum((lambda .0: [ flag[25] for _ in .0 ])(range(242)))) + sum((lambda .0: [ flag[29] for _ in .0 ])(range(flag[29]))) + sum((lambda .0: [ flag[5] for _ in .0 ])(range(flag[5]))) - sum((lambda .0: [ flag[30] for _ in .0 ])(range(142))) - sum((lambda .0: [ flag[26] for _ in .0 ])(range(170))) - sum((lambda .0: [ flag[19] for _ in .0 ])(range(176)))) + sum((lambda .0: [ flag[0] for _ in .0 ])(range(flag[0]))) - sum((lambda .0: [ flag[27] for _ in .0 ])(range(168)))) + sum((lambda .0: [ flag[20] for _ in .0 ])(range(flag[20]))) - sum((lambda .0: [ flag[20] for _ in .0 ])(range(212)))) + sum((lambda .0: [ flag[21] for _ in .0 ])(range(flag[21]))) + sum((lambda .0: [ flag[6] for _ in .0 ])(range(flag[6]))) + sum((lambda .0: [ flag[18] for _ in .0 ])(range(flag[18]))) - sum((lambda .0: [ flag[18] for _ in .0 ])(range(178)))) + 297412 == 0:
print('yes')

分析一下主要表达式sum((lambda .0: [ <参数1> for _ in .0 ])(range(<参数2>)))
lambda .0: [ <参数1> for _ in .0 ] 定义了一个匿名函数,接受参数.0(即range(<参数2>)生成的序列)
range(<参数2>) 生成一个长度为<参数2>的整数序列(如<参数2>=3时生成0,1,2)
列表推导式 [ <参数1> for _ in .0 ] 会创建包含<参数2>个<参数1>的列表(如[5,5,5])
sum(…) 对该列表求和,本质上是将<参数1>累加<参数2>次,即**<参数1> * <参数2>**
用 z3 约束求解

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
from z3 import *

solver = Solver()

flags = [Real(f'flag{i}') for i in range(32)]

solver.add(
(flags[23] ** 2) + (flags[12] ** 2) + (flags[1] ** 2) - (222 * flags[24]) + (flags[22] ** 2) +
(flags[31] ** 2) + (flags[26] ** 2) - (178 * flags[9]) - (232 * flags[29]) + (flags[17] ** 2) -
(150 * flags[23]) - (226 * flags[6]) - (110 * flags[7]) + (flags[19] ** 2) + (flags[2] ** 2) -
(176 * flags[0]) + (flags[10] ** 2) - (198 * flags[12]) + (flags[24] ** 2) + (flags[9] ** 2) -
(168 * flags[3]) + (flags[8] ** 2) - (134 * flags[2]) + (flags[14] ** 2) - (170 * flags[13]) +
(flags[4] ** 2) - (142 * flags[10]) + (flags[27] ** 2) + (flags[15] ** 2) - (224 * flags[15]) +
(flags[16] ** 2) - (230 * flags[11]) - (178 * flags[1]) + (flags[28] ** 2) - (246 * flags[5]) -
(168 * flags[17]) + (flags[30] ** 2) - (220 * flags[21]) - (212 * flags[22]) - (232 * flags[16]) +
(flags[25] ** 2) - (140 * flags[4]) - (250 * flags[31]) - (150 * flags[28]) + (flags[11] ** 2) +
(flags[13] ** 2) - (234 * flags[14]) + (flags[7] ** 2) - (174 * flags[8]) + (flags[3] ** 2) -
(242 * flags[25]) + (flags[29] ** 2) + (flags[5] ** 2) - (142 * flags[30]) - (170 * flags[26]) -
(176 * flags[19]) + (flags[0] ** 2) - (168 * flags[27]) + (flags[20] ** 2) - (212 * flags[20]) +
(flags[21] ** 2) + (flags[6] ** 2) + (flags[18] ** 2) - (178 * flags[18]) + 297412 == 0
)

if solver.check() == sat:
model = solver.model()
result = [str(model[flag]) if model[flag] is not None else '0' for flag in flags]
print(' '.join(result))
else:
print("No solution found.")

#88 89 67 84 70 123 113 55 87 89 71 115 99 85 117 112 116 84 89 88 106 110 106 75 111 121 85 84 75 116 71 125

这个好像要把类型设置成Real才跑得出来,用 Int 类型没解出来
把结果扔 CyberChef 里解

flag 为 XYCTF{q7WYGscUuptTYXjnjKoyUTKtG}

Trustme

jadx 打开,在 MainAcitity 里看到标准RC4,并且给出了密文和密钥
先解一下这个RC4

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
def rc4_dec(ciphertext, key):
S = list(range(256))
j = 0
for i in range(256):
j = (j + S[i] + key[i % len(key)]) % 256
S[i], S[j] = S[j], S[i]

i = j = 0
plaintext = []
for byte in ciphertext:
i = (i + 1) % 256
j = (j + S[i]) % 256
S[i], S[j] = S[j], S[i]
k = S[(S[i] + S[j]) % 256]
plaintext.append(byte ^ k)

return bytes(plaintext)
enc_data = [0x5A,0X3C,0X46,0XE0,0X22,0X8B,0X44,0X4D,0XEC,0XC7,0X65,0X1C,0X8A,0X7C,0XA9,0X3B,0XA4,0XCB,0X35,0XA4,0X6F,0X7E,0XB5,0X89,0XBE,0XF4]
key = b'XYCTF'

dec_data = rc4_dec(enc_data,key)
print(dec_data.decode('utf-8'))

输出为 The Real username is admin
在手机上试试 SQL 注入,username 填admin' --,密码随意
这样拼接后,SQL语句变成
SELECT * FROM users WHERE username = 'admin' -- ' AND password = 'anything';


flag 为 XYCTF{And0r1d_15_V3ryEasy}

今夕是何年

题目说的运行就能出 flag,但是在 win 和 linux 上都运行不了,用 DIE查看文件信息

well,是龙,在 Ubuntu 上装个 qemu-loongarch64,直接运行

flag 为 XYCTF{7e5165f1-385d-4fe9-1f2664d833a648a4}

easy language

三分逆向七分猜,ida 查找字符串,看到 AES-ECB

在后面又看到 16 字节的字符串,密钥 get,那密钥上面的就是密文了

CyberChef 一把梭

flag 为 XYCTF{y0u_@r3_v3ry_g00d_a7_E_l@ngu@ge}

舔狗四部曲–相逢已是上上签

直接用 ida 打开看又是一坨,题目提示 PE,用 010Editor 打开

64字节的 DOS 头(前四行)的最后一个成员是指向 PE 头的偏移地址的,从突出可以看出这道题两者是对不上的,被魔改了,直接手动改回来,把10 01 00 00改成00 01 00 00,保存,把改后的 exe 再扔进 DIE 看都能发现一切正常了

ida 看 main 函数,先用 z3 求出 key,加密函数是 xxtea
求解key

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
from z3 import *
solver = Solver()

keys=[Int(f'key{i}') for i in range(6)]

solver.add(532 * keys[5]+ 829 * keys[4]+ 258 *keys[3]+ 811 * keys[2]+ 997 * keys[1] + 593 * keys[0]== 292512)
solver.add(576*keys[5]+ 695 * keys[4]+ 602 *keys[3]+ 328 * keys[2]+ 686 * keys[1] + 605 * keys[0]== 254496)
solver.add(580*keys[5]+ 448 * keys[4]+ 756 *keys[3]+ 449* keys[2]+ 512 * keys[1] + 373* keys[0]== 222479) # <<9改成*512(512 = 2^9)
solver.add(597*keys[5]+ 855 * keys[4]+ 971 *keys[3]+ 422 * keys[2]+ 635 * keys[1] + 560 * keys[0]== 295184)
solver.add(524*keys[5]+ 324 * keys[4]+ 925 *keys[3]+ 388 * keys[2]+ 507 * keys[1] + 717 * keys[0]== 251887)
solver.add(414*keys[5]+ 495 * keys[4]+ 518 *keys[3]+ 884 * keys[2]+ 368 * keys[1] + 312 * keys[0]== 211260)

if solver.check() == sat:
model = solver.model()
key_values = [model[k].as_long() for k in keys]
# 将数值转换为字符
key = ''.join(chr(val) for val in key_values)
print(f"Find key: {key}")
else:
print("No solution found.")

# Find key: XYCTF!

求解flag

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

// 定义魔改参数
#define DELTA 0x61C88647
#define ROUNDS 12 //ida 中直接看可以算出来

// 魔改XXTEA
void decrypt(uint32_t* v, int n, uint8_t* key) {
uint32_t sum = 0x9E3779B9 * ROUNDS; // 初始sum = -DELTA * ROUNDS (无符号处理)
for (int i = 0; i < ROUNDS; i++) {
uint32_t v6 = (sum >> 2) & 5; // 计算轮次相关索引

// 先处理最后一个块
uint32_t v9 = v[n-2];
int idx = (v6 ^ ((n-1) & 5)) % 6;
uint32_t k = key[idx];
uint32_t expr = ((v9 ^ k) + (v[0] ^ sum)) ^
(((v9 << 4) ^ (v[0] >> 3)) +
((v[0] << 2) ^ (v9 >> 5)));
v[n-1] -= expr;

// 逆序处理前n-1个块
for (int j = n-2; j >= 0; j--) {
v9 = (j == 0) ? v[n-1] : v[j-1]; // 获取前一个块的值
idx = (v6 ^ (j & 5)) % 6; // 计算密钥索引
k = key[idx];
expr = ((v9 ^ k) + (v[j+1] ^ sum)) ^
(((v9 << 4) ^ (v[j+1] >> 3)) +
((v[j+1] << 2) ^ (v9 >> 5)));
v[j] -= expr;
}
sum += DELTA; // 解密时sum递增
}
}

int main() {
// 密文数据(小端序)
uint32_t cipher[] = {
0x66697271, 0x896E2285, 0xC5188C1B, 0x72BCFD03,
0x538011CA, 0x4DA146AC, 0x86630D6B, 0xF89797F0
};

// 密钥"XYCTF!"的字节数组
uint8_t key[] = {'X', 'Y', 'C', 'T', 'F', '!'};

// 执行解密
decrypt(cipher, 8, key);

// 输出解密后的16进制和ASCII
printf("Decrypted Hex:\n");
for (int i = 0; i < 8; i++) {
printf("%08X ", cipher[i]);
}

printf("\n\nASCII:\n");
unsigned char* flag = (unsigned char*)cipher;
for (int i = 0; i < 32; i++) {
printf("%c", flag[i]);
}
return 0;
}
// Decrypted Hex:
// 54435958 58587B46 5F414554 5F444E41 315F335A 30535F73 7361655F 7D212179

// ASCII:
// XYCTF{XXTEA_AND_Z3_1s_S0_easy!!}

flag 为XYCTF{XXTEA_AND_Z3_1s_S0_easy!!}


XYCTF2024 Reverse 方向复现
http://example.com/2025/05/03/XYCTF2024/
作者
Eleven
发布于
2025年5月3日
许可协议