2016年10月12日 星期三

HITCON CTF Qual 2016 - House of Orange Write up

HITCON CTF Qual 2016 - House of Orange Write up

Program

  • Build the house

    • Create a house which contains an orange with the chosen color and price.
    • It would allocate two object, orange and house, and than allocate a name buffer.
    struct orange{
      int price ;
      int color ;
    };
    
    struct house {
      struct orange *org;
      char *name ;
    };
    
    
    • You can only build the house four times.
  • See the house

    • Show the information of the house
  • Upgrade the house

    • Upgrade the information of the house
    • You can modify the name of house and the information of orange
    • You can only upgrade three times.
  • Give up

    • Exit

Vulnerability

  • Heap overflow

    • When you upgrade the name of house, it does not check the size of name, leading to heap overflow.
    printf("Length of name :");
    size = read_int();
    if(size > 0x1000){
        size = 0x1000;
    }
    printf("Name:");
    read_input(cur_house->name,size);
    printf("Price of Orange: ");
    cur_house->org->price = read_int();
    
  • Information leak

    • It use read() to read input without NULL byte which leads to information leak.
    void read_input(char *buf,unsigned int size){
        int ret ;
        ret = read(0,buf,size);
        if(ret <= 0){
            puts("read error");
            _exit(1);
        }
    }
    

Exploitation

  • Idea
    • [Fail] Use heap overflow to overwrite the size of top with a larger number. And then use house of force to overwrite the name pointer. The program uses unsigned int and size is less than 0x1000 , so this idea is impossible.
    • [Fail] Use heap overflow to overwrite the name pointer , but the program only uses malloc. This idea is impossible.
    • [Success] We need to create a free chunk on the heap by using _int_free in sysmalloc, then use the unsorted bin attack to overwrite the _IO_list_all in libc to control the program counter.
  • Overwrite the size of top chunk

    • We want to use the _int_free in the sysmalloc , so we have to overwrite the top chunk size to trigger the sysmalloc at first.
    • Trigger sysmalloc : If the top chunk size is not large enough, it would use sysmalloc to allocate a new memory area.(source) It would increase the size of the old heap or mmap a new memory area. We have to malloc a size smaller than the mmp_.mmap_threshold to extend the old heap.
    • Trigger _int_free in sysmalloc : In order to trigger the _int_free in sysmalloc.(source), we have to make the top chunk size larger than MINSIZE(0x10). The problem is that there is two assertion in the sysmalloc(), so we have to forge the legal size of top chunk to bypass it. To bypass the assertion, there are some requirements of the size must be met:

      • larger than MINSIZE(0x10)
      • smaller than need size + MINSIZE
      • prev inuse is set
      • old_top + oldsize must be aligned a page.
      assert ((old_top == initial_top (av) && old_size == 0) ||
        ((unsigned long) (old_size) >= MINSIZE &&
         prev_inuse (old_top) &&
         ((unsigned long) old_end & (pagesize - 1)) == 0));
      assert ((unsigned long) (old_size) < (unsigned long) (nb + MINSIZE));
      

      For example, if the top address is 0x6030d0 and the size is 0x20f31, we should overwrite the size with 0xf31 to bypass the assertion and then allocate a large chunk to trigger the sysmalloc and _int_free. Finally, we could get an unsorted bin chunk on the heap.

  • Information Leak

    • After creating an unsorted bin chunk, We can use it to leak the address of libc and heap.
    • Leak the address of libc: We can build a new house with a size of large chunk but smaller than the top chunk size that we’ve modified, to get the unsorted bin chunk. We can input eight bytes characters as the name of house, then use the See the house to get the address of libc. Since malloc would not clean the value on the heap, so we can get the address in libc.
    • Leak the address of heap: Since there is no any chunk with the matching size in the unsorted bin, it would be placed in the large bin at first. There are two member, fd_nextsize and bk_nextsize, in the large chunk, that pointer to next and prev large chunk. We can use it to leak the address of heap by upgrading the house.
  • Hijack the control flow in the malloc abort routine

    • Abort routine: When the glibc detects some memory corruption problem, it would enter the abort routine. (source) It would flush all streams in the stage one. In other words, it would enter the _IO_flush_all_lockp (source) function and use the _IO_FILE object, which is called _IO_list_all in it. If we overwrite the pointer and forge the object, then we could control the flow. Because the _IO_FILE uses virtual function table called _IO_jump_t(source) to do something, we can forge it. You can reference this article
    • Forge the _IO_FILE object: Our goal is to trigger _IO_flush_all_lockp to call _IO_OVERFLOW, so we need to satisfy some condition in the _IO_FILE objectsource.

      0841       if (((fp->_mode <= 0 && fp->_IO_write_ptr > fp->_IO_write_base)
      0842 #if defined _LIBC || defined _GLIBCPP_USE_WCHAR_T
      0843        || (_IO_vtable_offset (fp) == 0
      0844            && fp->_mode > 0 && (fp->_wide_data->_IO_write_ptr
      0845                     > fp->_wide_data->_IO_write_base))
      0846 #endif
      0847        )
      0848       && _IO_OVERFLOW (fp, EOF) == EOF)
      

      Using gdb to trace it is easier. In the end of the object, it has a virtual function table. We can forge it on the heap.

    • Unsorted bin Attack: When we allocate a chunk, it would process the unsorted bin first. It would remove the chunk in unsorted bin whether or not the size matches. However, it does not check the completeness of the linked list. Before the unsorted chunk is removed from the unsorted bin, we can overwrite the bk pointer with any address-0x10. And then the address will be overwritten with the address of unsorted bin. (source) We decide to use it to overwrite _IO_list_all with the address of unsorted bin

       /* remove from unsorted list */
        unsorted_chunks (av)->bk = bck;
        bck->fd = unsorted_chunks (av);
      
    • Control the world: When we use the unsorted bin attack to overwrite _IO_list_all with the address of unsorted bin, it would not control the flow first. Since we can't control the content in the main_arena, we decide to use the chain pointer which points to next _IO_FILE object. It would be a small bin chunk in the main_arena. We can use the upgrade function to overwrite the size of unsorted chunk to control it, and forge the _IO_FILE object at the same time. After then, we use the build function to trigger unsorted bin attack to overwrite the _IO_list_all. Finally, it would trigger unsorted bin chunk allocation and detect some memory corruption in malloc because the size of chunk is smaller than MINSIZE. We have hijacked the _IO_list_all so that we can control the world. By the way, if you can control the chain pointer in the _IO_list_all, you can continue to do anything. We call it File Stream Oriented Programming.

    I get the abort message as well as a shell. That is so fun isn't it ? Thank you for joining the HITCON CTF 2016 Qual. I hope everyone can learn more from our CTF.