遇到一题,需要此法,特分析源码,在此记录

版本:libc-2.29

众所周知,libc-2.29版本中,unsorted bin attack已经被防御了

但经过buuctf的新年红包题,原来大佬发现了新的类似于unsorted bin attack的方法

可利用之处在于,如果small bin中有空闲块,且该大小的tcache bin未满的时候,就会遍历small bin,把small bin从其最后的一个块开始,沿着bk,将块一个一个地放入tcache bin

源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
// glibc2.29/malloc/malloc.c 3560
if (in_smallbin_range(nb)) //所有小于large bin的大小的块都in_smallbin_range
{
idx = smallbin_index(nb); //得到请求大小的块对应的small bin的index
bin = bin_at(av, idx); //得到该bin

if ((victim = last(bin)) != bin) //last函数取的是bin->bk,此处的victim就是small bin链表的最后一个,如果此时victim==bin,就代表说small bin中没有块
{
bck = victim->bk; //bck指向的是该块所指的前一项
if (__glibc_unlikely(bck->fd != victim)) //这里的检查和unsorted bin那里添加的检查是一样的
malloc_printerr("malloc(): smallbin double linked list corrupted");
set_inuse_bit_at_offset(victim, nb);
bin->bk = bck;
bck->fd = bin; //直到这里就相当于说victim从small bin的最后一项中取出来了,因为small bin是一个双链表,所以进行了这两步操作进行unlink

if (av != &main_arena)
set_non_main_arena(victim);
check_malloced_chunk(av, victim, nb);
#if USE_TCACHE
/* While we're here, if we see other chunks of the same size,
stash them in the tcache. */
size_t tc_idx = csize2tidx(nb); //得到该大小相对于tcache bin的index
if (tcache && tc_idx < mp_.tcache_bins)
{
mchunkptr tc_victim;

/* While bin not empty and tcache not full, copy chunks over. */
while (tcache->counts[tc_idx] < mp_.tcache_count && (tc_victim = last(bin)) != bin) //这里测试tc_idx对应的tcache bin中块的数目是否小于mp_.tcache_count即一般来说为7,&&之后的一句与上面所说的一样
{
if (tc_victim != 0) //这里的判断tc_victim != 0我认为是不恰当的,因为最正常的情况而言,tc_victim到最后应该为bin,即while循环中的!=判断是应该成立的
{
bck = tc_victim->bk;
set_inuse_bit_at_offset(tc_victim, nb);
if (av != &main_arena)
set_non_main_arena(tc_victim);
bin->bk = bck;
bck->fd = bin; //这里将块unlink取出

tcache_put(tc_victim, tc_idx); //将取出的块放入tcache
}
}
}
#endif
void *p = chunk2mem(victim);
alloc_perturb(p, bytes);
return p;
}
}

利用方式

主要利用的点就在while循环中

以下假设所有块的size都是0x101

要利用,需要tcache bin中有5个块,且small bin中有3个块

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#include<stdio.h>
#include<malloc.h>
int main()
{
void *ptr[0x20] = {0};
for(int i=0;i<5;i++)
{
ptr[i] = malloc(0xf0);
}
for(int i=0;i<5;i++)
{
free(ptr[i]);
}
for(int i=5;i<12;i++)
{
ptr[i] = malloc(0x400);
}
void *p1 = malloc(0x400);
malloc(0x10);
void *p2 = malloc(0x400);
malloc(0x10);
void *p3 = malloc(0x400);
malloc(0x10);
void *p4 = malloc(0x400);
malloc(0x10);
for(int i=5;i<12;i++)
{
free(ptr[i]);
}
malloc(0x10);
int a;
free(p1);
malloc(0x300);
free(p2);
malloc(0x300);
free(p3);
malloc(0x300);
free(p4);
malloc(0x300);
malloc(0x300);
scanf("%d", &a);
return 0;
}

这样就得到了3个0x100的small bin

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
pwndbg> heapinfo
(0x20) fastbin[0]: 0x0
(0x30) fastbin[1]: 0x0
(0x40) fastbin[2]: 0x0
(0x50) fastbin[3]: 0x0
(0x60) fastbin[4]: 0x0
(0x70) fastbin[5]: 0x0
(0x80) fastbin[6]: 0x0
(0x90) fastbin[7]: 0x0
(0xa0) fastbin[8]: 0x0
(0xb0) fastbin[9]: 0x0
top: 0x55fed16d7ac0 (size : 0x1d540)
last_remainder: 0x55fed16d7360 (size : 0x100)
unsortbin: 0x0
(0x100) smallbin[14]: 0x55fed16d7360 <--> 0x55fed16d6b00 <--> 0x55fed16d66d0
(0x100) tcache_entry[14](5): 0x55fed16d4660 --> 0x55fed16d4560 --> 0x55fed16d4460 --> 0x55fed16d4360 --> 0x55fed16d4260
(0x410) tcache_entry[63](7): 0x55fed16d6c30 --> 0x55fed16d5bb0 --> 0x55fed16d57a0 --> 0x55fed16d5390 --> 0x55fed16d4f80 --> 0x55fed16d4b70 --> 0x55fed16d4760

利用的话,只需要将small bin中第一项的bk改为我们想要覆写的地址-0x10的地方,然后malloc(0x100)即可完成利用

前面源码中,last(bin)所取的就是最右边那一块,这一块作为victim取出,最后返回,bin->bk变成链表中的中间项

然后执行到while循环的地方

  • 通过tc_victim = last(bin)取到small bin中间那一块
  • bck = tc_victim->bk,这里bck指的就是small bin的第一块
  • 设置bin->bk = bck
  • 设置bck->fd = bin
  • tc_victim放入tcache
  • tc_victim = last(bin)取到small bin的第一块
  • bck = tc_victim->bk,此时tcache中有6个块,如果之前已经将bk改编成我们需要的地址,现在bck就是我们所需要的地址
  • 之后bin->bk = bck
  • bck->fd = bin就将我们所需要的地址+0x10的地方写成了bin的地址,即small bin的地址

至此就完成了利用,挺麻烦的 : )