Ikoct的饮冰室

你愿意和我学一辈子二进制吗?

0%

KCTF2025 Writeups

好玩, 以后不玩了

QQ_1757403693172

第一题

字符串定位到校验的位置:

QQ_1755397308067

校验函数里就是一个字节一个字节地明文校验, 得到flag:

QQ_1755397397041

第二题

主函数中输入长度小于0x70时会进入check:

QQ_1755397836021

但是函数结构很奇怪, push ebp后进入7011b6执行了:

1
2
mov     [esp+arg_0], offset sub_701EEB
retn

也就是调整栈指针然后直接修改返回地址, 这是一个跳板, 进入跳板跳到地函数, 先是又执行了一个strlen然后push edi又进入了一个跳板:

QQ_1755398022466

新的跳板跳向了loc_70129F:

1
2
mov     [esp+arg_0], offset loc_70129F
retn

所以实际上check函数先是通过跳板的方法执行了一个strlen然后跳回check来达成控制流混淆, patch成下面的样子就能让IDA正常反编译了:

QQ_1755398290721

然后分析check的功能, 先是根据当前位的输入在一个0-9a-z的表里取idx:

QQ_1755398397795

然后根据这个idx以及当前所在是奇偶位来确定三个变量x, y, z, 很容易联想到是一个迷宫程序:

QQ_1755398468338

也就是每一步由输入的2个字节来确定, 第一个确定y, 第二个确定x, z, 同时限定起点是0x2c0e终点是0x1fb2, 每次行动完的位置与上一次的位置曼哈顿距离必须是1, 剩下的一个限制就在defactor_prime中:

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
char __cdecl defactor_prime(int n4_1, int *p_n2, int *p_n3)
{
int n2; // edi
int n4_2; // ecx
int n2_3; // esi
int n2_2; // ecx
int n2_1; // [esp+1Ch] [ebp-4h]
int n4_3; // [esp+24h] [ebp+4h]

if ( n4_1 < 4 )
return 0;
n2 = 2;
n2_1 = 2;
n4_2 = (int)sqrt((double)n4_1);
n4_3 = n4_2;
if ( n4_2 < 2 )
return 0;
while ( 1 )
{
if ( n4_1 % n2 || n2 < 2 )
goto LABEL_10;
n2_3 = 2;
n2_2 = (int)sqrt((double)n2_1);
if ( n2_2 >= 2 )
{
while ( n2 % n2_3 )
{
if ( ++n2_3 > n2_2 )
goto LABEL_8;
}
goto LABEL_9;
}
LABEL_8:
if ( is_prime(n4_1 / n2) )
break;
LABEL_9:
n4_2 = n4_3;
LABEL_10:
n2_1 = ++n2;
if ( n2 > n4_2 )
return 0;
}
if ( p_n2 )
*p_n2 = n2;
if ( p_n3 )
*p_n3 = n4_1 / n2;
return 1;
}

其中sqrt是牛顿迭代法求根, 猜测这是分解质因数的程序, 调试也可以验证确实是, 接下来就是写程序打印地图:

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

def is_prime(n: int) -> bool:
if n < 2:
return False
if n == 2:
return True
for i in range(2, round(math.sqrt(n)) + 1):
if n % i == 0:
return False
return True

def defactor_prime(n: int):
if n < 4:
return False
limit = round(math.sqrt(n))
if limit < 2:
return False
for p in range(2, limit + 1):
if n % p == 0:
q = n // p
if is_prime(p) and is_prime(q):
return True
return False

map = [0x2C0E, 0x1312, 0x7F44, 0x4C2B, 0x966, 0x1E12, 0x1263, 0x1828, 0x31A1, 0x52AE, 0x1DC6, 0x4019, 0x3D89, 0x614D, 0x1A4A, 0x3A24, 0x5406, 0xE61, 0x658, 0xD4C, 0x5423, 0x4860, 0x7988, 0x1FA3, 0x705B, 0x4464, 0x63EF, 0x403E, 0x50CB, 0x1676, 0x5132, 0x68B5, 0x4A38, 0x6FF5, 0x189B, 0x170B, 0x2704, 0x20ED, 0x4938, 0x47F6, 0x7512, 0x5196, 0x2B7C, 0x4A50, 0x2E54, 0x6FBE, 0x3532, 0x7B54, 0xABD, 0x2889, 0x56C0, 0x323B, 0x3698, 0x31CF, 0x435A, 0x75FA, 0xA2B, 0x200B, 0x45ED, 0x163F, 0x2554, 0x62BB, 0x3393, 0x2A31, 0x2956, 0x3004, 0x3684, 0x3DEE, 0x47AC, 0x2267, 0x629E, 0x1E93, 0x7943, 0x661B, 0x7FB4, 0x255A, 0x3D37, 0x1804, 0x69E1, 0x312C, 0x3999, 0x293F, 0x2F0E, 0x743A, 0x117, 0x64CD, 0x7C76, 0x5CBF, 0x6AB0, 0x585, 0x6CFC, 0x3CE6, 0x6FAB, 0xF81, 0x511C, 0x24E4, 0x785, 0x5AEB, 0x2977, 0xBFB, 0x55F8, 0x39, 0x29DD, 0x4987, 0x133B, 0x5E2F, 0x7F4D, 0x6497, 0x62C2, 0x3774, 0x1826, 0x78C, 0x20BB, 0x5473, 0x68B6, 0x27FB, 0x7321, 0x1B0E, 0x6280, 0x54E2, 0x5133, 0x61E7, 0x2F6B, 0x5AE2, 0x1B09, 0x64BB, 0x71B, 0x6272, 0x453F, 0x22BD, 0xD68, 0x52A9, 0x3116, 0x19AD, 0x28F6, 0x141E, 0x6B10, 0x32AC, 0x2394, 0x353D, 0x6A40, 0x697, 0x80E, 0x6883, 0x4297, 0x175D, 0xB3A, 0x4584, 0x4FB8, 0x55D2, 0x2592, 0x5F99, 0x5986, 0x5A84, 0x71A3, 0x3975, 0x525, 0x3E15, 0x823, 0x12D7, 0x78EF, 0x636B, 0x471A, 0x531C, 0x23F8, 0x38D7, 0x7E64, 0x18DB, 0x344E, 0x5655, 0x1C69, 0xEE8, 0x23C, 0x25C8, 0x684, 0x776D, 0xB65, 0x7855, 0x602D, 0x277E, 0x60AC, 0x1885, 0x6E40, 0x3EEC, 0x175C, 0xD2B, 0x4F2E, 0x645, 0x6C70, 0x457D, 0x5CD8, 0x2B12, 0x11C, 0x7AAD, 0x2E5E, 0x37CB, 0x5CF8, 0x5C48, 0x648F, 0x3841, 0x6344, 0x7F63, 0x5C2, 0xBE2, 0x717A, 0x7317, 0x711A, 0x4344, 0x319, 0x756E, 0x2F5E, 0x6EF2, 0x23B0, 0x1ED5, 0x5A1B, 0x5FFF, 0x7494, 0x5C4C, 0x7D2C, 0x2C51, 0x4BE4, 0x5197, 0x2384, 0x64DD, 0x62E9, 0x5019, 0x447E, 0x1DA1, 0x70B3, 0x5B03, 0x17BC, 0x4ED4, 0x3E1F, 0x26F8, 0x2AF2, 0x7B64, 0x4634, 0x69D9, 0x75A, 0x344B, 0x27D0, 0xC9C, 0x385E, 0x2496, 0x5635, 0x57AA, 0x44A8, 0x62AF, 0x65E7, 0x3284, 0x7A73, 0x1CCF, 0x25F4, 0x5DCD, 0x5CDE, 0x109B, 0x7890, 0x45CF, 0x5FEF, 0x4ABB, 0x26, 0x47D, 0x6121, 0x3A42, 0x5519, 0x32AF, 0x2096, 0x18E7, 0x4181, 0x1FB2]

bitmap = [[0] * 90 for _ in range(3)]
for i in range(3):
for j in range(90):
bitmap[i][j] = '*' if defactor_prime(map[i * 90 + j]) else '#'

for i in range(3):
for j in range(9):
for z in range(10):
print(bitmap[i][j * 10 + z], end='')
print()
print()

得到地图后手动解出:

QQ_1755399778768

根据路径生成key:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
path = 'dsuddssaassddnssaauasuddddwwndddnwaawwuddwwdusdsssasssd'
table = '0123456789abcdefghijklmnopqrstuvwxyz'

key = ['?'] * 2 * len(path)
state = [0, 0, 0]

for idx, step in enumerate(path):
if step == 'd':
state[2] += 1
elif step == 's':
state[1] += 1
elif step == 'a':
state[2] -= 1
elif step == 'w':
state[1] -= 1
elif step == 'u':
state[0] += 1
elif step == 'n':
state[0] -= 1

key[2 * idx] = table[state[1]]
key[2 * idx + 1] = table[state[0] * 12 + state[2]]
print('00' + ''.join(key))

第三题

程序为32位程序, 核心加密和校验在0x0402380.

由于题目不用写注册机, 只需要求nameKCTF时的序列号, 所以有关name的处理全部无视, 根据格式化字符串可以定位到在这里进行了序列号的初步处理:

image-20250819212333637

以十六进制字符串形式将对应数据读取到内存后先进行了一个反转操作, 然后进入以下函数进行了按30bit进行分割:

QQ_1755610022715

有python逆向经验的都可以察觉到这和python底层中Py_Long类型储存大数字的形式是一样的, 结合下面某个函数中的报错:

image-20250819212923190

应该是将序列号作为一个大数字利进行了一些运算, 对于这些处理大数字的函数最好的分析方法就是调试猜测功能.

下面的处理还有几百行伪代码, 反正最后解密也要从尾开始, 接下来将从校验开始向上开始分析加密流程.

在最后校验处下断点, 修改用户名可以发现只有密文发生变化, 也就是说整个加密流程由序列号单独完成:

image-20250819214104301

并且最后一步加密是反转然后第一个_DWORD自增, 因为最后的密文第一个字节固定是b'J', 这里直接当是第一个字节自增就行了.

继续向上查找加密结果的来源, 在from_bignum上下断点, 最后的密文明显来自第二个参数:

image-20250819214918482

结合上面对序列号初步处理的分析, 应该是将一个大数转化为字节串, 用以下函数来验证(由于程序用到多种大数, 这里给出我用到的几种转化):

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
from idc import get_bytes
def origin_data2bignum(data):
bignum = 0
for i in range(len(data)):
r = data[i] & 0x3FFFFFFF
bignum |= r << (30 * i)
return bignum

def bignumat(addr):
digtsLen = int.from_bytes(get_bytes(addr, 4), 'little')
if digtsLen >= 0x1000:
return 0
digtsAddr = addr + 4
data = []
for i in range(digtsLen):
data.append(int.from_bytes(get_bytes(digtsAddr + i * 4, 4), 'little'))
return origin_data2bignum(data)

def bignum1at(addr):
digitsAddr = int.from_bytes(get_bytes(addr, 4), 'little')
digitsLen = int.from_bytes(get_bytes(addr + 4, 4), 'little')
data = get_bytes(digitsAddr, digitsLen * 4)
return int.from_bytes(data, 'little')

def bignum3at(addr):
digtsLen = int.from_bytes(get_bytes(addr, 4), 'little')
digtsAddr = int.from_bytes(get_bytes(addr + 0xc, 4), 'little')
data = []
for i in range(digtsLen):
data.append(int.from_bytes(get_bytes(digtsAddr + i * 4, 4), 'little'))
return origin_data2bignum(data)

接下来就是通过调试和拷打AI不断还原这个大数是通过什么运算得到的, 最终恢复了3个最重要的符号:

image-20250819215307588

分别是大数相乘, 取模, 求模逆操作, 用伪代码来表示这个阶段的加密逻辑就是:

1
2
3
4
5
6
7
m2 = big_num_type2(key)
m1 = m2 ** 3
m1 **= 4
m1 %= c
m1 **= 3
n = (m1 * m2) % c
final = inverse(n, c)

其中key是上阶段加密的结果, 并且所有相乘都化简对应的乘方, c是一个函数开头初始化的常数0x56f67550f16a00390dcf0b2715708e61c5b3f23101862fc1, 接下来就是拷打AI怎么解出上一阶段的key(不然呢, 我又不是密码手).

image-20250819215819269

这里放出怎么得到的37次方是方便后面还要解类似问题时能快速上手使用解密函数, 根据c可以分解为两个质因数, AI给出了以下通用解法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
from sympy import mod_inverse
def solve(result, c, p, q, k):
d_p = mod_inverse(k, p-1)
d_q = mod_inverse(k, q-1)

x_p = pow(result, d_p, p)
x_q = pow(result, d_q, q)

n = c
n1, n2 = q, p
m1, m2 = mod_inverse(n1, p), mod_inverse(n2, q)

code = (x_p * n1 * m1 + x_q * n2 * m2) % n
return code

参数的含义分别是: 最后取模的结果, 模数, 模数的第一个质因数, 第二个质因数, k次方

facordb分解一下c就能得到两个常数, 对于上面37次方的解法就是:

1
2
3
4
5
6
c = 0x56f67550f16a00390dcf0b2715708e61c5b3f23101862fc1
p1 = 45424490472579293708671645907
p2 = 46942075831425428541187578011

enc = inverse(enc, c)
keys = solve(enc, c, p1, p2, 37)

求模逆是一个可逆操作, 再对c做一次就能求回来, 然后solve函数求解得到上一阶段的结果key.

这里验证结果的方法是找到一开始通过字节串转化为大数的地方将字节串替换为求出的大数的小端序字节串, 然后让程序运行到最后校验处看看是否匹配密文.

现在基本上可以确定序列号就是作为大数不断进行运算得到最终结果, 实际上并没有针对序列号字节串的加密, 但是在最后一个阶段恢复的符号前面并没有任何一处调用, 猜测题目可能使用了多种数论库进行大数运算.

而中间常见的自增1再反转的操作可以通过调试来分析实际对一个大数的影响, 这里以倒数第二阶段到上面那个阶段为例探索这个操作的实际效果, 倒数第二阶段用到的是bignum1at:

image-20250819221608550

可以看到实际上就是最高一个_DWORD自增, 那么下面就不探索大数和字节串之间的转化了, 只会越绕越晕, 只把它当成一个数处理就行.

倒数第二阶段也能恢复出乘, 取模, 模逆的符号:

image-20250819221911827

运算对应的伪代码:

1
2
3
4
5
6
7
8
key = code
tmp = key
key **= 10
key %= c
key **= 3
key *= tmp
key %= c
key = inverse(key, c)

也能转化为和最后一个阶段一样的问题, 能用一样的解法, 这里对应的是10 * 3 + 1 = 31次

接下来继续向上分析也是一样的方法, 分析到倒数第三阶段还是一样的加密方式, 这时候已经开始不需要每次都验证每个函数的作用了, 因为上面说了程序用到了多种数论库(也可能是一个库的同样效果不同实现的函数?), 每一阶段都有自己的乘, 取模, 模逆, 这里直接通过这个加密模式来猜测每一个函数对应的运算即可, 其中有几个函数IDA识别参数有问题手动调一下参数个数能分别结果放在哪个参数即可, 最终一共是有9个阶段, 全部是同类型加密, 解密代码:

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
from Crypto.Util.number import inverse

c = 0x56f67550f16a00390dcf0b2715708e61c5b3f23101862fc1
p1 = 45424490472579293708671645907
p2 = 46942075831425428541187578011
enc = [0x4B, 0x43, 0x54, 0x46, 0x32, 0x30, 0x32, 0x35, 0x7A, 0x1A, 0xB1, 0xC6, 0xA2, 0x99, 0x9F, 0x97, 0x97, 0xF5, 0xAB, 0xD5, 0xB4, 0x9F, 0xD9, 0xA0]

def b2hex(b):
print(' '.join([f"{x:02x}" for x in b]))

def n2hex(n):
n = n.to_bytes(24, 'little')
b2hex(n)

from sympy import mod_inverse
def solve(result, c, p, q, k):
d_p = mod_inverse(k, p-1)
d_q = mod_inverse(k, q-1)

x_p = pow(result, d_p, p)
x_q = pow(result, d_q, q)

n = c
n1, n2 = q, p
m1, m2 = mod_inverse(n1, p), mod_inverse(n2, q)

code = (x_p * n1 * m1 + x_q * n2 * m2) % n
return code

enc[0] -= 1
enc = enc[::-1]
enc = int.from_bytes(enc, 'little')

enc = inverse(enc, c)
keys = solve(enc, c, p1, p2, 37)
print("found: ", hex(keys))
enc = bytearray(keys.to_bytes(24, 'big'))
enc[0] -= 1
enc = int.from_bytes(enc, 'big')

enc = inverse(enc, c)
code = solve(enc, c, p1, p2, 31)
print("found: ", hex(code))
enc = bytearray(code.to_bytes(24, 'big'))
enc[0] -= 1
enc = int.from_bytes(enc, 'big')

enc = inverse(enc, c)
ser = solve(enc, c, p1, p2, 29)
print("found: ", hex(ser))
enc = bytearray(ser.to_bytes(24, 'big'))
enc[0] -= 1
enc = int.from_bytes(enc, 'big')

enc = inverse(enc, c)
enc = solve(enc, c, p1, p2, 23)
print("found: ", hex(enc))
n2hex(enc)
enc = bytearray(enc.to_bytes(24, 'big'))
enc[0] -= 1
enc = int.from_bytes(enc, 'big')

enc = inverse(enc, c)
enc = solve(enc, c, p1, p2, 19)
print("found: ", hex(enc))
enc = bytearray(enc.to_bytes(24, 'big'))
enc[0] -= 1
enc = int.from_bytes(enc, 'big')

enc = inverse(enc, c)
enc = solve(enc, c, p1, p2, 17)
print("found: ", hex(enc))
enc = bytearray(enc.to_bytes(24, 'big'))
enc[0] -= 1
enc = int.from_bytes(enc, 'big')

enc = inverse(enc, c)
enc = solve(enc, c, p1, p2, 11)
print("found: ", hex(enc))
enc = bytearray(enc.to_bytes(24, 'big'))
enc[0] -= 1
enc = int.from_bytes(enc, 'big')

enc = inverse(enc, c)
enc = solve(enc, c, p1, p2, 7)
print("found: ", hex(enc))
enc = bytearray(enc.to_bytes(24, 'big'))
enc[0] -= 1
enc = int.from_bytes(enc, 'big')

enc = inverse(enc, c)
enc = solve(enc, c, p1, p2, 3)
print("found: ", hex(enc)[2:].upper())

第四题

注意到函数列表里IDA识别出来的TLS回调, 其中初始化了一些资源, 这些都不重要, 重点看看其中的反调试.

0000000140003774调用了0000000140007303来执行系统调用, 此处系统调用号为0x19, 接触过VMP的反调试就知道这是ZwQueryInformationProcess的系统调用, 第一个参数为自身进程句柄, 二个参数7说明查询的是调试器信息:

image-20250822085521631

将检测到调试器调用先前分配的可执行页的分支patch掉:

image-20250822085706338

main+373是一样的反调试, main+1ae处有同样的反调试手法, 调用的是SetInformation, 将调试器从进程剥离.

另外main+1c9注册了一个异常处理函数(0000000140006D40), 一上来就清除了进程的调试寄存器, 虽然不一定会用到硬件断点但还是patch掉:

image-20250822090352613

若触发软件断点就会根据一个全局变量进行不同的操作:

image-20250822090625101

之前的反调试就是将ctol设置为4然后执行int 3来让程序退出的, 由于不用写注册机, 所以这里不分析用户名的处理, 直接跳到处理序列号的地方, 对应ctol为3:

image-20250822090923888

核心处理逻辑在decryptstring(0000000140005D6C), 这是先前用来解密一些字符串使用的函数, 也就是说需要编写的是相应的加密函数, 从公开序列号可以看出最后一步的加密应该是base64编码(对应题目中的0000000140001684), 但是使用题目中发现的表会发现编码不回原来的字符串, 调试会发现题目中的解码过程将每3个字符合成时竟然是大端序的, 那只能自己手搓一个对应的base64编码函数了:

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
table = 'AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz0123456789+/'

idx = 0
res = []
eof = False
padding = 0
while True:
p = 0
if idx >= 0x40 - 3:
p = enc[idx :]
padding = 3 - len(p)
eof = True
else:
p = enc[idx : idx + 3]
n = 0
for i, b in enumerate(p):
n |= b << 8 * i
c = []
for _ in range(4 - padding):
c += [table[n & 0x3f]]
n >>= 6
c += ['='] * (4 - len(c))
res += c
if eof:
break
idx += 3

然后程序进行了一个类似AES的操作(000000014000593C):

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
_QWORD *__fastcall aes(__int64 a1, _QWORD *key)
{
size_t v4; // rdx
char *v5; // rdi
size_t Size; // rbx
BYTE **to_encrypt; // rdi
_DWORD *key_1; // rbx
int i; // ebp
_QWORD *v10; // r14
__int64 v11; // rbx
int i_1; // ebp
__int64 v13; // r12
int j; // r15d
__int64 v15; // r13
_BYTE *v16; // rbx
_QWORD *v17; // rcx
void *v19[2]; // [rsp+20h] [rbp-58h] BYREF
__int64 v20; // [rsp+30h] [rbp-48h]
_QWORD *v21; // [rsp+38h] [rbp-40h]
__int64 v22; // [rsp+40h] [rbp-38h]

v22 = a1;
*(_OWORD *)v19 = 0;
v20 = 0;
v4 = *(_QWORD *)(a1 + 8) - *(_QWORD *)a1;
if ( v4 )
{
sub_140002824(v19, v4);
v5 = (char *)v19[0];
Size = *(_QWORD *)(a1 + 8) - *(_QWORD *)a1;
memmove(v19[0], *(const void **)a1, Size);
v19[1] = &v5[Size];
}
to_encrypt = (BYTE **)sub_140004A60(v19);
AddRoundKey(to_encrypt, (_DWORD *)(*key + 160LL));
key_1 = (_DWORD *)(*key + 144LL);
for ( i = 9; i > 0; --i )
{
SBOXswap(to_encrypt);
ShiftRows(to_encrypt);
AddRoundKey(to_encrypt, key_1);
key_1 -= 4;
col_mix(to_encrypt);
}
SBOXswap(to_encrypt);
ShiftRows(to_encrypt);
AddRoundKey(to_encrypt, key_1);
v10 = operator new(0x18u);
v21 = v10;
*(_OWORD *)v10 = 0;
v10[2] = 0;
*v10 = 0;
v10[1] = 0;
v10[2] = 0;
sub_140002824(v10, 0x10u);
v11 = *v10;
memset((void *)*v10, 0, 0x10u);
v10[1] = v11 + 16;
i_1 = 0;
v13 = 0;
do
{
j = 0;
v15 = v13;
do
{
v16 = (_BYTE *)*v10;
v16[v15] = *(_BYTE *)byteat_matrix((__int64)to_encrypt, i_1, j++);
v15 += 4;
}
while ( j < 4 );
++i_1;
++v13;
}
while ( i_1 < 4 );
if ( to_encrypt )
{
j_j_free(*to_encrypt);
j_j_free(to_encrypt);
}
v17 = *(_QWORD **)a1;
if ( *(_QWORD *)a1 )
{
if ( *(_QWORD *)(a1 + 16) - (_QWORD)v17 >= 0x1000u )
{
if ( (unsigned __int64)v17 - *(v17 - 1) - 8 > 0x1F )
invalid_parameter_noinfo_noreturn();
v17 = (_QWORD *)*(v17 - 1);
}
j_j_free(v17);
*(_QWORD *)a1 = 0;
*(_QWORD *)(a1 + 8) = 0;
*(_QWORD *)(a1 + 16) = 0;
}
return v10;
}

从使用的SBOX可以看出是标准逆SBOX, 但是其中的列混合步骤用的貌似是白盒:

image-20250822160212647

tbl是一个整整5 * 0x100字节的盒, 拷打AI写出爆破脚本后来到最后一步解密, 是一个简单的异或, 调试拿到异或的字节流即可, 完整脚本:

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
from base64 import b64encode, b64decode

enc = bytearray(b'l/SxsR0BCjRZTmc7XWscrMv38iYuoiUECvi9Tuvi+ZiJZmdR5EcvVnTZOZvrnOrw')

