avatar

目录
Fastbin Double free 知识点总结

fastbin double freefastbin attack的一种,在glibc heap相关的漏洞中是较为常见也比较基础的。

原理(Ref1,Ref2,Ref3)

0 基础知识

  1. malloc chunk结构:

    c
    1
    2
    3
    4
    5
    6
    struct malloc_chunk {
    INTERNAL_SIZE_T prev_size; /*前一个chunk的大小*/
    INTERNAL_SIZE_T size; /*当前chunk的大小*/
    struct malloc_chunk * fd; /*指向前一个释放的chunk*/
    struct malloc_chunk * bk; /*指向后一个释放的chunk*/
    }

    An allocated chunk looks like this:

    Code
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
        chunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    | Size of previous chunk, if unallocated (P clear) |
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    | Size of chunk, in bytes |A|M|P|
    mem-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    | User data starts here... .
    . .
    . (malloc_usable_size() bytes) .
    . |
    nextchunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    | (size of chunk, but used for application data) |
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    | Size of next chunk, in bytes |A|0|1|
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

    Free chunks are stored in circular doubly-linked lists, and look like this:

    Code
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
        chunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    | Size of previous chunk, if unallocated (P clear) |
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    `head:' | Size of chunk, in bytes |A|0|P|
    mem-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    | Forward pointer to next chunk in list |
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    | Back pointer to previous chunk in list |
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    | Unused space (may be 0 bytes long) .
    . .
    . |
    nextchunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    `foot:' | Size of chunk, in bytes |
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    | Size of next chunk, in bytes |A|0|0|
    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  1. fast chunk 表示正在使用的长度在0x20-0x80的堆块

    fastbin 表示长度在0x20-0x80范围内的已经释放的堆块

  2. fast chunk 没有fd,只有prev_size、size、data。

    fastbin 只有 fd 指针,fd指向下一个fastbin,且标志位P=1,且(P=0表示前一个chunk空闲)

  3. fastbin 由单链表构成,采用 LIFO, chunk 从链表头部插入,从头部取出。

1 利用原理

什么是 double free

释放一个 chunk 两次。

double free 能干什么

能造成malloc()到同一个 chunk,造成混乱。如果利用得当,就可以达到任意地址写的目的。

怎么利用 double free

malloc()时,当用户所要求内存大小在 fastbin 的大小范围时,malloc()优先从 fastbin 中拿出 chunk。malloc()会查找与所要求大小相匹配的 fastbin 链表,然后从头部取出 chunk,并将取出 chunk 的 fd 赋给 fastbin 链表头部。这样就从 fastbin 链表的头部中取出了 chunk。

c
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
if ((unsigned long)(nb) <= (unsigned long)(get_max_fast()))
{
idx = fastbin_index(nb);
mfastbinptr *fb = &fastbin(av, idx);
mchunkptr pp;
victim = *fb;

if (victim != NULL)
{
if (SINGLE_THREAD_P)
*fb = victim->fd;
else
REMOVE_FB(fb, pp, victim);
if (__glibc_likely(victim != NULL))
{
size_t victim_idx = fastbin_index(chunksize(victim));
if (__builtin_expect(victim_idx != idx, 0))
malloc_printerr("malloc(): memory corruption (fast)");
check_remalloced_chunk(av, victim, nb);
#if USE_TCACHE
// 这里不是重点,所以省略了
#endif
void *p = chunk2mem(victim);
alloc_perturb(p, bytes);
return p;
}
}
}

由于free()时候检查了 fastbin 头部指向的 chunk 和被free()的 chunk 是否相等,即检查是否两次free()了同一个 chunk。所以不能通过直接free()同一个 chunk 来进行 double free。

c
1
2
3
4
5
6
7
8
9
10
unsigned int idx = fastbin_index(size);
fb = &fastbin (av, idx);
/* Atomically link P to its fastbin: P->FD = *FB; *FB = P; */
mchunkptr old = *fb;
// 这里省略部分代码
/* 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))
malloc_printerr ("double free or corruption (fasttop)");
p->fd = old;

free()还检查了 fastbin 里的 chunk 的 size 大小是否符合该 fastbin 的大小。

c
1
2
3
4
5
6
7
/* Check that size of fastbin chunk at the top is the same as
size of the chunk that we are adding. We can dereference OLD
only if we have the lock, otherwise it might have already been
allocated again. */
if (have_lock && old != NULL
&& __builtin_expect (fastbin_index (chunksize (old)) != idx, 0))
malloc_printerr ("invalid fastbin entry (free)");

