Strategies for Binary Exploitation

By Jonathan Myers

When it comes to breaking compiled applications an adversary’s best tools are his knowledge of binary exploitation and reverse engineering.  But these tools take an incredible amount of time to build and oftentimes newcomers to the field are deterred by the complexity of the subject.  This post will serve as an introduction to the strategies that I discovered over my brief time with working in binary exploitation.  Additionally I will be covering the applications of some of the tools that I use including Binary Ninja for decompiling, python for scripting clever solutions, and the linux command line for typical interactions.  In this post I will use challenges based on the popular wargame pwnable.kr to dive deep into the strategies behind binary exploitation and provide the deepest level of understanding in regards to the mechanisms that dictate each challenge.

Memory Corruption

A wide variety of system level exploits found in the wild use the concept of memory corruption to modify the memory of a running executable in a way that it is not intended for.  Memory corruption is directly associated and introduced with the classic buffer overflow vulnerability which has tainted the lives of developers for decades.When searching for this vulnerability it is best to look for a location where user input is being copied directly to a buffer without any form of sanitization or length validation.  Given this information we must now fully understand how the stack works so we can exploit our first binary.

1

This diagram focuses on the development of the stack when a function is called.  At the beginning we notice that the parameters are pushed onto the stack in reverse order.  Next the address which the program should return to is pushed onto the stack.  For example, a main function calls the foo function on line 10 of some arbitrary  c code.  An address to this specific line in the code is used as a reference for foo to return to when it is complete.  Next to be pushed onto the stack are the local variables for the function followed by our target buffer.  For this particular exploit we are going to want to fill the buffer to its maximum capacity and then continue further until we get to the exact location of the value or address which we want to overwrite.

The best place to begin searching for this buffer overflow vulnerability is going to be with a disassembled binary.  Throughout the rest of this post we will be using Binary Ninja as our disassembler however many other options also exist such as IDA or even the disas command in gdb.

2

In this image we see the disassembled main function which is essentially just pushing 0xdeadbeef onto the stack as a parameter for the function call func.  At this point I have found that the best course of action is to try to rebuild the source code for the binary that we are disassembling in c to better understand exactly what is going on.  So the main function will look something like the following:

int main(){

        func(0xdeadbeef);

        return 0;

}

Now let’s look at the func function.

3

The most important line here is:

        

0x643        lea        eax, [ebp-0x2c] {var_30}

The first line indicates the allocation of space starting at the base pointer (ebp) and moving down 0x2c bytes or in decimal 44 bytes.  The assembly also reveals that user input is taken directly from gets and put into the buffer.  The contents of this buffer is essentially worthless as the first parameter 0xdeadbeef is compared to the constant 0xcafebabe. Depending on the result of the comparison either a “Nah…” is displayed if the buffer is not the same, or a shell is return if they are the same.  The code for this is as follows:

Void func(int constant){

        char buffer[32];

        printf(“Overflow me : “);

        gets(buffer);

        if(constant != 0xcafebabe){

                printf(“Nah..”);

        }

        else{

                system(“/bin/sh“);

        }

}

Now it is just a matter of discovering the correct number of bytes we need to flood in order to overflow the buffer with the value that we desire.  On the stack we allocated a space of 44 bytes immediately after the parameters and return address were pushed on the stack.  The parameter and the return address take up 4 bytes each for a total of 8 bytes of distance between the value we wish to overwrite and the buffer.  This means that if we manipulate the buffer to travel 52 bytes up the stack we will reach the location of the parameters and replace it with whatever we want.  The final aspect that we must take in consideration is the endianness of the computer.  In this case the computer is little endian so we need to feed the desired value in reverse order. (\xbe\xba\xfe\xca = 0xcafebabe) The easiest way to generate 52 bytes of trash is to use python and then append the desired value onto the input.

python c print 52*’A + \xbe\xba\xfe\xca‘”;

This process reveals a vital strategy which is to develop of full understanding of all elements that are subject to the particular exploit you are engaging in.  Oftentimes I found myself ignoring vital components of the x86 syntax or even fundamentals of the stack which lead me down a dangerous path of frustration and wasting time.  It is best to develop a malleable plan and then break the overall problem down into much smaller parts.  This includes keeping track of the code that you have reverse engineered by forward engineering it from the assembly.  This will allow you to determine if your intuitions are correct while also giving you a platform to find patterns and spot suspicious interactions.

Use After Free

One of my favorite challenges from the pwnable.kr wargame is Dragon, a small text based RPG game which contains some interesting hidden functionality.  During the game you are given the option of playing a Priest or Knight who both have different abilities.  

4

In both scenarios it is impossible to defeat the Dragon but before we dive into the application it is important to discuss some of the defenses that are put in place to protect binaries from exploitation.  The first of these protection is ASLR which is Address Space Layout Randomization.  This protection is used to randomly offset the location of modules and memory based structures to prevent shellcode execution.  This is incredibly effective at mitigating some buffer overflow exploits as an adversary is no longer able to reliably jump to a vulnerable function in memory.  The keyword here is reliably since ASLR still allows the adversary to guess the locations of these randomly placed areas.  To prevent against this a high amount of entropy should be used to generate more random offsets.  The next defense that is important to us is DEP or Data Execution Protection.  This defense is used to mark certain regions of memory sectors such as the stack as being non-executable.  This means that if we wanted to inject shellcode into a vulnerable location within a marked sector it will be unsuccessful in executing.  Now we notice that both DEP and ASLR is enabled on the system that is running the challenge application so we must keep this in mind when we are searching for a vulnerability.  With this out of the way let’s begin reverse engineering the application.

