XYCTF2025 Reverse 方向复现

CrackMe

ida 打开看到 WinMain 函数,同时注意到有一个 TlsCallback_0 函数

1
2
3
4
5
6
>VOID NTAPI TlsCallback(PVOID DllHandle, DWORD dwReason, PVOID Reserved) {
//DllHandle:对于 exe 是进程句柄,对于 dll 是模块句柄;dwReason:回调原因;Reserved:保留参数,一般用不到
if (dwReason == DLL_PROCESS_ATTACH) {
MessageBoxA(NULL, "TlsCallback Triggered!", "Info", 0);
}
>}

点进 WinMain,有反调试,先打断点运行起来用 ScyllaHide 去一下反调;很多传入了多个参数的 sub 函数点进去看发现没有什么重要信息,找到 sub_xxxAC00(v39),点进去后同样注意到 v6 = sub_7FF7AD959470;

sub_xxx8770 里面像是一个消息处理器,根据不同的参数值分发不同的处理逻辑
而 Tlscallback 这边,进入 sub_xxx2370



switch 下有五个 case,分别对应不同的参数,case0 里面带点进 sub_xxxF280 挺复杂的,留到后面看,case1、case2、case3 没什么东西,case4 和 case5 有可疑的函数

case4 的 sub_xxxD010 里面有个异或

1
2
3
4
data=[0xDD,0xD7,0xDa,0xDC,0xC0]
for i in range(len(data)):
data[i] ^= 0xBB
print(chr(data[i]), end='')

