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).
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
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 _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  +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  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
_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