CSAW CTF was a lot of fun this year (mostly because I was actually able to solve challenges ;D) and solving Contacts was pretty satisfying.
Let’s start by getting an idea what is up with it:
Alright, and the security mitigations it has:
Great, so it doesn’t look like we are bouta drop some shellcode or overflow some stack based buffer anytime soon (we could in certain situations find the stack canary either with a leak or brute force).
So if we play around with this a bit, we get an idea as to what is going on:
So we can create, remove, edit and display contacts. There are some things that we could look at first, the path that I first chose was looking at was the fact that you could specify how long your description was in your contact (this is a pretty common vuln in ctf problems).
So if we look at the first place we can supply our description, it is a pretty clear that we will not be overflowing anything anytime soon as the program allocates based on the size we provide:
and then reads into the allocated space the number of bytes we specify:
But what about when we edit our contact? Well it turns out that that the description is allocated and read in the same way as when we create the contact :C
When trying to solve a challenge with so many places to input data, it doesn’t hurt to make a guess as to a potential vulnerability even if it turns out to be a dead end, especially when you are pressed for time.
Let’s now checkout that innocent looking name input (really just going through all potential inputs at this point):
Hmmm… what is arg_0 in add_contact (this was a stripped binary so these are names that I gave each function):
What does main pass add_contact as a parameter then?
…where the fuck is unk_804B0A0?
Oh! So we read the name into the bss segment. If we do a little more reversing of the add_contact function, we realize that each contact is stored in the bss segment in a struct that resembles:
So as we add more contacts, we are adding to the contacts list located in the bss segment. So if we make 3 contacts, 0x0804B0A0 will look like:
So when we initally create our contact, it reads the correct amount into our name buffer:
But what about when we change our name…
Hmmm, that is a little strange. What is that [ebp+n] set to? If you look at all the times [ebp+n] is referenced in edit_contact you will find that it is only ever accessed and never assigned!
That means that [ebp+n] is an uninitialized variable, meaning that whatever value that was left on the stack from a previous function call would be used as the value for [ebp+n]. I did not bother to really check what value would exactly be there, but if you shove a bunch of As in, you find out quickly that it is definetly a number bigger than 64:
Now what is important to realize is that you have to make a second contact because the crash is a result of you overflowing the name buffer of the first person all the way into a pointer of the second person. And when you go to print out the second person, the program doesn’t have access to the memory at 0x41414141:
So in our contacts list at the point of the crash, it would look something like:
If we take a quick look at print_contact we see something interesting…
A format string vulnerability at .text:08048C22! Cool, and if we look at the code, it looks like the description is the culprit.
So now the question is, how do we put this all together? Well for starters, we don’t have any calls to system or execve which would give us a shell on the system so that means we would need either a libc leak (by reading from a GOT pointer) or guessing the libc that the challenge is using. During the competition we exfiltrated the libc binary from the challenge, precision (for a 32 bit libc it is located here /lib/i386-linux-gnu/libc.so.6). And dropping this libc in IDA we can find out where system is relative to whatever we decide to leak.
But we are still left with the problem of leaking some sort of address located in libc that we can apply some offset to to find the address of libc.
Well we do have a write primitive with our format string so let’s see if that gives us anything interesting…
The second address looks pretty interesting, I wonder what it resolves to:
Too easy. So we can find the offset between _IO_vfscanf+17 and system (actually making this writeup I realized that I fucked up the offset, I found the offset from _IO_vfscanf to system forgetting to subtract 17 from the offset :c. I got really frusted that it wasn’t working so I bruteforced the offset and after 17 iterations it worked lol :3).
Also to cover all components of the exploit, the first address that we read off the stack in the format string 0x8f3a008 is actually the address of our phone number pointer which we know to be a heap address. Based on this address, we can actually determine where all other allocations will be made (by using a debugger and finding where things are allocated relative to our first phone number allocation).
What is interesting is that with our name overflow, we can actually overflow the phone number pointer and cause it to become any pointer that we want. For example, it could be the GOT address of strcmp for example ;D
If we look at edit_contact, we see that it is using strcmp to compare our inputed name to the names that it knows of:
So this code will turn from:
With the phone number pointer being on the stack at the time of our format string vuln, we can have an arbiturary write to anywhere in the program by just changing the phone number pointer in our overflow :3
So our approach now becomes:
Create the first person
Set their name and phone number to be anything
Set their description to be “%x.%x”
Print out the contact
Using the leaked phone number allocation and leaked _IO_vprintf address calculate all the addresses we need to have
Create a second person
Set their name and number to be anything
Set their description to be a format string payload to overwrite the lower part of the strcmp GOT address with the lower part of our system address
Change the first person’s name to overflow into the phone number, changing it to be the strcmp GOT address making sure to keep the second person’s description pointer intact (since we will be overflowing it too, and we need to the pointer to keep pointing to our format string payload)
Create a third person
Set their name and number to be anything
Set their description to be a format string payload to overwrite the upper part of the strcmp GOT address with the upper part of our system address
Change the second person’s name to overflow into the phone number, changing it to be the strcmp GOT address making sure to keep the third person’s description pointer intact
Print out the contacts, overwriting the strcmp GOT with our system address