inv_sbox = [0x52, 0x9, 0x6A, 0xD5, 0x30, 0x36, 0xA5, 0x38, 0xBF, 0x40, 0xA3, 0x9E, 0x81, 0xF3, 0xD7, 0xFB, 0x7C, 0xE3, 0x39, 0x82, 0x9B, 0x2F, 0xFF, 0x87, 0x34, 0x8E, 0x43, 0x44, 0xC4, 0xDE, 0xE9, 0xCB, 0x54, 0x7B, 0x94, 0x32, 0xA6, 0xC2, 0x23, 0x3D, 0xEE, 0x4C, 0x95, 0xB, 0x42, 0xFA, 0xC3, 0x4E, 0x8, 0x2E, 0xA1, 0x66, 0x28, 0xD9, 0x24, 0xB2, 0x76, 0x5B, 0xA2, 0x49, 0x6D, 0x8B, 0xD1, 0x25, 0x72, 0xF8, 0xF6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xD4, 0xA4, 0x5C, 0xCC, 0x5D, 0x65, 0xB6, 0x92, 0x6C, 0x70, 0x48, 0x50, 0xFD, 0xED, 0xB9, 0xDA, 0x5E, 0x15, 0x46, 0x57, 0xA7, 0x8D, 0x9D, 0x84, 0x90, 0xD8, 0xAB, 0x0, 0x8C, 0xBC, 0xD3, 0xA, 0xF7, 0xE4, 0x58, 0x5, 0xB8, 0xB3, 0x45, 0x6, 0xD0, 0x2C, 0x1E, 0x8F, 0xCA, 0x3F, 0xF, 0x2, 0xC1, 0xAF, 0xBD, 0x3, 0x1, 0x13, 0x8A, 0x6B, 0x3A, 0x91, 0x11, 0x41, 0x4F, 0x67, 0xDC, 0xEA, 0x97, 0xF2, 0xCF, 0xCE, 0xF0, 0xB4, 0xE6, 0x73, 0x96, 0xAC, 0x74, 0x22, 0xE7, 0xAD, 0x35, 0x85, 0xE2, 0xF9, 0x37, 0xE8, 0x1C, 0x75, 0xDF, 0x6E, 0x47, 0xF1, 0x1A, 0x71, 0x1D, 0x29, 0xC5, 0x89, 0x6F, 0xB7, 0x62, 0xE, 0xAA, 0x18, 0xBE, 0x1B, 0xFC, 0x56, 0x3E, 0x4B, 0xC6, 0xD2, 0x79, 0x20, 0x9A, 0xDB, 0xC0, 0xFE, 0x78, 0xCD, 0x5A, 0xF4, 0x1F, 0xDD, 0xA8, 0x33, 0x88, 0x7, 0xC7, 0x31, 0xB1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xEC, 0x5F, 0x60, 0x51, 0x7F, 0xA9, 0x19, 0xB5, 0x4A, 0xD, 0x2D, 0xE5, 0x7A, 0x9F, 0x93, 0xC9, 0x9C, 0xEF, 0xA0, 0xE0, 0x3B, 0x4D, 0xAE, 0x2A, 0xF5, 0xB0, 0xC8, 0xEB, 0xBB, 0x3C, 0x83, 0x53, 0x99, 0x61, 0x17, 0x2B, 0x4, 0x7E, 0xBA, 0x77, 0xD6, 0x26, 0xE1, 0x69, 0x14, 0x63, 0x55, 0x21, 0xC, 0x7D]

