In glibc, jumps from vtables are made via these macros:
The macros for WXXX are not protected by IO_validate_vtable, so we can use
this to call arbitrary function. These macros are used in functions of
_IO_wfile_jumps vtable:
The struct of f->_wide_data is
It’s quite similar to FILE, but vtable is at 0xe0.
_IO_wfile_overflow
Analysis
When exit is called, the FILE cleanup call stack is fcloseall -> _IO_cleanup -> _IO_flush_all_lockp.
So for each FILE in _IO_list_all, its vtable->__overflow is called when the below requirements are satisfied:
fp->_mode == 0
fp->_IO_write_ptr > fp->_IO_write_base
We can set our victim FILE (stderr) to point to this vtable. On exit, it will call the
overflow function, _IO_wfile_overflow, which is defined as:
The _IO_wfile_overflow function calls _IO_wdoallocbuf, which then calls
__doallocate of the _wide_vtable, passing the FILE struct as the first
argument. As mentioned, the _wide_vtable performs no checks so we can point
this to system.
Then our desired function goes into __doallocate (_wide_vtable+0x68), and rdi goes into flags.
Payload
With the above conditions satisfied, our FILE struct will look like this:
There are a few more gadget chains using _IO_wfile_underflow_mmap,
_IO_wdefault_xgetn, _IO_wfile_underflow, _IO_wdo_write and
_IO_wfile_sync which I will put here once I analyze them.