FILE STRUCTURE === # Introduction When a new FILE structure is allocated and its pointer returned from fopen(). , glibc has actually allocated an internal structure called struct `_IO_FILE_plus`, which contains struct `_IO_FILE` and a pointer to struct `_IO_jump_t`, which in turn contains a list of pointers for all the functions attached to the FILE. Overwrites a ‘FILE’ pointer (stdin, stdout, stderr or any other file handler opened by `fopen()`) to point to own forged structure for gain control over execution flow. File structure contains `vtable`, which is a pointer to a table which contains functions which are called when the original ‘FILE’ pointer is used to perform different operations (such as fread, fwrite, ...). ## File stream - Stream buffering: fprint, open, stdout, stdin, stderr,… ## File structure - a file stream descriptor - Created by fopen [_IO_FILE](https://code.woboq.org/userspace/glibc/libio/bits/types/struct_FILE.h.html#49) or [other link](https://elixir.bootlin.com/glibc/glibc-2.35/source/libio/bits/types/struct_FILE.h#L49) - flags: ```c int _flags; /* High-order word is _IO_MAGIC; rest is flags. */ ``` - record the attribute of the file stream: 'r', 'r+', 'w', 'w+', 'a', 'a+'. - Stream buffer: ```c /* The following pointers correspond to the C++ streambuf protocol. */ char *_IO_read_ptr; /* Current read pointer */ char *_IO_read_end; /* End of get area. */ char *_IO_read_base; /* Start of putback+get area. */ char *_IO_write_base;/* Start of put area. */ char *_IO_write_ptr; /* Current put pointer. */ char *_IO_write_end; /* End of put area. */ char *_IO_buf_base; /* Start of reserve area. */ char *_IO_buf_end; /* End of reserve area. */ ``` - read buffer - write buffer - reserve buffer - _fileno ```c int _fileno; ``` - File descriptor - return by sys_open - [`_IO_FILE_plus`](https://elixir.bootlin.com/glibc/glibc-2.35/source/libio/libioP.h#L324) struct is the extended version of `_IO_FILE` struct, which is `_IO_FILE` + `vtable` (virtual table = array of pointers to the helper fucntions during executing the IO operation): ```c // in FILE.h typedef struct _IO_FILE FILE; // This contains a pointer to the function jump table used. struct _IO_FILE_plus { FILE file; const struct _IO_jump_t *vtable; }; ``` How the `vtable` will be filled during creating a new extended `File` struct. Depending on the method u use. Ex: [fopen()](https://elixir.bootlin.com/glibc/glibc-2.24/source/libio/iofopen.c#L60), the `vtable` initialized with the existed `vtable` called `_IO_file_jumps` (other `vtavle` like `_IO_str_jumps`). ```c _IO_FILE * __fopen_internal (const char *filename, const char *mode, int is32) { ... _IO_JUMPS (&new_f->fp) = &_IO_file_jumps; ... } ``` [_IO_jump_t](https://code.woboq.org/userspace/glibc/libio/libioP.h.html#_IO_jump_t)/ [glibc2.35](https://elixir.bootlin.com/glibc/glibc-2.35/source/libio/libioP.h#L293): - stdin/stdout/stderr, fopen also use it. - Extra Virtual function table. - The export symbol of this type in the libc file is [_IO_file_jumps](https://code.woboq.org/userspace/glibc/libio/fileops.c.html#_IO_file_jumps). - Every FILE associate with a `_chain` (linked list). The head of linked list is called `_IO_list_all` ```c struct _IO_FILE { ... struct _IO_FILE *_chain; ... } ``` # Exploitation of File Some good targets in FILE structure: ## vtable (Virtual function Table) `_lock` (usually need to construct it for exploitation) - prevent race condition in multithread ```c struct _IO_FILE { ... _IO_lock_t *_lock; } ``` We can find out the offset of `_lock` = 0x88 ![_lock offset](https://raw.githubusercontent.com/vietd0x/ctf-writeups/main/blogImgs/FileStructure/lock_offset.png) ## stdin/stdout/stderr This is also a FILE structure in glibc. We can overwrite the global variable in glibc to control the flow execution. (in glibc symbol table) ## FSOP (File stream oriented programing) Control the linked list of File stream: - _chain - _IO_list_all Powerful function: [_IO_flush_all_lockp](https://code.woboq.org/userspace/glibc/libio/genops.c.html#_IO_flush_all_lockp) ```c= for (fp = (FILE *) _IO_list_all; fp != NULL; fp = fp->_chain) { run_fp = fp; ... if (( (fp->_mode <= 0 && fp->_IO_write_ptr > fp->_IO_write_base) || (_IO_vtable_offset (fp) == 0 && fp->_mode > 0 && (fp->_wide_data->_IO_write_ptr > fp->_wide_data->_IO_write_base) ) ) && _IO_OVERFLOW (fp, EOF) == EOF) result = EOF; } ``` It will process all FILE in FILE linked list. We can construct the linked list to do oriented programing. Then line 9 will trigger virtual function. ## Vtable verification in File The vtable must be in libc `_IO_vtable` section. Otherwise, it will check if the vtable permits virtual function call. ```c IO_validate_vtable (const struct _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; uintptr_t ptr = (uintptr_t) vtable; uintptr_t offset = ptr - (uintptr_t) __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; } ``` ### Check the foreign vtables [_IO_vtable_check](https://code.woboq.org/userspace/glibc/libio/vtables.c.html#_IO_vtable_check) For compatibility: ```c /* Honor the compatibility flag. */ void (*flag) (void) = atomic_load_relaxed (&IO_accept_foreign_vtables); #ifdef PTR_DEMANGLE PTR_DEMANGLE (flag); // Demangle w/ pointer guard #endif if (flag == &_IO_vtable_check) return; ``` - Can't overwirte _IO_accept_foreign_vtable, because of the ponter guard. For shared lib: ```c Dl_info di; struct link_map *l; if (!rtld_active () || (_dl_addr (_IO_vtable_check, &di, &l, NULL) != 0 && l->l_ns != LM_ID_BASE)) return; ``` # References https://outflux.net/blog/archives/2011/12/22/abusing-the-file-structure/ https://www.slideshare.net/AngelBoy1/play-with-file-structure-yet-another-binary-exploit-technique - [file-struct-attack-p1](https://chovid99.github.io/posts/file-structure-attack-part-1/) ###### tags: `research` `pwn`