This post is about an exercise I did which involves finding and exploiting vulnerabilities that have been deliberately left in a piece of software. I won’t be posting any references to the exercise in order not to spoil the solution for others. This post will outline the vulnerabilities I found, how I found them and what can be done with them.
Vulnerability #1: path traversal
After firing up the program, I decided to look for the most common HTTP vulnerabilities first. One of the most simple HTTP attacks to execute; the webserver allows you to traverse directories below its configured root directory. Simply send “GET /../../test.txt” (this particular webserver doesn’t care about the HTTP version after the GET) and you’ll be presented with a file that exists below the webroot:
Vulnerability #2: heap overflow
Using static analysis to chase through the program, we can see that after the socket initialisation code the main thread starts waiting for listen(). When a client connects, a new thread is spawned from entry point 0x401c50. This function allocates a buffer on the heap of 2048 bytes, but attempts to read 100000 bytes, which overflows the buffer.
Vulnerability #3: stack smashing
In the same function in the client thread there is a 400 byte long temporary buffer:
This is then used in sscanf to store the file being requested:
So, in essence: 100000 bytes read from the socket are being shoved into 2048 bytes on the heap and then into 400 bytes of buffer on the stack. By writing a stream of 416 trash bytes and then the value we want our function to return from; we can modify the instruction pointer and alter the flow of instructions in the HTTP-server, possibly executing malicious code.
When running this, you can see our alignment is correct; the data we sent is being loaded into the registers when the function returns:
As an exercise we’ll see if we can execute cmd.exe on the remote host. There are a few mitigations in place on the OS (Windows 10) we should be aware of:
- ASLR. ASLR randomizes the memory offset of the thread that handles our socket so we can’t predict memory addresses within the scope of our thread. Also, ASLR randomizes base addresses of DLLs which contain functions we’ll need, we’ll get to that later.
- DEP. With the stack marked as a non-executable memory region and DEP active, any attempt to change the instruction pointer to a location on the stack will cause an access violation exception; so we can’t just put our own shellcode on the stack.
DEP can be avoided by, instead of injecting our own code and executing it, changing the instruction pointer to an address to where execution is allowed (such as to a normal function call) and feeding it parameters by manipulating the stack. This technique is called ‘return-to-libc’.
In order to execute ‘return-to-libc’ effectively we’ll need to know the address of a function we want to call; in our case _system which is in msvcrt.dll at offset 0x00043AE0. To execute this we’ll also need the base address of msvcrt.dll in memory, which is a problem, because ASLR has randomized the base address of all DLLs in memory. For the purpose of this exercise, we’ll just look up the address of the DLLs we need with a debugger.
We can execute this string by changing the return address on the stack to _system (which is at 0x76FE3AE0). By adding _exit (0x74430FC0) as return address for _system we can gracefully exit the application afterwards without crashing. Adapting the exploit like this spawns a command shell on the log window for the webserver:
Vulnerability #4: Denial of Service
Very simple: open lots of sockets simultaneously. The server will crash. This is due to the server exhausting all it’s process memory on threads. Spawning one thread for each client connection is generally a bad design choice; as is not limiting the amount of simultaneous connections to a level that the system can handle without stability issues.