
Интерактивный разбор техники House of Apple 2 FSOP в GDB на glibc 2.43 с воспроизводимой песочницей, охватывающей обход vtable, перехват стека и ROP.
Этот репозиторий представляет собой самодостаточную площадку для экспериментов, вдохновлённую модулем File Struct Exploitation от pwn.college. Он не вводит новую вариацию House of Apple 2, а лишь отвечает на моё любопытство о том, как эта техника работает на последних версиях glibc и остаётся ли она жизнеспособным путём эксплуатации. Документ содержит интерактивное пошаговое руководство в GDB, которое читатели могут проходить параллельно с песочницей, чтобы развить более интуитивное понимание этого примитива. Все эксперименты используют glibc 2.43 в том виде, в каком она поставлялась в Ubuntu 26.04 и Fedora 44 на момент написания.
File Stream Oriented Programming (FSOP). Речь идёт о манипулировании структурами файловых потоков glibc для перехвата потока управления. Один из способов сделать это — повредить механизм диспетчеризации vtable в _IO_FILE_plus. Современный glibc проверяет эту vtable, поэтому очевидный подход с заменой её на произвольный адрес не работает.
House of Apple 2, изначально представленная Roderick, обходит это ограничение, используя действительную vtable _IO_FILE_plus для достижения механизма широкосимвольных потоков, где вторичная vtable вызывается напрямую без проверки диапазона. Это даёт примитив arbitrary call, который мы можем развить до переворота стека (stack pivot) и ROP-цепочки.
Это исследование предполагает, что мы можем перезаписать структуру FILE и что у нас есть как утечка кучи, так и утечка libc. Целевой бинарник уже предоставляет это.
Песочница работает на Ubuntu 26.04 LTS, предоставляя нам современную среду для исследования этой техники.
Образ включает GDB, pwndbg, pwntools, ropper и tmux. Он также содержит целевой бинарник с интерактивным меню для вызова операций с файловыми потоками, таких как fopen, fread, fwrite и fclose. Это даёт нам удобный способ манипулировать потоками во время отладки и проверки идей.
Соберите и запустите песочницу с помощью:``` ./build.sh ./run.sh
## Исследование
Начнём с изучения структур `_IO_FILE` и `_IO_FILE_plus`:```c
pwndbg> ptype struct _IO_FILE
type = struct _IO_FILE {
int _flags;
char *_IO_read_ptr;
char *_IO_read_end;
char *_IO_read_base;
char *_IO_write_base;
char *_IO_write_ptr;
char *_IO_write_end;
char *_IO_buf_base;
char *_IO_buf_end;
char *_IO_save_base;
char *_IO_backup_base;
char *_IO_save_end;
struct _IO_marker *_markers;
struct _IO_FILE *_chain;
int _fileno;
int _flags2 : 24;
char _short_backupbuf[1];
__off_t _old_offset;
unsigned short _cur_column;
signed char _vtable_offset;
char _shortbuf[1];
_IO_lock_t *_lock;
__off64_t _offset;
struct _IO_codecvt *_codecvt;
struct _IO_wide_data *_wide_data;
struct _IO_FILE *_freeres_list;
void *_freeres_buf;
struct _IO_FILE **_prevchain;
int _mode;
int _unused3;
__uint64_t _total_written;
char _unused2[8];
}
pwndbg> ptype struct _IO_FILE_plus
type = struct _IO_FILE_plus {
FILE file;
const struct _IO_jump_t *vtable;
}
С практической точки зрения, _IO_FILE_plus — это _IO_FILE с указателем на vtable. Это сразу выглядит интересно: если мы сможем управлять этим указателем, мы, возможно, сможем перенаправить косвенный вызов и перехватить поток управления.
Чтобы изучить vtable, давайте рассмотрим указатель FILE, возвращаемый fopen.```c
pwndbg> p *(struct _IO_FILE_plus *)0x37ecf010
$4 = {
file = {
_flags = 0xfbad2480,
_IO_read_ptr = 0x0,
_IO_read_end = 0x0,
_IO_read_base = 0x0,
_IO_write_base = 0x0,
_IO_write_ptr = 0x0,
_IO_write_end = 0x0,
_IO_buf_base = 0x0,
_IO_buf_end = 0x0,
_IO_save_base = 0x0,
_IO_backup_base = 0x0,
_IO_save_end = 0x0,
_markers = 0x0,
_chain = 0x7f58a7f4b4a0 <IO_2_1_stderr>,
_fileno = 0x3,
_flags2 = 0x0,
_short_backupbuf = "",
_old_offset = 0x0,
_cur_column = 0x0,
_vtable_offset = 0x0,
_shortbuf = "",
_lock = 0x37ecf0f0,
_offset = 0xffffffffffffffff,
_codecvt = 0x0,
_wide_data = 0x37ecf100,
_freeres_list = 0x0,
_freeres_buf = 0x0,
_prevchain = 0x7f58a7f4b480 <_IO_list_all>,
_mode = 0x0,
_unused3 = 0x0,
_total_written = 0x0,
_unused2 = "\000\000\000\000\000\000\000"
},
vtable = 0x7f58a7f49030 <_IO_file_jumps>
}
Указатель указывает на таблицу `_IO_file_jumps`.

Это набор из 21 указателя на функции. Операции с файловыми потоками диспетчеризуются через различные записи в зависимости от пути выполнения.
### Прослеживание пути `fwrite`
В этом исследовании я сосредоточусь на пути `fwrite`. После установки точек останова на каждую функцию и вызова `fwrite` первая точка останова, которую мы достигаем, — это `_IO_file_xsputn`.

Вызов происходит по адресу `fwrite+216`. Это соответствует [исходному коду glibc](https://elixir.bootlin.com/glibc/glibc-2.43/source/libio/iofwrite.c#L44): `_IO_sputn` — это макрос, который выполняет диспетчеризацию через vtable, разрешаясь в `_IO_file_xsputn` для этого потока.```asm
0x00007fd5181d362a <+202>: mov rdx,rcx
0x00007fd5181d362d <+205>: mov rdi,rbx
0x00007fd5181d3630 <+208>: mov QWORD PTR [rbp-0x30],r8
0x00007fd5181d3634 <+212>: mov QWORD PTR [rbp-0x28],rcx
0x00007fd5181d3638 <+216>: call QWORD PTR [rax+0x38]
Для первой попытки перезапишем указатель vtable значением desired_func - 0x38 и установим точку останова на fwrite+216.```c
pwndbg> p &win
$3 = (<text variable, no debug info> *) 0x4019e1
pwndbg> p/x &win - 0x38
$4 = 0x4019a9
pwndbg> set ((struct _IO_FILE_plus *)0x5334010)->vtable = (void *)0x4019a9
pwndbg> b *fwrite+216
Breakpoint 4 at 0x7fd5181d3638: file ./libio/libioP.h, line 1042.

Выполнение прерывается до достижения точки останова. Ошибка указывает на то, что glibc проверяет указатель vtable перед выполнением косвенного вызова. Давайте изучим стек вызовов и посмотрим, где это происходит.
`fwrite` достигает `_IO_vtable_check`, который отклоняет поддельный указатель vtable.

Реализация содержит механизм для принятия сторонних vtable, но он не находится под нашим контролем. Соответствующий код доступен в [`vtables.c`](https://elixir.bootlin.com/glibc/glibc-2.43/source/libio/vtables.c#L504).
### Понимание проверки vtable