2024 国赛 Reverse 方向
carbofish

2024 国赛 Reverse 方向

Reverse

rand0m

得到一个 rand0m.pyd 和 generate.py,rand0m.pyd 被去符号了,先随便写一个 py,然后用 python3.12 的 cython 编译

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def check(flag):
a = 1 * 1
b = 2 * 2
c = 2 ^ 3
a = [0] * 6
b = 0
while b:
print("11")
b -= 1
rand0m(flag)
return a + b + c + flag

def rand0m(x):
return x ^ 123

__test__ = {}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from setuptools import setup, Extension
from Cython.Build import cythonize

ext_module = [
Extension(
name = "test",
sources=["test.py"],
extra_compile_args=["/Zi"],
extra_link_args=["/DEBUG"]
)
]

setup(
name="test",
ext_modules=cythonize(ext_module, annotate=True),
)

python build.py build_ext --inplace,用编译后的 pyd 去和 rand0m.pyd bindiff 恢复符号

image-20241215171704163

第二步去找那个全局调用表 _pyx_mstate_global,恢复符号后找到 _Pyx_InitStrings_0,里面有 调用表的初始化,先写几个结构体导入 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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
typedef struct {
__int64 *__pyx_0;
__int64 *__pyx_1;
__int64 *__pyx_2;
__int64 *__pyx_3;
__int64 *__pyx_4;
__int64 *__pyx_5;
__int64 *__pyx_6;
__int64 *__pyx_7;
__int64 *__pyx_8;
__int64 *__pyx_9;
__int64 *__pyx_10;
__int64 *__pyx_11;
__int64 *__pyx_12;
__int64 *__pyx_13;
__int64 *__pyx_14;
__int64 *__pyx_15;
__int64 *__pyx_16;
__int64 *__pyx_17;
__int64 *__pyx_18;
__int64 *__pyx_19;
__int64 *__pyx_20;
__int64 *__pyx_21;
__int64 *__pyx_22;
__int64 *__pyx_23;
__int64 *__pyx_24;
__int64 *__pyx_25;
__int64 *__pyx_26;
__int64 *__pyx_27;
__int64 *__pyx_28;
__int64 *__pyx_29;
__int64 *__pyx_30;
__int64 *__pyx_31;
__int64 *__pyx_32;
__int64 *__pyx_33;
__int64 *__pyx_34;
__int64 *__pyx_35;
__int64 *__pyx_36;
__int64 *__pyx_37;
__int64 *__pyx_38;
__int64 *__pyx_39;
} __pyx_mstate_me;

struct __Pyx_StringTabEntry
{
__int64 p, s, n, encoding;
__int8 is_unicode, is_str, intern;
__int8 b1, b2, b3, b4, b5;
};

导入后手动设置类型,可以恢复大部分 _Pyx_InitStrings_0

image-20241215172041103

之后手动 重建结构体

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
typedef struct {
__int64 *__pyx_d;
__int64 *__pyx_b;
__int64 *__pyx_cython_runtime;
__int64 *__pyx_empty_tuple;
__int64 *__pyx_empty_bytes;
__int64 *__pyx_empty_unicode;
__int64 *__pyx_CyFunctionType;
__int64 *__pyx_wenhao;
__int64 *__pyx_asyncio_coroutines;
__int64 *__pyx_check;
__int64 *__pyx__class_getitem__;
__int64 *__pyx_cline_in_traceback;
__int64 *__pyx_delta;
__int64 *__pyx_enc;
__int64 *__pyx_flag;
__int64 *__pyx_i;
__int64 *__pyx__is_coroutine;
__int64 *__pyx___main__;
__int64 *__pyx___name__;
__int64 *__pyx_rand0m;
__int64 *__pyx_rand0m_pyx;
__int64 *__pyx_range;
__int64 *__pyx_ret;
__int64 *__pyx_right;
__int64 *__pyx_seed;
__int64 *__pyx_sum;
__int64 *__pyx___test__;
__int64 *__pyx_tmp;
__int64 *__pyx_x;
} __pyx_mstate_me_2;

之后可以开始分析了

先看 rand0m 下的 rand0m 方法

找到绑定的函数后

image-20241215172555432

这里是一个字符串转int的函数 等于 int(v0, 16)

image-20241215172655455

这里这个 v8 这么奇怪其实是被上面我们重建的调用表结构体影响了,这里我们要去找全局初始化整数的地方 _Pyx_CreateStringTabAndInitStrings

image-20241215172903680

image-20241215173028946

也可以看到确实是 int(v0, 16)

明白这些之后接下来只要循规蹈矩的一点点分析就好了

image-20241215173110636

这两句翻译过来就是

1
2
3
def rand0m_fake(x):
v6 = x ^ 0x9e3779b9
v3 = x >> 5

image-20241215173221796

1
v22 = (x << 4) & 0xfa3affff

同样的 rand0m 分析完就是

1
2
3
4
5
6
def rand0m_fake(x):
v25 = ((x << 4) & 0xfa3affff) + (x >> 28)
v27 = (x ^ 0x9e3779b9) >> 11
v27 = v27 ** 65537
v27 %= 0xfffffffd
return (v27, v25)

在分析 check 函数

image-20241215173415982

这里新建一个长度为 8 的密文列表,按照上面的方法设置值后得到

1
nums = [0x12287f38, 0x98d24b3a, 0x4a30f74d, 0xe0f1db77, 0x23a1268, 0xadf38403, 0x88108807, 0xd8499bb6]

image-20241215173521660

这里是一个数组切片 分析以下就是8个8个切片去加密

image-20241215173606915

然后获得结果后用 PyObject_RichCompare 去和密文比较

总体就是每次截取8个获得两个密文,然后比较,用 z3 去解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
for i in range(0, 8, 2):
nums = [0x12287f38, 0x98d24b3a, 0x4a30f74d, 0xe0f1db77, 0x23a1268, 0xadf38403, 0x88108807, 0xd8499bb6]

res = [nums[i + 1], nums[i]]

a = BitVec("data", 32)
s = Solver()
dt = ((a << 4) & 0xfa3affff) + (a >> 28)

s.add(And(res[1] - 0x10 <= dt, dt <= res[1] + 0x10))

while s.check() == sat:
module = s.model()
result = module[a].as_long()

chec = rand0m.rand0m(hex(result)[2:])
if chec[0] == res[0] and chec[1] == res[1]:
print(f"{result:#x} {chec[0]:#x} {chec[1]:#x}")
break
s.add(a != result)

这里奇偶数有误差,所有放大一点,然后可以解出来 flag

1
2
3
4
0x813a97f3 0x98d24b3a 0x12287f38
0xd4b34f74 0xe0f1db77 0x4a30f74d
0x802ba126 0xadf38403 0x23a1268
0x78950880 0xd8499bb6 0x88108807

拼接以下可以得到 flag

flag{813a97f3d4b34f74802ba12678950880}

cython

exe 解包后有一个 pyc 和 一个 pyd,pyc 有不支持的 opcode 所以直接看字节码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
for i in range(len(flag1) - 3):
b = (ord(flag1[i]) << 24) | (ord(flag1[i + 1]) << 16) | (ord(flag1[i + 2]) << 8) | ord(flag1[i + 3])
value.append(b)
i += 4

key = [102, 108, 97, 103]
flag_encrypt = []

print(value)

for i in range(6):
res = ez.encrypt(value[i], value[i + 1], key[i % len(key)])
flag_encrypt.append(res)

ck = ez.check(flag_encrypt)

翻译过来就是这个

