Aller au contenu

Attaques FSOP : détourner un objet FILE

Les hooks partis, l'objet FILE devient la cible : un appel stdio passe par un pointeur de vtable inscriptible — corrompez-le et le prochain fwrite tombe.

Publié le 5 min de lecture

Ceci est le onzième tutoriel de l'espace Techniques d'exploitation, et la clé de voûte de son arc actuel. Les guides surcharge de la GOT et tas concluaient une primitive d'écriture par l'exécution de code en visant la GOT ou un hook d'allocateur. Sur les glibc modernes, les hooks ont disparu — les attaquants se sont donc tournés vers un objet toujours présent et toujours sollicité : le FILE.

Fortement dépendant de la version, comme tout travail glibc de stade tardif. Votre propre binaire, labo jetable. Voir les règles du labo.

L'objet FILE dispatche via une vtable

Chaque flux stdio — stdin, stdout, stderr, et tout ce qui vient de fopen — est une struct _IO_FILE suivie d'un pointeur vers une vtable _IO_jump_t de pointeurs de fonction. Les appels de bibliothèque n'appellent pas du code fixe ; ils sont dispatchés à travers cette vtable :

Un FILE dispatche les appels stdio via son pointeur de vtable
FILE object (stdout)_flags, tampons, fds …
contient
vtable pointerinscriptible — l'attaquant écrase ceci
pointe vers
_IO_*_jumps tableune vtable de pointeurs de fonction
fwrite / fclose appellent
__xsputn / __finishl'entrée s'exécute avec des arguments contrôlés
datawritable memorycode

fwrite finit par appeler vtable->__xsputn, fclose appelle vtable->__finish, et ainsi de suite. Contrôlez ce vers quoi pointe le pointeur de vtable — ou ce que l'objet contient quand une entrée légitime s'exécute — et le prochain appel stdio transfère le contrôle.

La technique classique (modèle pédagogique)

Sur les vieilles glibc (avant la validation de 2.24), la FSOP était directe : utiliser une écriture arbitraire pour remplacer le pointeur de vtable d'un FILE par un pointeur vers une fausse vtable que vous avez construite en mémoire inscriptible, dont l'emplacement __xsputn/__overflow contient l'adresse de system (ou d'un one-gadget). La prochaine sortie via ce flux l'appelle.

/* vuln.c — a write-what-where, then output through stdout. */
#include <stdio.h>
int main(void){
    setvbuf(stdout, NULL, _IONBF, 0);
    unsigned long addr, val; 
    scanf("%lx %lx", &addr, &val);
    *(unsigned long*)addr = val;   /* arbitrary write primitive */
    printf("done\n");              /* stdio call dispatches through the vtable */
    return 0;
}
# fsop_classic.py — conceptual, for a glibc WITHOUT vtable validation
from pwn import *
context.binary = elf = ELF("./vuln")
libc = ELF("./libc.so.6")     # the exact target libc

# Suppose a leak gave us libc.address (see the info-leak guide).
# Build a fake vtable whose __xsputn entry is system, and point
# stdout's vtable pointer at it; craft the object so system("...") runs.
# The precise field offsets depend on the glibc version.

Ici, la mécanique importe plus que les octets exacts : un appel stdio est un appel indirect à travers un pointeur en mémoire inscriptible, et n'importe quelle écriture arbitraire peut le détourner.

Pourquoi les glibc modernes vous compliquent la tâche

glibc 2.24 a ajouté _IO_vtable_check : avant de dispatcher, elle vérifie que le pointeur de vtable se situe à l'intérieur du tableau en lecture seule __libc_IO_vtables, et avorte avec Fatal error: glibc detected an invalid stdio handle sinon. Une fausse vtable dans votre propre tampon échoue immédiatement à cette vérification.