tbl = [0x0, 0xD, 0x1A, 0x17, 0x34, 0x39, 0x2E, 0x23, 0x68, 0x65, 0x72, 0x7F, 0x5C, 0x51, 0x46, 0x4B, 0xD0, 0xDD, 0xCA, 0xC7, 0xE4, 0xE9, 0xFE, 0xF3, 0xB8, 0xB5, 0xA2, 0xAF, 0x8C, 0x81, 0x96, 0x9B, 0xBB, 0xB6, 0xA1, 0xAC, 0x8F, 0x82, 0x95, 0x98, 0xD3, 0xDE, 0xC9, 0xC4, 0xE7, 0xEA, 0xFD, 0xF0, 0x6B, 0x66, 0x71, 0x7C, 0x5F, 0x52, 0x45, 0x48, 0x3, 0xE, 0x19, 0x14, 0x37, 0x3A, 0x2D, 0x20, 0x6D, 0x60, 0x77, 0x7A, 0x59, 0x54, 0x43, 0x4E, 0x5, 0x8, 0x1F, 0x12, 0x31, 0x3C, 0x2B, 0x26, 0xBD, 0xB0, 0xA7, 0xAA, 0x89, 0x84, 0x93, 0x9E, 0xD5, 0xD8, 0xCF, 0xC2, 0xE1, 0xEC, 0xFB, 0xF6, 0xD6, 0xDB, 0xCC, 0xC1, 0xE2, 0xEF, 0xF8, 0xF5, 0xBE, 0xB3, 0xA4, 0xA9, 0x8A, 0x87, 0x90, 0x9D, 0x6, 0xB, 0x1C, 0x11, 0x32, 0x3F, 0x28, 0x25, 0x6E, 0x63, 0x74, 0x79, 0x5A, 0x57, 0x40, 0x4D, 0xDA, 0xD7, 0xC0, 0xCD, 0xEE, 0xE3, 0xF4, 0xF9, 0xB2, 0xBF, 0xA8, 0xA5, 0x86, 0x8B, 0x9C, 0x91, 0xA, 0x7, 0x10, 0x1D, 0x3E, 0x33, 0x24, 0x29, 0x62, 0x6F, 0x78, 0x75, 0x56, 0x5B, 0x4C, 0x41, 0x61, 0x6C, 0x7B, 0x76, 0x55, 0x58, 0x4F, 0x42, 0x9, 0x4, 0x13, 0x1E, 0x3D, 0x30, 0x27, 0x2A, 0xB1, 0xBC, 0xAB, 0xA6, 0x85, 0x88, 0x9F, 0x92, 0xD9, 0xD4, 0xC3, 0xCE, 0xED, 0xE0, 0xF7, 0xFA, 0xB7, 0xBA, 0xAD, 0xA0, 0x83, 0x8E, 0x99, 0x94, 0xDF, 0xD2, 0xC5, 0xC8, 0xEB, 0xE6, 0xF1, 0xFC, 0x67, 0x6A, 0x7D, 0x70, 0x53, 0x5E, 0x49, 0x44, 0xF, 0x2, 0x15, 0x18, 0x3B, 0x36, 0x21, 0x2C, 0xC, 0x1, 0x16, 0x1B, 0x38, 0x35, 0x22, 0x2F, 0x64, 0x69, 0x7E, 0x73, 0x50, 0x5D, 0x4A, 0x47, 0xDC, 0xD1, 0xC6, 0xCB, 0xE8, 0xE5, 0xF2, 0xFF, 0xB4, 0xB9, 0xAE, 0xA3, 0x80, 0x8D, 0x9A, 0x97, 0x0, 0xE, 0x1C, 0x12, 0x38, 0x36, 0x24, 0x2A, 0x70, 0x7E, 0x6C, 0x62, 0x48, 0x46, 0x54, 0x5A, 0xE0, 0xEE, 0xFC, 0xF2, 0xD8, 0xD6, 0xC4, 0xCA, 0x90, 0x9E, 0x8C, 0x82, 0xA8, 0xA6, 0xB4, 0xBA, 0xDB, 0xD5, 0xC7, 0xC9, 0xE3, 0xED, 0xFF, 0xF1, 0xAB, 0xA5, 0xB7, 0xB9, 0x93, 0x9D, 0x8F, 0x81, 0x3B, 0x35, 0x27, 0x29, 0x3, 0xD, 0x1F, 0x11, 0x4B, 0x45, 0x57, 0x59, 0x73, 0x7D, 0x6F, 0x61, 0xAD, 0xA3, 0xB1, 0xBF, 0x95, 0x9B, 0x89, 0x87, 0xDD, 0xD3, 0xC1, 0xCF, 0xE5, 0xEB, 0xF9, 0xF7, 0x4D, 0x43, 0x51, 0x5F, 0x75, 0x7B, 0x69, 0x67, 0x3D, 0x33, 0x21, 0x2F, 0x5, 0xB, 0x19, 0x17, 0x76, 0x78, 0x6A, 0x64, 0x4E, 0x40, 0x52, 0x5C, 0x6, 0x8, 0x1A, 0x14, 0x3E, 0x30, 0x22, 0x2C, 0x96, 0x98, 0x8A, 0x84, 0xAE, 0xA0, 0xB2, 0xBC, 0xE6, 0xE8, 0xFA, 0xF4, 0xDE, 0xD0, 0xC2, 0xCC, 0x41, 0x4F, 0x5D, 0x53, 0x79, 0x77, 0x65, 0x6B, 0x31, 0x3F, 0x2D, 0x23, 0x9, 0x7, 0x15, 0x1B, 0xA1, 0xAF, 0xBD, 0xB3, 0x99, 0x97, 0x85, 0x8B, 0xD1, 0xDF, 0xCD, 0xC3, 0xE9, 0xE7, 0xF5, 0xFB, 0x9A, 0x94, 0x86, 0x88, 0xA2, 0xAC, 0xBE, 0xB0, 0xEA, 0xE4, 0xF6, 0xF8, 0xD2, 0xDC, 0xCE, 0xC0, 0x7A, 0x74, 0x66, 0x68, 0x42, 0x4C, 0x5E, 0x50, 0xA, 0x4, 0x16, 0x18, 0x32, 0x3C, 0x2E, 0x20, 0xEC, 0xE2, 0xF0, 0xFE, 0xD4, 0xDA, 0xC8, 0xC6, 0x9C, 0x92, 0x80, 0x8E, 0xA4, 0xAA, 0xB8, 0xB6, 0xC, 0x2, 0x10, 0x1E, 0x34, 0x3A, 0x28, 0x26, 0x7C, 0x72, 0x60, 0x6E, 0x44, 0x4A, 0x58, 0x56, 0x37, 0x39, 0x2B, 0x25, 0xF, 0x1, 0x13, 0x1D, 0x47, 0x49, 0x5B, 0x55, 0x7F, 0x71, 0x63, 0x6D, 0xD7, 0xD9, 0xCB, 0xC5, 0xEF, 0xE1, 0xF3, 0xFD, 0xA7, 0xA9, 0xBB, 0xB5, 0x9F, 0x91, 0x83, 0x8D, 0x0, 0x2, 0x4, 0x6, 0x8, 0xA, 0xC, 0xE, 0x10, 0x12, 0x14, 0x16, 0x18, 0x1A, 0x1C, 0x1E, 0x20, 0x22, 0x24, 0x26, 0x28, 0x2A, 0x2C, 0x2E, 0x30, 0x32, 0x34, 0x36, 0x38, 0x3A, 0x3C, 0x3E, 0x40, 0x42, 0x44, 0x46, 0x48, 0x4A, 0x4C, 0x4E, 0x50, 0x52, 0x54, 0x56, 0x58, 0x5A, 0x5C, 0x5E, 0x60, 0x62, 0x64, 0x66, 0x68, 0x6A, 0x6C, 0x6E, 0x70, 0x72, 0x74, 0x76, 0x78, 0x7A, 0x7C, 0x7E, 0x80, 0x82, 0x84, 0x86, 0x88, 0x8A, 0x8C, 0x8E, 0x90, 0x92, 0x94, 0x96, 0x98, 0x9A, 0x9C, 0x9E, 0xA0, 0xA2, 0xA4, 0xA6, 0xA8, 0xAA, 0xAC, 0xAE, 0xB0, 0xB2, 0xB4, 0xB6, 0xB8, 0xBA, 0xBC, 0xBE, 0xC0, 0xC2, 0xC4, 0xC6, 0xC8, 0xCA, 0xCC, 0xCE, 0xD0, 0xD2, 0xD4, 0xD6, 0xD8, 0xDA, 0xDC, 0xDE, 0xE0, 0xE2, 0xE4, 0xE6, 0xE8, 0xEA, 0xEC, 0xEE, 0xF0, 0xF2, 0xF4, 0xF6, 0xF8, 0xFA, 0xFC, 0xFE, 0x1B, 0x19, 0x1F, 0x1D, 0x13, 0x11, 0x17, 0x15, 0xB, 0x9, 0xF, 0xD, 0x3, 0x1, 0x7, 0x5, 0x3B, 0x39, 0x3F, 0x3D, 0x33, 0x31, 0x37, 0x35, 0x2B, 0x29, 0x2F, 0x2D, 0x23, 0x21, 0x27, 0x25, 0x5B, 0x59, 0x5F, 0x5D, 0x53, 0x51, 0x57, 0x55, 0x4B, 0x49, 0x4F, 0x4D, 0x43, 0x41, 0x47, 0x45, 0x7B, 0x79, 0x7F, 0x7D, 0x73, 0x71, 0x77, 0x75, 0x6B, 0x69, 0x6F, 0x6D, 0x63, 0x61, 0x67, 0x65, 0x9B, 0x99, 0x9F, 0x9D, 0x93, 0x91, 0x97, 0x95, 0x8B, 0x89, 0x8F, 0x8D, 0x83, 0x81, 0x87, 0x85, 0xBB, 0xB9, 0xBF, 0xBD, 0xB3, 0xB1, 0xB7, 0xB5, 0xAB, 0xA9, 0xAF, 0xAD, 0xA3, 0xA1, 0xA7, 0xA5, 0xDB, 0xD9, 0xDF, 0xDD, 0xD3, 0xD1, 0xD7, 0xD5, 0xCB, 0xC9, 0xCF, 0xCD, 0xC3, 0xC1, 0xC7, 0xC5, 0xFB, 0xF9, 0xFF, 0xFD, 0xF3, 0xF1, 0xF7, 0xF5, 0xEB, 0xE9, 0xEF, 0xED, 0xE3, 0xE1, 0xE7, 0xE5, 0x0, 0x9, 0x12, 0x1B, 0x24, 0x2D, 0x36, 0x3F, 0x48, 0x41, 0x5A, 0x53, 0x6C, 0x65, 0x7E, 0x77, 0x90, 0x99, 0x82, 0x8B, 0xB4, 0xBD, 0xA6, 0xAF, 0xD8, 0xD1, 0xCA, 0xC3, 0xFC, 0xF5, 0xEE, 0xE7, 0x3B, 0x32, 0x29, 0x20, 0x1F, 0x16, 0xD, 0x4, 0x73, 0x7A, 0x61, 0x68, 0x57, 0x5E, 0x45, 0x4C, 0xAB, 0xA2, 0xB9, 0xB0, 0x8F, 0x86, 0x9D, 0x94, 0xE3, 0xEA, 0xF1, 0xF8, 0xC7, 0xCE, 0xD5, 0xDC, 0x76, 0x7F, 0x64, 0x6D, 0x52, 0x5B, 0x40, 0x49, 0x3E, 0x37, 0x2C, 0x25, 0x1A, 0x13, 0x8, 0x1, 0xE6, 0xEF, 0xF4, 0xFD, 0xC2, 0xCB, 0xD0, 0xD9, 0xAE, 0xA7, 0xBC, 0xB5, 0x8A, 0x83, 0x98, 0x91, 0x4D, 0x44, 0x5F, 0x56, 0x69, 0x60, 0x7B, 0x72, 0x5, 0xC, 0x17, 0x1E, 0x21, 0x28, 0x33, 0x3A, 0xDD, 0xD4, 0xCF, 0xC6, 0xF9, 0xF0, 0xEB, 0xE2, 0x95, 0x9C, 0x87, 0x8E, 0xB1, 0xB8, 0xA3, 0xAA, 0xEC, 0xE5, 0xFE, 0xF7, 0xC8, 0xC1, 0xDA, 0xD3, 0xA4, 0xAD, 0xB6, 0xBF, 0x80, 0x89, 0x92, 0x9B, 0x7C, 0x75, 0x6E, 0x67, 0x58, 0x51, 0x4A, 0x43, 0x34, 0x3D, 0x26, 0x2F, 0x10, 0x19, 0x2, 0xB, 0xD7, 0xDE, 0xC5, 0xCC, 0xF3, 0xFA, 0xE1, 0xE8, 0x9F, 0x96, 0x8D, 0x84, 0xBB, 0xB2, 0xA9, 0xA0, 0x47, 0x4E, 0x55, 0x5C, 0x63, 0x6A, 0x71, 0x78, 0xF, 0x6, 0x1D, 0x14, 0x2B, 0x22, 0x39, 0x30, 0x9A, 0x93, 0x88, 0x81, 0xBE, 0xB7, 0xAC, 0xA5, 0xD2, 0xDB, 0xC0, 0xC9, 0xF6, 0xFF, 0xE4, 0xED, 0xA, 0x3, 0x18, 0x11, 0x2E, 0x27, 0x3C, 0x35, 0x42, 0x4B, 0x50, 0x59, 0x66, 0x6F, 0x74, 0x7D, 0xA1, 0xA8, 0xB3, 0xBA, 0x85, 0x8C, 0x97, 0x9E, 0xE9, 0xE0, 0xFB, 0xF2, 0xCD, 0xC4, 0xDF, 0xD6, 0x31, 0x38, 0x23, 0x2A, 0x15, 0x1C, 0x7, 0xE, 0x79, 0x70, 0x6B, 0x62, 0x5D, 0x54, 0x4F, 0x46, 0x0, 0xB, 0x16, 0x1D, 0x2C, 0x27, 0x3A, 0x31, 0x58, 0x53, 0x4E, 0x45, 0x74, 0x7F, 0x62, 0x69, 0xB0, 0xBB, 0xA6, 0xAD, 0x9C, 0x97, 0x8A, 0x81, 0xE8, 0xE3, 0xFE, 0xF5, 0xC4, 0xCF, 0xD2, 0xD9, 0x7B, 0x70, 0x6D, 0x66, 0x57, 0x5C, 0x41, 0x4A, 0x23, 0x28, 0x35, 0x3E, 0xF, 0x4, 0x19, 0x12, 0xCB, 0xC0, 0xDD, 0xD6, 0xE7, 0xEC, 0xF1, 0xFA, 0x93, 0x98, 0x85, 0x8E, 0xBF, 0xB4, 0xA9, 0xA2, 0xF6, 0xFD, 0xE0, 0xEB, 0xDA, 0xD1, 0xCC, 0xC7, 0xAE, 0xA5, 0xB8, 0xB3, 0x82, 0x89, 0x94, 0x9F, 0x46, 0x4D, 0x50, 0x5B, 0x6A, 0x61, 0x7C, 0x77, 0x1E, 0x15, 0x8, 0x3, 0x32, 0x39, 0x24, 0x2F, 0x8D, 0x86, 0x9B, 0x90, 0xA1, 0xAA, 0xB7, 0xBC, 0xD5, 0xDE, 0xC3, 0xC8, 0xF9, 0xF2, 0xEF, 0xE4, 0x3D, 0x36, 0x2B, 0x20, 0x11, 0x1A, 0x7, 0xC, 0x65, 0x6E, 0x73, 0x78, 0x49, 0x42, 0x5F, 0x54, 0xF7, 0xFC, 0xE1, 0xEA, 0xDB, 0xD0, 0xCD, 0xC6, 0xAF, 0xA4, 0xB9, 0xB2, 0x83, 0x88, 0x95, 0x9E, 0x47, 0x4C, 0x51, 0x5A, 0x6B, 0x60, 0x7D, 0x76, 0x1F, 0x14, 0x9, 0x2, 0x33, 0x38, 0x25, 0x2E, 0x8C, 0x87, 0x9A, 0x91, 0xA0, 0xAB, 0xB6, 0xBD, 0xD4, 0xDF, 0xC2, 0xC9, 0xF8, 0xF3, 0xEE, 0xE5, 0x3C, 0x37, 0x2A, 0x21, 0x10, 0x1B, 0x6, 0xD, 0x64, 0x6F, 0x72, 0x79, 0x48, 0x43, 0x5E, 0x55, 0x1, 0xA, 0x17, 0x1C, 0x2D, 0x26, 0x3B, 0x30, 0x59, 0x52, 0x4F, 0x44, 0x75, 0x7E, 0x63, 0x68, 0xB1, 0xBA, 0xA7, 0xAC, 0x9D, 0x96, 0x8B, 0x80, 0xE9, 0xE2, 0xFF, 0xF4, 0xC5, 0xCE, 0xD3, 0xD8, 0x7A, 0x71, 0x6C, 0x67, 0x56, 0x5D, 0x40, 0x4B, 0x22, 0x29, 0x34, 0x3F, 0xE, 0x5, 0x18, 0x13, 0xCA, 0xC1, 0xDC, 0xD7, 0xE6, 0xED, 0xF0, 0xFB, 0x92, 0x99, 0x84, 0x8F, 0xBE, 0xB5, 0xA8, 0xA3]
tblstate = []
for i in range(5):
tblstate.append(tbl[0x100 * i:0x100 * (i + 1)])


def bytes2state(data: bytes) -> list[list[int]]:
return [[data[i * 4 + j] for j in range(4)] for i in range(4)]

def state2bytes(state: list[list[int]]) -> bytes:
return bytes(state[i][j] for i in range(4) for j in range(4))