之后逆向 ez.pyd 前面部分同上,恢复符号后重建调用表

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
typedef struct {
__int64 *__pyx_d;
__int64 *__pyx_b;
__int64 *__pyx_cython_runtime;
__int64 *__pyx_empty_tuple;
__int64 *__pyx_empty_bytes;
__int64 *__pyx_empty_unicode;
__int64 *__pyx_CyFunctionType;
__int64 *__pyx_kp_s_11;
__int64 *__pyx_V0;
__int64 *__pyx_V1;
__int64 *__pyx_wenhao;
__int64 *__pyx_asyncio_coroutines;
__int64 *__pyx_c_uint32;
__int64 *__pyx_check;
__int64 *__pyx__class_getitem__;
__int64 *__pyx_cline_in_traceback;
__int64 *__pyx_ctypes;
__int64 *__pyx_data;
__int64 *__pyx_delta;
__int64 *__pyx_encrypt;
__int64 *__pyx_ez;
__int64 *__pyx_ez_py;
__int64 *__pyx_flag_encrypt;
__int64 *__pyx_i;
__int64 *__pyx___import__;
__int64 *__pyx__is_coroutine;
__int64 *__pyx_key;
__int64 *__pyx___main__;
__int64 *__pyx___name__;
__int64 *__pyx_range;
__int64 *__pyx___test__;
__int64 *__pyx_total;
__int64 *__pyx_zero_v;
__int64 *__pyx_v1;
__int64 *__pyx_value;
} __pyx_mstate_me_2;


struct __Pyx_StringTabEntry
{
__int64 p, s, n, encoding;
__int8 is_unicode, is_str, intern;
__int8 b1, b2, b3, b4, b5;
};

然后分析 encrypt

其实就是一个魔改 xtea,魔改了 delta 轮数 还有移位数

image-20241215174633527

这里就是

1
ctypes.uint32_t(v0).value

image-20241215174800199

这里翻译过来是

1
v0 += (((v1 << 3) ^ (v1 >> 6)) + v1) ^ (sum + key[sum & 3]);

image-20241215174855166

这里翻译过来是

1
v1 += (((v0 << 3) ^ (v0 >> 6)) + v0) ^ (sum + key[(sum >> 11) & 3]);

image-20241215175010327

这里 加了 delta,然后就可以写一个脚本解密了

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

void decipher(unsigned int num_rounds, uint32_t v[2], uint32_t const key[4])
{
unsigned int i;
uint32_t v0 = v[0], v1 = v[1], delta = 0x54646454, sum = delta * num_rounds;
for (i = 0; i < num_rounds; i++)
{
v1 -= (((v0 << 3) ^ (v0 >> 6)) + v0) ^ (sum + key[(sum >> 11) & 3]);
sum -= delta;
v0 -= (((v1 << 3) ^ (v1 >> 6)) + v1) ^ (sum + key[sum & 3]);
}
v[0] = v0;
v[1] = v1;
}

int main()
{
uint32 v[] = {
874806256, 3846097014, 2954414927, 947032420, 142915619, 250019369
};

uint32 key[] = {
102, 108, 97, 103
};

for (int i = 0; i < 6; i += 2) {
decipher(64, v + i, key);
}

uint8 *ptr = (uint8 *) v;

for (int i = 0; i < 24; i += 4) {
printf("%c%c%c%c", ptr[i + 3], ptr[i + 2], ptr[i + 1], ptr[i]);
}

return 0;
}

flag{bfXtvbVZfRfeqoSXWD}

dump

到手一个 re.exe,随便输入点东西发现会根据你输入的内容给出加密的内容

image-20241215175656889

发现是一个单字节加密,直接输入所有可打印字符串导出表

1
2
3
0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!#$%&()*+,-./:;<=>?@[\\]^_`{|}~ 

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

s = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!#$%&()*+,-./:;<=>?@[\\]^_`{|}~ "
d = list(binascii.unhexlify("001c1d000000000000001e1f202122232425262728292a2b2c2d2e2f303132333435363702030405060708090a0b0c0d0e0f101112131415161718191a1b000000000000000000000000000000000100000000000000000038003900"))

mp = {}

idx = 0
for i in range(len(d)):
mp[d[i]] = s[i]

data = open("flag", "rb").read()

for i in data:
print(mp[i], end="")

flag{MTczMDc~MzQ2Ng==}

这个 ~ 是因为密文里是 00,而加密后是 00 太多了不知道是什么,这个 flag 包裹的内容很像 base64,先随便填一下,很像时间戳,测试后发现 4 以后的数字还有 英文字母都是无效时间戳,随便爆破下,4是正确答案

flag{MTczMDc4MzQ2Ng==}

 评论
评论插件加载失败
正在加载评论插件
由 Hexo 驱动 & 主题 Keep
总字数 54k 访客数 访问量