解出来是 flag{,由此可知此处是校验 flag 的格式
case5 的 sub_xxxC850 里看到了 CRC 校验

爆破出输入

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
def generate_crc32_table():
table = []
for i in range(256):
x = i
for _ in range(8):
if x & 1:
x = (x >> 1) ^ 0xEDB88321
else:
x = x >> 1
table.append(x)
return table


data = [
0x46A95BAD, 0x1CAC84B6, 0xA67CB2B2,
0x32188937, 0x4872D39F, 0xF2A2E59B, 0x011B94D2
]

crc_table = generate_crc32_table()

result_bytes = []
for val in data:

target_crc = (~(val ^ 0xB0E0E879)) & 0xFFFFFFFF

found_idx = None
for idx, crc_val in enumerate(crc_table):
if crc_val == target_crc:
found_idx = idx
break

if found_idx is not None:
input_byte = found_idx ^ 0x79
result_bytes.append(input_byte)
else:
result_bytes.append(0xFF) # 未找到标记

printable_bytes = [b for b in result_bytes if 32 <= b <= 126]
flag_part = ''.join(chr(b) for b in printable_bytes)
print(f" {flag_part}")
print(f" {result_bytes}")
# moshui_
# [109, 111, 115, 104, 117, 105, 95]

所以这里得到部分flag moshui_
case0 的 sub_xxxF280

里面依旧有 crc ,不过这里的 crc 不是进行校验,而是使用输入的前 5 个字节(“flag{“)加上 {} 内部的前 7 个字节(v7 数组里面)生成了两个四字节的密钥(v6[0]和v6[2]),再加上另外连个直接赋值的即可得到完整密钥

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
def build_crc32_table():
table = []
for i in range(256):
crc = i
for _ in range(8):
if crc & 1:
crc = (crc >> 1) ^ 0xEDB88321
else:
crc >>= 1
table.append(crc)
return table

def crc_variant(data, table):
result = 0xFFFFFFFF
for b in data:
result ^= table[(b ^ (result & 0xFF))]
result = ~result & 0xFFFFFFFF
return result

data = bytes([0x66, 0x6C, 0x61, 0x67, 0x7B, 0x5C, 0x2F, 0xD0, 0xEC, 0x82, 0x0E, 0x67])

part1 = data[:5]
part2 = data[5:]

crc_table = build_crc32_table()

key0 = crc_variant(part1, crc_table)
key1 = 0x12345678
key2 = crc_variant(part2, crc_table)
key3 = 0x89ABCDEF

key = [key0, key1, key2, key3]
print([hex(k) for k in key])

# ['0x42b2986c', '0x12345678', '0xe40ecf0a', '0x89abcdef']

密钥 :0x42b2986c,0x12345678,0xe40ecf0a,0x89abcdef
同时在里面发现sub_xxx1500
sub_7FF7AD961500(v6, *(_QWORD *)(v5 + 8) + 12LL, 16LL, v8);
分析后可知 a1 传入的是原始密钥

sub_xxx1FF0 :将128位原始密钥扩展为52个16位轮密钥(符合IDEA标准)

sub_xxx1640 :IDEA 加密

分组长度:64 位
密钥长度:128 位
共 8 轮 + 1 次输出变换
每轮用到了三种不同的运算:
模 2¹⁶ 加法(模加)
模 2¹⁶ + 1 乘法(模乘)
按位异或 XOR

主要特点包括使用模乘(模65537)和模加(模65536)运算,以及大量的异或操作

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
__int64 __fastcall sub_7FF7AD961640(
unsigned __int16 *a1, // 输入块(4×16位)
_WORD *a2, // 输出块(4×16位)
unsigned __int16 *a3 // 轮密钥数组
)
// 输入块字节序转换(小端→主机序)
v4 = (*a1 << 8) | (*a1 >> 8); // 字1
v7 = (a1[1] << 8) | (a1[1] >> 8); // 字2
v12 = (a1[2] << 8) | (a1[2] >> 8); // 字3
v18 = (a1[3] << 8) | (a1[3] >> 8); // 字4

// 8轮混淆操作(每轮用6个子密钥)
v24 = 8; // 轮计数器
do {
// 模乘操作 (mod 0x10001) + 加法/异或
v5 = modmul(*a3++, v4); // 轮密钥与v4模乘
v8 = *a3++ + v7; // 轮密钥与v7模加
v13 = *a3++ + v12; // 轮密钥与v12模加
v19 = modmul(*a3++, v18); // 轮密钥与v18模乘
v14 = v5 ^ v13;
v15 = modmul(*a3++, v14); // 轮密钥与v14模乘
v9 = v15 + (v19 ^ v8);
v10 = modmul(*a3++, v9); // 轮密钥与v9模乘
v16 = v10 + v15;
// 状态更新
v4 = v10 ^ v5;
v7 = v13 ^ v10; // 下一轮v7
v12 = v8 ^ v16; // 下一轮v12
v18 = v16 ^ v19; // 下一轮v18
} while (--v24);

// 最终轮(4个子密钥)
v6 = modmul(*a3++, v4);
v17 = *a3++ + v12;
v11 = *a3++ + v7;
v20 = modmul(*a3, v18);

// 输出字节序转换(主机序→小端)
*a2 = (v6 << 8) | (v6 >> 8);
a2[1] = (v17 << 8) | (v17 >> 8);
a2[2] = (v11 << 8) | (v11 >> 8);
a2[3] = (v20 << 8) | (v20 >> 8);

密文在 v7 数组中:

解出第二部分:

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
sub_key = [0] * 52
inv_sub_key = [0] * 52

def plus(a, b):
return (a + b) & 0xFFFF

def times(a, b):
a = a if a != 0 else 0x10000
b = b if b != 0 else 0x10000
r = (a * b) % 0x10001
return r if r != 0x10000 else 0

def xor(a, b):
return a ^ b

def inv_plus(a):
return (-a) & 0xFFFF

def inv_times(a):
if a == 0: return 0
return pow(a, -1, 0x10001)

def subkeys_get(keys_input):
for i in range(8):
sub_key[i] = keys_input[i]

main_key = 0
for i in range(8):
main_key = (main_key << 16) | keys_input[i]

mask128 = (1 << 128) - 1

key_offset = 8
for _ in range(5):
main_key = ((main_key << 25) | (main_key >> 103)) & mask128
for j in range(8):
sub_key[key_offset + j] = (main_key >> (112 - 16 * j)) & 0xFFFF
key_offset += 8

main_key = ((main_key << 25) | (main_key >> 103)) & mask128
for i in range(4):
sub_key[48 + i] = (main_key >> (112 - 16 * i)) & 0xFFFF

def inv_subkeys_get(sub_key_list):
for i in range(6, 48, 6):
inv_sub_key[i] = inv_times(sub_key_list[48 - i])
inv_sub_key[i + 1] = inv_plus(sub_key_list[50 - i])
inv_sub_key[i + 2] = inv_plus(sub_key_list[49 - i])
inv_sub_key[i + 3] = inv_times(sub_key_list[51 - i])
for i in range(0, 48, 6):
inv_sub_key[i + 4] = sub_key_list[46 - i]
inv_sub_key[i + 5] = sub_key_list[47 - i]
inv_sub_key[0] = inv_times(sub_key_list[48])
inv_sub_key[1] = inv_plus(sub_key_list[49])
inv_sub_key[2] = inv_plus(sub_key_list[50])
inv_sub_key[3] = inv_times(sub_key_list[51])
inv_sub_key[48] = inv_times(sub_key_list[0])
inv_sub_key[49] = inv_plus(sub_key_list[1])
inv_sub_key[50] = inv_plus(sub_key_list[2])
inv_sub_key[51] = inv_times(sub_key_list[3])

def dencrypt_final(cipher_block):
i1, i2, i3, i4 = (cipher_block >> 48)&0xFFFF, (cipher_block >> 32)&0xFFFF, (cipher_block >> 16)&0xFFFF, cipher_block&0xFFFF

for i in range(0, 48, 6):
t1, t2, t3, t4 = times(i1, inv_sub_key[i]), plus(i2, inv_sub_key[i+1]), plus(i3, inv_sub_key[i+2]), times(i4, inv_sub_key[i+3])
t5, t6 = xor(t1, t3), xor(t2, t4)
t7 = times(t5, inv_sub_key[i+4])
t8 = plus(t6, t7)
t9 = times(t8, inv_sub_key[i+5])
t10 = plus(t7, t9)

i1 = xor(t1, t9)
i2 = xor(t3, t9)
i3 = xor(t2, t10)
i4 = xor(t4, t10)

y1 = times(i1, inv_sub_key[48])
y2 = plus(i3, inv_sub_key[49]) # 注意:这里I3和I2是交换使用的,这是非标准部分
y3 = plus(i2, inv_sub_key[50]) # 注意:这里I3和I2是交换使用的,这是非标准部分
y4 = times(i4, inv_sub_key[51])

return (y1 << 48) | (y2 << 32) | (y3 << 16) | y4

if __name__ == "__main__":
part2 = bytes([0x5C, 0x2F, 0xD0, 0xEC, 0x82, 0x0E, 0x67, 0x57, 0x6A, 0x9F, 0x91, 0xF6, 0x95, 0xA4, 0xAC, 0x90])
key_data = [0x6C98B242, 0x78563412, 0x3E6A6D0D, 0xEFCDAB89] # 端序转换
keys_input = [val for k in key_data for val in [(k >> 16) & 0xFFFF, k & 0xFFFF]]

cipher1 = int.from_bytes(part2[0:8], 'big')
cipher2 = int.from_bytes(part2[8:16], 'big')

subkeys_get(keys_input)
inv_subkeys_get(sub_key)

plain1 = dencrypt_final(cipher1)
plain2 = dencrypt_final(cipher2)

p1_bytes, p2_bytes = plain1.to_bytes(8, 'big'), plain2.to_bytes(8, 'big')

result_bytes = p1_bytes + p2_bytes

hex_output = ' '.join(f'{b:02X}' for b in result_bytes)
print(f"Decrypted bytes (hex): {hex_output}")
try:
str_output = result_bytes.decode('utf-8')
print(f"Decrypted string : {str_output}")
except UnicodeDecodeError:
print(f"Decrypted string (raw) : {result_bytes!r}")

# Decrypted bytes (hex): 62 75 69 6C 64 5F 74 68 69 73 5F 62 6C 6F 63 6B
# Decrypted string : build_this_block

所以第一部分的 moshui_ 和第二部分的 build_this_block 拼接起来就是完整的 flag{moshui_build_this_block}

Dragon

附件是一个 .bc 文件,用 file Dragon.bc 查看文件类型
得到Dragon.bc: LLVM IR bitcode
继续往下编译 clang Dragon.bc -o Dragon
然后 ida 打开编译好的 Dragon 文件,main 函数
读取输入后进入一个加密函数(CRC 64),最后又字符串比较,动调提取出密文

1
2
3
4
5
6
7
8
9
10
data=[ 0x47, 0x7B, 0x9F, 0x41, 0x4E, 0xE3, 0x63, 0xDC, 0xC6, 0xBF, 
0xB2, 0xE7, 0xD4, 0xF8, 0x1E, 0x03, 0x9E, 0xD8, 0x5F, 0x62,
0xBC, 0x2F, 0xD6, 0x12, 0xE8, 0x55, 0x57, 0xCC, 0xE1, 0xB6,
0xE8, 0x83, 0xCC, 0x65, 0xB6, 0x2A, 0xEB, 0xB1, 0x7B, 0xFC,
0x6B, 0xD9, 0x62, 0x2A, 0x1B, 0xCA, 0x82, 0x93, 0x87, 0xC3,
0x73, 0x76, 0xA0, 0xF8, 0xFF, 0xB1, 0xE1, 0x05, 0x8E, 0x38,
0x27, 0x16, 0xA8, 0x0D, 0xB7, 0xAA, 0xD0, 0xE8, 0x1A, 0xE6,
0xF1, 0x9E, 0x45, 0x61, 0xF2, 0xE7, 0xD2, 0x3F, 0x78, 0x92,
0x0B, 0xE6, 0x6F, 0xF5, 0xA1, 0x7C, 0xC9, 0x63, 0xAB, 0x3A,
0xB7, 0x43, 0xB0, 0xA8, 0xD3, 0x9B]

密文有 96 字节 -> 12 个 64 位整数,每两个字符为一组,生成一个 64 位的 CRC 值,与密文对比
因此可以得出输入是 24 个字符,需要爆破 12 组的 2 字符组合
加密函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
__int64 __fastcall sub_7FF6EA4B1000(char *a1, unsigned __int64 n2)
{
unsigned __int64 n8; // [rsp+0h] [rbp-28h]
unsigned __int64 n2_1; // [rsp+8h] [rbp-20h]
__int64 v5; // [rsp+10h] [rbp-18h]

v5 = -1; //0xFFFFFFFFFFFFFFFF
for ( n2_1 = 0; n2_1 < n2; ++n2_1 ) // n2=2
{
v5 ^= (unsigned __int64)(unsigned __int8)a1[n2_1] << 56;
for ( n8 = 0; n8 < 8; ++n8 ) //每个字节处理 8 位
{
if ( v5 >= 0 ) //相当于 v5 & (1 << 63) == 0
v5 *= 2;
else
v5 = (2 * v5) ^ 0x42F0E1EBA9EA3693LL;
}
}
return ~v5;
}

解密脚本:

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
data=[ 0x47, 0x7B, 0x9F, 0x41, 0x4E, 0xE3, 0x63, 0xDC, 0xC6, 0xBF, 
0xB2, 0xE7, 0xD4, 0xF8, 0x1E, 0x03, 0x9E, 0xD8, 0x5F, 0x62,
0xBC, 0x2F, 0xD6, 0x12, 0xE8, 0x55, 0x57, 0xCC, 0xE1, 0xB6,
0xE8, 0x83, 0xCC, 0x65, 0xB6, 0x2A, 0xEB, 0xB1, 0x7B, 0xFC,
0x6B, 0xD9, 0x62, 0x2A, 0x1B, 0xCA, 0x82, 0x93, 0x87, 0xC3,
0x73, 0x76, 0xA0, 0xF8, 0xFF, 0xB1, 0xE1, 0x05, 0x8E, 0x38,
0x27, 0x16, 0xA8, 0x0D, 0xB7, 0xAA, 0xD0, 0xE8, 0x1A, 0xE6,
0xF1, 0x9E, 0x45, 0x61, 0xF2, 0xE7, 0xD2, 0x3F, 0x78, 0x92,
0x0B, 0xE6, 0x6F, 0xF5, 0xA1, 0x7C, 0xC9, 0x63, 0xAB, 0x3A,
0xB7, 0x43, 0xB0, 0xA8, 0xD3, 0x9B]
# print(len(data)) # 96
target =[
int.from_bytes(bytes(data[i:i+8]),'little',)
for i in range(0,96,8)
]

def crc64(enc):
crc=0xFFFFFFFFFFFFFFFF
for byte in enc:
crc ^= (byte << 56)
crc &= 0xFFFFFFFFFFFFFFFF
for _ in range(8):
if crc &(1 << 63):
crc = (crc << 1) ^ 0x42F0E1EBA9EA3693
else:
crc <<= 1
crc &= 0xFFFFFFFFFFFFFFFF
return crc ^ 0xFFFFFFFFFFFFFFFF #取反
#爆破
flag=[]
for i in target:
found =False
for j in range(0x10000):
byte0 = j & 0xFF #低字节
byte1 = (j >> 8) & 0xFF #高字节
temp = bytes([byte0,byte1])

crc_val = crc64(temp)
if crc_val == i:
flag.append(temp)
found = True
break

final =b''.join(flag).decode('ascii')

print(f"Flag is : {final}")

flag 为flag{LLVM_1s_Fun_Ri9h7?}

WARMUP

附件是一个 .vbs 文件,是一种包含用Visual Basic Scripting Edition编写的脚本代码的文本文件,不需要编译,直接由Windows Script Host (WSH)解释执行
在网上看到相关处理方式是:用 vscode 或者 其他文本编辑器打开,把开头的 Execute 改为 wscript.echo
直接运行即可得到源代码

Execute 是用来执行代码
wscript.echo 的作用是将一个或多个字符串作为输出打印到控制台(或者在图形界面中弹出一个对话框)

运行后果然看到了源代码

不难看出这是一个标准的 rc4 ,
密文是 “90df4407ee093d309098d85a42be57a2979f1e51463a31e8d15e2fac4e84ea0df622a55c4ddfb535ef3e51e8b2528b826d5347e165912e99118333151273cc3fa8b2b3b413cf2bdb1e8c9c52865efc095a8dd89b3b3cfbb200bbadbf4a6cd4”
密钥是
“rc4key”
解密脚本

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
def rc4_decrypt(ciphertext_hex, key_string):

try:
ciphertext = bytes.fromhex(ciphertext_hex)
except ValueError:
return "WRONG FORMAT"

S = list(range(256))
key_length = len(key_string)
T = [ord(key_string[i % key_length]) for i in range(256)]

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

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

plaintext_bytes = bytearray()
for i in range(len(ciphertext)):
plaintext_bytes.append(ciphertext[i] ^ key_stream[i])


return plaintext_bytes.decode('utf-8', errors='ignore')

ciphertext_given = "90df4407ee093d309098d85a42be57a2979f1e51463a31e8d15e2fac4e84ea0df622a55c4ddfb535ef3e51e8b2528b826d5347e165912e99118333151273cc3fa8b2b3b413cf2bdb1e8c9c52865efc095a8dd89b3b3cfbb200bbadbf4a6cd4"

key = "rc4key"

decrypted_text = rc4_decrypt(ciphertext_given, key)

print(f"Flag is: {decrypted_text}")

# Flag is: flag{We1c0me_t0_XYCTF_2025_reverse_ch@lleng3_by_th3_w@y_p3cd0wn's_chall_is_r3@lly_gr3@t_&_fuN!}

题目中说 flag为flag{}包含的内容进行md5加密并用XYCTF{}包含

所以最终的 flag 为 XYCTF{5f9f46c147645dd1e2c8044325d4f93c}

Lake

静态看一堆 sub,于是打断点动调找函数逻辑,调试时发现了一个可疑的函数 sub_100001B70
前面有好几个do while 循环,这是在打印题目叙述部分

打印结束后读取输入并对输入做了一些处理

然后进入一个 switch case ,这是一个小的 vm

操作码在 word_100015470,长度为 3 个字节

每个 case 点进去都对应一个操作,三字节一组的opcode,第一个数是 case 的情况,第二个是 input 的索引值 index,第三个是操作数,从 opcode 就可以看出其实涉及到的 case 就只有三个,case 1、case 2 和 case 8,分别对应的操作是 加法、减法和异或


再往下走是一个加密和比较,比较之后同开始一样是输出一些字符串
比较处找到密文,一共 40 字节

然后看加密部分,是一个循环移位,把密文分成 10 组,每组 4 字节

简化一下它的逻辑

1
2
3
4
v5[0] = input[1] << 3 | input[2] >> 5
v5[1] = input[2] << 3 | input[3] >> 5
v5[2] = input[3] << 3 | input[0] >> 5
v5[3] = input[0] << 3 | input[1] >> 5

解密 enc1

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
# v5[0] = input[1] << 3 | input[2] >> 5
# v5[1] = input[2] << 3 | input[3] >> 5
# v5[2] = input[3] << 3 | input[0] >> 5
# v5[3] = input[0] << 3 | input[1] >> 5

def decrypt_block(o0, o1, o2, o3):
a = ((o2 & 0x07) << 5) | ((o3 & 0xF8) >> 3)
b = ((o3 & 0x07) << 5) | ((o0 & 0xF8) >> 3)
c = ((o0 & 0x07) << 5) | ((o1 & 0xF8) >> 3)
d = ((o1 & 0x07) << 5) | ((o2 & 0xF8) >> 3)
return a, b, c, d

# & 0x07 和 & 0xF8 分别是为了提取低三位和高五位

def decrypt_data(enc):
dec = [0] * 40
for k in range(10):
i = 4 * k
o0 = enc[i]
o1 = enc[i+1]
o2 = enc[i+2]
o3 = enc[i+3]
a, b, c, d = decrypt_block(o0, o1, o2, o3)
dec[i] = a
dec[i+1] = b
dec[i+2] = c
dec[i+3] = d
return dec

enc_data = [
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
]

decrypted_data = decrypt_data(enc_data)

print("Decrypted data (hex):", [hex(x) for x in decrypted_data])

decrypted_bytes = bytes(decrypted_data)
print("Decrypted data as bytes:", decrypted_bytes)


# Decrypted data (hex): ['0x63', '0x69', '0x55', '0x73', '0x66', '0x4c', '0x36', '0x3e', '0x7d', '0x7a', '0x31', '0x6e', '0x64', '0x5d', '0x2e', '0x6d', '0x66', '0x30', '0x30', '0x64', '0x5f', '0x79', '0x63', '0x64', '0x30', '0x24', '0xb8', '0x50', '0x40', '0x6e', '0x64', '0x5f', '0x69', '0x33', '0x89', '0x6b', '0x6a', '0x32', '0xf0', '0xfb']
# Decrypted data as bytes: b'ciUsfL6>}z1nd].mf00d_ycd0$\xb8P@nd_i3\x89kj2\xf0\xfb'

得到中间结果 ciUsfL6>}z1nd].mf00d_ycd0$\xb8P@nd_i3\x89kj2\xf0\xfb

