Today's goal is simple: reacquaint myself with basic stack-based buffer overflows that don't require you to fight DEP or ASLR or Stack Canaries or any other system protections. This is the basic "My First Exploit" kind of thing, but its good practice and frankly I haven't done it since SEC660 ended. So here we go.
First off, we'll be working with the following:
- Attacker Box: Kali Rolling at 192.168.99.161
- Victim Box: Windows 7 Professional with Service Pack 1 at 192.168.99.160
- Victim Box: Running "vulnserver.exe"
- Victim Box: Running Immunity Debugger
I'll leave explaining the concepts behind Buffer Overflows to other posts; but essentially we want to do the following:
A quick caveat: I originally wrote all the code here in Python3, which is my preferred language. It turns out how it encodes data is a pain, so I've swapped it for Python2 code. Use 3 if you want but remember its going to have some unintended consequences.
Oh hey, another caveat: I'm not a developer, so code will be rough at best. Additionally, this isn't a tutorial - its review for myself. Input greatly appreciated, but if you're looking for anything more than a refresher, I'd suggest checking out another blog.
Okay, lets do this.
First off, launch vulnserver.exe. You get a simple interface that tells you when a client connects. The server runs on port 9999/TCP. On the Kali box, log into it with "nc 192.168.99.160 9999"
You can see running the HELP command gives you a bunch of options. Vulnserver.exe was created with the intent to be a buffer overflow training tool. Nothing really interesting happens with the commands; however, we're going to look at TRUN which is vulnerable to a buffer overflow. Remember: The format here is "TRUN .value" followed by the enter key - so our format for submitting a payload will be "TRUN ." + BOF/Exploit Code + "\r\n"
First, we need to see what position the buffer overflow actually is actually at. We're going to do this by writing some quick code that'll send a number of characters, in this case, "A" or "\x41" to the server and seeing how it reacts. "A" is arbitrary, but its easy to recognize in dumps; use whichever character you want. Start Vulnserver.exe, start Immunity Debugger, attach Immunity to the "vulnserver.exe" process, and hit the Start button. Immunity is now ready to capture whatever happens to vulnserver.exe for analysis. We want to end up with a crash, and then we want to control EIP to control what happens during the crash.
Here's the basic code I ended up with to fuzz the server. I started with an arbitrary number of "A"s - 2,500 - by sending the command "./attack.py -t 192.168.99.160 -p 9999 -l 2500". The server crashed. So we have established that 2500 causes a crash. I restarted vulnserver.exe and Immunity, re-attached, and sent a payload of 1,000 "A"s - no crash this time. I repeated this until I determined that it would crash above about 2,000, and was still crashing at 3,000. As you can see here, I can see my "A" characters ("\x41," remember) in the memory registers, but not where I need them (in EIP):
So we need to fine-tune exactly where EIP is located; how many "A"s do we send before we get there? We can find out by using a set of Metasploit tools, "pattern_create.rb" and "pattern_offset.rb". Pattern Create creates a non-repeating pattern that you send as your attack payload. You then take the value that lands in EIP and feed it into Pattern Offset, which tells you exactly how many characters of it'll take to land you in EIP. I decided since vulnserver.exe was still crashing at 3,000 characters, I'd create a pattern of 3,000 nonrepeating sequences and send that to vulnserver with the following command:
Another quick note: If you're using vim, Syntax highlighting breaks by default at 3,000 characters, which means your vim session suddenly looks like things have gone terribly wrong. Do yourself a favor and add in "syntax on" and "set synmaxcol=0" in your ~/.vimrc file. Okay, sidenote over.
I updated my python script to send the above value as the "attack_payload" and send it to vulnserver.
Now we can look at EIP and see that it has a value of 43396F43. We can also see our non-repeating pattern in other parts of the stack. We'll now decode the position of 43396F43 using pattern offset:
Looks like we're set at 2007. So, we update the script again: now the number of "A"s is set to 2007, and we insert a few "B" characters ("\x42") afterward to make sure we're in control of EIP. If this works as intended, we should get all four "B"s in EIP. We'll also pad the attack since we know 3,000 chars crashes it, by filling the rest of the 3,000 character space with "C"s.
And we run the script:
Well, crap. EIP shows 3 of the "B" characters, and one of the "A" characters ("42424241" = "\x42\x42\x42\x41" = "BBBA") meaning one of two things happened: The pattern offset tool was wrong by one character, or (much more likely) I screwed something up. Fortunately its only one character too many, so we'll adjust the code to use 2006 "A"s instead. Adjustments made:
And lets run it again:
If you right-click ESP and select "Follow in Dump," you can see our padding of "C" characters - which is great, because they represent where our exploit code is going to go. So far, so good.
This would be a good time to test for bad characters - that is, characters that will, if included, cause problems in our exploitation of vulnserver.exe. In the past, I've always auto-disincluded troublesome characters like "\x00" "\x0a" "\x0d" and "\x20" - they represent things like carriage returns, line breaks, spaces, etc - not something you want to include when you're trying to run code. The most common method I've seen to check for others is to iterate through a list of all characters, and follow the dump to see where things go corrupt. Ideally, in the stack, if you send a 0x46, you should see a 46 - if 0x46 is a badchar, its going to be something else. So you go through, find where things go wonky, remove that from the list, and send the updated list. For reference, here's a badchar list I use:
I've pulled out the typically troublesome characters mentioned above already. I'll set these as my payload in the Python script and fire it off at Vulnserver, examining the results in Immunity:
We can see in the dump that all characters sent iterate, from \x01 to \xff, so we're in good shape! Now we need to find a JMP ESP instruction to put into EIP, to have it execute the shellcode we'll create in a few minutes.
For this, we can use the Mona module from Corelan. In the command bar of Immunity, we'll use Mona to search for an address that contains a JMP ESP instruction, is NOT using ASLR or any other buffer overflow defenses, and then pop that into our code as the EIP location.
In the Immunity command bar, type in "!mona modules" to list loaded modules. It does a great job of showing us what defenses are in place - for our purposes, we're looking for "false" across the board, and we have two options:
We can use essfunc.dll or vulnserver.exe itself - but look at the addresses. Vulnserver.exe starts with 0x00, meaning at some point we'll hit a badchar identified above and everything breaks. Instead, we can use essfunc.dll. We know we need a JMP ESP instruction; and we also know the JMP ESP instruction can be decoded to "ffe4" (or "\xff\xe4") - if you need to look this up, check out Metasploit's NASM shell at /usr/share/metasploit-framework/tools/exploit/nasm_shell.rb and type "jmp esp".
The mona command to search for JMP ESP inside of essfunc.dll is:
!mona find -s "\xff\xe4" -m "essfunc.dll". We can see a few good candidates:
I picked the first one, because its late and I'm tired - 0x625011af; there's no ASLR or any other defenses. Remember that we'll need to swap this to Little Endian format, so drop the 0x and reverse the bytes to "\xaf\x11\x50\x62" - thats what we want to land into EIP.
Lets update our attack code again. Set the EIP, and this time I'm going to add some NOPs (NO OPS) to put a little padding between the EIP and the exploit. I'm also going to generate shellcode using MSFVenom - I want a reverse command shell back to my box, exlcuding the bad characters identified earlier, i want it in Python format, and I'm going to set the variable name to "exploit":
Copy everything from 'exploit = \"\"' on down, and paste it into the exploit. Then we modify the exploit to adjust the length of padding and NOPs, and slap our exploit code right between the NOPs and padding:
Then we fire up msfconsole, start multi/handler, set the payload, port, and IP, and run the handler:
Fire off the exploit one more time...
And we have a shell!
Using a simple buffer overflow, we've gained access over the PC running vulnserver.exe and can continue hacking away at it from the shell provided.