Code Execution by Faking IO_FILE->vtable in GLIBC 2.36 [0x0]
Since vtables were added to a specific read-only segment in GLIBC and IO_validate_vtable()
will verify the vtable
of IO_FILE
structure, exploitation of IO_FILE->vtable
is becoming much more complex than before. Although we have some great exploitation chains such as House of banana or House of apple, some of them require a series of complex structure construction. So I would like to put forward another way to exploit IO_FILE->vtable
here which can enable the attacker to gain code execution by faking vtable
directly.
vtable validation
When calling the method in vtable
, GLIBC will call IO_validate_vtable
first
when vtable
isn’t located in __libc_IO_vtables
, it will then invoke IO_vtable_check()
to do further validation.
vtable
is actually kind of a implementation of polymorphism, so there may be a chance to modify the vtable
pointer to perform custom IO operations. In IO_vtable_check()
We can notice that it does give us a chance to use another foreign vtable. Now the question becomes how can we bypass the checking here.
Let’s take a look at
First, !rtlf_active()
seems a great choice. But the value it returns is based on a value in read-only memory. Then what about _dl_addr (_IO_vtable_check, &di, &l, NULL) != 0
? This line is performed to check the ld namespace of a given address. To bypass it we are required to construct a really complex linker-related structure. So let’s check the very first flag
checking.
_IO_check_vtable
will load IO_accept_foreign_vtables
and compare it against with _IO_vtable_check
and luckily IO_accept_foreign_vtables
is writable! So the problem is how can we defeat the PTR_MANGLE
stuff.
Defeat PTR_MAGLE
Pointer guard is used to proctect some critical pointers from being forged. We can use the following pseudocode to compute a PTR_MANGLE
d pointer value.
to demangle we can just
Now the problem becomes how can we leak or overwrite the pointer_guard.
Pointer guard is located in TLS structure. Although we do have method to modify the value, but why not ROP directly when we have this kind of primitive. So let’s take a look at how to leak pointer guard.
We have two ways to do so. Directly leak them from TLS or leak a mangled pointer and then calculate the pointer_guard xor key.
Leaking from TLS requires us to leak TLS address first so we will focu on the second method here.
Fortunately we do have a mangled pointer! Consider the following code.
When calling _cxa_atexit
, rdi==_dl_fini
__GI___cxa_atexit (
QWORD var_0 = 0x007ffff7fceaa0 → <_dl_fini+0> push rbp,
QWORD var_1 = 0x00000000000000,
QWORD var_2 = 0x00000000000000
)
_cxa_atexit
will then register the function to _exit_function
after mangling!
So leaking _dl_fini
in _exit_function
means leaking xor key now. Then override IO_accepte_foreign_vtable
with a mangled _IO_vtable_check
pointer, we are able to use any vtable to completely take over the control flow.
Here is an example.
Run the example with a simple script
Fatal error: glibc detected an invalid stdio handle
Fatal error: glibc detected an invalid stdio handle
Fatal error: glibc detected an invalid stdio handle
pwn!!!
pwn after 234 attempts
Conclusion
The offset between libc and ld is not a constant value so we need to bruteforce the _dl_fini
here since it’s in ld.
With a ld leaking the exploitation can be more reliable. And I will show a much more reliable way to exploit in next chapter.