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
