Post

File Struct Exploits P2

File Struct Exploits P2 is an explanation of how to use File Struct overwrites to gain Arbitrary Code Execution.

Introduction

In File Struct Exploits P1, I went over how FILE structure corruption can be used to achieve arbitrary reads and writes. Now, we take it a step further and look at code execution. This assumes a basic understanding of FILE structures and the primitives discussed in Part 1. All the images shown in this article are from the wonderful folks at pwn.college, who have excellent videos covering this topic.

vtables

In glibc, most FILE structures are actually embedded inside a larger structure called _IO_FILE_plus. This structure contains a FILE structure and a pointer to a vtable. A vtable here is simply a table of function pointers used to resolve various file helper functions at runtime, which makes _IO_FILE_plus compatible with dynamic polymorphism in C++. That is why the vtable exists. For exploitation, the important point is that it contains the addresses of multiple functions, and many file operations call one of those functions by using a fixed offset from the vtable pointer. If the vtable pointer can be overwritten, those indirect calls can be redirected to unintended functions.

vtable Safety

However, it is not as simple as overwriting the vtable pointer with any address in memory. The glibc maintainers added protections that restrict the main _IO_FILE_plus vtable pointer to the vtable region, which is the area of memory where the legitimate libio vtables are stored. This means the vtable pointer cannot simply be changed to an arbitrary address.

Hackers found a way around this by targeting _wide_data. The FILE structure contains a _wide_data field, which points to an _IO_wide_data structure used for wide-character stream handling. The wide-character functionality itself is not important here. What matters is that _IO_wide_data has its own vtable pointer, and that the wide data vtable is not protected by the same validation as the main _IO_FILE_plus vtable.

Chaining Execution

One of the most common ways to get glibc to dispatch through the _wide_data vtable is House of Apple 2. This is the main technique that will be discussed here because it is the most popular, although, other techniques exist including several found through symbolic execution with angr (https://github.com/Kyle-Kyle/angry-FSROP/tree/main).

There are several vtables in the valid vtable region including the _IO_wfile_jumps vtable, which is the vtable we will be targetting for House of Apple 2.

House of Apple 2 works by overwriting the main _IO_FILE_plus vtable pointer so that a function call that would normally go through the _IO_file_jumps vtable instead resolves to the _IO_wfile_overflow function, which lives in _IO_wfile_jumps vtable. This satisfies the main vtable check while still redirecting control flow.

Once _IO_wfile_overflow is called, execution continues into _IO_wdoallocbuf, which eventually performs another indirect call through the _IO_wide_data vtable at offset 0x68. The address at offset 0x68 can be overwritten to any location we want by overwriting the _IO_wide_data vtable pointer.

fwrite Example

Several functions call through the _IO_FILE_plus vtable pointer, including fwrite() and printf(). For demonstration purposes, consider fwrite() in glibc 2.31.

Looking at the disassembly, the important call is at fwrite+189:

call QWORD PTR [r15+0x38]

A few instructions earlier, at fwrite+141, r15 is loaded from:

mov r15, QWORD PTR [rbx+0xd8]

At that point, rbx points to the _IO_FILE_plus structure, and the value at offset 0xd8 is the main vtable pointer. This 0xd8 offset is always a good indicator to remember when looking for vtable calls because the _IO_FILE_plus structure has a total size of 0xe0, which means an offset of 0xd8 often references the main vtable pointer (the vtable pointer is the last 8 byte field in the _IO_FILE_plus structure).

So to recap, fwrite() loads the vtable pointer from [rbx+0xd8] at fwrite+141, then calls the function pointer stored at vtable + 0x38 at fwrite+189. To redirect execution to _IO_wfile_overflow, the overwritten vtable pointer must be set so that the entry at offset 0x38 resolves to _IO_wfile_overflow. Since that target still has to lie inside the legitimate vtable region, a libc leak (where the vtable region is located) is usually needed first.

&(_IO_wfile_overflow position in _IO_wfile_jumps vtable) - 0x38

Once _IO_wfile_overflow is reached, it eventually calls _IO_wdoallocbuf. That function follows the _wide_data pointer and then performs a call through the wide vtable at offset 0x68. To complete the exploit, the _wide_data pointer inside the FILE structure must be overwritten so that it points to a fake _IO_wide_data structure controlled by the attacker. Inside that fake structure, the wide vtable pointer (offset 0xe0 since the _IO_wide_data structure has a total size of 0xe8) must be set to:

target_address - 0x68

where target_address is the function, one-gadget, or ROP gadget the attacker wants to execute. When glibc adds 0x68 and performs the call, execution lands on that chosen address.

NOTE: A helpful fact when trying to get a shell is that the pointer to the _flags value is used as the first argument when calling the function at _wide_data vtable + 0x68. This means that system(/bin/sh) can be executed with one _IO_FILE_plus structure where _flags is set to /bin/sh and House of Apple 2 is performed to call system.

_lock

One other field that matters is _lock. This pointer is used to coordinate access to the stream during operations such as fwrite() in cases of multi-threaded access. If _lock points to invalid memory or a non-null value, the exploit thread will not be able to claim access to the stream, and the exploit will fail.

To avoid that, _lock should be set to writable memory containing a null value. This tells the program that the file is currently unlocked, and glibc can then safely write to that memory when it marks the stream as locked during fwrite(). This did not come up in the arbitrary read and write cases because all the values being modified were above the _lock pointer in the structure.

_IO_list_all

_IO_flush_all_lockp is a function that is used to flush and close all active file buffers. It can be triggered in many circumstances but the most common is upon a clean program exit. It uses the _IO_list_all pointer to determine all active FILE structures that need to be flushed and closed. The _IO_list_all pointer points to the _IO_2_1_stderr FILE structure, which points to the _IO_2_1_stdout FILE structure through its _chain field, which then points to the _IO_2_1_stdin FILE structure through its _chain field. This linked chain continues for all open files with an associated FILE structure. With an _IO_list_all overwrite, we can point it to a fake _IO_FILE_plus structure where we can ensure the fields are manufactured for House of Apple 2. _IO_flush_all_lockp calls a vtable entry at the same offset as fwrite() does on every _IO_FILE_plus structure meaning several functions or gadgets could potentially be executed. Most often only 1 call is needed to obtain a shell, meaning the structure _IO_list_all points to does not need to have a _chain entry. It just needs to have /bin/sh as _flags and set system as the target of House of Apple 2.

Overlapping Structures

Making fake structures left and right can quickly take out writable space. If theres not enough space to forge full fake structures (ie. _IO_wide_data), just remember only the fields which actively influence exploitation need to be set. For example, a _wide_data pointer can point to the overall _IO_FILE_plus structure which means its vtable field will be the 8 bytes after the end of the _IO_FILE_plus structure. The wide data vtable pointer itself could be set to the _IO_FILE_plus structure, and its offset at 0x68 would be whatever is in the _chain field (offset 0x68 from the beginning of _IO_FILE_plus). You can be very creative when overlapping structures, and this is often neccesary when you have limited writable memory. As long as you don’t modify a field that matters for exploitation of any of the overlapping structures, everything should work like normal.

Conclusion

This article covers the fundamentals of File Struct Exploitation to gain Arbitrary Code Execution. Many other methods besides House of Apple 2 can be explored by reading writeups and doing CTF challenges. To start practicing the tactics learned from this article, I would recommend trying pwn.college’s File Struct Exploits module: Levels 7-21. Some of the levels focus on Arbitrary Reading and Writing, but most of them focus on code execution.

https://pwn.college/software-exploitation/file-struct-exploits/

This post is licensed under CC BY 4.0 by the author.