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); } }
- It use
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 themmp_.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 is0x20f31
, we should overwrite the size with0xf31
to bypass the assertion and then allocate a large chunk to trigger thesysmalloc
and_int_free
. Finally, we could get an unsorted bin chunk on the heap.
- We want to use the
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 theSee 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
andbk_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 object
source.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 themain_arena
, we decide to use the chain pointer which points to next_IO_FILE
object. It would be a small bin chunk in themain_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 itFile Stream Oriented Programming
.
Exploit script
:houseoforange.pyScreenshot
flag
:
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.