linux双重释放漏洞
2024-08-21 16:22:39

最近刚好一个项目涉及到了double free漏洞,于是有了本篇学习记录。

介绍

double free(双重释放)是一个经典的利用堆的漏洞,指释放同一块内存区域两次,那么当再次申请内存的时候,可以通过修改chunk的fd指针来达到获取任意地址写的目的。下面通过两个测试来说明。

测试环境

首先是一个简单的测试,使用的是Ubuntu 20.04版本,glibc版本为2.31

测试一

测试代码:

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

int main()
{
setbuf(stdout, NULL);

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

printf("Fill up tcache first.\n");
void *ptrs[8];
for (int i=0; i<8; i++) {
ptrs[i] = malloc(8);
}
for (int i=0; i<7; i++) {
free(ptrs[i]);
}

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

printf("1st calloc(1, 8): %p\n", a);
printf("2nd calloc(1, 8): %p\n", b);
printf("3rd calloc(1, 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);
a = calloc(1, 8);
b = calloc(1, 8);
c = calloc(1, 8);
printf("1st calloc(1, 8): %p\n", a);
printf("2nd calloc(1, 8): %p\n", b);
printf("3rd calloc(1, 8): %p\n", c);

assert(a == c);
}

一开始是跟着b站上的一个视频来学习的,但视频中使用的是2.23版本的glibc,而在2.26 版本中引入了一种新的堆管理机制Tcachebin,所以前面需要先把Tcachebin给用malloc和free占满,然后使用calloc来申请新的内存。

输出结果:

image-20240427220311528

在申请3个chunk:A,B,C之后

按照 A、B、A 的顺序释放时,会发生以下情况:

  1. 释放 A:当释放 A 时,它被放入 fastbin 的的链表头部。
  2. 释放 B:释放 B 时,B 也被放入 fastbin 的链表中,但由于是后释放的,它被放在 A 的后面。
  3. 释放 A(再次释放):由于 fastbin 是 LIFO(先进后出) 的,再次释放 A 时,它不会被放入链表的头部,而是覆盖了之前释放的 A 节点。因此,链表中 A 的指针现在指向了 B。

现在,fastbin 的 链表看起来是这样的:

1
0x555756e7a390 —▸ 0x555756e7a3b0 ◂— 0x555756e7a390

测试二

尝试获取已存在变量的地址并修改变量值。

测试代码:

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

int main()
{

//和之前一样占满Tcachebin
void *ptrs[7];
for (int i=0; i<7; i++) {
ptrs[i] = malloc(8);
}
for (int i=0; i<7; i++) {
free(ptrs[i]);
}

//创建两个变量,其中stack_var是伪chunk;t是我们要获取的变量地址以及要修改的变量值
unsigned long stack_var;
unsigned long t=1;
fprintf(stderr, "t:%ld,&t:%p\n",t,&t);

//申请三块堆内存
fprintf(stderr, "Allocating 3 buffers.\n");
int *a = calloc(1,8);
int *b = calloc(1,8);
int *c = calloc(1,8);

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

//按aba的顺序释放掉,此时fastbin:a->b<-a
free(a);
free(b);
free(a);

fprintf(stderr, "Now the free list has [ %p, %p, %p ]. "
"We'll now carry out our attack by modifying data at %p.\n", a, b, a, a);

//获取a的地址给d,此时fastbin:b<-a
unsigned long *d = calloc(1,8);
fprintf(stderr, "1st calloc(1,8): %p\n", d);
//第二次用掉b的地址,此时fastbin中还剩下a的地址也就是d
fprintf(stderr, "2nd calloc(1,8): %p\n", calloc(1,8));
fprintf(stderr, "Now the free list has [ %p ].\n", a);
fprintf(stderr, "Now, we have access to %p while it remains at the head of the free list.\n"
"so now we are writing a fake free size (in this case, 0x20) to the stack,\n"
"so that calloc will think there is a free chunk there and agree to\n"
"return a pointer to it.\n", a);
//开始构造伪chunk,并设定chunk size为0x20
stack_var = 0x20;

//覆盖指针,此时fastbin:a->&t
*d = (unsigned long)(((char*)&stack_var)-8);

//用掉a,此时fastbin:&t<-0
fprintf(stderr, "3rd calloc(1,8): %p, putting the stack address on the free list\n", calloc(1,8));

//此时p指针指向t
unsigned long *p = calloc(1,8);

fprintf(stderr, "4th calloc(1,8): %p\n", p);

//修改*p也就是t的值
*p = 123;
fprintf(stderr, "t:%ld\n", t);

}

运行结果:

image-20240428002932670

解析:

在64位机器上,unsigned long 占8字节,所以&t比&stack_var高了8字节

image-20240427235343429

*d = (unsigned long)(((char*)&stack_var)-8);处打一个断点,未执行前

fastbin:

image-20240427235950720

对应的chunk

image-20240428000243115

而d是一个指针变量,指向0x555555559380

在执行结束后对应的chunk:

image-20240428000807295

可以明显看到fd变成了0x7fffffffdec0那么此时的fastbin为:

image-20240428000845104

0x7fffffffdec0是链表的头部地址,在经过两次calloc后就可以拿到的地址是0x7fffffffded0,也就是变量t的地址。

image-20240428002356526

通过这种方式就可以拿到变量t的地址,并对t进行修改,同时glibc版本不同,会对指针覆盖进行限制(例如加密之类的),所以要根据具体的版本去修改利用代码。

参考:

堆利用详解:fastbin dup

【CTF】GLibc堆利用-Double Free