CSAW 2015 - 'memeshop' writeup
‘memeshop’ was a pwnable worth 400 points in the latest CSAW CTF.
Gathering Information
We are only given an ip/port to connect to, no binary was provided. After connecting we see a menu like this:
Most options will simply output a meme, but there is some interesting ones though. With p
we can print a receipt and it will ask for an order number:
We can get the order number if we use the c
heck out option, which will output it base64 encoded:
As we can see the base64 decoded string is simply a path to a temporary file. With these information one can assume that the p
rint receipt option will probably open the file and read the content. And that is exactly what it does. If we provide /etc/passwd base64 encoded to p
rint receipt, we will get the output:
Dumping Files
Ok so we can dump arbitrary files with this primitive. Next step would be to dump the binary so we can reverse engineer it and find a way to actually exploit it. To dump the binary we can simply read from /proc/self/exe. Doing so, we will receive a binary, but in fact it is the ruby interpreter. To find the actual ruby script that is running we can first check /proc/self/cmdline which will return rubymemeshop.rb
. Afterwards it’s a matter of consulting /etc/passwd and /home/ctf/.bash_history to find where the script is located (you might also just use /proc/self/cwd):
I have removed the unimportant parts of the script, but you can find the complete script here.
Ok the script itself does not provide anything interesting for exploitation. However, we notice that an extension called mememachine.so is loaded and the skeletal meme is obviosly calling an addskeletal
method which must be located inside the extension. Furthermore each meme is calling the addmeme
method. Next step was dumping the extension and reversing it.
Reversing the extension
We notice above three methods in IDA: method_addmeme
, method_addskeletal
and method_checkout
. Let’s checkout how a meme is added:
First we notice types_ptr
and memerz_ptr
. Upon further inspection, we find out that pointers to the memes are kept in a 256 elements array to which memerz_ptr
is pointing to, and that there is another array of same size keeping track of the type of the meme. This array is pointed to by types_ptr
. Furthermore, types_tracker_ptr
and counter_ptr
are pointers to counters, which tell us how many elements we have in those two arrays respectively. As you can see in the disassembly, this method basically allocates memory for a new meme and adds the memory pointer to the array, sets its type to 0
and then increments both counters. And it writes a function address (gooder_ptr
) to the meme structure at offset 8! This will be important soon.
If there is a type 0
meme, there must also be a type 1
meme, which is added by the method_addskeletal
method:
Ok this is a bit different. Here we provide the method a string argument (not on screenshot; you are prompted to provide this when you are trying to add a skelet). Of this string argument the first 128 characters are copied over to the new meme. Then the method continues similar the method_addmeme
method. It writes the pointer to the new meme’s memory into the memes
array (pointed to by memerz_ptr
) and sets its type to 1
this time. Then it also adds a function pointer, but this time at offset 264. (to which function this points depends on whether you provided thanks mr skeletal as argument to the method, but it is irrelevant to the exploitation).
And the final function method_checkout
will then iterate over all memes and call the function pointers in each meme. Depending on whether it is a type 0
or type 1
meme, it will call different offsets in the meme structure (either offset 8 or offset 264). Even though we have not found the bug yet, it is rather obvious that we will have to get the service to confuse two memes, so that we actually control the function address being called: We control the first 128 bytes of the type 1
meme, and the type 0
meme has its function pointer at offset 8.
The bug is quite subtle, but if you look closely, you will notice the following:
and
Do you see the bug? The meme-counter
is defined as byte
, while the types_tracker
is defined as int
. Why is this a problem? Well, let’s assume we are very motivated and add 256 memes. The counter for the types will be fine and will be set to 256, but the counter for the memes can not hold 256 as value as it is only 8 bits large! Hence it overflows and the value will be 0! With the 257th meme we will actually be overwriting the first meme again! But this time, the type will not be set for the first meme, but will be set actually for the 257th:
Exploitation
The strategy now: First add a meme type 0
. Then fill up 255 more memes to trigger the overflow in the counters. Finally add a type 1
meme with a custom function pointer at offset 8 (remember we control the first 128 bytes!) and then call checkout
. The service will think the first meme is a type 0
meme and will call our custom function :)
Easy peasy, we just need to jump to system
in libc now. For this we have to leak the libc base address. No problem, we have the file read primitive from the beginning and can just read /proc/self/maps
which will give us the mapped memory for the current process:
Extract the base address, add the system offset (if we don’t know which libc is used, we can also just dump the libc using the file read primitive again) and we are good to go, right? Not quite, we still need our /bin/sh
argument for system. Lucky enough for us, the method_checkout
loads an argument from offset 16:
Unlucky for us though, it does not load it into rdi
but into edi
, meaning the high 4 bytes will be set to 0. The 4 MSB of libc addresses are not 0 though! We can not use the /bin/sh
string from the libc. What can we do? Well we need to find a /bin/sh
string in memory that has 0’s on the 4 MSB. Candidates are only the ruby binary and the heap as you can see above. We do control strings on the heap, so we can just spray the heap and hope to guess the correct offsets. But there is a more elegant solution. We don’t actually need a /bin/sh
string. We can spawn a shell with simply a 'ed\0'
or 'ed '
string. This opens a vim-like text editor and you can then execute commands via !cat /etc/passwd
. And it is really easy to find an ed
string in any binary. You can take any that you like from the ruby binary :)
The final exploit:
I have added some comments which should explain the exploit. The exploit is really not complicated, the hard part was spotting the integer overflow in my opinion. Finally here is the exploit in action:
Remember you need to preceed your shell commands with an exclamation mark since you are in an ed session!
The final exploit can be found here the extension here and the ruby script here.
PS: I was told there is also a cool ruby method which basically opens a shell and to which you can directly jump to instead of system. I was not aware of that, but that would make the exploit even simpler. But the “ed” trick is a good one to know anyway :)