# 2025 羊城杯逆向 wp

新博客,随便水一篇先

# GD1

这个图标一眼 godot 逆向,使用 gdre_tools 工具进行解包,一般来说,里面的一些数据可能是被加密过的,但这个题没有,直接就能打开

godot 的源代码语言以.gd 文件结尾,gd 文件编译后会变成 gdc,而 remap 文件是用来资源重映射的,一个 main.gd.remap 就应该指向 main.gdc 文件

分析 main.gdc,发现当 socre 到达 7906 分时,会初始化一个字符串并显示,同构出代码逻辑运行即可

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
#include <stdint.h>
#include <iostream>
#include <string.h>
int main()
{
std::string a = "000001101000000001100101000010000011000001100111000010000100000001110000000100100011000100100000000001100111000100010111000001100110000100000101000001110000000010001001000100010100000001000101000100010111000001010011000010010111000010000000000001010000000001000101000010000001000100000110000100010101000100010010000001110101000100000111000001000101000100010100000100000100000001001000000001110110000001111001000001000101000100011001000001010111000010000111000010010000000001010110000001101000000100000001000010000011000100100101";
std::string flag = "";
for(int i = 0; i < a.length(); i += 12)
{
std::string bin_chunk = a.substr(i,12);
std::string hundreds_str = bin_chunk.substr(0,4);
std::string tens_str = bin_chunk.substr(4,4);
std::string units_str = bin_chunk.substr(8,4);

int hundreds = std::stoi(hundreds_str,nullptr,2);
int tens = std::stoi(tens_str,nullptr,2);
int units = std::stoi(units_str,nullptr,2);
int ascii_value = hundreds * 100 + tens * 10 + units;
flag += ascii_value;
}

std::cout << flag << std::endl;
return 0;
}
// DASCTF{xCuBiFYr-u5aP2-QjspKk-rh0LO-w9WZ8DeS}

# chal

将 pyd 文件拖到 ida 里搜索字符串可以看到环境是 python3.9,在同版本环境里看一下基本信息

看到这个模块里自己实现的一些函数和一个 unicorn 实例

配置 python 环境进行调试

参数这边可以看到所有函数和进行比对的密文

将其中某个表达式进行化简后如下

1
methodcaller(exec(eval(num1),eval(num2),eval(num3))(e);

可以肯定这是在调用 unicorn 中的一个方法,假如 num1 为 reg_write,num2,num3 为参数,那么整个表达式类似于

1
e.reg_write(114, 0x1919810)

为了查看这个 python 函数的执行流,可以尝试将 m 函数改为 print 函数,将 unicorn 模拟执行的函数打印出来

在初始化两个地址以后,向一段地址写入了机器码,把这段机器码 pacth 到 ida 进行分析

已知加密写解密脚本即可,未知 a2 但是 char 类型可以尝试爆破

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

int main()
{
uint8_t data[] = {0xe3,0x6e,0x4c,0xbc,0x73,0x31,0xb4,0xba,0x99,0xde,0xdc,0xb7,0x45,0x99,0x30,0xde,0x6c,0x30,0xba,0x53,0x51,0x8e,0x4b,0x30,0x6e,0x99,0x03,0x30,0xaf,0xb7,0x08,0x30,0x8e,0xb5,0xb7,0xba,0x93,0x30,0x2a,0x3f,0x2a,0xdc,0x10,0x02};
int key;
for(int i = 0; i < 255; i++)
{
int temp = (8 * 'D' + (i ^ 'D') + 32 * 'D') & 0xFF;
if( temp == 0xE3)
{
printf("key = %X\n",i);
key = i;
break;
}
}

for(int i = 0; i < 44; i++)
{
for(int j = 33; j < 127; j++)
{
int temp = (8 * j + (key ^ j) + 32 * j) & 0xFF;
if(temp == data[i])
{
printf("%c",j);
}
}
}

return 0;
}
// DASCTF{un1c0rn_1s_u4fal_And_h0w_ab0ut_exec?}

# ez_py

题目给了一个 pyinstall 打包的 exe 文件,版本较高,尝试使用在线网站反编译,有部分错误,不能直接看。再将其转成 Python 字节码,结合反编译的 py 文件一起丢给 ai 更正,对比输出的回显得到正确的 key.py

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
# Decompiled with PyLingual (https://pylingual.io) and manual analysis
# Internal filename: key.py
# Bytecode version: 3.13.0rc3 (3571)
# Source timestamp: 1970-01-01 00:00:00 UTC (0)

import ast
import types
import sys

# A list of target integer values used for verification.
o0o0o0 = [105084753, 3212558540, 351342182, 844102737, 2002504052, 356536456, 2463183122, 615034880, 1156203296]

def changli(o0o0o1, o0o0o2, o0o0o3):
"""
An encryption function that appears to be a variant of the TEA (Tiny Encryption Algorithm).
It takes two values and a key, then performs 32 rounds of bitwise operations.
"""
o0o0o4 = 2269471011
o0o0o5 = o0o0o3 & 4294967295
o0o0o6 = (o0o0o3 >> 8 ^ 305419896) & 4294967295
o0o0o7 = (o0o0o3 << 4 ^ 2271560481) & 4294967295
o0o0o8 = (o0o0o3 >> 12 ^ 2882400000) & 4294967295
o0o0o9 = o0o0o1 & 4294967295
o0o0o10 = o0o0o2 & 4294967295
o0o0o11 = 0
for _ in range(32):
o0o0o11 = (o0o0o11 + o0o0o4) & 4294967295
o0o0o9 = (o0o0o9 + (((o0o0o10 << 4) + o0o0o5) ^ (o0o0o10 + o0o0o11) ^ ((o0o0o10 >> 4) + o0o0o6))) & 4294967295
o0o0o10 = (o0o0o10 + (((o0o0o9 << 4) + o0o0o7) ^ (o0o0o9 + o0o0o11) ^ ((o0o0o9 >> 4) + o0o0o8))) & 4294967295
return (o0o0o9, o0o0o10)

def Shorekeeper(o0o0o12):
"""Splits a 32-bit integer into two 16-bit integers."""
o0o0o13 = o0o0o12 >> 16
o0o0o14 = o0o0o12 & 65535
return (o0o0o13, o0o0o14)

def Kathysia(o0o0o15, o0o0o16):
"""Combines two 16-bit integers into a single 32-bit integer."""
return (o0o0o15 << 16) | (o0o0o16 + 0)

def Phrolova(o0o0o17):
"""
Dynamically generates and injects a function named 'Carlotta' into the global scope.
This is a form of runtime code generation and obfuscation.
"""
o0oA = 'Carlotta'
o0oB = ['o0oC', 'o0oD', 'o0oE', 'o0oF']
o0oG = []
o0oG.append(ast.Assign(targets=[ast.Name(id='o0oH', ctx=ast.Store())], value=ast.Constant(305419896)))
o0oG.append(ast.Assign(targets=[ast.Name(id='o0oI', ctx=ast.Store())], value=ast.BinOp(ast.Name(id='o0oE', ctx=ast.Load()), ast.BitAnd(), ast.Constant(65535))))
o0oG.append(ast.Assign(targets=[ast.Name(id='o0oJ', ctx=ast.Store())], value=ast.BinOp(ast.BinOp(ast.Name(id='o0oE', ctx=ast.Load()), ast.RShift(), ast.Constant(16)), ast.BitAnd(), ast.Constant(65535))))
o0oG.append(ast.Assign(targets=[ast.Name(id='o0oK', ctx=ast.Store())], value=ast.BinOp(ast.BinOp(ast.Name(id='o0oE', ctx=ast.Load()), ast.BitXor(), ast.Name(id='o0oF', ctx=ast.Load())), ast.BitAnd(), ast.Constant(65535))))
o0oG.append(ast.Assign(targets=[ast.Name(id='o0oL', ctx=ast.Store())], value=ast.BinOp(ast.BinOp(ast.BinOp(ast.Name(id='o0oE', ctx=ast.Load()), ast.RShift(), ast.Constant(8)), ast.BitXor(), ast.Name(id='o0oF', ctx=ast.Load())), ast.BitAnd(), ast.Constant(65535))))
o0oG.append(ast.Assign(targets=[ast.Name(id='o0oM', ctx=ast.Store())], value=ast.BinOp(ast.BinOp(ast.Name(id='o0oH', ctx=ast.Load()), ast.Mult(), ast.BinOp(ast.Name(id='o0oF', ctx=ast.Load()), ast.Add(), ast.Constant(1))), ast.BitAnd(), ast.Constant(4294967295))))
o0oG.append(ast.Assign(targets=[ast.Name(id='o0oN', ctx=ast.Store())], value=ast.BinOp(ast.BinOp(ast.BinOp(ast.BinOp(ast.Name(id='o0oD', ctx=ast.Load()), ast.LShift(), ast.Constant(5)), ast.Add(), ast.Name(id='o0oI', ctx=ast.Load())), ast.BitXor(), ast.BinOp(ast.Name(id='o0oD', ctx=ast.Load()), ast.Add(), ast.Name(id='o0oM', ctx=ast.Load()))), ast.BitXor(), ast.BinOp(ast.BinOp(ast.Name(id='o0oD', ctx=ast.Load()), ast.RShift(), ast.Constant(5)), ast.Add(), ast.Name(id='o0oJ', ctx=ast.Load())))))
o0oG.append(ast.Assign(targets=[ast.Name(id='o0oP', ctx=ast.Store())], value=ast.BinOp(ast.BinOp(ast.Name(id='o0oC', ctx=ast.Load()), ast.Add(), ast.Name(id='o0oN', ctx=ast.Load())), ast.BitAnd(), ast.Constant(65535))))
o0oG.append(ast.Assign(targets=[ast.Name(id='o0oN', ctx=ast.Store())], value=ast.BinOp(ast.BinOp(ast.BinOp(ast.BinOp(ast.Name(id='o0oP', ctx=ast.Load()), ast.LShift(), ast.Constant(5)), ast.Add(), ast.Name(id='o0oK', ctx=ast.Load())), ast.BitXor(), ast.BinOp(ast.Name(id='o0oP', ctx=ast.Load()), ast.Add(), ast.Name(id='o0oM', ctx=ast.Load()))), ast.BitXor(), ast.BinOp(ast.BinOp(ast.Name(id='o0oP', ctx=ast.Load()), ast.RShift(), ast.Constant(5)), ast.Add(), ast.Name(id='o0oL', ctx=ast.Load())))))
o0oG.append(ast.Assign(targets=[ast.Name(id='o0oQ', ctx=ast.Store())], value=ast.BinOp(ast.BinOp(ast.Name(id='o0oD', ctx=ast.Load()), ast.Add(), ast.Name(id='o0oN', ctx=ast.Load())), ast.BitAnd(), ast.Constant(65535))))
o0oG.append(ast.Return(ast.Tuple(elts=[ast.Name(id='o0oP', ctx=ast.Load()), ast.Name(id='o0oQ', ctx=ast.Load())], ctx=ast.Load())))
o0oU = ast.FunctionDef(name=o0oA, args=ast.arguments(posonlyargs=[], args=[ast.arg(arg=a) for a in o0oB], kwonlyargs=[], kw_defaults=[], defaults=[]), body=o0oG, decorator_list=[])
o0oV = ast.parse('\ndef _tea_helper_func(a, b, c):\n magic1 = (a ^ b) & 0xDEADBEEF\n magic2 = (c << 3) | (a >> 5)\n return (magic1 + magic2 - (b & 0xCAFEBABE)) & 0xFFFFFFFF\n\ndef _fake_tea_round(x, y):\n return ((x * 0x9E3779B9) ^ (y + 0x12345678)) & 0xFFFFFFFF\n\n_tea_magic_delta = 0x9E3779B9 ^ 0x12345678\n_tea_dummy_keys = [0x1111, 0x2222, 0x3333, 0x4444]\n').body
o0oW = ast.Module(body=[o0oU] + o0oV, type_ignores=[])
ast.fix_missing_locations(o0oW)
o0oX = compile(o0oW, filename='<tea_obf_ast>', mode='exec')
o0oY = {}
exec(o0oX, o0oY)
if o0oA in o0oY:
o0o0o17[o0oA] = o0oY[o0oA]
return None

# This function call executes the dynamic code generation
Phrolova(globals())

def shouan(o0o0o32):
"""Processes the user's input list through a series of transformations."""
if len(o0o0o32) != 9:
raise ValueError('需要输入9个key')
o0o0o35 = []
for o0o0o49, o0o0o34 in enumerate(o0o0o32):
o0o0o33 = o0o0o49 * o0o0o49
o0o0o36, o0o0o37 = Shorekeeper(o0o0o34)
# The 'Carlotta' function was created dynamically by Phrolova
o0o0o38, o0o0o39 = Carlotta(o0o0o36, o0o0o37, o0o0o49 + 2025, o0o0o33)
o0o0o40 = Kathysia(o0o0o38, o0o0o39)
o0o0o35.append(o0o0o40)
o0o0o41 = []
for i in range(8):
(o0o0o35[i], o0o0o35[i + 1]) = changli(o0o0o35[i], o0o0o35[i + 1], 2025)
o0o0o41.append(o0o0o35[i])
o0o0o41.append(o0o0o35[8])
return o0o0o41

def jinhsi():
"""Main function to handle user input and check the key."""
print('请输入9个数字:')
try:
o0o0o46 = input().strip()
if ',' in o0o0o46:
o0o0o42 = o0o0o46.split(',')
else:
o0o0o42 = o0o0o46.split()
if len(o0o0o42) != 9:
print('错误: 需要输入9个数')
return None
o0o0o43 = []
for o0o0o44 in o0o0o42:
try:
o0o0o45 = int(o0o0o44.strip())
o0o0o43.append(o0o0o45)
except ValueError:
print(f"错误: '{o0o0o44}' 不是有效的整数")
return None
o0o0o48 = shouan(o0o0o43)
if o0o0o48 == o0o0o0:
print('正确!这是真正的key')
sys.exit(0)
else:
print('错误!这不是正确的key')
print(f'你的结果: {o0o0o48}')
sys.exit(0)
except Exception as o0o0o47:
print(f'发生错误: {o0o0o47}')
return None

if __name__ == '__main__':
jinhsi()

同构一个比较简洁的代码

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

void Carlotta(uint16_t v0, uint16_t v1, uint32_t key1, uint32_t key2, uint16_t enc_data[])
{
uint32_t MASK_16 = 0xFFFF;
uint32_t MASK_32 = 0xFFFFFFFF;
uint32_t const_val = 305419896U;
uint32_t sub_k0 = key1 & MASK_16;
uint32_t sub_k1 = (key1 >> 16) & MASK_16;
uint32_t sub_k2 = (key1 ^ key2) & MASK_16;
uint32_t sub_k3 = ((key1 >> 8) ^ key2) & MASK_16;

uint32_t round_key = (const_val * (key2 + 1)) & MASK_32;

uint32_t round1_out = (((v1 << 5) + sub_k0) ^ (v1 + round_key) ^ ((v1 >> 5) + sub_k1));
uint32_t res0 = (v0 + round1_out) & MASK_16;

uint32_t round2_out = (((res0 << 5) + sub_k2) ^ (res0 + round_key) ^ ((res0 >> 5) + sub_k3));
uint32_t res1 = (v1 + round2_out) & MASK_16;

enc_data[0] = (uint16_t)res0;
enc_data[1] = (uint16_t)res1;
}

int main()
{
uint32_t target_data[] = {105084753, 3212558540, 351342182, 844102737, 2002504052, 356536456, 2463183122, 615034880, 1156203296};

uint32_t input[] = {1111, 2222, 3333, 4444, 5555, 6666, 7777, 8888, 9999};

uint32_t stage1_output[9]{};

for(int i = 0; i < 9; i++)
{
uint32_t key1 = i + 2025;
uint32_t key2 = i * i;

uint16_t high_16 = (input[i] >> 16) & 0xFFFF;
uint16_t low_16 = input[i] & 0xFFFF;

uint16_t enc_data[2];
Carlotta(high_16, low_16, key1, key2, enc_data);

uint32_t processed_num = (uint32_t(enc_data[0]) << 16) | enc_data[1];
stage1_output[i] = processed_num;
}


for(int i = 0; i < 8; i++)
{

uint32_t A = stage1_output[i];
uint32_t B = stage1_output[i + 1];
uint32_t MASK_32 = 0xFFFFFFFF;
uint32_t DELTA = 2269471011U;
uint32_t sum_val = 0;
uint32_t key = 2025;

uint32_t k0 = key & MASK_32;
uint32_t k1 = (key >> 8 ^ 305419896U) & MASK_32;
uint32_t k2 = (key << 4 ^ 2271560481U) & MASK_32;
uint32_t k3 = (key >> 12 ^ 2882400000U) & MASK_32;

for(int j = 0; j < 32; j++)
{
sum_val = (sum_val + DELTA) & MASK_32;
A = (A + (((B << 4) + k0) ^ (B + sum_val) ^ ((B >> 4) + k1))) & MASK_32;
B = (B + (((A << 4) + k2) ^ (A + sum_val) ^ ((A >> 4) + k3))) & MASK_32;
}

stage1_output[i] = A;
stage1_output[i + 1] = B;
}

for(int i = 0; i < 9; i++)
{
printf("%d ",stage1_output[i]);
}

return 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
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
void dec_Carlotta(uint16_t res0, uint16_t res1, uint32_t key1, uint32_t key2, uint16_t enc_data[])
{
uint32_t MASK_16 = 0xFFFF;
uint32_t MASK_32 = 0xFFFFFFFF;
uint32_t const_val = 305419896U;
uint32_t sub_k0 = key1 & MASK_16;
uint32_t sub_k1 = (key1 >> 16) & MASK_16;
uint32_t sub_k2 = (key1 ^ key2) & MASK_16;
uint32_t sub_k3 = ((key1 >> 8) ^ key2) & MASK_16;

uint32_t round_key = (const_val * (key2 + 1)) & MASK_32;

uint32_t round2_out = (((res0 << 5) + sub_k2) ^ (res0 + round_key) ^ ((res0 >> 5) + sub_k3));
uint32_t v1 = (res1 - round2_out) & MASK_16;

uint32_t round1_out = (((v1 << 5) + sub_k0) ^ (v1 + round_key) ^ ((v1 >> 5) + sub_k1));
uint32_t v0 = (res0 - round1_out) & MASK_16;

enc_data[0] = (uint16_t)v0;
enc_data[1] = (uint16_t)v1;
}


int main()
{
uint32_t target_data[] = {105084753, 3212558540, 351342182, 844102737, 2002504052, 356536456, 2463183122, 615034880, 1156203296};

uint32_t input[9] = {0};

uint32_t stage1_output[9] = {0x37F76176,0xD7A18B0D,0xE9A7E164,0xF36E2D7B,0x7829C84,0x1646BD50,0x45D02BC6,0x1CE9D62C,0x460FECFC};

for(int i = 7; i >= 0; i--)
{

uint32_t A = stage1_output[i];
uint32_t B = stage1_output[i + 1];
uint32_t MASK_32 = 0xFFFFFFFF;
uint32_t DELTA = 2269471011U;
uint32_t sum_val = DELTA * 32;
uint32_t key = 2025;

uint32_t k0 = key & MASK_32;
uint32_t k1 = (key >> 8 ^ 305419896U) & MASK_32;
uint32_t k2 = (key << 4 ^ 2271560481U) & MASK_32;
uint32_t k3 = (key >> 12 ^ 2882400000U) & MASK_32;

for(int j = 0; j < 32; j++)
{
B = (B - (((A << 4) + k2) ^ (A + sum_val) ^ ((A >> 4) + k3))) & MASK_32;
A = (A - (((B << 4) + k0) ^ (B + sum_val) ^ ((B >> 4) + k1))) & MASK_32;
sum_val = (sum_val - DELTA) & MASK_32;
}

stage1_output[i] = A;
stage1_output[i + 1] = B;
}

for(int i = 0; i < 9; i++)
{
uint32_t key1 = i + 2025;
uint32_t key2 = i * i;

uint16_t high_16 = (stage1_output[i] >> 16) & 0xFFFF;
uint16_t low_16 = stage1_output[i] & 0xFFFF;

uint16_t enc_data[2];
dec_Carlotta(high_16, low_16, key1, key2, enc_data);

uint32_t processed_num = (uint32_t(enc_data[0]) << 16) | enc_data[1];
input[i] = processed_num;
}


for(int i = 0; i < 9; i++)
{
printf("%d ",input[i]);
}

return 0;
}

最后可以得到 key 数组 1234 5678 9123 4567 8912 3456 7891 2345 6789 ,还可以看到一个 src.py 文件以及 pyarmor 的 pyd 文件,通过搜索得知这是用来保护 python 代码的工具,找到工具尝试解包

发现工具报错 No armored data found ,表示没有找到加密数据,可以确定是出题人改了什么地方导致无法识别

注意到工具页面有这样的提示

表示数据应该由 PY + 6 个数字开头,加上 PY000000 后可以正常解包,原始 py 文件如下

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
# Source Generated with Decompyle++
# File: src.py.1shot.seq (Python 3.13)

'__pyarmor_enter_54743__(...)'
cipher = [
1473,
3419,
9156,
1267,
9185,
2823,
7945,
618,
7036,
2479,
5791,
1945,
4639,
1548,
3634,
3502,
2433,
1407,
1263,
3354,
9274,
1085,
8851,
3022,
8031,
734,
6869,
2644,
5798,
1862,
4745,
1554,
3523,
3631,
2512,
1499,
1221,
3226,
9237]

def init(key, key_len):
'__pyarmor_enter_54746__(...)'
_var_var_0 = 0
_var_var_1 = None(list, None(range, 256))
for _var_var_2 in None(range, 256):
_var_var_0 = (_var_var_0 + _var_var_1[_var_var_2] + key[_var_var_2 % key_len]) % 256
_var_var_1[_var_var_2], _var_var_1[_var_var_0] = _var_var_1[_var_var_0], _var_var_1[_var_var_2]
'__pyarmor_exit_54747__(...)'
return _var_var_1


def make(box):
'__pyarmor_enter_54749__(...)'
_var_var_2 = 0
_var_var_0 = 0
_var_var_3 = []
for _var_var_4 in None(range, 256):
_var_var_2 = (_var_var_2 + 1) % 256
_var_var_0 = (_var_var_0 + box[_var_var_2]) % 256
box[_var_var_2], box[_var_var_0] = box[_var_var_0], box[_var_var_2]
_var_var_5 = (box[_var_var_2] + box[_var_var_0] + _var_var_4 % 23) % 256
None(_var_var_3.append, box[_var_var_5])
'__pyarmor_exit_54750__(...)'
return _var_var_3

if __name__ == '__main__':
init.__doc__ = '欢迎来到羊城!\nThe key len is:9'
make.__doc__ = ' flag = list(b"flag{???}")\n fuck_key = [1,2,3,4,5,6,7,8,9]\n __ = [i % 0xff for i in fuck_key]\n key = make(init(bytes(__), len(__)))\n for i in range(len(cipher)):\n _ = fuck_key[i % 9] if i % 2 == 0 else (fuck_key[i % 9] * 2) % 0xFFF\n flag[i] ^= key[i] + _\n '
print(init.__doc__)
exit(0)
exit(0)
'__pyarmor_exit_54744__(...)'

写出解密代码即可

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

int init(int key[],int key_len,int sbox[])
{
int j = 0;
for(int i = 0; i < 256; i++)
{
sbox[i] = i;
}

for(int i = 0; i < 256; i++)
{
j = (j + sbox[i] + key[i % key_len]) % 256;
int temp = sbox[i];
sbox[i] = sbox[j];
sbox[j] = temp;
}
return 0;
}

int make(int sbox[],int key_box[])
{
int i = 0;
int j = 0;
for(int k = 0; k < 256; k++)
{
i = (i + 1) % 256;
j = (j + sbox[i]) % 256;
int temp = sbox[i];
sbox[i] = sbox[j];
sbox[j] = temp;
temp = (sbox[i] + sbox[j] + k % 23) % 256;
key_box[k] = sbox[temp];
}
return 0;
}

int main()
{
int data[] =
{
1473, 3419, 9156, 1267, 9185, 2823, 7945, 618, 7036, 2479, 5791, 1945, 4639, 1548, 3634, 3502, 2433, 1407, 1263, 3354, 9274, 1085, 8851, 3022, 8031, 734, 6869, 2644, 5798, 1862, 4745, 1554, 3523, 3631, 2512, 1499, 1221, 3226, 9237
};
int sbox[256]{};
int key_box[256]{};
int key[] = {1234, 5678, 9123, 4567, 8912, 3456, 7891, 2345, 6789};
int init_key[9];
for(int i = 0; i < 9; i++)
{
init_key[i] = key[i] % 255;
}
init(init_key,9,sbox);
make(sbox,key_box);
int aa;
for(int i = 0; i < 39; i++)
{
if( i % 2 == 0 )
aa = key[i % 9];
else
aa = (key[i % 9] * 2) % 0xFFF;
data[i] ^= key_box[i] + aa;
}
for(int i = 0; i < 39; i++)
{
printf("%c",data[i]);
}
return 0;
}
// flag{8561a-852sad-7561b-asd-4896-qwx56}

# easyTauri

根据题目名字和图标可以看出是 Tauri 框架逆向,先静态提取资源

提取脚本取下:

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
import idc
import brotli
import os
import pathlib
import shutil
import json

start = 0x140642660
end = 0x140642AE0

def extract(start_ea,end_ea,output_dir="assets"):
shutil.rmtree(output_dir,ignore_errors=True)
for offset in range(start,end,0x20):
asset_name_ea = idc.get_qword(offset)
asset_length = idc.get_qword(offset +0x8)
asset_name = idc.get_strlit_contents(asset_name_ea,asset_length,0).decode()[1:]
asset_data_ea = idc.get_qword(offset+0x10)
asset_data_length = idc.get_qword(offset+0x18)
try:
asset_data = brotli.decompress(idc.get_bytes(asset_data_ea,asset_data_length))
except:
print(1)
output_dir=pathlib.Path(output_dir)
asset_path: pathlib.Path = output_dir / asset_name
if not asset_path.parent.exists():
os.makedirs(asset_path.parent)
with open(asset_path,"wb") as f:
f.write(asset_data)
if asset_path.name.startswith("app") and asset_path.name.endswith(".js.map"):
content = json.loads(asset_data)['sourcesContent']
for i in range(len(content)):
with open(f"{asset_path}-content{i}.js","w",encoding='utf-8') as f:
f.write(content[i])

extract(start, end,r'E:\CTF_Challenge\DasCTF\2025ycb-chall-easyTauri/assets')

在提取出的 js 脚本中,可以看到 html_actuator.js 这个脚本里有 flag 验证的前端逻辑,是一个混淆过的 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
function Encrypt_0xa31304(_key, _input)
{
const key = new TextEncoder().encode(_key);
const input = new TextEncoder().encode(_input);
sbox = new Uint8Array(0x100);
let j= 0;
for(let i = 0; i < 0x100; i++)
{
sbox[i] = i;
j = (j + sbox[i] + key[i % key.length()]) % 0x100;
[sbox[i],sbox[j]] = [sbox[j],sbox[i]];
}
let i = 0;
j = 0;
const enc_data = new Uint8Array(input.length());
for(let k = 0; k < input.length(); k++)
{
i = (i + 1) % 0x100;
j = (j + sbox[i]) % 0x100;
[sbox[i],sbox[j]] = [sbox[j],sbox[i]];
const temp = (sbox[i] + sbox[j]) % 0x100;
enc_data[k] = input[k] ^ sbox[temp];
}
return enc_data;
}

同时可以看到把前端加密后的 flag 再 base64 加密后发往后端验证,在 ida 里查找 base 字符串跟到后端验证处

发现 xtea 加密,再经过一次 base64 后通过 memcmp 比较字符串

解密代码如下:

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

int main()
{
uint8_t buf[] = {0x75,0xa1,0x7f,0x0e,0x44,0x31,0x8b,0x11,0xa6,0xce,0x7d,0x1a,0x3c,0x55,0xb6,0x13,0x63,0xe1,0x33,0xc3,0x5a,0x6d,0x1b,0x4b,0x8e,0x9e,0xa9,0x23,0xe7,0x3c,0x4e,0xd6,0x37,0x58,0xcb,0x8f,0xc5,0xf9,0xef,0x94,0x0b,0x29,0xf5,0xa9,0x6e,0x7f,0xc9,0xe8,0x67,0x2f,0xd3,0xe9,0x2c,0xfd,0x0c,0x98};
uint32_t data[15] = {0};
int j = 0;
for(int i = 0; i < 56; i += 4)
{
data[j++] = *(uint32_t *)&buf[i];
}

for(int i = 0; i < 14; i += 2)
{
uint32_t A,B;
A = _byteswap_ulong(data[i]);
B = _byteswap_ulong(data[i + 1]);
int sum = 32 * 0x7E3997B7;
for( int j = 0; j < 32; j++)
{
B -= (16 * A + 1937076784) ^ (sum + A) ^ ((A >> 5) + 1432441972);
A -= (16 * B + 1668048215) ^ (B + sum) ^ ((B >> 5) + 1949527375);
sum -= 0x7E3997B7;
}
data[i] = A;
data[i + 1] = B;
}


// for(int i = 0; i < 14; i++)
// {
// for(int j = 0; j < 4; j++)
// {
// printf("%c",(data[i] >> (j * 8)) & 0xFF);
// }
// }

int arr[] = {0x8e,0x6c,0x87,0x06,0x7b,0x63,0x3e,0x60,0x62,0x13,0xd9,0x39,0x39,0x39,0x7e,0xed,0x61,0xd4,0x8d,0xce,0x9a,0x2c,0x76,0x35,0x65,0x38,0x66,0x31,0x64,0x61,0xbd,0x40,0x31,0x64,0x85,0x61,0x66,0x7d};
uint8_t key[] = "SadTongYiAiRC4HH";
int len = strlen((const char *)key);
int sbox[256] = {0};
j = 0;
for(int i = 0; i < 0x100; i++)
{
sbox[i] = i;
j = (j + sbox[i] + key[i % len]) % 0x100;
int temp = sbox[i];
sbox[i] = sbox[j];
sbox[j] = temp;
}
int i = 0;
j = 0;
int enc_data[38] = {0};
for(int k = 0; k < 38; k++)
{
i = (i + 1) % 0x100;
j = (j + sbox[i]) % 0x100;
int temp = sbox[i];
sbox[i] = sbox[j];
sbox[j] = temp;
int aa = (sbox[i] + sbox[j]) % 0x100;
enc_data[k] = arr[k] ^ sbox[aa];
}

for(int i = 0; i < 38; i++)
{
printf("%c",enc_data[i]);
}

return 0;
}
// flag{cf8be09b1c8a415f8b5e8f1dac71d4af}