接下来看那个小型 vm,整理之后如下

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
opcode=[
0x02, 0x02, 0x0C,
0x01, 0x1A, 0x55,
0x01, 0x23, 0x0C,
0x02, 0x0E, 0x09,
0x01, 0x1B, 0x06,
0x08, 0x06, 0x05,
0x08, 0x01, 0x05,
0x02, 0x1B, 0x0E,
0x02, 0x19, 0x03,
0x02, 0x1A, 0x04,
0x08, 0x04, 0x08,
0x01, 0x03, 0x0C,
0x02, 0x0C, 0x0A,
0x01, 0x25, 0x02,
0x01, 0x20, 0x02,
0x01, 0x09, 0x0C,
0x08, 0x1A, 0x05,
0x02, 0x04, 0x0D,
0x08, 0x08, 0x0F,
0x02, 0x0A, 0x0E,
0x01, 0x10, 0x07,
0x01, 0x0C, 0x07,
0x08, 0x22, 0x08,
0x08, 0x15, 0x0A,
0x01, 0x27, 0x7E,
0x02, 0x07, 0x02,
0x08, 0x0F, 0x03,
0x08, 0x0A, 0x0A,
0x01, 0x22, 0x0B,
0x02, 0x12, 0x08,
0x02, 0x19, 0x09,
0x08, 0x0E, 0x06,
0x08, 0x00, 0x05,
0x01, 0x0A, 0x08,
0x08, 0x1B, 0x07,
0x08, 0x0D, 0x06,
0x08, 0x0D, 0x04,
0x08, 0x17, 0x0C,
0x08, 0x22, 0x0E,
0x02, 0x12, 0x34,
0x01, 0x26, 0x77

]

