As I go through the Fusion machine from Exploit Education. I will be doing a writeup for each level.
You can find this level here.
First we take a look at the challenge page, we have an about section, the source of our application and the protections enabled.
Lets start by reviewing the source code. In main() we see that it backgrounds the process and sets up stdin/out/err to work across a socket. Last we call parse_http_request(). Once execution has entered the request parser it declares some pointers: 'buffer', 'path', and 'q'. We are interested in the *path pointer variable.
This line sets the *path pointer value to buffer's address+4.
path = &buffer;
This means once our input checks out with the memcmp() call, it will pass whatever we put after "GET<space>" into fix_path().
Once we enter fix_path() we declare a char array of 128 bytes called 'resolved'. Next realpath() is called, the issue here is that the MAX_PATH macro could limit our buffer. Also another thing about the realpath() call is that if the path does not exist then it returns NULL. In this binary if realpath returns NULL the strcpy() is not called. Remember what the hint said in the about section? If not go read it, we should store our shellcode after the 'HTTP/1.1' part of our HTTP request.
Alright lets break some stuff now!
Lets attach to the process and take a looksies at level00 in gdb!
Also right now would be a good time to let you know I moved all the Fusion binaries into my own 32 bit VM so I could setup PEDA for GDB and install other packages.
gdb-peda$ attach <PID-OF-LEVEL00>
c to continue execution.
Next I wrote a quick python exploit skeleton to guess at the offset length to overflow the stack.
Now if I take a look in gdb I see that there was a crash!
ESP was overwritten with 0xadbeef41. This means our offset was 1 byte over! So therefore our offset is 139 bytes before overwriting ESP.
Now that we have control over execution flow, lets take a look at the binary's protections to see possible ways to attack it.
No protections this level! Also because the binary is just forking each connection to a new process, it is just a copy so we don't need to worry about ASLR either!
Looks like we will just be hardcoding a stack address and returning execution to there!
To get the address of the C's / shellcode just add 0x9D to the address stored in the buffer pointer variable.
To get the address in my case I would add 0xbfc6a768+0x9d which would equate to 0xbfc6a805 <- My shellcode address.
Now lets edit our exploit to work!
With the PEDA extension for GDB you can generate shellcode while in the debugger!
gdb-peda$ shellcode generate x86/linux exec
This will generate a normal execute /bin/sh payload, we can get away with this because of how the process redirects everything to the socket file descriptor.
Here is my finished exploit, I also added four NOPs to keep the instructions aligned so they don't get misinterpreted.
Now lets run our exploit!
Hurray! A shell!
I want to re-analyze one thing before I end this writeup. If we take a look back at the fix_path() function. There was that line with the realpath() call.
if(realpath(path, resolved) == NULL) return 1;
Some people at a first glance might be fooled by the fix_path() function. And think oh a strcpy()! OVERFLOW OVERFLOW!!!! But in this code that is not the case in fact we never actually execute the strcpy() call unless we send a path to a file that actually exists! A quick look at the man page for realpath()
man 3 realpath tells us that if the path is not found it errors, and when it errors it returns NULL. If it returns NULL it returns from the fix_path() function. However it seems that whether or not it fails it still copies the contents of path passed to it to the resolved buffer on the stack anyways! This means that we can overflow the resolved buffer!
I enjoy binary exploitation a lot so I think I will be doing more writeups like these let me know what you think! I hope you learned something :D