reverse it

前半段大量代码都是运行时和 scanf/printf 噪音。

main%33s 读入输入后,会先对前 32 字节做一次置换,再调用同一个加密函数两次处理两个 16 字节块。

最终比较发生在 data segment 的 offset 1040,长度 32 字节。

把白盒查表函数单独抠出来后,可以确认它本质上是 AES-128 的白盒实现。继续对第一轮 lookup tables 做分析,可以恢复出白盒 AES key:

1
92cd335ab10f78e4296cd58a11f04b9e

用上面的 key 对 offset 1040 开始的 32 字节目标密文做 AES-ECB 解密,可以得到一串中间态明文:

1
T33tWb_{0k0AS40pl03lL0x1Bhfa}usg

从其他题目可以知道flag格式为flag{}

main 里这层置换用 emscripten_date_now()/1000 - 1 初始化 MT19937,然后做 32 次交换。

进一步看初始化逻辑会发现它实际只保留了 16 位 seed,所以可以直接爆破 0..65535,把每个 seed 对应的置换逆回去,再按 flag{...} 形式打分筛选。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
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
from Crypto.Cipher import AES
from pathlib import Path

WASM_PATH = Path(r".main.wasm")
WHITEBOX_AES_KEY = bytes.fromhex("92cd335ab10f78e4296cd58a11f04b9e")


def uleb(data: bytes, i: int):
value = 0
shift = 0
while True:
b = data[i]
i += 1
value |= (b & 0x7F) << shift
if b < 0x80:
return value, i
shift += 7


def load_memory_image(path: Path) -> bytearray:
data = path.read_bytes()
memory = bytearray(700000)
i = 8
while i < len(data):
section_id = data[i]
i += 1
section_size, i = uleb(data, i)
section = data[i : i + section_size]
i += section_size
if section_id != 11:
continue
j = 0
count, j = uleb(section, j)
for _ in range(count):
flags, j = uleb(section, j)
if flags == 0:
assert section[j] == 0x41
j += 1
offset, j = uleb(section, j)
assert section[j] == 0x0B
j += 1
size, j = uleb(section, j)
memory[offset : offset + size] = section[j : j + size]
j += size
elif flags == 1:
size, j = uleb(section, j)
j += size
elif flags == 2:
_, j = uleb(section, j)
assert section[j] == 0x41
j += 1
offset, j = uleb(section, j)
assert section[j] == 0x0B
j += 1
size, j = uleb(section, j)
memory[offset : offset + size] = section[j : j + size]
j += size
else:
raise ValueError(flags)
break
return memory


class MT19937:
def __init__(self, seed: int):
self.mt = [0] * 624
self.index = 624
self.mt[0] = seed & 0xFFFFFFFF
for i in range(1, 624):
prev = self.mt[i - 1]
self.mt[i] = (1812433253 * (prev ^ (prev >> 30)) + i) & 0xFFFFFFFF

def twist(self):
for i in range(624):
y = (self.mt[i] & 0x80000000) | (self.mt[(i + 1) % 624] & 0x7FFFFFFF)
self.mt[i] = self.mt[(i + 397) % 624] ^ (y >> 1)
if y & 1:
self.mt[i] ^= 0x9908B0DF
self.index = 0

def extract(self) -> int:
if self.index >= 624:
self.twist()
y = self.mt[self.index]
self.index += 1
y ^= y >> 11
y ^= (y << 7) & 0x9D2C5680
y ^= (y << 15) & 0xEFC60000
y ^= y >> 18
return y & 0xFFFFFFFF


def permutation_for_seed(seed: int, length: int = 32):
arr = list(range(length))
mt = MT19937(seed)
for i in range(length):
j = mt.extract() & 31
arr[i], arr[j] = arr[j], arr[i]
return arr


def invert_permutation(permuted_text: str, perm):
original = ["?"] * len(permuted_text)
for out_idx, src_idx in enumerate(perm):
original[src_idx] = permuted_text[out_idx]
return "".join(original)


def candidate_score(text: str) -> int:
score = 0
if text.endswith("}"):
score += 20
if text.count("{") == 1 and text.count("}") == 1 and text.index("{") < text.index("}"):
score += 20
if text.lower().startswith("flag{"):
score += 100
if text.lower().startswith("ctf{"):
score += 80
if all(32 <= ord(ch) < 127 for ch in text):
score += 5
if "{" in text and "}" in text and text.index("{") < text.index("}"):
inner = text[text.index("{") + 1 : text.rindex("}")]
if inner and all(ch.isalnum() or ch in "_-" for ch in inner):
score += 20
if text[:4].isalpha():
score += 5
return score


memory = load_memory_image(WASM_PATH)
target = bytes(memory[1040:1072])

cipher = AES.new(WHITEBOX_AES_KEY, AES.MODE_ECB)
intermediate = cipher.decrypt(target).decode("ascii")
print("intermediate:", intermediate)

best_seed = -1
best_flag = ""
best_score = -1
for seed in range(65536):
candidate = invert_permutation(intermediate, permutation_for_seed(seed))
score = candidate_score(candidate)
if score > best_score:
best_score = score
best_seed = seed
best_flag = candidate

print("seed16:", best_seed)
print("flag:", best_flag)

得到flag

image-20260426114857283