input = [0] * 40
for i in range(0,len(opcode),3):
if opcode[i] == 0x01:
input[opcode[i+1]] += opcode[i+2]
print(f"input[{opcode[i+1]}] += {opcode[i+2]}")
elif opcode[i] == 0x02:
input[opcode[i+1]] -= opcode[i+2]
print(f"input[{opcode[i+1]}] -= {opcode[i+2]}")
elif opcode[i] == 0x03:
input[opcode[i+1]] *= opcode[i+2]
elif opcode[i] == 0x04:
if opcode[i+2] != 0:
input[opcode[i+1]] //= opcode[i+2]
elif opcode[i] == 0x05:
input[opcode[i+1]] %= opcode[i+2]
elif opcode[i] == 0x06:
input[opcode[i+1]] &= opcode[i+2]
elif opcode[i] == 0x07:
input[opcode[i+1]] |= opcode[i+2]
elif opcode[i] == 0x08:
input[opcode[i+1]] ^= opcode[i+2]
print(f"input[{opcode[i+1]}] ^= {opcode[i+2]}")

打印出它的过程

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
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

然后进行简单的逆向

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
data = b'ciUsfL6>}z1nd].mf00d_ycd0$\xb8P@nd_i3\x89kj2\xf0\xfb'
# 将bytes转换为可修改的列表
input_data = list(data)

input_data[38] -= 119
input_data[18] += 52
input_data[34] ^= 14
input_data[23] ^= 12
input_data[13] ^= 4
input_data[13] ^= 6
input_data[27] ^= 7
input_data[10] -= 8
input_data[0] ^= 5
input_data[14] ^= 6
input_data[25] += 9
input_data[18] += 8
input_data[34] -= 11
input_data[10] ^= 10
input_data[15] ^= 3
input_data[7] += 2
input_data[39] -= 126
input_data[21] ^= 10
input_data[34] ^= 8
input_data[12] -= 7
input_data[16] -= 7
input_data[10] += 14
input_data[8] ^= 15
input_data[4] += 13
input_data[26] ^= 5
input_data[9] -= 12
input_data[32] -= 2
input_data[37] -= 2
input_data[12] += 10
input_data[3] -= 12
input_data[4] ^= 8
input_data[26] += 4
input_data[25] += 3
input_data[27] += 14
input_data[27] -= 6
input_data[14] += 9
input_data[35] -= 12
input_data[26] -= 85
input_data[2] += 12
input_data[6] ^= 5
input_data[1] ^= 5

original_input = bytes(input_data)
print("Original input:", original_input)

# flag{L3@rn1ng_1n_0ld_sch00l_@nd_g3t_j0y}

所以最终的 flag 为 flag{L3@rn1ng_1n_0ld_sch00l_@nd_g3t_j0y}

moon

pyd 逆向,扔 ida 之后不难发现这是 python 3.11 写的,所以做题也用相同的 python 版本
这次发现了一个很奇妙的方法来做 pyd 的题
附件的 main.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import moon 

print("I tried my best, to live an ordinary life.")
print("But I hope you can look up and see the moonlight through reverse engineering on the streets full of sixpence.")

user_input = input("Enter your flag: ").strip()

result, error = moon.check_flag(user_input)

if error:
print("Error.")
elif result:
print("I think you have found the right way.")
else:
print("You seem to be lost.")

然后可以在这里面添加一行 help(moon)
运行 main.py,就可以得到很多有用的信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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:\ctf\reverse\pyd\moon\moon.pyd

从这里就可以直接看到关键函数和关键数据,TARGET_HEX 是密文,xor_crypt 是加密函数,而其中传入的 seed_value 应该就是下面的 SEED
因此直接修改 main.py

1
2
3
4
import moon 
from moon import xor_crypt, TARGET_HEX, SEED
flag= xor_crypt(SEED, TARGET_HEX)
print(flag)

运行发现有报错,TARGET_HEX 是一个字符串,需要转换成字节数据
稍作修改

1
2
3
4
5
6
7
import moon
from moon import xor_crypt, TARGET_HEX, SEED
import binascii
target_bytes = binascii.unhexlify(TARGET_HEX)
# 或者用 target_bytes = bytes.fromhex(TARGET_HEX)
flag= xor_crypt(SEED, target_bytes)
print(flag)

直接运行打印出 flag

flag 即为 flag{but_y0u_l00k3d_up_@t_th3_mOOn}

除了这种做法外,我依旧尝试了之前的方法
main.py 中加个 import os 和 print(os.getpid()),以便 ida attach 到 process 进行动调
frida hook 脚本

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
import frida
import sys
import time

