_IO_FILE_PLUS struct is a powerful target to attack after getting a single
arb write. It has a pointer to a vtable that contains different functions to
perform read/write etc.
Unfortunately, >2.23 there is a check to make sure that the vtable pointer is
pointing to a valid vtable in the __libc_IO_vtables section:
/* Perform vtable pointer validation. If validation fails, terminate the process. */ staticinlineconststruct _IO_jump_t * IO_validate_vtable(conststruct _IO_jump_t *vtable) { /* Fast path: The vtable pointer is within the __libc_IO_vtables section. */ uintptr_t section_length = __stop___libc_IO_vtables - __start___libc_IO_vtables; constchar *ptr = (constchar *) vtable; uintptr_t offset = ptr - __start___libc_IO_vtables; if (__glibc_unlikely (offset >= section_length)) /* The vtable pointer is not in the expected section. Use the slow path, which will terminate the process if necessary. */ _IO_vtable_check (); return vtable; }
/* In case this libc copy is in a non-default namespace, we always need to accept foreign vtables because there is always a possibility that FILE * objects are passed across the linking boundary. */ { Dl_info di; structlink_map *l; if (_dl_open_hook != NULL || (_dl_addr (_IO_vtable_check, &di, &l, NULL) != 0 && l->l_ns != LM_ID_BASE)) return; }
#else/* !SHARED */ /* We cannot perform vtable validation in the static dlopen case because FILE * handles might be passed back and forth across the boundary. Therefore, we disable checking in this case. */ if (__dlopen != NULL) return; #endif
__libc_fatal ("Fatal error: glibc detected an invalid stdio handle\n"); }
With this check the vtable pointer must be one of the vtables defined
here.
This actually makes things quite fun - how can we exploit these vtables and
other FILE features to get RCE?
The next few posts will be my notes while exploring some previously discovered houses.