House of orange involves heap overflow, file stream exploitation as well as unsorted bin attack. This technique was developed by Angelboy.

Unsorted bin attack

Chunks present in the unsorted bin are moved into the smallbins and largebins via the sorting process which occurs during calls to malloc. The sorting process starts when malloc starts scanning the unsorted bin when trying to service a request. An unsorted bin is searched when a request size falls outside of tcache/ fastbin / smallbin range or if the target bin is empty. During requests, the unsorted bin is searched from back to front. Suppose a chunk is present at the end of an unsorted bin. In that case, malloc doesn’t need to use the unlink macro or function because it knows the bin as well as the position of the chunk. It results in a partial unlink. The victim chunk’s fd is ignored because it would always point to the head of the unsorted bin(since the chunk under attack would be the last chunk in the unsorted bin). Unlike the unlink macro and partial unlink checks in smallbins, there are very few integrity checks in case of partial unlink from unsorted bins. The technique is to insert an address in the unsorted bin’s bk and then triggering a partial unlink. It will write the (address of the unsorted bin) into the address stored at bk + 16. A side effect of unsorted bin attack is that the forged bk is copied over the unsorted bin’s bk. This means that as malloc continues to search the unsorted bin after sorting the current chunk, it will follow the forged bk and try to determine whether the chunk at its destination could be used to service the current request. This can be used to leak libc addresses as well as to perform a House of Orange attack. libc2.28 eliminates the unsorted bin attack by adding security checks.

The Actual Attack

Unsorted bin attack can be used to overwrite vtable pointers with the main arena’s unsorted bin. After that, whenever a standard i/o related function is called, the main arena would be treated as a vtable. But, it’s not a good idea. But, we can target the _IO_list_all pointer, replacing it with a pointer in the main arena. The main arena will be treated as a file stream and we can point one of the fields of that file stream at controllable heap memory, which can be done by sorting the chunk in the unsorted bin into a bin of our choice. In order to sort out the chunk, we need to find out in which smallbin the _chain field gets inserted. To do that, corrupt the SIZE field of the unsorted chunk by a heap overflow and allocate a chunk. When the program is about to terminate, _IO_cleanup is used to perform a cleanup which internally calls _IO_flush_all_lockp which uses a check to find out if a file stream requires flushing.

run_fp = fp;
      if (do_lock)
        _IO_flockfile (fp);
      if (((fp->_mode <= 0 && fp->_IO_write_ptr > fp->_IO_write_base)
           || (_IO_vtable_offset (fp) == 0
               && fp->_mode > 0 && (fp->_wide_data->_IO_write_ptr
                                    > fp->_wide_data->_IO_write_base))
          && _IO_OVERFLOW (fp, EOF) == EOF)
        result = EOF;
      if (do_lock)
        _IO_funlockfile (fp);
      run_fp = NULL;

Our target is to call _IO_OVERFLOW. fp represents the file stream. Run p *_IO_list_all to find out various fields. Check out the value stored at _chain field in _IO_list_all and find out the smallbin that it belongs to. Change the SIZE field in the previous step (while allocating from the unsorted chunk) to the size value of the smallbin that contains the _chain field. Next malloc will sort the chunk into the smallbin, and end up as the _chain pointer and allow us to craft a fake file structure on the heap. Before doing that, we need to craft a fake _IO_FILE structure. Do not forget to run the ptype _IO_file in pwndbg to find out the correct offsets.

pwndbg> dt _IO_FILE
    +0x0000 _flags               : int
    +0x0008 _IO_read_ptr         : char *
    +0x0010 _IO_read_end         : char *
    +0x0018 _IO_read_base        : char *
    +0x0020 _IO_write_base       : char *
    +0x0028 _IO_write_ptr        : char *
    +0x0030 _IO_write_end        : char *
    +0x0038 _IO_buf_base         : char *
    +0x0040 _IO_buf_end          : char *
    +0x0048 _IO_save_base        : char *
    +0x0050 _IO_backup_base      : char *
    +0x0058 _IO_save_end         : char *
    +0x0060 _markers             : _IO_marker *
    +0x0068 _chain               : _IO_FILE *
    +0x0070 _fileno              : int
    +0x0074 _flags2              : int
    +0x0078 _old_offset          : __off_t
    +0x0080 _cur_column          : short unsigned int
    +0x0082 _vtable_offset       : signed char
    +0x0083 _shortbuf            : char [1]
    +0x0088 _lock                : _IO_lock_t *
    +0x0090 _offset              : __off64_t
    +0x0098 _codecvt             : _IO_codecvt *
    +0x00a0 _wide_data           : _IO_wide_data *
    +0x00a8 _freeres_list        : _IO_FILE *
    +0x00b0 _freeres_buf         : void *
    +0x00b8 __pad5               : size_t
    +0x00c0 _mode                : int
    +0x00c4 _unused2             : char [20]

write_base = 0x1
write_ptr = 0x2        (write_base must be less than write_ptr, any value)
mode = 0                (mode must be <= 0)
overflow = target_function

vtable_ptr must be a pointer to a fake vtable entry that is populated with a function that we want to execute (which means that the fake overflow pointer must be present at the 4th quadword from the start of the address present in vtable).

fake_io_file = flags + p64(size)+ p64(fd) + p64(bk) + p64(write_base) + p64(write_ptr) +p64(0x0)*18 + p32(mode)+ p32(0x0)+ p64(0x0)+p64(overflow)+p64(vtable_ptr)

Note: the gap between _IO_write_end and _mode is (0xc0-0x30 = 144), which gives 18 on dividing by 8. So, we use p64(0x0)*8. fd can be anything, bk should be &_IO_list_all-16 to trigger an unsorted bin attack. size field must be set very carefully, according to the smallbin that stores the _chain field. When the _IO_flush_all_lockp function is triggered, the flow will follow the corrupt _IO_list_all to the main arena and then move on to the next stream via _chain pointer which would be present in a smallbin and then reach the chunk where the fake file stream resides, perform flushing checks and then call the overflow function.

To view the vtable,

p *_IO_list_all.vtable