Skip to main content0xAdham

Command Palette

Search for a command to run...

BYUCTF: heap2win

Written by
Avatar of 0xAdham
0xAdham
Published on
--
BYUCTF: heap2win

Platform: BYUCTF Challenge: heap2win Category: Pwn Difficulty: Medium

Flag

byuctf{y34h,..._you're_a_pro}

Target

<remote-host>:1364. heap2win + main.cpp (source given).

  • ELF 64-bit, non-PIE (EXEC), Full RELRO, no canary. C++ with virtual functions.
  • A C++ "button" app: make Hype/Custom/Winner buttons, then "push" one (a virtual call).

Bugs (from source)

The win primitive is WinnerButton::pushsystem("/bin/sh"). Goal: make a button's vptr point at the WinnerButton vtable so its push() lands in system.

Vtable math

  • nm: vtable for WinnerButton @ 0x403630 → object vptr = 0x403640.
  • The call site does rdx = *vptr; rdx += 0x10; call *rdx*(0x403640+0x10) = *(0x403650) = 0x401376 = WinnerButton::push. ✓
  • So writing 0x403640 into a victim button's first 8 bytes wins. Non-PIE ⇒ this address is constant.

Heap groom: the trick

HypeButton (8 B), CustomButton (0x18 B) and the small vector<Button*> arrays are all 0x20 chunks. Each push_back that grows the vector frees the old array into tcache-0x20, and the next new Button reuses it (LIFO). gdb dump confirmed allocation order ≠ address order. Exploit the reuse so a Custom button lands immediately before a live Hype button, then overflow forward into that Hype's vptr:

  1. Make Hype (H0).
  2. Make Hype (H1), allocated fresh after H0.
  3. Make Custom (C). new reuses the freed array chunk that sits right before H1, i.e. C is adjacent and lower than H1.
  4. C's scanf("%s") overflows out of name into H1's vptr.

Chunk layout (per gdb): C@P, name@P+8, next chunk header at P+0x10/0x18, H1 object (vptr) at P+0x20. Distance name → H1.vptr = 0x18 = 24.

Payload

Overwrite only the low 3 bytes; scanf appends the NUL (byte 3), and H1's original vptr (0x00403668) already has zero high bytes:

Drive sequence (menus): make Hype, make Hype, make Custom w/ payload, push button #2 (= H1):

>> WINNER WINNER WINNER → shell → byuctf{y34h,..._you're_a_pro}.

Takeaways

  • C++ vtable hijack: overwriting an object's vptr redirects every virtual call. With a win method in another class, point the victim vptr at that class's vtable and trigger the matching slot. Verify the slot offset from the call site (*(vptr+0x10) here).
  • tcache reuse drives adjacency. When victim objects and container backing-arrays share a size class, freed arrays let a later allocation slot in before an earlier object, turning a forward-only %s overflow into a victim-vptr overwrite. Confirm layout in gdb instead of guessing.
  • Partial overwrite avoids null-byte problems: write only the low bytes that differ; let scanf's NUL terminator zero the next byte and rely on the original high bytes already being zero.
  • The pushButton negative-index bug (choice<=0) is a second, independent path (OOB button_list[-k]), but the overflow was the deterministic one.
  • Non-PIE + a fixed win vtable means no leak needed. The whole exploit is constants + heap shaping.

0xAdham

Edit on GitHub
Last updated: --