key = [0x0, 0x73, 0x69, 0x68, 0x0, 0x41, 0x63, 0x6E, 0x0, 0x65, 0x44, 0x64, 0x0, 0x79, 0x65, 0x4B, 0x0, 0x73, 0x69, 0x69, 0x0, 0x32, 0xA, 0x7, 0x0, 0x57, 0x4E, 0x63, 0x0, 0x2E, 0x2B, 0x28, 0x0, 0x73, 0x69, 0x68, 0x0, 0x41, 0x63, 0x6F, 0x0, 0x16, 0x2D, 0xC, 0x0, 0x38, 0x6, 0x24, 0x0, 0x73, 0x69, 0x6A, 0x0, 0x32, 0xA, 0x5, 0x0, 0x24, 0x27, 0x9, 0x0, 0x1C, 0x21, 0x2D, 0x0, 0x73, 0x69, 0x6E, 0x0, 0x41, 0x63, 0x6B, 0x0, 0x65, 0x44, 0x62, 0x0, 0x79, 0x65, 0x4F, 0x0, 0x73, 0x69, 0x66, 0x0, 0x32, 0xA, 0xD, 0x0, 0x57, 0x4E, 0x6F, 0x0, 0x2E, 0x2B, 0x20, 0x0, 0x73, 0x69, 0x76, 0x0, 0x41, 0x63, 0x7B, 0x0, 0x16, 0x2D, 0x14, 0x0, 0x38, 0x6, 0x34, 0x0, 0x73, 0x69, 0x56, 0x0, 0x32, 0xA, 0x2D, 0x0, 0x24, 0x27, 0x39, 0x0, 0x1C, 0x21, 0xD, 0x0, 0x73, 0x69, 0x16, 0x0, 0x41, 0x63, 0x3B, 0x0, 0x65, 0x44, 0x2, 0x0, 0x79, 0x65, 0xF, 0x0, 0x73, 0x69, 0x96, 0x0, 0x32, 0xA, 0xAD, 0x0, 0x57, 0x4E, 0xAF, 0x0, 0x2E, 0x2B, 0xA0, 0x0, 0x73, 0x69, 0x8D, 0x0, 0x41, 0x63, 0x20, 0x0, 0x16, 0x2D, 0x8F, 0x0, 0x38, 0x6, 0x2F]
key = [key[0x10 * i : 0x10 * (i + 1)] for i in range(len(key) // 16)]
key = [bytes2state(k) for k in key]

from collections import defaultdict
from typing import List, Tuple

def build_pair_maps(tbl: List[bytes]):
assert len(tbl) == 5 and all(len(t) == 256 for t in tbl)
p_map = defaultdict(list)
q_map = defaultdict(list)

t1 = tbl[1]
t4 = tbl[4]
t0 = tbl[0]
t3 = tbl[3]

# build p_map: p = t1[a0] ^ t4[a1]
for a0 in range(256):
v0 = t1[a0]
for a1 in range(256):
p = v0 ^ t4[a1]
p_map[p].append((a0, a1))

# build q_map: q = t0[a2] ^ t3[a3]
for a2 in range(256):
v2 = t0[a2]
for a3 in range(256):
q = v2 ^ t3[a3]
q_map[q].append((a2, a3))

return p_map, q_map

def invert_one_column(b0: int, b1: int, b2: int, b3: int,
tbl: List[bytes],
p_map, q_map) -> List[Tuple[int,int,int,int]]:
t0, t1, t2, t3, t4 = tbl # t2 not used directly here but kept for clarity
solutions = []
# p ^ q = b0 => q = p ^ b0
for p, left_pairs in p_map.items():
q = p ^ b0
right_pairs = q_map.get(q)
if not right_pairs:
continue

for (a0, a1) in left_pairs:
# prepare some reused values to speed up
t1_a0 = t3[a0] # note: b1 uses tbl[3][a0]
t1_a1 = t1[a1] # tbl[1][a1] used in b1 (but careful indices below)
for (a2, a3) in right_pairs:
# compute b1,b2,b3 according to formulas:
# b1 = tbl[3][a0] ^ tbl[1][a1] ^ tbl[4][a2] ^ tbl[0][a3]
if (t3[a0] ^ t1[a1] ^ t4[a2] ^ t0[a3]) != b1:
continue
# b2 = tbl[0][a0] ^ tbl[3][a1] ^ tbl[1][a2] ^ tbl[4][a3]
if (t0[a0] ^ t3[a1] ^ t1[a2] ^ t4[a3]) != b2:
continue
# b3 = tbl[4][a0] ^ tbl[0][a1] ^ tbl[3][a2] ^ tbl[1][a3]
if (t4[a0] ^ t0[a1] ^ t3[a2] ^ t1[a3]) != b3:
continue

# all matched
solutions.append((a0, a1, a2, a3))

return solutions

def inv_col_mix(state: List[List[int]], tbl: List[bytes]) -> List[List[int]]:
assert len(state) == 4 and all(len(row) == 4 for row in state)
assert len(tbl) == 5 and all(len(t) == 256 for t in tbl)

p_map, q_map = build_pair_maps(tbl)

out = [[0]*4 for _ in range(4)]
solutions_per_col = []

for col in range(4):
# read output bytes b0..b3 from rows 0..3 at this column
b0 = state[0][col] & 0xFF
b1 = state[1][col] & 0xFF
b2 = state[2][col] & 0xFF
b3 = state[3][col] & 0xFF

sols = invert_one_column(b0, b1, b2, b3, tbl, p_map, q_map)
if not sols:
raise ValueError(f"No inverse found for column {col} with bytes {(b0,b1,b2,b3)}")
a0,a1,a2,a3 = sols[0]
out[0][col] = a0
out[1][col] = a1
out[2][col] = a2
out[3][col] = a3
solutions_per_col.append(sols)

return out, solutions_per_col

def inv_addroundkey(state : list[list[int]], round_key : list[list[int]]):
for i in range(4):
for j in range(4):
state[i][j] ^= round_key[j][i]
return state

def inv_SBOXswap(state: list[list[int]]):
for i in range(4):
for j in range(4):
state[i][j] = inv_sbox.index(state[i][j])
return state

def inv_ShiftRows(state: list[list[int]]):
for i in range(4):
state[i] = state[i][i:] + state[i][:i]
return state

def inv_AES(data):
global key, inv_sbox, tblstate

state = bytes2state(data)
real_state = [[0] * 4 for _ in range(4)]
for i in range(4):
for j in range(4):
real_state[i][j] = state[j][i]
real_state = inv_addroundkey(real_state, key[0])
real_state = inv_ShiftRows(real_state)
real_state = inv_SBOXswap(real_state)
for i in range(1, 10):
real_state, _ = inv_col_mix(real_state, tblstate)
real_state = inv_addroundkey(real_state, key[i])
real_state = inv_ShiftRows(real_state)
real_state = inv_SBOXswap(real_state)
real_state = inv_addroundkey(real_state, key[10])
for i in range(4):
for j in range(4):
state[i][j] = real_state[j][i]

return state2bytes(state)

xor_key = [0xA0, 0x81, 0x3E, 0x4D, 0x5B, 0x6A, 0xD4, 0xC2, 0x7B, 0xA5, 0x46, 0x20, 0x82, 0xB5, 0x1C, 0xEF, 0x43, 0x51, 0x8B, 0xC3, 0x5E, 0x57, 0xCC, 0x4, 0xF0, 0xBC, 0x43, 0x85, 0x1E, 0x9C, 0xBB, 0x51, 0xED, 0x59, 0x82, 0xE8, 0x1C, 0x4, 0xAC, 0xB, 0xE4, 0xBA, 0xC2, 0xBA, 0x53, 0x27, 0xF0, 0x1B, 0xA1, 0x35, 0x1, 0xDF, 0xBB, 0x6F, 0xD2, 0x83, 0xEB, 0x49, 0xB5, 0x4A, 0xF7, 0xE5, 0x40, 0x4B]
xor_key = [xor_key[0x10 * i : 0x10 * (i + 1)] for i in range(len(xor_key) // 16)]
for i in range(len(enc) // 0x10):
enc[0x10 * i : 0x10 * (i + 1)] = map(int.__xor__, enc[0x10 * i : 0x10 * (i + 1)], xor_key[i])
enc[0x10 * i : 0x10 * (i + 1)] = bytearray(inv_AES(enc[0x10 * i : 0x10 * (i + 1)]))

print(enc)

table = 'AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz0123456789+/'

idx = 0
res = []
eof = False
padding = 0
while True:
p = 0
if idx >= 0x40 - 3:
p = enc[idx :]
padding = 3 - len(p)
eof = True
else:
p = enc[idx : idx + 3]
n = 0
for i, b in enumerate(p):
n |= b << 8 * i
c = []
for _ in range(4 - padding):
c += [table[n & 0x3f]]
n >>= 6
c += ['='] * (4 - len(c))
res += c
if eof:
break
idx += 3

print(''.join(res))

沪赛毁了我的第五题(其实没有沪赛也做不出来)

第六题

简单整体分析

先简单运行一下程序, 会发现是一个易语言写的GUI, 结合文件大小应该是静态编译的. 然后随意输入一些数据点击登录没有反应, 题目说明中提到验证失败没有弹窗或弹出错误提示, 再试了一下更长的输入发现有错误提示的图片了, 二分法可以快速定位到这个临界值是31字节, 说明要提交的答案大概率就是31字节长的.

接下来开始分析, 既然是WindowsGUI程序, 获取输入框内容大概率要用到GetWindowTextW, 刚好题目也导入了这个函数, 为了避开一些初始化时的反调试(如果有的话), 下面的调试都是启动程序后附加调试的.

GetWindowTextW下好断点后点击登录成功断下, step out发现获取到了输入:

1

硬件断点跟踪数据流到某个地方发现反汇编失败了:

QQ_1756216739186

观察一下会发现一条陌生的指令bextr, 应该就是它导致了这个错误, 并且这个错误发生后IDA的反汇编器会直接摆烂不再可以进行反汇编.

继续跟踪数据流发现了输入长度和一个熟悉的立即数进行了比较:

QQ_1756217008845

并且这个函数没有那条神必指令, 重启IDA来到这个函数进行反汇编, 若输入长度>= 0x1f就会把输入当参数进到do_check(12351552), 从f5结果来看输入只进到了这几个函数里进行处理:

QQ_1756217486555

调试可以发现前两个都是拷贝函数, 第三个函数真正进行了加密操作, 而为了确定加密的范围继续追踪这一步的密文, 下一次程序断下就来到了最后一个函数中, 并且和一段字节进行了比较:

QQ_1756217709357

在内存中搜索和加密结果比较的字节串可以在数据段找到:

QQ_1756217788362

修改比较结果发现可以弹出正确提示:

QQ_1756217833370

第一步加密

也就是说真正的加密逻辑只用看encrypt(12352547), 继续硬断追踪数据流, 第一步加密是将输入零扩展成DWORD:

img

再跟到下一步会发现在123533C6进行了写回:

img

在上面两个调用上下断点, 实际上callsm就是一个跳板函数, 用于调用放在EBX中的函数, 第一个跳板在每i次调用时从未被零扩展的ser取出ser[(4 - (i & 3)) * (i >> 2) : (i >> 2) << 2], 例如输入的是1234, 那么每次取出的就是:

1
2
3
4
'?234'
'234'
'34'
'4'

这里1替换成?是因为调试会发现每次取出一个DWORD时最高字节都会变化, 这个变化才是真正的第二步加密, 这里的两个跳板实际上执行的就是第二步加密完后再零扩展存入.

取出的DWORD来自[ebp-30h], 向上溯源, 这个值来自:

QQ_1756219144134

跳板执行的do_xor实际上是将第二个参数和第五个参数进行异或:

QQ_1756219390304

再向上溯源, 这个异或的字节来自[ebp-0x64]:

QQ_1756219495802

是一个有意义的字节串, 应该就是异或的key, 到这里总结一下真正的第一步加密:

  • 对输入的下标4 * i的字节异或一个字节流

第二步加密

继续跟踪数据流, 第二个字节的处理先是加了0x100:

QQ_1756219843233

这里用浮点数实现貌似是易语言特性, 接下来这个值被当作下标从一个表中取出一个字节存回.

第三步加密

第三个字节的断点在12353581被触发, 准备通过跳板进行异或操作, 和它异或的字节就是上面加密完的第二个字节:

img

在这两个操作上下断点会发现它们是交替执行的, 总结一下这两步加密

  • 奇数下标的字节+0x100取表存回
  • 偶数下标的字节异或前一个字节

接下来要解决的问题就是执行轮次的问题, 从f5的结果来看大概是执行114514 * 0x20轮:

QQ_1756220363380

在那两个操作上下条件断点分析轮次问题:

1
2
last = open('log.txt', 'r').read()
open('log.txt', 'w').write(str(int(last) + 1))

QQ_1756220474464

此时的大轮是0x1442次, 简单计算一下:

QQ_1756220525035

我的输入是0x20字节, 从子轮次次数和两次操作数量不同和计算结果来看, 有一个字节实际上没有参加2, 3步加密, 使得2, 3步加密的顺序是2; 3; 2;...; 3; 2导致取表的次数比异或的次数多了很多, 调试会发现是第1个字节没有参加这个过程.

然后就得到之前和密文对比的加密结果了.

至此可以写出keygen:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
table = [0xB5, 0xF4, 0x53, 0x8F, 0xC2, 0x9F, 0x17, 0x33, 0x47, 0x2, 0x5D, 0x55, 0x42, 0x2F, 0xBD, 0xC0, 0xA3, 0x66, 0x48, 0xCD, 0xB0, 0xE6, 0x11, 0xD6, 0xA8, 0x3, 0xED, 0xED, 0xA6, 0x79, 0x76, 0xCE, 0xC9, 0x0, 0x56, 0x13, 0x92, 0x21, 0xC2, 0xA7, 0x8D, 0x47, 0x44, 0x7D, 0x34, 0x19, 0xBE, 0x82, 0x10, 0x7, 0xAC, 0xD0, 0x21, 0x23, 0xA9, 0x24, 0x80, 0x33, 0x35, 0x92, 0x43, 0x4, 0xB5, 0x77, 0xA1, 0x1, 0xBB, 0xB0, 0x57, 0x3, 0x88, 0x9, 0x49, 0x6B, 0xCF, 0xF8, 0x6D, 0x6F, 0xBC, 0x8C, 0xE5, 0xB1, 0x35, 0xA0, 0x6B, 0x16, 0x60, 0x54, 0xF2, 0xD5, 0x65, 0xBE, 0x8A, 0xCE, 0x75, 0xDC, 0x85, 0x1E, 0xB, 0xCD, 0xD8, 0xF0, 0x71, 0x41, 0xC4, 0x95, 0x87, 0x2F, 0xB5, 0xD8, 0xC0, 0xC6, 0x6A, 0x8B, 0x6D, 0xA5, 0x56, 0x66, 0x3E, 0x4E, 0x46, 0x12, 0x5, 0xD8, 0x45, 0x80, 0xBE, 0xE5, 0xBC, 0x7F, 0xCD, 0xD4, 0xDE, 0x8E, 0x86, 0x38, 0x43, 0xEE, 0xF2, 0x88, 0xD3, 0xFC, 0xD0, 0x18, 0xE6, 0xBE, 0xDB, 0x47, 0xAA, 0xBC, 0x4B, 0xFA, 0xC4, 0x11, 0x9E, 0x4A, 0x3A, 0xC1, 0x98, 0x7A, 0x90, 0x4D, 0x89, 0x2C, 0x31, 0x85, 0xCE, 0xD4, 0x11, 0x9E, 0x9A, 0x6C, 0x91, 0x84, 0xF7, 0x6A, 0xA3, 0x71, 0x7, 0xEF, 0x2E, 0xBF, 0x90, 0x41, 0xB4, 0xFB, 0xB7, 0x7B, 0x32, 0x3A, 0xC, 0x83, 0x47, 0xB0, 0xC7, 0x3D, 0x99, 0x7E, 0x51, 0xFE, 0x75, 0xCC, 0x7, 0x44, 0xB5, 0x18, 0x3A, 0xA4, 0xE7, 0xCD, 0x7A, 0x3, 0xAB, 0x18, 0x14, 0x9, 0x5D, 0xF7, 0xD9, 0xD3, 0xF4, 0x93, 0x21, 0xE8, 0x2A, 0xCF, 0x10, 0x6F, 0xDE, 0x21, 0x18, 0x9F, 0xB6, 0xA1, 0xBF, 0x76, 0x8, 0x5F, 0xA3, 0xAE, 0xFB, 0xFA, 0xBB, 0xED, 0xE9, 0x6E, 0xDF, 0x3C, 0x8, 0x2E, 0x8B, 0xBA, 0x4A, 0x73, 0xE0, 0x91, 0x5E, 0x7C, 0x6D, 0xFF, 0xAE, 0xE2, 0xA7, 0x39, 0x5F, 0x99, 0xCE, 0x6E, 0xF1, 0x95, 0x19, 0x80, 0x87, 0xB1, 0x96, 0x58, 0xCD, 0x54, 0xFC, 0x4C, 0x6C, 0x9C, 0x1E, 0x1A, 0x40, 0x42, 0xE, 0x65, 0xBE, 0x13, 0x8D, 0x4D, 0x85, 0x66, 0xC3, 0xBC, 0x11, 0xDE, 0xFE, 0xA2, 0x2C, 0xDA, 0xC5, 0xC8, 0xD3, 0xB7, 0xB4, 0x48, 0x5A, 0x45, 0xEA, 0x18, 0x89, 0xE5, 0xE0, 0xF9, 0x52, 0x35, 0xEC, 0x1B, 0x47, 0xAF, 0xDB, 0xA0, 0x1F, 0x12, 0xE9, 0xB3, 0x3F, 0xC7, 0x24, 0x33, 0xE6, 0xBD, 0x46, 0x2A, 0x88, 0x53, 0x76, 0x7A, 0x0, 0x6F, 0xD8, 0x3C, 0xD, 0x81, 0x59, 0xD0, 0xAA, 0xDD, 0x1, 0x75, 0xD1, 0x26, 0xAC, 0x77, 0x4A, 0x9, 0x5, 0x8B, 0xF, 0xF3, 0x2, 0x17, 0xED, 0x57, 0xF2, 0x5D, 0x70, 0x8, 0xB2, 0x3E, 0xEF, 0xC1, 0xA9, 0x2F, 0xF5, 0xA8, 0x6, 0x60, 0x51, 0x9F, 0x1C, 0xCC, 0x72, 0x8C, 0x31, 0x98, 0x29, 0xEB, 0xAD, 0x64, 0x9E, 0xF8, 0xB0, 0x30, 0x78, 0xE4, 0x9A, 0x62, 0xE1, 0x9B, 0x1D, 0x63, 0x10, 0x84, 0x74, 0xC0, 0xDC, 0x15, 0x49, 0x7D, 0x4B, 0xCB, 0xFB, 0x16, 0xB, 0x56, 0x2D, 0xA1, 0xE7, 0x34, 0x27, 0x86, 0xEE, 0xA, 0xC2, 0xCF, 0x50, 0xC6, 0x55, 0x9D, 0xD4, 0x61, 0x8F, 0x41, 0xC9, 0xD5, 0x94, 0xBB, 0x20, 0x79, 0x8E, 0x92, 0xBA, 0x68, 0xE8, 0xA3, 0x25, 0x3A, 0x7F, 0xD9, 0xBF, 0xA5, 0x5B, 0x14, 0xDF, 0xFD, 0x37, 0x44, 0x23, 0xD6, 0x83, 0x32, 0xA6, 0xD7, 0x7E, 0x5C, 0x6A, 0x3D, 0xB8, 0xF0, 0xE3, 0x7B, 0x28, 0xC, 0xD2, 0xCA, 0xB6, 0xC4, 0x43, 0x22, 0x82, 0x3, 0xF6, 0x7, 0xFA, 0x97, 0x21, 0x90, 0x6B, 0x4E, 0xA4, 0xAB, 0x93, 0x67, 0xF4, 0x71, 0x38, 0xF7, 0x3B, 0x69, 0x2E, 0x4F, 0xB5, 0xB9, 0x2B, 0x8A, 0x36, 0x91, 0x73, 0x4, 0x42, 0xD5, 0x18, 0x8, 0x0, 0xEF, 0x12, 0x8B, 0x67, 0x29, 0x50, 0x46, 0x17, 0xB9, 0x9, 0x24, 0x9E, 0xFC, 0xF0, 0x9E, 0xE4, 0x52, 0xB7, 0x2E, 0xC7, 0x2F, 0xD1, 0x7, 0x2, 0x6F, 0x7D, 0x3, 0x53, 0xEA, 0x0, 0xDD, 0xDD, 0x49, 0x31, 0xA0, 0xCB, 0x18, 0x3B, 0x5F, 0x36, 0x1C, 0x9F, 0x27, 0x48, 0xE6, 0x78, 0x32, 0xA2, 0xA8, 0x3, 0x5D, 0xFC, 0x48, 0x5E, 0xDC, 0xB, 0xB3, 0x90, 0x2D, 0xA8, 0x74, 0xCA, 0x4A, 0x2E, 0x85, 0xED, 0x23, 0x24, 0x64, 0x4B, 0x4B, 0x1C, 0x6A, 0xB2, 0xF2, 0xDA, 0x59, 0xA7, 0x13, 0xB9, 0x34, 0xEF, 0xEE, 0x4B, 0x53, 0x54, 0xB9, 0x40, 0xB6, 0xA5, 0x93, 0x89, 0x9A, 0xFF, 0xB9, 0xBD, 0x4A, 0x4B, 0xFC, 0xBB, 0x38, 0x8, 0x73, 0x91, 0x4C, 0x4B, 0x6D, 0x9C, 0x7C, 0x3, 0xA9, 0xF1, 0x9D, 0x82, 0xCA, 0xFC, 0x78, 0x39, 0x5, 0x67, 0x21, 0xC3, 0x1D, 0x3D, 0x84, 0x26, 0x91, 0x50, 0x41, 0x55, 0x14, 0xD8, 0xBA, 0xF9, 0x3D, 0x5C, 0x69, 0x70, 0x80, 0xD6, 0x78, 0x16, 0x5D, 0x12, 0x8B, 0xC4, 0xD7, 0x57, 0xE1, 0x97, 0x28, 0x49, 0x9B, 0xF3, 0xB3, 0xE, 0x5B, 0xC7, 0x3A, 0xB0, 0x11, 0x12, 0x51, 0xC2, 0x12, 0xA6, 0x12, 0x47, 0x6B, 0x2C, 0x13, 0xCF, 0x74, 0x68, 0x95, 0xE3, 0xA8, 0xBE, 0xFE, 0xA3, 0xB3, 0xF5, 0x8A, 0xAE, 0xCD, 0x3C, 0x3D, 0x42, 0x47, 0x6A, 0x1C, 0xA5, 0x63, 0x8A, 0x9C, 0xC3, 0x69, 0x97, 0x5B, 0x18, 0xF7, 0x84, 0xE, 0xD0, 0x99, 0x7F, 0xBA, 0x2D, 0x99, 0x77, 0x28, 0x2A, 0x19, 0xDC, 0x93, 0x5E, 0x5E, 0xA6, 0xA3, 0x22, 0x6F, 0x98, 0x9F, 0xF6, 0xDF, 0xC6, 0xDE, 0x21, 0xE7, 0x55, 0x7E, 0x98, 0xB8, 0x82, 0x59, 0x21, 0xE, 0xE5, 0x35, 0xB8, 0x9, 0xF7, 0x3B, 0x32, 0x39, 0xD3, 0xAB, 0x20, 0xF7, 0x39, 0xCD, 0xF6, 0xFC, 0xD8, 0x2B, 0x6D, 0x2C, 0xCD, 0xFD, 0x25, 0xB3, 0x67, 0xE5, 0x8F, 0x53, 0x2D, 0xDC, 0xA, 0xFC, 0x22, 0x6C, 0x4C, 0x9E, 0x47, 0x21, 0x4, 0x3B, 0x62, 0x3A, 0xBD, 0x40, 0xFE, 0xA3, 0x6, 0x15, 0xB3, 0x28, 0xD0, 0xF3, 0xA7, 0xE3, 0x17, 0xF6, 0x55, 0xF6, 0xC5, 0x73, 0x8D, 0x80, 0xD3, 0x8B, 0xBC, 0xC9, 0xB1, 0x0, 0x6E, 0xC0, 0xE8, 0x48, 0x11, 0xA8, 0xFE, 0xE0, 0xFC, 0xE, 0x99, 0xE3, 0xB0, 0xFE, 0xE8, 0xDB, 0x5D, 0x76, 0x3F, 0xD7, 0xA8, 0x1B, 0x1, 0xBE, 0xAB, 0x2B, 0xC3, 0xE2, 0x3D, 0xB3, 0xAE, 0xD8, 0x74, 0x2, 0x25, 0x88, 0x69, 0x5D, 0xA8, 0x80, 0x3B, 0xF4, 0xF9, 0x8E, 0x57, 0x15, 0x7D, 0x8D, 0xF6, 0xA0, 0xE4, 0x7F, 0xE7, 0xBB, 0xD, 0xDC, 0x8E, 0xC6, 0x23, 0x2A, 0x2D, 0x92, 0xD, 0xCE, 0x62, 0xCD, 0x5, 0x22, 0xF1, 0xC1, 0x86, 0xC7, 0xC4, 0x3F, 0x6C, 0x3D, 0x30, 0xD5, 0x57, 0xB0, 0x7A, 0x47, 0x50, 0x15, 0x9A, 0x3D, 0xAF, 0x76, 0x3E, 0x3A, 0x3B, 0x8A, 0x12, 0xCD, 0x94, 0x89, 0x3F, 0xB, 0xCE, 0x3E, 0x31, 0x3C, 0x5F, 0x5E, 0x9E, 0xD5, 0x3B, 0x18, 0xC4, 0xA7, 0x3D, 0xED, 0xF2, 0x55, 0xC9, 0xC2, 0x49, 0xB, 0xB0, 0x34, 0xC4, 0x6D, 0x53, 0x2B, 0x76, 0xCE, 0xC, 0xB2, 0x13, 0xA3, 0xC9, 0x6, 0xB2, 0x37, 0xFA, 0xEC, 0xD1, 0xA0, 0xAE, 0x48, 0x9A, 0xF1, 0xF8, 0xEC, 0x65, 0xB1, 0x98, 0xAE, 0x7D, 0x8C, 0xD7, 0xBD, 0x27, 0x49, 0xB3, 0x35, 0xE0, 0xFC, 0x3C, 0xF0, 0xE7, 0x7D, 0x3E, 0xA0, 0xFB, 0x18, 0x20, 0x1A, 0x66, 0x86, 0xC, 0xF5, 0x3A, 0x1C, 0x51, 0x54, 0xDB, 0x43, 0x5, 0x0, 0xBD, 0x28, 0xEE, 0xBA, 0x6F, 0xB5, 0xA3, 0xCF, 0xD9, 0xBF, 0xEE, 0xEC, 0xC2, 0x81, 0x75, 0x34, 0x95, 0x49, 0x99, 0x90, 0x64, 0x71][0x100:]

assert len(set(table)) == 256

enc = [0x0, 0x1D, 0x3B, 0x29, 0x70, 0x12, 0x69, 0xB7, 0x6C, 0xF, 0x4D, 0x5C, 0x9F, 0x5B, 0x6C, 0x1B, 0xB5, 0x47, 0xA2, 0x28, 0xC0, 0xF8, 0xDC, 0xE0, 0x7A, 0xF8, 0xD6, 0x28, 0xF6, 0xF8, 0x93, 0xb3]

key = [0x4B, 0xC, 0x54, 0xF, 0x32, 0x0, 0x2, 0x35]

enc[0] ^= key[0]
print(chr(enc[0]), end='')
enc = enc[1:]
key = key[1:]
for _ in range(114514):
for idx in range(len(enc) - 1, -1, -1):
if idx & 1:
enc[idx] ^= enc[idx - 1]
else:
enc[idx] = table.index(enc[idx])

for i, b in enumerate(key):
enc[3 + 4 * i] ^= b

print(bytes(enc).decode('GBK'))

需要注意的是用chr输出最后的字符会发现有乱码, 但是密文的解密有互相依赖性, 不可能某一部分成功解密而其他不行, 结合这是易语言写的程序, 大概率是中文编码, 结果还真是.

第七题

题目为了混淆用string_to_code(buf)替换了很多立即数, 实际上string_to_code就是一个map, 用以下patcher可以美化一下代码:

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
from idc import GetDisasm, patch_byte
from ida_ua import insn_t, decode_insn
from keystone import *

ks = Ks(KS_ARCH_X86, KS_MODE_32)

def patch_bytes(address, new_bytes):
"""
Patch the bytes at the given address with the new bytes.
"""
for i, byte in enumerate(new_bytes):
patch_byte(address + i, byte)

start = 0x0804888F
end = 0x08049461

mapping = {
"act": 0,
"abort": 1,
"con": 0,
"cancel": 1,
"enable": 2,
"reset": 4,
"start": 2,
"stop": 3,
"run": 2,
"reboot": 4,
}

_ = insn_t()
while start <= end:
disasm = GetDisasm(start)
length = decode_insn(_, start)
if disasm == 'call string_to_code':
key = GetDisasm(start - 7).split()[-1].strip().strip('"')
# print(f"Found {key} at {hex(start - 7)}")
new_code = f"mov eax, {mapping[key]}"
asm_code = bytes(ks.asm(new_code)[0]).ljust(12, b'\x90')
patch_bytes(start - 7, asm_code)
print(f"Patched {hex(start)} with {asm_code}")

start += length

程序第一步是将输入的前10字节从十六进制字符转成数字, 然后通过这些hexnum初始化了两个矩阵:

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
idx1 = 0;
idx2 = 0;
idx3 = 0;
idx4 = 0;
idx5 = 0;
idx6 = 0;
cns2 = 0;
for ( i = 0; i <= 4; ++i )
{
for ( j = 0; j <= 4; ++j )
{
if ( i || j != 3 )
{
if ( i != 1 || j )
{
if ( i == 1 && j == 2 )
{
m1[i][2] = hexnum[idx1++];
m2[0][idx2++] = m1[i][j];
m2[2][idx4++] = m1[i][j];
}
else if ( i == 2 && j == 1 )
{
m1[i][1] = hexnum[idx1++];
m2[0][idx2++] = m1[i][j];
m2[1][idx3++] = m1[i][j];
}
else if ( i == 2 && j == 3 )
{
m1[i][3] = hexnum[idx1++];
m2[2][idx4++] = m1[i][j];
m2[3][idx5++] = m1[i][j];
}
else if ( i != 3 || j )
{
if ( i == 3 && j == 2 )
{
m1[i][2] = hexnum[idx1++];
m2[1][idx3++] = m1[i][j];
m2[4][idx6++] = m1[i][j];
}
else if ( i == 3 && j == 3 )
{
m1[i][3] = hexnum[idx1++];
m2[3][idx5++] = m1[i][j];
m2[4][idx6++] = m1[i][j];
}
else if ( i == 3 && j == 4 )
{
m1[i][4] = hexnum[idx1++];
m2[2][idx4++] = m1[i][j];
}
else if ( i == 4 && j == 2 )
{
m1[i][2] = hexnum[idx1++];
m2[3][idx5++] = m1[i][j];
}
else
{
m1[i][j] = i + j;
}
}
else
{
m1[i][0] = hexnum[idx1++];
m2[4][idx6++] = m1[i][j];
}
}
else
{
m1[i][0] = hexnum[idx1++];
m2[1][idx3++] = m1[i][j];
}
}
else
{
m1[i][3] = hexnum[idx1++];
m2[0][idx2++] = m1[i][j];
}
if ( i == 4 )
cns2 += m1[4][j];
}
if ( stat("/etc/rc.d", (struct stat *)buf) )
cns2 += m2[i][j];
}

然后判断了一些条件后检验输入的11~14字节是否满足某些条件, 一眼丁真出asas:

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
cns1 = 1;
for ( i = 0; i <= 4; ++i )
{
n19 = 0;
for ( j = 0; j <= 2; ++j )
{
n19 += m2[i][j];
n19 -= 5;
}
if ( n19 != 19 )
cns1 = 0;
}
memset(s, 0, sizeof(s));
v22 = open("/proc/self/as", (int)"r");
if ( v22 == -1 )
++cns2;
if ( devctl(v22, 0x41100801, s, 272, 0) )
++cns2;
if ( cns2 != m1[0][0] + m1[0][3] * m1[0][2] * m1[0][1] * m1[0][4] + (s[8] & 0x80) - 34 )
cns1 = 0;
sm = 123;
if ( input[10] == 97 ) // asas
sm += 45;
if ( input[10] == 115 )
sm -= 45;
if ( input[11] == 97 )
sm += 67;
if ( input[11] == 115 )
sm -= 67;
if ( input[12] == 97 )
sm += 8;
if ( input[12] == 115 )
sm -= 8;
if ( input[13] == 97 )
sm += 9;
if ( input[13] == 115 )
sm -= 9;
if ( sm != 100 )
cns1 = 0;
if ( cns1 == 1 && strlen(input) <= 0xE )
puts("ok");
else
puts("no");
return 0;

写个z3就能解出符合条件的14字节输入了:

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

ans = [BitVec('ans_%d' % i, 8) for i in range(10)]
s = Solver()
for i in range(10):
s.add(And(ans[i] >= 0, ans[i] <= 0xf))

idx1 = 0
idx2 = 0
idx3 = 0
idx4 = 0
idx5 = 0
idx6 = 0
cns2 = 0

m1 = [[0] * 5 for _ in range(5)]
m2 = [[0] * 3 for _ in range(5)]

"""
emulate:
for ( i = 0; i <= 4; ++i )
{
for ( j = 0; j <= 4; ++j )
{
if ( i || j != 3 )
{
if ( i != 1 || j )
{
if ( i == 1 && j == 2 )
{
m1[i][2] = hexnum[idx1++];
m2[0][idx2++] = m1[i][j];
m2[2][idx4++] = m1[i][j];
}
else if ( i == 2 && j == 1 )
{
m1[i][1] = hexnum[idx1++];
m2[0][idx2++] = m1[i][j];
m2[1][idx3++] = m1[i][j];
}
else if ( i == 2 && j == 3 )
{
m1[i][3] = hexnum[idx1++];
m2[2][idx4++] = m1[i][j];
m2[3][idx5++] = m1[i][j];
}
else if ( i != 3 || j )
{
if ( i == 3 && j == 2 )
{
m1[i][2] = hexnum[idx1++];
m2[1][idx3++] = m1[i][j];
m2[4][idx6++] = m1[i][j];
}
else if ( i == 3 && j == 3 )
{
m1[i][3] = hexnum[idx1++];
m2[3][idx5++] = m1[i][j];
m2[4][idx6++] = m1[i][j];
}
else if ( i == 3 && j == 4 )
{
m1[i][4] = hexnum[idx1++];
m2[2][idx4++] = m1[i][j];
}
else if ( i == 4 && j == 2 )
{
m1[i][2] = hexnum[idx1++];
m2[3][idx5++] = m1[i][j];
}
else
{
m1[i][j] = i + j;
}
}
else
{
m1[i][0] = hexnum[idx1++];
m2[4][idx6++] = m1[i][j];
}
}
else
{
m1[i][0] = hexnum[idx1++];
m2[1][idx3++] = m1[i][j];
}
}
else
{
m1[i][3] = hexnum[idx1++];
m2[0][idx2++] = m1[i][j];
}
if ( i == 4 )
cns2 += m1[4][j];
}
if ( stat("/etc/rc.d", (struct stat *)buf) )
cns2 += m2[i][j];
}
"""

for i in range(5):
for j in range(5):
if i or j != 3:
if i != 1 or j:
if i == 1 and j == 2:
m1[i][2] = ans[idx1]
m2[0][idx2] = m1[i][j]
m2[2][idx4] = m1[i][j]
idx1 += 1
idx2 += 1
idx4 += 1
elif i == 2 and j == 1:
m1[i][1] = ans[idx1]
m2[0][idx2] = m1[i][j]
m2[1][idx3] = m1[i][j]
idx1 += 1
idx2 += 1
idx3 += 1
elif i == 2 and j == 3:
m1[i][3] = ans[idx1]
m2[2][idx4] = m1[i][j]
m2[3][idx5] = m1[i][j]
idx1 += 1
idx4 += 1
idx5 += 1
elif i != 3 or j:
if i == 3 and j == 2:
m1[i][2] = ans[idx1]
m2[1][idx3] = m1[i][j]
m2[4][idx6] = m1[i][j]
idx1 += 1
idx3 += 1
idx6 += 1
elif i == 3 and j == 3:
m1[i][3] = ans[idx1]
m2[3][idx5] = m1[i][j]
m2[4][idx6] = m1[i][j]
idx1 += 1
idx5 += 1
idx6 += 1
elif i == 3 and j == 4:
m1[i][4] = ans[idx1]
m2[2][idx4] = m1[i][j]
idx1 += 1
idx4 += 1
elif i == 4 and j == 2:
m1[i][2] = ans[idx1]
m2[3][idx5] = m1[i][j]
idx1 += 1
idx5 += 1
else:
m1[i][j] = i + j
else:
m1[i][0] = ans[idx1]
m2[4][idx6] = m1[i][j]
idx1 += 1
idx6 += 1
else:
m1[i][0] = ans[idx1]
m2[1][idx3] = m1[i][j]
idx1 += 1
idx3 += 1
else:
m1[i][3] = ans[idx1]
m2[0][idx2] = m1[i][j]
idx1 += 1
idx2 += 1
if i == 4:
cns2 += m1[4][j]
if False:
cns2 += m2[i][j]

for i in range(5):
sm = 0
for j in range(3):
sm += m2[i][j]
sm -= 5
s.add(sm == 19)

if False:
cns2 += 1

if False:
cns2 += 1

s.add(cns2 == m1[0][0] + m1[0][3] * m1[0][2] * m1[0][1] * m1[0][4] + 0 - 34)
count = 0
while s.check() == sat:
m = s.model()
print(count, "".join([f"{m[a].as_long():1x}" for a in ans]) + "asas")
s.add(Or([a != m[a] for a in ans]))
count += 1

坏消息是总共有400多个可行解, 这时候观察一血会发现他在一小时二十分左右提交了正确序列号, 如果他的python, z3版本和我相同并且是从头试到尾那么他应该是20分左右解出题目, 花了1个小时交序列号, 在第30~39个内大概率有正确序列号, 找到正确序列号在第31个:

1
2
3
4
5
...
30 9acdcfb8aeasas
31 86bfd6dfa6asas
32 8ebfde5fa6asas
...

下次出题的自己能不能验证一下有没有多解

第八题

记一次VMPWN分析

第九题

DIE查出来一个Enigma Virtual Box壳, 直接放弃静态分析, 通过附加调试的方式进行动态分析

定位主函数

输入SN前在IDA挂起线程, 然后输入后步出到用户领空为止:

QQ_1756776323922

查看RAX可以看到刚刚输入的SN字符串, 同时可以观察出程序使用的库底层储存字符串的逻辑:

QQ_1756776499071

1
2
3
4
5
6
struct str
{
void *nop;
_DWORD len;
_WORD s[];
};

对Name的处理

程序先对Name进行了简单的hash, 最终结果存放在v8:

QQ_1756776603933

当输入的是公开序列号对应的Name时v8为55, 输入KCTF时v8为27

对SN的处理

展开SN

第一次用到SN在main+12B, 观察处理完的结果, 看到了大量重复的9, 结合序列号的格式, 大概率是{digit}{count}l的格式, 只是最后的l被省略, 而这里处理完的字符串因为太长是用类似链表的方式储存的, 偏移8, 10h, 18h的成员分别代表当前段, 上一段, 当前段的长度:

QQ_1756777070979

除法运算

下一次对SN的处理在main+276, 同时还用到了上面Name的hash, 它被以十进制字符串的形式储存起来了:

QQ_1756777504285

通过硬件断点跟踪数据流发现它和SN用空格拼接起来了:

QQ_1756777620479

步出至用户领空, 发现这个拼接的字符串上面还有一个路径字符串:

QQ_1756777745414

QQ_1756777886003

结合上面用空格拼接的字符串, 很有可能是当作命令行参数传给了这个释放出来的PE

找到这个PE, 发现是一个python打包程序, 解包反编译后得到以下py代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from decimal import Decimal, getcontext
import os
import sys

def reciprocal(m, n, prec):
getcontext().prec = int(prec)
result = Decimal(m) / Decimal(n)
return result

def main(arg1, arg2, arg3):
sys.set_int_max_str_digits(int(arg3))
reciprocal_value = reciprocal(arg1, arg2, arg3)
print(reciprocal_value)
if __name__ == '__main__':
arg1 = sys.argv[1]
arg2 = sys.argv[2]
arg3 = sys.argv[3]
main(arg1, arg2, arg3)

和上面的想法差不多, 通过命令行参数进行了一个除法, 得到的是十进制字符串, 但是有3个命令行参数, 找到最后拼接的作为精度的参数:

QQ_1756778121548

实际上到这一步就不用分析下面的程序的, 因为程序给出了能通过校验的55 / N1的N1, 对应到KCTF的27, 只需要算出27 / N2 = 27 / (N1 * 27 / 55) = 55 / N1的N2即为正确答案:

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
from decimal import Decimal, getcontext
prec = 0x400000
getcontext().prec = prec
ser = '51l41l92022l51l01l41l92022l52l92023l71l21l51l'
length = 0
num = ''
for seg in ser.split('l')[:-1]:
digit = seg[0]
count = int(seg[1:])
num += digit * count
length += count

N2 = Decimal('27') * Decimal(num) / Decimal('55')
getcontext().prec = length
def to_repeat_format(s):
result = []
i = 0
while i < len(s):
digit = s[i]
count = 1
while i + count < len(s) and s[i+count] == digit:
count += 1
result.append(f'{digit}{count}l')
i += count
return ''.join(result)

print(to_repeat_format(str(N2))[:-1])

第十题

没有任何技巧, 只有数据流追踪

输入的数据第一步处理在0000000140044310, 大概可以看出来是一个将数据转化为二进制串的函数, 第一个参数是结果, 第二个参数为要转化的数据, 最后是要转化成的长度, 只是把0换成2, 1换成3然后拼接每一次转化的结果, 以16字节为一块:

img

然后就是不停下硬件断点跟踪, 直到进到000000014000E5A0进行了第二步处理, 第一步处理得到的binstr的每一位作为一级索引, 配合一个同样由23组成的key在一张9 * 9的表(00000001400C10C0)中取值:

QQ_1756888952753

再继续跟这一步得到的密文, 在sub_14000CB50进行了最终校验.

然后在拼接完二进制字符串后的00000001400587A6下断点进行加密轮次分析, 一共断下3次, 说明密文长度应该为0x30, 然后就是跟踪每一块的二进制字符串到加密和校验部分分别获取key和密文, 其中加密函数在程序运行中大概要被调用30000多次, 所以还是使用数据追踪的方式获取key, keygen如下:

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
from Crypto.Util.number import long_to_bytes

def bin2long(bs):
if isinstance(bs, list):
bs = bytes(bs)
return int(''.join(bs.replace(b'\x02', b'0').replace(b'\x03', b'1').decode()), 2)

key = [0x3,0x2,0x3,0x3,0x3,0x3,0x3,0x2,0x2,0x2,0x3,0x2,0x2,0x3,0x3,0x3,0x2,0x3,0x2,0x2,0x3,0x3,0x3,0x3,0x2,0x2,0x3,0x2,0x3,0x3,0x3,0x2,0x3,0x2,0x2,0x3,0x3,0x3,0x2,0x2,0x3,0x2,0x3,0x3,0x3,0x3,0x2,0x3,0x2,0x3,0x2,0x3,0x2,0x3,0x3,0x3,0x2,0x2,0x2,0x3,0x2,0x2,0x2,0x2,0x3,0x2,0x2,0x3,0x2,0x2,0x2,0x3,0x3,0x3,0x2,0x2,0x3,0x3,0x3,0x2,0x3,0x3,0x3,0x2,0x2,0x2,0x2,0x3,0x3,0x3,0x2,0x2,0x2,0x3,0x2,0x3,0x2,0x3,0x2,0x3,0x2,0x2,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x2,0x2,0x2,0x3,0x3,0x3,0x3,0x3,0x3,0x2,0x3,0x3,0x3,0x2,0x2,0x3,0x2,0x2,0x2,0x3,0x3,0x2,0x3,0x2,0x2,0x2,0x3,0x2,0x3,0x3,0x2,0x3,0x3,0x3,0x3,0x3,0x2,0x2,0x2,0x3,0x3,0x2,0x3,0x2,0x3,0x2,0x3,0x2,0x2,0x3,0x2,0x2,0x2,0x2,0x2,0x2,0x2,0x2,0x2,0x3,0x2,0x2,0x2,0x3,0x2,0x2,0x2,0x2,0x3,0x2,0x2,0x3,0x2,0x2,0x3,0x3,0x2,0x2,0x3,0x2,0x3,0x2,0x2,0x2,0x3,0x3,0x3,0x2,0x2,0x2,0x2,0x3,0x3,0x2,0x2,0x3,0x3,0x2,0x2,0x3,0x2,0x3,0x2,0x3,0x2,0x3,0x2,0x2,0x3,0x3,0x2,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x2,0x3,0x3,0x2,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x2,0x3,0x2,0x3,0x2,0x2,0x2,0x3,0x3,0x2,0x3,0x3,0x3,0x2,0x2,0x3,0x2,0x2,0x3,0x2,0x2,0x2,0x3,0x3,0x3,0x3,0x2,0x2,0x2,0x2,0x2,0x2,0x3,0x3,0x2,0x3,0x3,0x2,0x3,0x2,0x2,0x2,0x3,0x2,0x3,0x3,0x2,0x3,0x3,0x2,0x3,0x2,0x3,0x2,0x2,0x3,0x2,0x2,0x3,0x3,0x3,0x2,0x3,0x2,0x2,0x3,0x3,0x3,0x2,0x3,0x2,0x2,0x2,0x3,0x2,0x3,0x3,0x3,0x2,0x2,0x2,0x2,0x2,0x2,0x2,0x3,0x3,0x3,0x2,0x2,0x2,0x2,0x3,0x3,0x3,0x3,0x2,0x3,0x2,0x3,0x2,0x3,0x2,0x2,0x3,0x2,0x3,0x2,0x2,0x2,0x3,0x2,0x2,0x2,0x3,0x2,0x3,0x3,0x2,0x2,0x3,0x2,0x2,0x3,0x3,0x2,0x3,0x2,0x3,0x3,0x2,0x3,0x3,0x3,0x3,0x3,0x2,0x2,0x3,0x3,0x3,0x3,0x2,0x3,0x3]
key = [key[0x80 * i : 0x80 * (i + 1)] for i in range(len(key) // 0x80)]

enc = [0x3,0x3,0x3,0x3,0x2,0x3,0x2,0x3,0x2,0x3,0x3,0x2,0x2,0x3,0x2,0x2,0x2,0x2,0x2,0x3,0x3,0x2,0x3,0x3,0x2,0x3,0x3,0x2,0x3,0x2,0x2,0x2,0x3,0x3,0x3,0x2,0x2,0x3,0x3,0x3,0x3,0x2,0x2,0x2,0x2,0x3,0x2,0x3,0x2,0x3,0x3,0x2,0x2,0x2,0x3,0x3,0x2,0x3,0x3,0x3,0x2,0x3,0x2,0x3,0x3,0x2,0x3,0x2,0x2,0x2,0x3,0x2,0x3,0x3,0x3,0x3,0x3,0x3,0x2,0x2,0x3,0x3,0x2,0x3,0x2,0x2,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x3,0x2,0x2,0x2,0x2,0x3,0x3,0x2,0x2,0x2,0x2,0x3,0x3,0x2,0x3,0x2,0x3,0x2,0x3,0x3,0x3,0x2,0x2,0x3,0x3,0x3,0x2,0x3,0x3,0x3,0x3,0x3,0x2,0x2,0x3,0x3,0x3,0x3,0x2,0x2,0x2,0x2,0x3,0x2,0x2,0x2,0x2,0x3,0x3,0x2,0x3,0x3,0x2,0x3,0x2,0x2,0x2,0x2,0x2,0x2,0x3,0x3,0x2,0x2,0x3,0x3,0x2,0x2,0x3,0x3,0x2,0x2,0x2,0x2,0x3,0x3,0x2,0x3,0x3,0x2,0x2,0x2,0x3,0x2,0x2,0x3,0x2,0x3,0x2,0x3,0x3,0x3,0x2,0x3,0x3,0x3,0x2,0x2,0x3,0x2,0x3,0x2,0x2,0x3,0x2,0x2,0x3,0x3,0x3,0x3,0x3,0x2,0x3,0x2,0x3,0x3,0x2,0x2,0x3,0x3,0x3,0x2,0x3,0x2,0x3,0x2,0x3,0x3,0x2,0x2,0x2,0x3,0x3,0x2,0x3,0x3,0x2,0x3,0x3,0x3,0x2,0x2,0x3,0x3,0x3,0x2,0x2,0x3,0x2,0x2,0x3,0x2,0x2,0x2,0x3,0x2,0x3,0x2,0x2,0x2,0x2,0x3,0x2,0x2,0x2,0x3,0x2,0x2,0x2,0x3,0x3,0x3,0x3,0x3,0x2,0x2,0x2,0x3,0x3,0x3,0x2,0x2,0x2,0x2,0x2,0x3,0x2,0x2,0x3,0x3,0x2,0x2,0x2,0x3,0x3,0x2,0x3,0x3,0x2,0x3,0x2,0x3,0x3,0x2,0x2,0x2,0x3,0x2,0x2,0x2,0x2,0x3,0x3,0x2,0x3,0x3,0x3,0x3,0x3,0x3,0x2,0x2,0x2,0x3,0x2,0x2,0x2,0x2,0x2,0x3,0x3,0x3,0x3,0x3,0x3,0x2,0x3,0x3,0x2,0x2,0x2,0x3,0x3,0x3,0x2,0x3,0x2,0x3,0x2,0x2,0x3,0x2,0x3,0x2,0x2,0x2,0x3,0x2,0x2,0x2,0x3,0x2,0x3,0x3,0x2,0x2,0x3,0x2,0x2,0x3,0x3,0x2,0x3,0x2,0x3,0x3,0x2,0x3,0x3,0x3,0x3,0x3,0x2,0x2,0x3,0x3,0x3,0x3,0x2,0x3,0x3]

table = [0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x0, 0x1, 0x2, 0x3, 0x1, 0x1, 0x2, 0x3, 0x1, 0x0, 0x1, 0x3, 0x2, 0x1, 0x1, 0x3, 0x2, 0x1, 0x0, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x0, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x0, 0x1, 0x2, 0x3, 0x1, 0x1, 0x2, 0x3, 0x1, 0x0, 0x1, 0x3, 0x2, 0x1, 0x1, 0x3, 0x2, 0x1, 0x0, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1]

enc = [enc[0x80 * i : 0x80 * (i + 1)] for i in range(len(enc) // 0x80)]

ans = b''
for idx, e in enumerate(enc):
pans = []
for i, b in enumerate(e):
if b == table[key[idx][i] + 9 * 2]:
pans.append(2)
else:
pans.append(3)
ans += long_to_bytes(bin2long(pans))

print(ans)

总结

题目质量配不上比赛名气