1.漏洞利用条件
- glibc版本小于2.30,因为2.30之后加入了检查
- 需要攻击者在
large_bin
和unsorted_bin
中分别布置一个chunk 这两个chunk需要在归位之后处于同一个largebin
的index中且unsorted_bin
中的chunk要比large_bin
中的大 - 需要
unsorted_bin
中的bk指针
可控 - 需要
large_bin
中的bk指针和bk_nextsize
指针可控
2.漏洞功能
任意写(任意分配chunk再写)
3.漏洞源码
//#define unsorted_chunks(M) (bin_at (M, 1)) //如果unsorted bins不为空,从尾到头遍历unsorted bin中的每个chunk while ((victim = unsorted_chunks(av)->bk) != unsorted_chunks(av)) { bck = victim->bk;//取出unsorted的尾部的chunk /* 检查当前遍历的 chunk 是否合法,chunk 的大小不能小于等于 2 * SIZE_SZ, 也不能超过 该分配区总的内存分配量。然后获取 chunk 的大小并赋值给 size。 这里的检查似乎有点小问题,直接使用了 victim->size,但 victim->size 中包含了相关的标志位信息,使用 chunksize(victim) 才比较合理,但在 unsorted bin 中的空闲 chunk 的所有标志位都清零了,所以这里直接 victim->size 没有问题。 */ if (__builtin_expect(victim->size <= 2 * SIZE_SZ, 0) || __builtin_expect(victim->size > av->system_mem, 0)) malloc_printerr(check_action, "malloc(): memory corruption", chunk2mem(victim), av); size = chunksize(victim);//获取victim的size /* 如果要申请的大小在smallbin范围 且 unsorted chunks 只有一个chunk,且 victim是last_remainder 且 victim的size大于请求的chunk的大小nb加上 (MINSIZE)最小chunk的size,那么就切割remainder,然后返回victim。 last_remainder 是一个 chunk 指针,分配区上次分配 small chunk 时, 从一个 chunk 中分 裂出一个 small chunk 返回给用户,分裂后的剩余部分 形成一个 chunk,last_remainder 就是 指向的这个 chunk。 */ if (in_smallbin_range(nb) && bck == unsorted_chunks(av) && victim == av->last_remainder && (unsigned long) (size) > (unsigned long) (nb + MINSIZE)) { //分割remainder remainder_size = size - nb;//计算分割后剩下的size remainder = chunk_at_offset(victim, nb);//获取remainder的地址 //把remainder加入unsorted bin中 unsorted_chunks(av)->bk = unsorted_chunks(av)->fd = remainder; av->last_remainder = remainder; // 设置last_remainder为remainder remainder->bk = remainder->fd = unsorted_chunks(av); //如果是remainder在large bin的范围,则把fd_nextsize,fd_nextsize清零 if (!in_smallbin_range(remainder_size)) { remainder->fd_nextsize = NULL; remainder->fd_nextsize = NULL; } //设置victim的size set_head(victim, nb | PREV_INUSE | (av != &main_arena ? NON_MAIN_ARENA : 0)); //设置remainder的size set_head(remainder, remainder_size | PREV_INUSE); //设置remainder的物理相邻的下一个chunk的prev_size set_foot(remainder, remainder_size); check_malloced_chunk(av, victim, nb);//默认不做任何操作 void *p = chunk2mem(victim);//将chunk指针转化为mem指针 alloc_perturb(p, bytes);//将p的mem部分全部设置为bytes ,默认什么也不做 return p; } //把victim从unsorted bin 中移除 unsorted_chunks(av)->bk = bck; bck->fd = unsorted_chunks(av); //如果 victim 的size 与申请的size相等,那么就返回其。 if (size == nb) { //设置victim物理相邻的下一个chunk的prev_inuse位 set_inuse_bit_at_offset(victim, size); //如果av不是main_arena 也就是说如果不是主进程,设置NON_MAIN_ARENA位 if (av != &main_arena) victim->size |= NON_MAIN_ARENA; check_malloced_chunk(av, victim, nb); // 默认不做任何操作 void *p = chunk2mem(victim);//把chunk转换为mem指针 alloc_perturb(p, bytes);//将p的mem部分全部设置为bytes ,默认什么也不做 return p; } //如果上一步取出的chunk没有匹配成功,那么将该chunk放入对应的bin中 //如果在smallbin的范围,则放到对应多small bin中 if (in_smallbin_range(size)) { victim_index = smallbin_index(size);//获取size对应的smallbin的index bck = bin_at(av, victim_index);//bck指向size对应的smallbin的链表头 //fwd指向size对应的smallbin的链表中的新加入的chunk(small bin使用头插法) fwd = bck->fd; } else//如果不再smallbin的范围,也就是说在large bin 的范围 { victim_index = largebin_index(size);//获取size对应的large bin的index bck = bin_at(av, victim_index);//bck指向size对应的large bin的链表头 fwd = bck->fd;//fwd指向size对应的large bin的链表中的新加入的chunk //如果large bin 非空,在largbin进行按顺序插入 if (fwd != bck) { /* Or with inuse bit to speed comparisons */ size |= PREV_INUSE; assert((bck->bk->size & NON_MAIN_ARENA) == 0);//默认不启用assert /* large bin中的chunk是按从大到小排列的,如果size < large bin 的最后一个chunk,说明size是这个large bin中的最小的,我们把它 加入到此large bin尾部。 */ if ((unsigned long) (size) < (unsigned long) (bck->bk->size)) { fwd = bck; bck = bck->bk; /* large bin 中size最小的chunk的fd_nextsize会指向size最大的 那个chunk,也就是首部的chunk。同样,large bin 中size最大的 chunk的bk_nextsize会指向size最小的那个chunk。 victim的bk_nextsize指向large bin原来最小的chunk,它的 bk_nextsize指向最大的那个chunk。那么原来的最小的就成了第二小的了。 把它fd_nextsize和bk_nextsize都修正。 */ victim->fd_nextsize = fwd->fd; victim->bk_nextsize = fwd->fd->bk_nextsize; //最大size的chunk的bk_nextsize,和原来最小chunk的bk_nextsize都指向victim fwd->fd->bk_nextsize = victim->bk_nextsize->fd_nextsize = victim; } else //如果victim不是large bin 中最小的chunk { assert((fwd->size & NON_MAIN_ARENA) == 0);//默认不启用assert //从大到小(从头到尾)找到合适的位置 while ((unsigned long) size < fwd->size) { fwd = fwd->fd_nextsize; assert((fwd->size & NON_MAIN_ARENA) == 0); } //如果size刚好相等,就直接加入到其后面省的改fd_nextsize和bk_nextsize了 if ((unsigned long) size == (unsigned long) fwd->size) fwd = fwd->fd; else { //size不相等,即size>fwd->size,把victim加入到纵向链表中 victim->fd_nextsize = fwd; victim->bk_nextsize = fwd->bk_nextsize; fwd->bk_nextsize = victim; victim->bk_nextsize->fd_nextsize = victim; } bck = fwd->bk; } } else //如果large bin 为空,将victim加入到纵向列表 victim->fd_nextsize = victim->bk_nextsize = victim; } //#define mark_bin(m, i) ((m)->binmap[idx2block (i)] |= idx2bit (i)) mark_bin(av, victim_index); //把victim加入到的bin的表示为非空 //把victim加入到large bin的链表中 victim->bk = bck; victim->fd = fwd; fwd->bk = victim; bck->fd = victim; }
主要部分简化(unsortedbin放入largebin的过程)
else { /* 如果unsorted_chunk->size 大于 largbin_chunk->size, 把unsorted_chunk加入到纵向链表中 我们控制 large_chunk->bk = fake_chunk+0x8 large_chunk->bk_nextsize=fake_chunk-0x18-5 */ unsorted_chunk->fd_nextsize = largbin_chunk; //unsorted_chunk->bk_nextsize = fake_chunk-0x18-5 unsorted_chunk->bk_nextsize = largbin_chunk->bk_nextsize; largbin_chunk->bk_nextsize = unsorted_chunk; //fake_chunk+0x3 = unsorted_chunk unsorted_chunk->bk_nextsize->fd_nextsize = unsorted_chunk; } //bck = fake_chunk+0x8 bck = largbin_chunk->bk; } } mark_bin(av, unsorted_chunk_index); //把unsorted_chunk加入到的bin的表示为非空 //把unsorted_chunk加入到large bin的链表中 unsorted_chunk->bk = bck; unsorted_chunk->fd = largbin_chunk; largbin_chunk->bk = unsorted_chunk; //fake_chunk+0x18 = unsorted_chunk bck->fd = unsorted_chunk;
核心的四步操作:
- unsorted_bin->bk = fake_chunk #把fake_chunk链到了unsorted_bin中
- fake_chunk+0x10 = unsorted_bin #伪造fake_chunk的fd
- fake_chunk+0x3 = unsorted_chunk #伪造fake_chunk的size
- fake_chunk+0x18 = unsorted_chunk #伪造fake_chunk的bk
通过以上4步,我们成功伪造一个合法的fake_chunk,满足unsorted bin的要求,并把它链到了unsorted bin中,当我们申请的size和0x56
经过对齐后相等的话,那么就可以拿到任意的chunk。