FASTBIN 利用方法(更新中~咕)

First Post:

Last Update:

FASTBIN 利用方法(更新中~咕)

fastbin采用一个单向链表来维护释放的堆块。我们先来看一下释放堆块源码中逻辑上的关键部分

1
2
3
unsigned int idx = fastbin_index(size);
fb = &fastbin (av, idx);
/* Atomically link P to its fastbin: P->FD = *FB; *FB = P; */

为了不在这里介绍一些很长的判断,我选择将源码中的注释放了过来。fastbin (av, idx)是一个宏的调用,实现如下:

1
#define fastbin(ar_ptr, idx) ((ar_ptr)->fastbinsY[idx])

不难发现它根据传入的分配区,和idx变量(根据释放堆块的大小判断出应放入fastbinsY的下标)返回了这一堆块该放入的fastbinsY的地址,之后进行如下操作P->FD = *FB; *FB = P;

用叙述性的语言表达出来的话就是,fastbinsY[idx]中存着最后链入其中的堆块的地址。在最初时这个值为0。当有新的堆块链入时,他会把这一地址写入新来的堆块的fd中。同时将fastbinsY[idx]更新为新的堆块的地址。

打一个可能没那么形象的比方,向FASTBIN中插入堆块就像是排队,队尾的人会举着一个标记队尾的牌子,当有新的人来排队时,新来的人会跟在队尾后面,接过前队尾传来的牌子。(之所以说不太形象大概是因为fastbin是后进先出,显然排队讲究先进先出)。

double free

简单理解fastbin的结构之后我们来看double free。double free的利用场景通常是程序在释放内存时没有检查堆块是否已经被释放。因此我们可以将一个已经放入fastbin的堆块再次free。

但在free中对这种行为并不是没有任何检查。如下为2.23版本的free函数

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
/* Check that the top of the bin is not the record we are going to add
(i.e., double free). */
if (__builtin_expect (old == p, 0))
{
errstr = "double free or corruption (fasttop)";
goto errout;
}

if (__glibc_unlikely (p == av->top))
{
errstr = "double free or corruption (top)";
goto errout;
}
/* Or whether the next chunk is beyond the boundaries of the arena. */
if (__builtin_expect (contiguous (av)
&& (char *) nextchunk
>= ((char *) av->top + chunksize(av->top)), 0))
{
errstr = "double free or corruption (out)";
goto errout;
}
/* Or whether the block is actually not marked used. */
if (__glibc_unlikely (!prev_inuse(nextchunk)))
{
errstr = "double free or corruption (!prev)";
goto errout;
}

这里检查了四件事:

1,free的堆块是不是fastbin的头堆块(即最后一个放入fastbin的堆块)

2,free的堆块是不是arena中的top chunk。

3,free的堆块的nextchunk(这里的nextchunk指物理相邻的chunk)是否超过了topchunk的边界。

​ 这里使用了nextchunk这一变量来源如下:

1
nextchunk = chunk_at_offset(p, size);

顾名思义是根据堆块大小偏移获取nextchunk的地址,如果nextchunk的地址超过了topchunk则报错。

4,最后检查nextchunk中是否将prev_inuse置1。

由于上述条件的存在,我们可以得出一些堆利用中应该注意的点。

1
2
3
1,我们不能连续释放同一个堆块
2,修改chunk大小时应该注意不应该让这个大小超出topchunk范围
3,释放的堆块的nextchunk必须将prev_inuse置1

所以我们在进行double free的利用时通常如下:

1
2
3
free(chunk1);
free(chunk2);
free(chunk1);

收益

经过double free之后我们的收益是什么?经过double free我们的chunk1被放入了fastbin中2次,我们先申请出来一个,我们就能获得操作一个还在fastbin中的chunk的机会。

例如我们修改它的FD指向我们想要修改的地址,如hook等,当我们第二次申请chunk1时,它便会将fd中的内容写入fastbin的头(fb = p->fd)。我们再次申请堆块时,新申请的堆块地址就是我们写入的地址。