Sunday, September 20, 2015

Writeup - CSAW 2015 : Exploitation 400 - memeshop

Memeshop 

- edwood

This challenge was a huge pain. First we were prompted with multiple options for different meme's that we could "buy." Once we chose some meme's, we then had to check out. Checking out gave us a base64 encoded string that turned out to be the address of my receipt, which just a file in the '/tmp/' directory.

Considering that this challenge didn't offer much insight, including the fact that it didn't give us a binary to start, I used the file read vulnerability to gather the ctf users .bash_history by passing in /home/ctf/.bash_history in as a b64 encoded string.

The .bash_history contained a few key pieces of information, including the path to the memeshop.rb script and the mememachine.so object that is linked into the Ruby Interpreter at run-time. Using the same vulnerability as before and some Python-Foo, we downloaded the script and the .so. Also, we read the file /proc/sys/kernel/randomize_va_space, which was '2'. Get ready for some fun!

SWEET!

Drop that ish into IDA and here we go.

A few functions of note are actively used in the memeshop.rb ruby script. Calls to addmeme, addskeletal, and checkout are made within the script but not defined therein. They must be the functions of the Mememachine module. When writing Ruby-C extensions, modules must be initialized with Init_insertmodname. The function for Init_mememachine is our first function of interest.
Nothing too crazy going on here. First, the program calls the define module function so that Ruby can interact with it. Next, the module adds three methods: addmeme, addskeletal, and checkout; the functions being connected in to the Ruby script. Great.

 

I quickly noticed that whenever you print a meme in the shop, it calls Memeshop.addmeme( ). We looked at addmeme, and found out that it creates a heap structure, 24 bytes in size with 8 bytes of blank space, a function pointer to another function gooder( ), and then 8 more bytes of space. A pointer to this heap space gets put in the struct meme memerz[ ] array, which has a max size of 256 memes. The counter, the index into this array, gets increased by one. Next, the types_tracker is increase by one, and it saves the type of this meme as 0 in the types[] array, with a max size of also 256.


After this, we checked out the addskeletal function. This function does the same thing as addmeme, only instead the heap structure is larger and the function pointer is at base+264 instead of base+8.


Finally we looked at checkout, which loops through all of the pointers, references the type array for its type, and based on the type calls the pointer at the appropriate offset. This can mean only one thing..

TYPE CONFUSION!

We found that the counter for the memes is a byte in size, meaning that if you add 256 or more memes, the 0th meme will get overwritten and continue to loop around the meme[] array. However, the type counter is a dword, so if we make 256+ memes, the type will be wrong for the newly created meme. Then when checkout is called, it will call somewhere in the user controlled buffer of a large meme rather than the gooder() function pointer in the smaller meme.

Control EIP:
- Make 256 small meme's
- Make a skeletal
- A*8 + p64(EIP) + p64(param0)
- Call checkout [c].

Our first thought was to point this to system in libc, but a few problem arose. First, the call to that pointer moves data from the heap into edi to make the function call, not rdi. So if we put the pointer to '/bin/sh' in the heap, it would only pull the lower 32 bytes. SO, we found a really sexy ropgadget in libc-2.19 (which we also downloaded from the host) to solve this.

mov rdi, rax; call [rax+20]; ret;
-angelic music is heard-

Essentially what this ROPgadget does is moves the base pointer to our meme (user controlled), and then call the address that is 20 bytes into our meme, also user controlled.

After having a remote exploit that worked precisely on localhost, the next step was executing this remotely. Sadly, as stated ASLR is on for libraries and binaries so this makes exploitation really difficult. Considering that we had a file read, we could read /proc/pid/maps and get the base address of libc. Silly me, I did not know that /proc/self/maps was a thing. Our team ended up solving this challenge at 5AM when everyone else was asleep just after the challenge had crashed and ultimately got very lucky. However, the approved solution is /proc/self/maps to find the library mappings of thes binaries.

By reading into /proc/self/maps, we yeilded the following result:

import base64
from pwn import *

p = remote('52.3.190.202', 1337)


for x in range(256):
    p.sendline('n')

rbase = 0x7f81d15de000 #base address
gadg = 0x6e0cf #rop gadget
system = 0x46640 #address of system

p.sendline("m")
print "Working...."
p.recvuntil("skeletal?\n")
# gadget = mov rdi, rax; call [rax+0x20];
# ^ moves the pointer to /bin/sh into rdi calls system.
buff = '/bin/sh;' + p64(gadg+rbase)+'C'*16+p64(system+rbase)
p.sendline(base64.b64encode(buff))
p.sendline('c')
p.interactive()
p.close()

#flag{dwn: please tell us your meme. I'm not going to stop asking}