REVERSE- QuorumGlyph
ISCC2026 WriteUp 提交模板
REVERSE- QuorumGlyph
解题思路
1.从main函数开始


输入长度必须是 42
2. 前缀必须是 ISCC{QRM_
3. 第 14 位必须是 _
4. 最后一位必须是 }
5. 所有字符都必须落在可打印 ASCII 范围

继续往后跟,会发现前两段大校验在进入核心逻辑前都做了同一件事:
s_1[13] = 0;
*(_DWORD *)&s_1[9] = 0;
也就是把 s[9..13] 这 5 个字符整段清零。



所以这里能得到一个非常重要的结论:
- witness-1 和 witness-2 从设计上就不打算告诉我们中间 5 位
- 它们只负责把 flag 外壳钉死
- 中间 5 位一定是由最后那一段单独恢复
在 main 的后半段,有一段 37 轮的 u32 变换,最后会检查两类结果:
- 16 个
u32经过混洗后的整块内容,是否等于.bss中的一块 64 字节目标 - 四个聚合指标和一个 64 位哈希,是否分别等于
0x110
0xEE
0xFC
0x616E8714
0x4A9B46E51D6EEEB3
但如果只做静态查看,会发现 .bss 那块目标在文件里不是明文,而是启动构造器 sub_401080 运行后才写进去。
所以顺着数据流走,先把构造器抄出来更稳。构造器做完后,关键运行时目标会变成:
qword_405068 = 0x4A9B46E51D6EEEB3
qword_405080 = 0xE04CF5339F776142
xmmword_405070 = [0x110, 0xEE, 0xFC, 0x616E8714]
unk_4050E0 = 64-byte runtime block
接下来把 main 里第二段的 64 字节输入构造过程和 37 轮 u32 变换翻成 Python,拿“中间 5 位清零”的外壳去跑,就能稳定命中这些运行时常量。
也就是说,前两段 witness 共同锁死的其实是这一层:
ISCC{QRM_....._3_w1tness_no_single_path!!}
最后一段逻辑单独读取 s[9:14] 这 5 个字符,并要求它们都来自下面这个 32 字符表:
ABCDEFGHJKLMNPQRSTUVWXYZ23456789
程序会把这 5 个字符当作 base32 数字,逐个打包成一个 24-bit 整数 x:
n0xFFFFFF = (idx(ch) | (32 * n0xFFFFFF))
随后程序并不是直接把 x 送进哈希,而是先做一步模 2^24 的可逆变换:
y = ((x - 0xB3EF33) * inv(0xD1E995)) mod 2^24
这里 inv(0xD1E995) 是 0xD1E995 在 2^32 下的逆元低 24 位。
从代码里的牛顿迭代可以算出:
inv(0xD1E995) mod 2^24 = 0x9B19BD
然后程序检查:
sub_401CA0(y) == 0xE04CF5339F776142
这一段的搜索空间只有 2^24,完全可以离线复现。
我这次复现时直接把 sub_401CA0 精确翻成 Python,最后唯一命中的值是:
y = 0x0C0DE5
再代回去恢复 x:
x = ((0x0C0DE5 * 0xD1E995) + 0xB3EF33) mod 2^24
= 0x52727C
把 0x52727C 按程序同样的 base32 打包顺序还原回 5 个字符,得到:
FE6V6
于是完整 flag 就出来了:
ISCC{QRM_FE6V6_3_w1tness_no_single_path!!}

