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.
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 :
FILE object (stdout)_flags, tampons, fds …vtable pointerinscriptible — l'attaquant écrase ceci_IO_*_jumps tableune vtable de pointeurs de fonction__xsputn / __finishl'entrée s'exécute avec des arguments contrôlésfwrite 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éfense | Effet 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 pointeurs | Certains pointeurs internes de FILE sont brouillés, nécessitant un secret pour les forger |
| ASLR + exigence de fuite | Les 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
FILEdispatche 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.