结合上面的代码,我们可以通过如下方法绕过检查:分配两个 chunk,分别命名为 chunk0 和 chunk1,然后free(chunk0);free(chunk1);free(chunk0)就可以绕过上面的检查来进行 double free。此时的 fastbin 链表应该如下图所示。

c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
+------------+
| |
| fastbinY[i]|
| |
+-----+------+
|
|
v
+-----+------+
| |
| chunk0 +<-----+
| | |
+-----+------+ |
| |
| |
v |
+-----+------+ |
| | |
| chunk1 +------+
| |
+------------+

在前面的free()后我们进行第一次malloc(),就可以分配到 chunk0。我们伪造一个 fake chunk,往 chunk0 的数据段中写入 fake chunk 的地址,就可以将 chunk0 的 fd 指向 fake chunk,即将 fake chunk 添加进了 fastbin 链表中。

c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 第一次 malloc() 分配到 chunk0,修改其 fd 指针
+------------+
| |
+------+ fastbinY[i]|
| | |
| +------------+ +-------------+
| | |
| +------------->+ fake chunk |
| | | |
| +-----+------+ +-------------+
| | |
| | chunk0 +<-----+
| | | |
| +------------+ |
| |
| |
| |
| +------------+ |
| | | |
+----->+ chunk1 +------+
| |
+------------+

然后进行两次malloc(),依次分配到 chunk1 和 chunk0,最后再进行malloc()时就可以分配到 fake chunk。

c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 第二次 malloc()
+------------+
| |
+------+ fastbinY[i]|
| | |
| +------------+ +-------------+
| | |
| +------------->+ fake chunk |
| | | |
| +-----+------+ +-------------+
| | |
+----->+ chunk0 |
| |
+------------+



+------------+
| |
| chunk1 |
| allocated |
+------------+

由于之前将 chunk0 的 fd 指针指向了 fake chunk,并且 chunk0 还呆在 fastbin 里,所以第三次 malloc()时,malloc()会将 chunk0 的 fd 赋给 fastbinY[i],此时只要再进行一次malloc()就可以分配到 fake chunk 了。

c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 第三次 malloc()
+------------+
| |
| fastbinY[i]|
| |
+-----+------+ +-------------+
| | |
+------------->+ fake chunk |
| |
+------------+ +-------------+
| |
| chunk0 |
| allocated |
+------------+



+------------+
| |
| chunk1 |
| allocated |
+------------+

注意事项

malloc到fake chunk时,会检查chunk的size是否合法,所以在伪造chunk时,需要考虑size的问题。

c
1
2
3
4
5
fake     +---------------------+
chunk -> | xxxx | size |1|
| *fd | xxxx |
| xxxxx |
+---------------------+

例如:malloc(0x40)时,将要分配一个0x50大小的fast chunk

Code
1
2
  0x4005c7 <main+97>     mov    edi, 0x40
► 0x4005cc <main+102> call malloc@plt <0x400450>

此时fastbin中存在0x50的chunk:

c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
pwndbg> bins
fastbins
0x20: 0x0
0x30: 0x0
0x40: 0x0
0x50: 0x602000 —▸ 0x602050 ◂— 0x602000
0x60: 0x0
0x70: 0x0
0x80: 0x0
unsortedbin
all: 0x0
smallbins
empty
largebins
empty

查看这个chunk的size,为0x51(P=1):

c
1
2
3
4
5
6
pwndbg> x/10xg 0x602000
0x602000: 0x0000000000000000 0x0000000000000051
0x602010: 0x0000000000602050 0x0000000000000000
0x602020: 0x0000000000000000 0x0000000000000000
0x602030: 0x0000000000000000 0x0000000000000000
0x602040: 0x0000000000000000 0x0000000000000000

此时可以malloc成功,经测试,只要size在0x50-0x60的范围内都可以。

小结&例题

通过 fastbin double free 可以使用多个指针控制同一个堆块,这可以用于篡改一些堆块中的关键数据域或者是实现类似于类型混淆的效果。(栗子:MetasequoiaCTF Summoner)

如果更进一步修改 fd 指针,则能够实现任意地址分配堆块的效果 (首先要通过验证),这就相当于任意地址写任意值的效果。(栗子:Hgame2020 Roc826MetasequoiaCTF Samsara)

文章作者: TaQini
文章链接: http://taqini.space/2020/02/22/Double-free/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 TaQini
打赏
  • Wechat
    Wechat
  • Alipay
    Alipay

评论