La FSOP moderne travaille dans les règles :

  • Réutiliser une vtable légitime. Pointez l'objet vers une vraie vtable autorisée telle que _IO_wfile_jumps, puis fabriquez les champs du FILE de sorte qu'une entrée valide (p. ex. _IO_wfile_overflow) réalise l'appel voulu. C'est la technique House of Apple 2 et ses proches.
  • Cibler les chemins de caractères larges, qui ont plus d'appels indirects exploitables passant la validation.
  • Enchaîner depuis une primitive de tas : un empoisonnement de tcache qui renvoie un chunk chevauchant un objet FILE, puis corrompre ses champs.

Ce sont parmi les techniques les plus complexes du pwn CTF actuel, et chaque étape est liée à la disposition de structure d'une version glibc précise. Le résumé honnête : la FSOP est bien vivante, mais c'est de l'artisanat, pas un one-liner.

Réactivez les mitigations

DéfenseEffet sur la FSOP
Validation de vtable (glibc 2.24+)Rejette les fausses vtables hors de __libc_IO_vtables
Suppression des hooks (glibc 2.34+)Force les attaquants ici au départ — mais le chemin FILE est vérifié
Brouillage de pointeursCertains pointeurs internes de FILE sont brouillés, nécessitant un secret pour les forger
ASLR + exigence de fuiteLes adresses libc dans le faux objet nécessitent une fuite
AddressSanitizer (CI)Attrape le débordement/UAF en amont qui rend l'écriture possible

Le schéma de toute cette série tient une fois de plus : la technique exploite un dispatch légitime, donc les défenses durables sont (1) ne jamais obtenir l'écriture arbitraire — corrigez le débordement, l'UAF, la chaîne de format — et (2) les vérifications d'intégrité à l'exécution que la glibc ne cesse d'ajouter.

Ce que cela enseigne à un défenseur

  • Le dispatch indirect est une surface d'attaque. Tout objet contenant des pointeurs de fonction en mémoire inscriptible — un FILE, une vtable C++, une table de rappels — transforme une écriture arbitraire en flux de contrôle. Les bases de code C++ devraient noter le parallèle : un vptr d'objet corrompu, c'est la même idée.
  • Maintenir la glibc à jour est une vraie mitigation. La validation de vtable, la suppression des hooks et le brouillage de pointeurs ont chacun clos une technique. « On est sur une vieille glibc pour la compatibilité » est une décision de sécurité, pas seulement d'exploitation.
  • Tout remonte à une seule écriture. FSOP, surcharge de la GOT et hooks sont des finals interchangeables pour le même bug en amont. Tuez le bug en CI avec les sanitizers et le fuzzing et aucun des finals n'a son tour.

Points clés

  • Un objet FILE dispatche les appels stdio via un pointeur de vtable en mémoire inscriptible ; le corrompre (ou corrompre l'objet) redirige le prochain appel.
  • L'attaque classique substituait une fausse vtable ; la validation de vtable de glibc 2.24 a mis fin à cette forme directe.
  • La FSOP moderne (House of Apple et consorts) réutilise des vtables légitimes et fabrique l'objet pour passer la validation — puissante mais étroitement spécifique à la version.
  • Chaque final de cette série dépense une écriture arbitraire ; le correctif durable est d'empêcher cette écriture, tout en restant sur une glibc à jour et vérifiée.

Cela clôt l'arc actuel des Techniques d'exploitation. Lisez la vue d'ensemble de l'espace pour le parcours complet — de ret2win au tas et au FILE — et le parcours d'apprentissage CTF pour savoir où le pratiquer légalement.

Guides associés

0x7000 · Techniques d'exploitation

Détourner la GOT : rediriger un appel à la libc

La liaison paresseuse laisse la GOT inscriptible : une écriture arbitraire sur une entrée transforme puts() en system() — jusqu'à ce que Full RELRO la fige.

0x7000 · Techniques d'exploitation

Exploitation du tas : tcache et use-after-free

Les métadonnées du tas sont la primitive d'écriture. Un use-after-free empoisonne le tcache de la glibc pour allouer un chunk à une adresse choisie.