with open("rand0m_more.js", encoding="utf-8") as f:
jscode = f.read()

def on_message(message, data):
print("[*]", message)

def main():
try:
device = frida.get_local_device()

# target = input("Enter PID or process name: ").strip()
target = 31112

try:
pid = int(target)
session = device.attach(pid)
except ValueError:
session = device.attach(target)

print(f"[*] Attached to {target}")

script = session.create_script(jscode)
script.on('message', on_message)
script.load()

print("[*] Script loaded. Monitoring Python operations...")
print("[*] Press Ctrl+C to stop and analyze.\n")


try:
while True:
time.sleep(1)
except KeyboardInterrupt:
print("\n[*] Stopping trace and analyzing...")
script.exports.stop()
time.sleep(2)

except KeyboardInterrupt:
print("\n[*] Exiting...")
except Exception as e:
print(f"[-] Error: {e}")

if __name__ == "__main__":
main()

js 脚本

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
// rand0m_more.js
console.log("[*] Python Crypto Trace Script Started");

var hook_list = [
"PyNumber_Add", "PyNumber_And", "PyNumber_Rshift", "PyNumber_Lshift",
"PyNumber_Xor", "PyNumber_InPlaceRshift", "PyNumber_InPlaceAdd",
"PyNumber_Multiply", "PyNumber_Power", "PyNumber_Index",
"PyObject_RichCompare", "PyNumber_Remainder"
];

// 全局变量用于跟踪执行流程
var callSequence = 0;
var traceData = [];
var isTracing = true;

// 兼容的 findExportByName 函数
function findExportByName(moduleName, exportName) {
if (typeof Module.findExportByName === 'function') {
return Module.findExportByName(moduleName, exportName);
} else if (typeof Module.getExportByName === 'function') {
return Module.getExportByName(moduleName, exportName);
} else if (typeof Module.getGlobalExportByName === 'function') {
if (moduleName === null) {
return Module.getGlobalExportByName(exportName);
} else {
const module = Process.findModuleByName(moduleName);
if (module) {
const exports = module.enumerateExports();
for (let i = 0; i < exports.length; i++) {
if (exports[i].name === exportName) {
return exports[i].address;
}
}
}
}
}
return null;
}

// 优化的 Python 长整型解析
function parsePyLong(addr) {
try {
if (!addr || addr.isNull()) {
return 0;
}

const ob_size = addr.add(0x10).readS64();
if (ob_size === 0) return 0;

const isNegative = ob_size < 0;
const numdigits = Math.abs(Number(ob_size));

if (numdigits > 0x1000) {
const digit = addr.add(0x18).readU32();
return isNegative ? -digit : digit;
}

if (numdigits > 2) {
let bigVal = 0n;
for (let i = 0; i < numdigits; i++) {
const digit = addr.add(0x18 + 4 * i).readU32();
bigVal += BigInt(digit) * (1n << (30n * BigInt(i)));
}
return isNegative ? -bigVal : bigVal;
}

let val = 0;
for (let i = 0; i < numdigits; i++) {
val += addr.add(0x18 + 4 * i).readU32() * Math.pow(2, 30 * i);
}
return isNegative ? -val : val;

} catch (e) {
return 0;
}
}

// 格式化数值为十六进制
function formatHex(val, minWidth = 8) {
if (typeof val === 'bigint') {
return val.toString(16).toUpperCase().padStart(minWidth, '0');
}
return val.toString(16).toUpperCase().padStart(minWidth, '0');
}

// 操作类型分类
function getOperationType(op) {
if (op.includes('Add') || op.includes('Subtract') || op.includes('Multiply')) {
return 'ARITHMETIC';
} else if (op.includes('And') || op.includes('Or') || op.includes('Xor')) {
return 'BITWISE';
} else if (op.includes('Rshift') || op.includes('Lshift')) {
return 'SHIFT';
} else if (op.includes('Power')) {
return 'POWER';
} else if (op.includes('RichCompare')) {
return 'COMPARE';
} else {
return 'OTHER';
}
}

// 结构化输出
function logOperation(seq, op, arg1, arg2, result) {
const type = getOperationType(op);
const arg1Hex = formatHex(arg1, 16);
const arg2Hex = formatHex(arg2, 16);
const resultHex = formatHex(result, 16);

console.log(`\n[${seq.toString().padStart(4, '0')}] ${type.padEnd(10)} | ${op.padEnd(20)}`);
console.log(` INPUT: 0x${arg1Hex}`);
console.log(` PARAM: 0x${arg2Hex}`);
console.log(` OUTPUT: 0x${resultHex}`);

// 保存到分析数据
traceData.push({
sequence: seq,
type: type,
operation: op,
arg1: arg1,
arg2: arg2,
result: result,
timestamp: Date.now()
});
}

// 最简化的分析流程
function analyzeEncryptionFlow() {
console.log("\n" + "=".repeat(50));
console.log(" TRACE COMPLETED");
console.log("=".repeat(50));

// 只保留位移操作的详细分析
const shiftOps = traceData.filter(op => op.type === 'SHIFT');

if (shiftOps.length > 0) {
console.log("\nSHIFT OPERATIONS:");
shiftOps.forEach(op => {
const direction = op.operation.includes('Lshift') ? 'LEFT' : 'RIGHT';
console.log(` [${op.sequence}] ${direction.padEnd(5)} shift by ${op.arg2} bits: 0x${formatHex(op.arg1)} -> 0x${formatHex(op.result)}`);
});
}

console.log("=".repeat(50));
}

