This post will walk through the libc’s IO structures and their exploitation paths. It will also cover the House of Paper FSOP technique.
To begin with, it is necessary to get familiar with the FILE (_IO_FILE) structure:
The _IO_read_*, _IO_write_* and _IO_buf_* members of the structure are used for buffering purposes. Angel Boy has a great presentation explaining that attack surface.
The default FILE variables (stderr, stdin, stdout) and also other variables initialized using fopen() are in reality _IO_FILE_plus structures (or _IO_FILE_complete_plus in newer versions), which simply concatenate a virtual table pointer at the end of the _IO_FILE structure:
The member vtable is a pointer to a virtual table ( a _IO_jump_t structure-like struct called _IO_file_jumps), which is a struct that contains multiple pointers to functions used during the IO operations. Here is what _IO_jump_t looks like:
The member _wide_data is a pointer to a structure similar to _IO_FILE used for wider character streams, and looks like this:
As you can observe, it has the member _wide_vtable at the end of the structure, which is also a pointer to another virtual table. The difference between this virtual table pointer and the one on _IO_FILE_plus is that the macros used to execute the functions of this virtual table do not check whether it is a valid virtual table pointer or not (they don’t execute _IO_validate_vtable()). Here is a side by side comparison of the macros used:
The function _IO_validate_vtable() was added on glibc-2.24 and does a couple of checks on the virtual table pointer. First it checks whether it lands inside the offset where the default vtables exist on the libc:
If not fulfilled, it will run _IO_vtable_check() where it will compare the value of &IO_accept_foreign_vtables to that of &_IO_vtable_check, proceed if equals, else abort.
The following is a diagram representing these structures and the relationship between them:
The exploitation aproach is to leverage the fact that the macros that execute the functions of the _wide_data virtual table are not checked through _IO_validate_vtable(). The idea is to first make fp->vtable point to a different valid virtual table (one that lies inside the valid offset), we need that during the execution of a function of the new virtual table tries to execute any function inside fp->_wide_data->_wide_vtable (macros with a W in their name i.e. _IO_WOVERFLOW(FP, CH)). Leveraging the fact that the validity of _wide_vtable is not checked, we can make it point to a controlled area, where we will finally make it execute system or any arbitrary function instead of the intended one.
This is the same aproach followed in all three house’s of apple discovered by Roderick and multiple other houses.
House of paper
The different houses are just different paths that follow the idea explained above. In this case, I will be leveraging _IO_wfile_seekoff to execute _IO_switch_to_wget_mode which ultimately executes the macro _IO_WOVERFLOW.
As I said before, some functions such as exit or fflush execute the functions from the virtual table _IO_FILE_plus->vtable points to. These are called via macros, but they are ultimately just offsets. For instance when fflush tries to call the __sync function the code will run _IO_SYNC(fp) (fp is the FILE pointer) which ultimately will do: *(fp->vtable->__sync)(fp). So it will execute the function at offset fp->vtable + 0x58:
In order to call _IO_wfile_seekoff we need to take into account that it is at offset 0x40 in its virtual table (_IO_wfile_jumps code):
Therefore we need to make vtable point to _IO_wfile_jumps - 0x18 so that when it adds 0x58 (offset to _IO_wfile_sync) and executes the function it lands on our desired _IO_wfile_seekoff instead:
Here we have two conditions to meet in order to continue to our desired function (_IO_switch_to_wget_mode):
When the conditions are met we will enter _IO_switch_to_wget_mode. Here we will need to fulfill some other conditions in oder to reach the _IO_WOVERFLOW macro, which executes from the wide virtual table (fp->_wide_data->_wide_vtable) and results in the following being executed: *(fp->_wide_data->_wide_vtable->_IO_wfile_overflow) (fp). As you can see, the address of the FILE pointer is passed as first argument, which means we will need to write /bin/sh\0 in the fp->_flags position (at offset 0x0).
Also, bear in mind that _IO_wfile_overflow is at offset 0x18 from the base of the virtual table.
Look how lucky we are, that the conditions of this if are the same as before!
Conditions:
Previous conditions needed to call a function of fp->vtable
The following is a diagram of how the different structures will result:
Here is an example program to further show the exploitation path:
Thanks
Huge thanks specially to niftic’s blog, as the exploitation path was previously discovered and documented by him. I just tried to make a blog explaining the FSOP technique as I would have liked to find it when learning about the technique.
I also called it House of Paper for fun, as I do not try by any means to get credit of the discovery of this path, all credits go to Niftic.