5

When you open an ELF binary in Binary Ninja you are immersed into an environment that allows you to navigate the binary in an organized and intuitive manner.  The first function that you enter is called _start and is typically the entry point for the binary.  This function is not particularly interesting and is usually used to call the main function.  By clicking on either the main function in the left function panel or clicking on the main call in the ELF graph we can navigate to the contents of main.  

6

Here see that nothing more than a intro banner being displayed and a function call to PlayGame.  Just like our last buffer overflow endeavour I strongly recommend documenting the progress we have made by forward engineering the source code.  This will appear as follows:

main(){

        puts(“Welcome to Dragon Hunter!”);

        PlayGame();

}

Next we will check out the PlayGame function.

7

The beginning of this function handles the character selection but we discover that by entering 3 we can call the hidden SecretLevel function.

8

We find out that we need a password so let’s see what we can find in the SecretLevel function.

9

We discover that after entering the correct password we can get a shell, however there appears to be no way we can enter Nice_Try_But_The_Dragons_Won’t_Let_You! since we are only creating a space of 0x16 for the scanf function.  Let’s continue to forward engineer the program with what we have found.  

Int PlayGame(){

Int result;

puts(“Choose your Hero\n[1] Priest\n[2] Knight“);

Result = GetChoice();

If ( result == 1 || result == 2){

        FightDragon(result);

}

Else if (result == 3){

        SecretLevel();

}

}

Int SecretLevel(){

        Char pass[16];

        

        printf(“Welcome to Secret Level!\nInput Password : “);

        scanf(%s, pass);

        if(strcmp(pass, Nice_Try_But_The_Dragons_Wont_Let_You!”)){

                puts(“Wrong!\n“)

                exit(1);

        }

        system(“/bin/sh“)

}

Now it is time to observe the FightDragon function.

11b.png

Here we notice that two objects are being malloced for at the very beginning.  After some digging I noticed that offsets were being used on these objects to retrieve data.  Incidentally these objects were associated with either a dragon or the player so using ctrl-n I was able to rename all instances of the variable within Binary Ninja.  This is a vital technique as it allows you to easily reference a multitude of variables and thoroughly understand what each of them means.

11a.png

After allocating space for the two structs we notice that there is a counter which is being used to select which dragon the player will fight.  After playing the game another time I noticed that after defeating a baby dragon I would be tasked with fighting a mama dragon.  This is where we start to notice something very interesting.

3a

11

So it appears that the values in the assembly are the stats for the dragon.  If we look very closely at the line that I highlighted we can see that only 4 bits were used to store the health of the dragon.  This reveals that the absolute maximum health the dragon can have is 127 (1 1 1 1 if all bits are on we get 127 in decimal).  With this information I started to speculate if I could instead use the Dragon’s health regeneration to overflow the integer and force it to 0.  This concept is observed in the following image.

12

Integers should be thought of as this wheel.  If a number is increased to it’s maximum it will simply start the cycle all over again.  Let’s pair the priest’s invincibility spell with the Dragon’s health regeneration to see if we can make this work.

13

We successfully discovered how to defeat the Dragon but we still want our shell.  

14

When we observe the PriestAttack function we notice that regardless of what happens the Dragon struct is freed.  However if the game is won we see the following:

15

At first I thought it was re mallocing another Dragon struct but instead it is actually mallocing for the players name.  This means that even though the Dragon struct was freed in the PriestAttack function the program is still using the struct.  This combination of events introduces a use after free vulnerability.    

16

 By inserting the location of what line we want executed as the player name we can call whatever part of the code we want.  In particular we want access to the shell that was found in SecretLevel.  We are going to need write some clever python in order to exploit this so I will use a CTF optimized library called pwntools (https://pwntools.readthedocs.io/en/2.2/about.html) to write a quick exploit.

17

In this script we first connect to the pwnable.kr site.  Then we proc the event for the mother dragon by spamming ones.  Then we defeat the mother dragon by overflowing the integer.  Once we the dragon is defeated we send the location of the SecretLevel function call to a shell and proceed to make it interactive.  

Conclusion

 While this post only covered a couple of vulnerabilities, exploits, and strategies I hope that it still produced a useful starting point for those entering the field of binary exploitation.  We discovered that it is important to layout a malleable plan of exploration when working with a binary as this will provide structure to the overall discovery process.  As you traverse the binary you will learn new things which will cause you to think differently about the overall problem and potentially modify your plan.  It is also important to have a good understanding of how the binary was first engineered by writing what you believe the source code may have looked like.  This will allow you to visualize the problem more thoroughly and quickly spot locations that may produce vulnerabilities that you can exploit.  As a final bit of advice I encourage newcomers to be patient and take the harder route to the solution.  While it may be tempting to quickly look up a solution to the entirety of a problem, you will surely learn more by struggling and exploring all of the crevices that surround the issue.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s