// 主 Hook 逻辑
function installHooks() {
console.log("[*] Installing Python operation hooks...");

let pythonModule = Process.findModuleByName('python311.dll') ||
Process.findModuleByName('python312.dll') ||
Process.findModuleByName('python310.dll') ||
Process.findModuleByName('python39.dll');

if (!pythonModule) {
console.error('Python DLL module not found (tried python311.dll, python312.dll, python310.dll, python39.dll)');
return;
}

console.log(`[*] Found Python module: ${pythonModule.name} at ${pythonModule.base}`);
console.log(`[*] Starting trace...\n`);

let successCount = 0;

for (let i = 0; i < hook_list.length; i++) {
const op = hook_list[i];

try {
const funcAddress = findExportByName(pythonModule.name, op);

if (!funcAddress || funcAddress.isNull()) {
console.warn(`[WARN] Function not found: ${op}`);
continue;
}

Interceptor.attach(funcAddress, {
onEnter: function(args) {
if (!isTracing) return;

this.op = op;
this.sequence = ++callSequence;
this.timestamp = Date.now();

try {
this.arg1 = parsePyLong(args[0]);
this.arg2 = parsePyLong(args[1]);
} catch (e) {
this.arg1 = 0;
this.arg2 = 0;
}
},

onLeave: function(retval) {
if (!isTracing) return;

try {
const result = parsePyLong(retval);
logOperation(this.sequence, this.op, this.arg1, this.arg2, result);
} catch (e) {
console.log(`[${this.sequence}] ${this.op} completed with error: ${e.message}`);
}
}
});

successCount++;

} catch (e) {
console.error(`Failed to hook ${op}: ${e.message}`);
}
}

console.log(`[*] Successfully hooked ${successCount}/${hook_list.length} functions\n`);
}

// 简化的控制命令
rpc.exports = {
stop: function() {
isTracing = false;
console.log("\n[*] Tracing stopped. Analyzing...");
analyzeEncryptionFlow();
}
};

// 初始化
setTimeout(() => {
installHooks();
}, 1000);

trace 出来的只有两种,一个是 PyObject_RichCompare,一个是 PyNumber_Xor
这里有个注意的地方只有在输入长度正确的时候它才能够完整输出所有的异或,否则 trace 出来的 xor 的数量是不准确的,而字符串长度我自己做的时候没找出来,后来看到 liv 师傅的 wp 之后才发现输入的长度需要满足 35 字节长度
密文的话在 ida 中 string 窗口不难发现426b87abd0ceaa3c58761bbb0172606dd8ab064491a2a76af9a93e1ae56fa84206a2f7

trace 结果截取出 Xor 的部分

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
[0011] BITWISE    | PyNumber_Xor
INPUT: 0x0000000000000033
PARAM: 0x0000000000000024
OUTPUT: 0x0000000000000017

[0019] BITWISE | PyNumber_Xor
INPUT: 0x0000000000000033
PARAM: 0x0000000000000007
OUTPUT: 0x0000000000000034

[0029] BITWISE | PyNumber_Xor
INPUT: 0x0000000000000033
PARAM: 0x00000000000000E6
OUTPUT: 0x00000000000000D5

[0034] BITWISE | PyNumber_Xor
INPUT: 0x0000000000000033
PARAM: 0x00000000000000CC
OUTPUT: 0x00000000000000FF

[0040] BITWISE | PyNumber_Xor
INPUT: 0x0000000000000033
PARAM: 0x00000000000000AB
OUTPUT: 0x0000000000000098

[0045] BITWISE | PyNumber_Xor
INPUT: 0x0000000000000033
PARAM: 0x00000000000000AC
OUTPUT: 0x000000000000009F

[0051] BITWISE | PyNumber_Xor
INPUT: 0x0000000000000033
PARAM: 0x00000000000000DF
OUTPUT: 0x00000000000000E

[0052] BITWISE | PyNumber_Xor
INPUT: 0x0000000000000033
PARAM: 0x0000000000000048
OUTPUT: 0x000000000000007B

[0053] BITWISE | PyNumber_Xor
INPUT: 0x0000000000000033
PARAM: 0x0000000000000007
OUTPUT: 0x0000000000000034

[0054] BITWISE | PyNumber_Xor
INPUT: 0x0000000000000033
PARAM: 0x000000000000000F
OUTPUT: 0x000000000000003C

[0055] BITWISE | PyNumber_Xor
INPUT: 0x0000000000000033
PARAM: 0x000000000000002B
OUTPUT: 0x0000000000000018

[0056] BITWISE | PyNumber_Xor
INPUT: 0x0000000000000033
PARAM: 0x00000000000000CE
OUTPUT: 0x00000000000000FD

[0057] BITWISE | PyNumber_Xor
INPUT: 0x0000000000000033
PARAM: 0x000000000000005E
OUTPUT: 0x000000000000006D

[0058] BITWISE | PyNumber_Xor
INPUT: 0x0000000000000033
PARAM: 0x000000000000001E
OUTPUT: 0x000000000000002D

[0059] BITWISE | PyNumber_Xor
INPUT: 0x0000000000000033
PARAM: 0x0000000000000050
OUTPUT: 0x0000000000000063

[0060] BITWISE | PyNumber_Xor
INPUT: 0x0000000000000033
PARAM: 0x000000000000005D
OUTPUT: 0x000000000000006E

[0061] BITWISE | PyNumber_Xor
INPUT: 0x0000000000000033
PARAM: 0x00000000000000B3
OUTPUT: 0x0000000000000080

[0062] BITWISE | PyNumber_Xor
INPUT: 0x000000000000006A
PARAM: 0x0000000000000098
OUTPUT: 0x00000000000000F2

[0063] BITWISE | PyNumber_Xor
INPUT: 0x0000000000000033
PARAM: 0x0000000000000062
OUTPUT: 0x0000000000000051

[0064] BITWISE | PyNumber_Xor
INPUT: 0x0000000000000033
PARAM: 0x000000000000001B
OUTPUT: 0x0000000000000028

[0065] BITWISE | PyNumber_Xor
INPUT: 0x0000000000000033
PARAM: 0x00000000000000E4
OUTPUT: 0x00000000000000D7

[0066] BITWISE | PyNumber_Xor
INPUT: 0x0000000000000033
PARAM: 0x00000000000000D2
OUTPUT: 0x00000000000000E1

[0067] BITWISE | PyNumber_Xor
INPUT: 0x0000000000000033
PARAM: 0x00000000000000F8
OUTPUT: 0x00000000000000CB

[0068] BITWISE | PyNumber_Xor
INPUT: 0x0000000000000033
PARAM: 0x000000000000002A
OUTPUT: 0x0000000000000019

[0069] BITWISE | PyNumber_Xor
INPUT: 0x0000000000000033
PARAM: 0x000000000000008D
OUTPUT: 0x00000000000000BE

[0070] BITWISE | PyNumber_Xor
INPUT: 0x0000000000000033
PARAM: 0x00000000000000F6
OUTPUT: 0x00000000000000C5

[0071] BITWISE | PyNumber_Xor
INPUT: 0x0000000000000033
PARAM: 0x000000000000004A
OUTPUT: 0x0000000000000079

[0072] BITWISE | PyNumber_Xor
INPUT: 0x0000000000000033
PARAM: 0x0000000000000072
OUTPUT: 0x0000000000000041

[0073] BITWISE | PyNumber_Xor
INPUT: 0x0000000000000033
PARAM: 0x00000000000000D6
OUTPUT: 0x00000000000000E5

[0074] BITWISE | PyNumber_Xor
INPUT: 0x0000000000000033
PARAM: 0x0000000000000030
OUTPUT: 0x0000000000000003

[0075] BITWISE | PyNumber_Xor
INPUT: 0x0000000000000033
PARAM: 0x00000000000000C5
OUTPUT: 0x00000000000000F6

[0076] BITWISE | PyNumber_Xor
INPUT: 0x0000000000000033
PARAM: 0x000000000000000D
OUTPUT: 0x000000000000003E

[0077] BITWISE | PyNumber_Xor
INPUT: 0x0000000000000033
PARAM: 0x0000000000000049
OUTPUT: 0x000000000000007A

[0078] BITWISE | PyNumber_Xor
INPUT: 0x0000000000000033
PARAM: 0x00000000000000CC
PARAM: 0x00000000000000CC
OUTPUT: 0x00000000000000FF

[0079] BITWISE | PyNumber_Xor
INPUT: 0x0000000000000033
PARAM: 0x000000000000008A
OUTPUT: 0x00000000000000B9

解密脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
ciphertext_hex = "426b87abd0ceaa3c58761bbb0172606dd8ab064491a2a76af9a93e1ae56fa84206a2f7"
xor_key_hex = "2407e6ccabacdf48070f2bce5e1e505db398621be4d2f82a8df64a72d630c50d49cc8a"
ciphertext = bytes.fromhex(ciphertext_hex)
xor_key = bytes.fromhex(xor_key_hex)

plaintext = bytearray()
for i in range(len(ciphertext)):
plaintext.append(ciphertext[i] ^ xor_key[i])

result = plaintext.decode('utf-8', errors='ignore')

print(f"Decrypted flag: {result}")

# Decrypted flag: flag{but_y0u_l00k3d_up_@t_th3_mOOn}

EzObf

找到 main 函数,跟进看到 main_0,发现被混淆了

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
.vmpt:00000001401F4000 ; int __fastcall main_0(int argc, const char **argv, const char **envp)
.vmpt:00000001401F4000 main_0 proc near ; CODE XREF: main↑p
.vmpt:00000001401F4000 pushfq
.vmpt:00000001401F4001 push rax
.vmpt:00000001401F4002 push rbx
.vmpt:00000001401F4003 push rcx
.vmpt:00000001401F4004 push rdx
.vmpt:00000001401F4005 push rsi
.vmpt:00000001401F4006 push rdi
.vmpt:00000001401F4007 push r8
.vmpt:00000001401F4009 push r9
.vmpt:00000001401F400B push rsp
.vmpt:00000001401F400C push rbp
.vmpt:00000001401F400D pop rbp
.vmpt:00000001401F400E pop rsp
.vmpt:00000001401F400F pop r9
.vmpt:00000001401F4011 pop r8
.vmpt:00000001401F4013 pop rdi
.vmpt:00000001401F4014 pop rsi
.vmpt:00000001401F4015 pop rdx
.vmpt:00000001401F4016 pop rcx
.vmpt:00000001401F4017 pop rbx
.vmpt:00000001401F4018 pop rax
.vmpt:00000001401F4019 popfq
.vmpt:00000001401F401A push rbp
.vmpt:00000001401F401B pushfq
.vmpt:00000001401F401C push rax
.vmpt:00000001401F401D push rbx
.vmpt:00000001401F401E push rcx
.vmpt:00000001401F401F push rdx
.vmpt:00000001401F4020 push rsi
.vmpt:00000001401F4021 push rdi
.vmpt:00000001401F4022 push r8
.vmpt:00000001401F4024 push r9
.vmpt:00000001401F4026 push rsp
.vmpt:00000001401F4027 push rbp
.vmpt:00000001401F4028 and rax, 0
.vmpt:00000001401F402C xor rbx, rbx
.vmpt:00000001401F402F call $+5
.vmpt:00000001401F4034 pop rax
.vmpt:00000001401F4035 mov ebx, 0A1000064h
.vmpt:00000001401F403A rol ebx, 28h
.vmpt:00000001401F403D add rax, rbx
.vmpt:00000001401F4040 jmp rax
.vmpt:00000001401F4040 main_0 endp

这后面也是一大片未识别成代码的数据,动调 f7 步进
可以这之后也一直都是这个结构
把真实指令藏在 popfqpushfq 之间,最后通过 jmp rax 进行跳转
对抗的思路就是把中间有效的指令提取出来,其他的 nop 掉,再计算出每个结构最后的跳转地址


XYCTF2025 Reverse 方向复现
http://example.com/2025/07/20/XYCTF2025/
作者
Eleven
发布于
2025年7月20日
许可协议