Exp
from __future__ import annotations
import sys
from pathlib import Path
ALPHABET = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789"
MASK24 = (1 << 24) - 1
STATIC_WITNESS2 = bytes(
[
0x25,
0x4F,
0x69,
0x5E,
0x54,
0xD4,
0xA3,
0x30,
0x6C,
0x91,
0x0E,
0x60,
0xDA,
0x36,
0x87,
0x76,
0x6A,
0x9C,
0x3B,
0xBB,
0x28,
0x9F,
0xB9,
0x6D,
0x11,
0xF4,
0x92,
0x4C,
0x8F,
0xF4,
0x9A,
0x66,
0x03,
0x26,
0x30,
0xC4,
0x56,
0x1E,
0xD5,
0x4C,
0xA5,
0xFD,
0x01,
0x73,
0x46,
0x20,
0x01,
0x7E,
0x28,
0x99,
0xC2,
0x64,
0xA5,
0xE5,
0x2D,
0x02,
0xAB,
0x63,
0x28,
0x3F,
0xEE,
0x66,
0x8D,
0xC1,
]
)
WITNESS2_TARGET_WORDS = [0x110, 0xEE, 0xFC, 0x616E8714]
WITNESS2_TARGET_HASH = 0x4A9B46E51D6EEEB3
WITNESS3_TARGET = 0xE04CF5339F776142
def u32(x: int) -> int:
return x & 0xFFFFFFFF
def u64(x: int) -> int:
return x & 0xFFFFFFFFFFFFFFFF
def rol32(x: int, r: int) -> int:
x &= 0xFFFFFFFF
r &= 31
return ((x << r) | (x >> (32 - r))) & 0xFFFFFFFF
def mix64(x: int) -> int:
x &= 0xFFFFFFFFFFFFFFFF
y = (0xBF58476D1CE4E5B9 * (x ^ (x >> 30))) & 0xFFFFFFFFFFFFFFFF
z = (0x94D049BB133111EB * ((y >> 27) ^ y)) & 0xFFFFFFFFFFFFFFFF
return ((z >> 31) ^ z) & 0xFFFFFFFFFFFFFFFF
def rol64(x: int, r: int) -> int:
x &= 0xFFFFFFFFFFFFFFFF
r &= 63
return ((x << r) | (x >> ((64 - r) & 63))) & 0xFFFFFFFFFFFFFFFF
def hash_witness3(seed24: int) -> int:
seed24 &= 0xFFFFFFFF
v2 = seed24 ^ 0xB198FA1458A7836C
v3 = 0
v4 = ((2654435761 * seed24) ^ 0xC5C9AFD93FF67D70) & 0xFFFFFFFFFFFFFFFF
table = [0] * 1024
for i in range(1024):
tmp = (v2 + i + 0x9E3779B97F4A7C15) & 0xFFFFFFFFFFFFFFFF
v2 = mix64(tmp)
v7 = v3 ^ v2
v3 = (v3 + 0x100000001B3) & 0xFFFFFFFFFFFFFFFF
v4 = mix64(v7 ^ v4)
table[i] = v2 ^ rol64(v4, i)
v9 = 0
v10 = 0
n1536 = 0
while n1536 != 1536:
v12 = table[((n1536 & 0xFFFF) ^ ((v4 ^ v2) & 0xFFFF)) & 0x3FF]
v13 = table[((v12 & 0xFFFF) ^ ((v10 ^ (v2 >> 17)) & 0xFFFF)) & 0x3FF]
v2 = mix64((v12 + v2 + v9) & 0xFFFFFFFFFFFFFFFF)
rot = (v2 >> 59) & 63
v4 ^= rol64((v13 + v2) & 0xFFFFFFFFFFFFFFFF, rot)
v15 = (v12 + n1536 + v13) & 0xFFFFFFFFFFFFFFFF
v16 = ((v4 & 0xFFFF) ^ (((n1536 & 0xFFFF) << 7) ^ v12) & 0xFFFF) & 0x3FF
v17 = mix64(v15)
n1536 += 1
v10 = (v10 + 131) & 0xFFFFFFFFFFFFFFFF
v9 = (v9 - 0x2917014799A6026D) & 0xFFFFFFFFFFFFFFFF
table[v16] = (v2 ^ v4 ^ v17) & 0xFFFFFFFFFFFFFFFF
v18 = table[((v2 & 0xFFFF) + (v4 & 0xFFFF)) & 0x3FF] ^ v2 ^ v4 ^ 0x51474C5950484153
return mix64(v18)
def build_runtime_witness2_buf() -> bytes:
out = bytearray(64)
state = 0
for off in range(0, 64, 4):
prev = state
state = u64(state - 0x61C8864680B583EB)
word = int.from_bytes(STATIC_WITNESS2[off : off + 4], "little")
word ^= mix64(prev ^ 0xA781EC1645A68B63) & 0xFFFFFFFF
out[off : off + 4] = word.to_bytes(4, "little")
return bytes(out)
def witness2_shell(flag_bytes: bytes) -> bytes:
shell = bytearray(flag_bytes)
shell[9:14] = b"\x00" * 5
return bytes(shell)
def derive_witness2_input(shell: bytes) -> list[int]:
buf = bytearray(64)
buf[:42] = shell
v27 = 0x51554F52554D474C
acc = 0
for b in shell:
v27 = mix64(v27 ^ (acc + b))
acc += 40503
for i in range(8):
buf[42 + i] = (v27 >> (8 * i)) & 0xFF
v38 = v27 ^ 0x123456789ABCDEF0
acc2 = 0
idx = 0
for i in range(14):
v41 = acc2 + shell[idx % 0x2A]
acc2 += 65537
v38 = mix64(v38 + v41)
buf[50 + i] = v38 & 0xFF
idx += 7
return [int.from_bytes(buf[i : i + 4], "little") for i in range(0, 64, 4)]
def run_witness2(shell: bytes) -> tuple[bool, list[int], tuple[int, int, int, int], int]:
words = derive_witness2_input(shell)
target_words = [
int.from_bytes(build_runtime_witness2_buf()[i : i + 4], "little")
for i in range(0, 64, 4)
]
n37 = 0
n183 = 1838395311
v50 = 1
n25 = 25
for _ in range(37):
n25_1 = n37
n37 = u32(n37 + 1)
n183_1 = n183
n37_1 = n37
for i in range(16):
v78 = n183_1 ^ words[i]
n183_1 = u32(n183_1 + 2135587861)
words[i] = rol32(v78, n37_1)
n37_1 += 3
v80 = words[0]
v81 = n25_1 + 5
for i in range(1, 16):
v80 = words[i] ^ rol32(v80, v81)
words[i] = v80
v81 += 5
v83 = words[15]
v84 = v50 + 20
for i in range(14, -1, -1):
v83 = words[i] ^ rol32(v83, v84)
words[i] = v83
v84 -= 1
cur = n25_1
n5_7 = v50
while cur != n25:
a = cur & 0xF
b = n5_7 & 0xF
if a != b:
words[a], words[b] = words[b], words[a]
cur += 5
n5_7 = u32(n5_7 + 7)
n25 = cur + 1
v50 = u32(v50 + 3)
n183 = u32(n183 - 1640531527)
v128 = words[0].bit_count()
v130 = (words[0] ^ rol32(words[0], 1)).bit_count()
v129 = 0
v127 = 0
v95 = words[0]
v102 = words[0]
v94 = 0
v104 = 0
n5 = 5
for i in range(16):
v111 = rol32(v95, n5)
n5_4 = n5 + 3
v113 = v94 ^ v102
v127 ^= v111
v94 += 40503
v104 = mix64(v104 ^ v113)
if i == 15:
break
nxt = words[i + 1]
v128 += nxt.bit_count()
v130 += (nxt ^ rol32(nxt, 1)).bit_count()
v129 += (words[i] ^ nxt).bit_count()
v95 = nxt
v102 = nxt
n5 = n5_4
ok = (
words == target_words
and (v128, v130, v129, v127) == tuple(WITNESS2_TARGET_WORDS)
and v104 == WITNESS2_TARGET_HASH
)
return ok, words, (v128, v130, v129, v127), v104
def solve_middle() -> tuple[str, int, int]:
# This value is the unique hit obtained during reproduction by brute-forcing
# the 24-bit witness-3 domain and checking sub_401CA0 against 0xE04CF5339F776142.
y = 0x0C0DE5
packed = ((y * 0xD1E995) + 0xB3EF33) & MASK24
tmp = packed
chars = []
for _ in range(5):
chars.append(ALPHABET[tmp & 0x1F])
tmp >>= 5
middle = "".join(reversed(chars))
if hash_witness3(y) != WITNESS3_TARGET:
raise RuntimeError("witness-3 verification failed")
return middle, packed, y
def validate_full_flag(flag: str) -> bool:
data = flag.encode()
if len(data) != 42:
return False
if not data.startswith(b"ISCC{QRM_"):
return False
if data[14] != ord("_") or data[41] != ord("}"):
return False
if any(not (33 <= b <= 126) for b in data):
return False
shell = witness2_shell(data)
ok2, _, _, _ = run_witness2(shell)
if not ok2:
return False
packed = 0
for ch in flag[9:14]:
idx = ALPHABET.find(ch)
if idx < 0:
return False
packed = (packed << 5) | idx
if packed > MASK24:
return False
inv = 1
for _ in range(5):
inv = (inv * (2 - 0xD1E995 * inv)) & 0xFFFFFFFF
y = ((packed - 0xB3EF33) * (inv & MASK24)) & MASK24
return hash_witness3(y) == WITNESS3_TARGET
def main() -> int:
_ = Path(sys.argv[1]) if len(sys.argv) > 1 else Path("./QuorumGlyph")
runtime = build_runtime_witness2_buf()
shell = "ISCC{QRM_....._3_w1tness_no_single_path!!}"
middle, packed, y = solve_middle()
flag = shell.replace(".....", middle)
ok2, _, witness2_metrics, witness2_hash = run_witness2(witness2_shell(flag.encode()))
accepted = validate_full_flag(flag)
print(f"[*] witness-2 hash: 0x{witness2_hash:016x}")
print(f"[*] witness-3 target: 0x{WITNESS3_TARGET:016x}")
print(f"[*] witness-2 metrics: {[hex(x) for x in witness2_metrics]}")
print(f"[*] shell: {shell}")
print(f"[*] gap solved: {middle}")
print(f"[*] packed x = 0x{packed:06x}")
print(f"[*] packed y = 0x{y:06x}")
print(f"[*] runtime witness-2 bytes prefix: {runtime[:16].hex()}")
print(f"[*] witness-2 match: {ok2}")
print(f"[*] checker says: {'Yes???' if accepted else 'No???'}")
print(flag)
return 0
if __name__ == "__main__":
raise SystemExit(main())

浙公网安备 33010602011771号