CSAW CTF 2015 - Contacts
TL; DR
- Overflow
- Uninitialized Variable
- Format String
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:
{% highlight bash %}
vagrant@precise64:~/csawctf$ file contacts
contacts: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.24, BuildID[sha1]=0x9736c7a26b5c55f97874c5e62d359e02c88cf2f1, stripped
{% endhighlight %}
Alright, and the security mitigations it has:
{% highlight bash %}
vagrant@precise64:~/csawctf$ ~/Template/checksec.sh --file contacts
RELRO STACK CANARY NX PIE RPATH RUNPATH FILE
Partial RELRO Canary found NX enabled No PIE No RPATH No RUNPATH contacts
{% endhighlight %}
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:
{% highlight bash %}
vagrant@precise64:~/csawctf$ ./contacts
Menu:
1)Create contact
2)Remove contact
3)Edit contact
4)Display contacts
5)Exit
{% endhighlight %}
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).
{% highlight bash %}
1
Contact info:
Name: Poopy
[DEBUG] Haven't written a parser for phone numbers; You have 10 numbers
Enter Phone No: 1231231234
Length of description: 50
Enter description:
asdf
{% endhighlight %}
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:
{% highlight nasm %}
.text:0804884E mov [esp+4], eax ; Destination of our size
.text:08048852 mov dword ptr [esp], offset aUC ; "%u%*c"
.text:08048859 call ___isoc99_scanf
.text:0804885E mov edx, [ebp+var_C]
.text:08048861 mov eax, [ebp+arg_0]
.text:08048864 mov [eax+48h], edx
.text:08048867 mov eax, [ebp+var_C]
.text:0804886A add eax, 1
.text:0804886D mov [esp], eax ; size
.text:08048870 call _malloc
{% endhighlight %}
and then reads into the allocated space the number of bytes we specify:
{% highlight nasm %}
.text:0804889D mov ecx, ds:stdin
.text:080488A3 mov eax, [ebp+var_C] ; Our size
.text:080488A6 add eax, 1
.text:080488A9 mov edx, eax
.text:080488AB mov eax, [ebp+arg_0]
.text:080488AE mov eax, [eax]
.text:080488B0 mov [esp+8], ecx ; stream
.text:080488B4 mov [esp+4], edx ; n
.text:080488B8 mov [esp], eax ; s
.text:080488BB call _fgets
{% endhighlight %}
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):
{% highlight nasm %}
.text:080487D3 get_name proc near ; CODE XREF: add_contact+3Fp
.text:080487D3
.text:080487D3 arg_0 = dword ptr 8
...
.text:080487E5 mov eax, ds:stdin
.text:080487EA mov edx, [ebp+arg_0]
.text:080487ED add edx, 8
.text:080487F0 mov [esp+8], eax ; stream
.text:080487F4 mov dword ptr [esp+4], 64 ; n
.text:080487FC mov [esp], edx ; s
.text:080487FF call _fgets
{% endhighlight %}
Hmmm... what is arg_0 in add_contact
(this was a stripped binary so these are names that I gave each function):
{% highlight nasm %}
.text:08048B5E add_contact proc near ; CODE XREF: main+B6p
.text:08048B5E
.text:08048B5E var_10 = dword ptr -10h
.text:08048B5E var_C = dword ptr -0Ch
.text:08048B5E arg_0 = dword ptr 8
...
.text:08048B64 mov eax, [ebp+arg_0]
.text:08048B67 mov [ebp+var_10], eax
...
.text:08048B97 mov eax, [ebp+var_10]
.text:08048B9A mov [esp], eax
.text:08048B9D call get_name
{% endhighlight %}
What does main pass add_contact
as a parameter then?
{% highlight nasm %}
.text:0804876C mov dword ptr [esp], offset unk_804B0A0 ; jumptable 0804876A case 1
.text:08048773 call add_contact
{% endhighlight %}
...where the fuck is unk_804B0A0
?
{% highlight nasm %}
.bss:0804B0A0 unk_804B0A0 db ? ; ; DATA XREF: main:loc_804876Co
.bss:0804B0A0 ; main:loc_804877Ao ...
.bss:0804B0A1 db ? ;
.bss:0804B0A2 db ? ;
{% endhighlight %}
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:
{% highlight c %}
struct contact {
char *desc; // Heap pointer
char *phone; // Heap pointer
char name[64];
int description_len;
int is_person; // Used in print_contacts
}
Total bytes for a contact = 2 * sizeof(char *) + 2 * sizeof(int) + 64 * sizeof(char) = 80 bytes
{% endhighlight %}
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:
{% highlight nasm %}
0x0804B0A0+0x0 Contact 1
0x0804B0A0+0x80 Contact 2
0x0804B0A0+0x100 Contact 3
{% endhighlight %}
So when we initally create our contact, it reads the correct amount into our name buffer:
{% highlight nasm %}
.text:080487F4 mov dword ptr [esp+4], 64 ; n
{% endhighlight %}
But what about when we change our name...
{% highlight nasm %}
.text:08048A4E mov edx, ds:stdin
.text:08048A54 mov eax, [ebp+n]
.text:08048A57 mov ecx, [ebp+var_54]
.text:08048A5A add ecx, 8
.text:08048A5D mov [esp+8], edx ; stream
.text:08048A61 mov [esp+4], eax ; n
.text:08048A65 mov [esp], ecx ; s
.text:08048A68 call _fgets
{% endhighlight %}
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:
{% highlight bash %}
Name to change? Dolan
1.Change name
2.Change description
1
New name: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
Menu:
1)Create contact
2)Remove contact
3)Edit contact
4)Display contacts
5)Exit
4
Contacts:
Name: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
Length 1094795585
Phone #: 8675309
Description: Drop it like it's hot
Name: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
Length 1337
Segmentation fault
{% endhighlight %}
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
: