velvet-table
umdctf
Task: a stripped PIE casino allocator exposes seat-based heap operations, encrypted inspection output, and a payout path guarded by a stack checksum. Solution: combine UAF and post-ledger OOB write for safe-linked tcache poisoning onto the payout stack frame, leak and decrypt stack data, replace the reject callback with the hidden win function, repair the checksum, and trigger payout.
$ ls tags/ techniques/
$ cat /etc/rate-limit
Rate limit reached (20 reads/hour per IP). Showing preview only — full content returns at the next hour roll-over.
velvet-table — UMDCTF
Challenge
Remote: nc challs.umdctf.io 30304
Binary: velvet-table
Protections:
- PIE
- Full RELRO
- NX
- no canary
- SHSTK
- IBT
The program is a stripped menu-driven allocator with 16 seat records. Each seat stores:
ptrsizeoccupied
Two nearby internal callbacks are especially important:
0x1b50printsticket rejected.0x1b60printsyay.and callssystem("/bin/sh")
The goal is to redirect the payout path to the hidden function.
Analysis
Reversing the binary shows three key primitives.
1. Use-after-free via cashout
For 0x80 chunks, cashout frees the chunk but leaves the seat pointer behind. That gives a reliable dangling-pointer primitive.
2. Unchecked write after settle-ledger
After enough actions, settle-ledger enables a stronger update path that performs:
memcpy(ptr, buf, len)
with no bounds check. Combined with the dangling pointer, this lets us overwrite freed tcache metadata.
3. Reversible leak via inspect
inspect prints up to 0x40 bytes from a seat, but XORed with an internal keystream. That is still useful: if we first write known plaintext into a live chunk, then inspect it, we recover the keystream with:
keystream = ciphertext ^ plaintext
Then later inspect outputs can be decrypted the same way.
Payout target on the stack
The service prints a table marker. That value is enough to reconstruct the payout frame location on the stack:
low32 = marker ^ 0x9ac90307 stackbuf = 0x7ff000000000 | (low32 << 4) saved_highbits = ((low32 ^ 0x5a17c3d9) & 0xffffffff) << 32
Relevant fields inside that stack buffer:
- function pointer:
stackbuf + 0x20 - checksum:
stackbuf + 0x28
The integrity formula is:
checksum = saved_highbits ^ funcptr ^ 0x686f7573655f6564
So the full plan is:
...
$ grep --similar