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
| #!/usr/bin/env python3
import struct, sys, random, subprocess, re
BIN = "cartographer"
HOST = "butter-basted-tiramisu-wrapped-in-cured-soy-foam-ezzn.gpn24.ctf.kitctf.de"
PORT, N = 443, 250
data = open(BIN, "rb").read()
s32 = lambda va: struct.unpack("<i", data[va:va+4])[0] # vaddr == file offset
# 1) Find every state block by signature: movzx edx,[rdi+rcx]; cmp rdx, imm8
SIG = bytes.fromhex("0fb6140f4883fa")
addr2state, blocks = {}, []
pos = data.find(SIG)
while pos != -1:
maxsym = data[pos + len(SIG)]
p = pos + len(SIG) + 1
p += 6 if data[p:p+2] == b"\x0f\x87" else 2 # ja rel32 or ja rel8
p += 3 # inc rcx
if data[p:p+3] in (b"\x48\x8d\x35", b"\x48\x8d\x05"): # lea rsi/rax, [rip+rel]
table = p + 7 + struct.unpack("<i", data[p+3:p+7])[0]
else:
table = 0x6004 # state 0 uses preloaded rax
if data[pos-3:pos] == b"\xfe\x04\x24": state, start = 0, pos-3
elif data[pos-4:pos-1] == b"\xfe\x44\x24": state, start = data[pos-1], pos-4
else: state, start = struct.unpack("<i", data[pos-4:pos])[0], pos-7 # disp32
addr2state[start] = state
blocks.append((state, table, maxsym))
pos = data.find(SIG, pos + 1)
# 2) Build transition graph and adjacency (one witnessing symbol per edge)
trans = {st: [addr2state[t + s32(t + 4*s)] for s in range(m+1)]
for st, t, m in blocks}
maxsym = {st: len(r)-1 for st, r in trans.items()}
adj = [{} for _ in range(N)]
for u in range(N):
for sym, v in enumerate(trans[u]):
adj[u].setdefault(v, sym)
succ = [list(a) for a in adj]
# 3) Hamiltonian path from state 0 (Warnsdorff ordering + backtracking)
sys.setrecursionlimit(10000)
def ham(seed):
random.seed(seed); vis = [False]*N; vis[0] = True; path = [0]; bud = [3_000_000]
def dfs(u):
if len(path) == N: return True
bud[0] -= 1
if bud[0] < 0: raise TimeoutError
cand = sorted((v for v in succ[u] if not vis[v]),
key=lambda v: sum(not vis[w] for w in succ[v]))
random.shuffle(cand)
cand.sort(key=lambda v: sum(not vis[w] for w in succ[v]))
for v in cand:
vis[v] = True; path.append(v)
if dfs(v): return True
vis[v] = False; path.pop()
return False
try: return path if dfs(0) else None
except TimeoutError: return None
path = next(filter(None, (ham(s) for s in range(100))), None)
assert path and len(set(path)) == N
# 4) symbols: one per edge, then a halt symbol (out of range for last state)
syms = [adj[path[i]][path[i+1]] for i in range(N-1)] + [127]
assert 127 > maxsym[path[-1]]
payload = (";".join(map(str, syms)) + ";").encode()
# 5) send to remote (instance handshake is flaky -> retry)
cmd = ["openssl", "s_client", "-quiet", "-servername", HOST, "-connect", f"{HOST}:{PORT}"]
for _ in range(8):
try: out = subprocess.run(cmd, input=payload, capture_output=True, timeout=15).stdout
except subprocess.TimeoutExpired: out = b""
m = re.search(rb"GPNCTF\{[^}]*\}", out)
if m:
print(m.group().decode()); break
|