Sunday, December 29, 2013

30C3 CTF - todos writeup

I don't have much experience with exploitation, so this one was pretty hard for me, but it was a very exciting challenge and I had lots of fun solving it. Here is the task description:
A simple todo manager, try it - when you find bugs, tell us, will add to our todo list... Thanks!
todos.tar.gz running on
Let's test the service first, so we get an idea about how it works.
$ nc 1234
Welcome to TTT (the todo tool)!
If you're new, try help
help: Print this help screen
register <user> <pass>: register a new user
login <user> <pass>: Login when you have registered already.
register balidani balidani
User successfully created.
login balidani balidani
logged in...
help: Print this help screen
show <num>: show a record from the last search
search <substring>: search for entries
add <content>: add an entry
We can register a user, store items and search them. The registered user and the stored items are persistent.

The tar contains a 64-bit ELF binary. Loading it up in IDA, we can see that it imports MySQL functions and not much else. It doesn't handle sockets, so it's probably just piped to a socket. If we look at the strings, we can also read the queries that the service uses:

After checking the code that uses these strings, the query used for searching looks vulnerable to SQL injection. We can quickly confirm this:
search x' union select 'AAAA
Found 1 entries, use 'show <num>' to show them.
show 0
0: AAAA%
Here we can sigh and mumble something about how SQL injection is overused in CTFs, blah, blah. But there is much more to this task, the CCCAC won't let us down with such a boring challenge. Trying to dump the database doesn't yield any interesting results. LOAD_FILE doesn't work either. It's time to move on.

There is a bigger vulnerability in the search function. It trusts the output of the first MySQL query, and uses that to determine if it has to allocate more space for the search results. If we use an union in the injected SQL query, we can bypass this, and overflow data after the struct that holds the search results. The first value after the search results is the result counter (at 203b68, relative address). After that, a struct follows with data for the menu of the service.

First, there is an integer that tells the program where the command is used (before (1) or after login (2), or both (3)). Then there are 4 pointers.

  • Command name
  • Command regex
  • Function pointer
  • Command help string
After this, there is some space for a compiled regex.

The service uses ASLR, so first we have to leak an address to get the base address. Fortunately, this is very easy to do in this case. We just have to overflow a bigger number to the result count part, and then read the 11th result, which just points to 0x203c68, where the function pointer for the login function is stored. After we get the address, we can subtract 0x19d0, which is the relative address for the login function. Now we have the base address, and we can bypass ASLR!

How can we exploit the service now? I decided to go with a Return to libc attack. Here are the steps to exploitation:

  • Find libc
  • Find the system functions address
  • Replace the function pointer of the add command (at 0x203d68) to system
  • Call the add command with our payload (ls, cat, any command)
To find libc addresses, we needed yet another way to leak information. One way to do this is by overflowing the help string pointer of a command and then checking the help.

During exploitation, there was a problem with overflowing non-ASCII data. The original approach was to use a payload like this:
x' <union select 10 thins from somewhere> union select char(10) union select unhex('aaaaaaaa')#
Where 'aaaaaaaa' is the payload, hex encoded. Somehow this didn't work for non-ASCII characters. Interestingly, it worked when the data wasn't overflown, but after the 10th selected item it stopped working. Then I switched to another method. We can register a new user, add an item containing non-ASCII values, and select that for the 12th or 13rd items. Here is the version of the exploit that does this:

Let's test the script:
$ python 0x00
Leaked: 7f454c46020101
And that is the start of a beautiful ELF header, hex encoded. Of course encountering any zero bytes will terminate the string. Now we can start reading stuff from the got. After a few tries, I found the address for fread. The entry in the got was at 0x2030f0.
$ python 0x2030f0
Leaked: 5098940cf87f
So fread is at 0x7ff80c949850, but this address is also changing because of ASLR, so we will need the offset from the base address instead. We find that it is -0xe917b0.

Now we can start poking around in libc, with the goal of finding system. Unfortunately the version of libc I had was different from the one on the server. How do we find the libc version? I spent lots of time on this, until a teammate showed me that there is a copyright string in libc with version info. After reading random addresses around fread, I found some strings, and from there finding the version info was easy. The address was -0xd75180.
Leaked: GNU C Library (Ubuntu EGLIBC 2.17-93ubuntu4) stable release version 2.17, by Roland McGrath et al.
After trying a few versions, I found that the AMD64 one matched the bytecode found in fread identically. From here, we can load the library in IDA, and calculate the offset difference between system and fread. The final offset for system was at -0xebace0. Here is the final exploit that overflows this address and then calls system with cat /home/user/flag

In the end we got a flag, and 300 points.

Thanks to CCCAC for the CTF, and to SpamAndHex for being an awesome team. This task felt very well thought out. I'm waiting to see other people's writeups, I'm sure there is a better way than mine.

1 comment: