ASLR DEP NX canaries et CFI contre les exploits mémoire
- Accueil
- Fondations
- Architecture machine
- ASLR DEP NX canaries et CFI contre les exploits mémoire
ASLR DEP NX canaries et CFI contre les exploits mémoire
Section intitulée « ASLR DEP NX canaries et CFI contre les exploits mémoire »Les protections mémoire ne corrigent pas un dépassement de tampon, une confusion de type ou un accès après libération : elles rendent son exploitation moins déterministe, plus coûteuse ou directement impossible dans certains chemins. La stratégie défendable est donc l’empilement : corriger le défaut, compiler avec les mitigations, supprimer les primitives de fuite et surveiller les violations.
Un bâtiment sensible ne repose pas sur une seule serrure : les pièces changent de place, certains objets ne peuvent pas servir d’outil, des scellés révèlent une ouverture et les couloirs autorisent seulement certains passages. Chaque obstacle force l’intrus à trouver une nouvelle faille avant d’atteindre ce qu’il cherche.
→ Lire l’explication complète de la marmotte
ASLR : randomiser ne veut pas dire cacher
Section intitulée « ASLR : randomiser ne veut pas dire cacher »L’ASLR (Address Space Layout Randomization, randomisation de la disposition de l’espace d’adressage) place à des adresses variables la pile, le tas, les bibliothèques partagées, les zones de mappage et, lorsque le binaire le permet, son image principale. Un exploit qui suppose que system() ou une chaîne de caractères se trouve toujours à la même adresse échoue au lancement suivant. C’est une protection de probabiliste, pas un coffre-fort : son efficacité dépend de l’entropie réellement disponible et de la capacité adverse à relancer le processus.
Sous Linux, l’ASLR devient réellement intéressant pour le code applicatif avec un exécutable indépendant de sa position, ou PIE (Position Independent Executable). Un binaire compilé sans PIE conserve souvent une adresse de base fixe, même si les bibliothèques sont randomisées. Laisser un exécutable non-PIE dans une chaîne de compilation moderne revient à installer un antivol sur une roue, puis à laisser les clés sur le capot parce que le parking est éclairé.
L’entropie n’est pas uniforme. En espace utilisateur 32 bits, le nombre de positions possibles est faible, parfois assez faible pour permettre une attaque par force brute contre un service qui redémarre automatiquement. La situation est meilleure en 64 bits, mais l’immensité théorique de l’espace virtuel ne devient pas magiquement de l’entropie exploitable : alignement des pages, compatibilité applicative, noyau et chargeur imposent des contraintes. Un démon qui accepte des milliers de tentatives sans limitation transforme toute randomisation imparfaite en loterie dont l’attaquant achète les tickets.
Le contournement dominant est la fuite d’adresse. Une lecture hors limites, un message d’erreur contenant un pointeur, une trace accessible, un format %p conservé en production ou une sérialisation de structure native peut révéler une adresse de bibliothèque. À partir d’une adresse connue et de l’offset d’un symbole dans la version exacte de la bibliothèque, l’attaquant retrouve sa base et reconstruit le reste. ASLR ne résiste pas à une primitive de lecture fiable. C’est le point qui sépare une mitigation utile d’un slogan de slide.
Il faut donc traiter les fuites d’information avec la même priorité que les corruptions mémoire. Les règles de journalisation doivent interdire l’exposition de pointeurs aux clients. Les crash dumps, traces de débogage et interfaces de diagnostic doivent être accessibles à un périmètre d’administration distinct. Les mécanismes décrits dans l’analyse des failles restent nécessaires : ASLR augmente le coût d’exploitation, il ne réduit pas la gravité intrinsèque d’une lecture arbitraire.
DEP/NX et canaries : bloquer la charge, détecter l’écrasement
Section intitulée « DEP/NX et canaries : bloquer la charge, détecter l’écrasement »DEP (Data Execution Prevention, prévention de l’exécution des données), souvent matérialisée par le bit NX (No eXecute, non exécutable), marque des pages mémoire comme non exécutables. Une pile ou un tas contenant une charge injectée reste lisible et inscriptible, mais le processeur refuse de l’interpréter comme des instructions. Le système arrête alors le processus, généralement avec une faute de protection mémoire.
La propriété à viser est l’absence de mémoire simultanément inscriptible et exécutable dans le processus applicatif. Certains moteurs de compilation à la volée demandent légitimement des transitions de droits, mais le chemin doit rester étroit : écrire dans une page, la passer en lecture-exécution, puis ne plus la rendre inscriptible. Une page durablement RWX (Readable, Writable, eXecutable, lisible, inscriptible et exécutable) est une dette technique avec un gyrophare rouge. Elle existe souvent pour une bonne raison, jusqu’au jour où cette raison devient une chaîne de compromission.
NX a déplacé les attaques vers le réemploi de code existant. Le retour vers la bibliothèque C consiste à détourner une adresse de retour vers une fonction déjà chargée, telle que system ou une primitive de changement de droits. ROP (Return-Oriented Programming, programmation orientée retours) généralise l’idée : une pile corrompue enchaîne de courts fragments d’instructions, nommés gadgets, qui se terminent par un retour. Aucun nouveau code n’est exécuté, donc NX ne voit qu’un flux d’instructions parfaitement autorisées. DEP élimine l’injection directe, pas l’exécution détournée.
Les canaries de pile complètent ce dispositif. Le compilateur place une valeur secrète entre les variables locales vulnérables et les données de contrôle, notamment l’adresse de retour. Avant de quitter la fonction, le code compare cette valeur à l’originale. Un écrasement linéaire de pile modifie généralement le canari et provoque l’arrêt contrôlé du processus.
Le mot déterminant est « généralement ». Un canari ne protège ni le tas, ni les pointeurs de fonction hors pile, ni une écriture ciblée qui évite sa position. Sa valeur doit être imprévisible et ne jamais quitter le processus. Une fuite du canari annule son rôle pour l’exécution concernée. La protection détecte tardivement, juste avant le retour de fonction, ce qui est précisément son objectif : empêcher la prise de contrôle, pas préserver un état métier déjà corrompu.
CFI : restreindre les branches légitimes
Section intitulée « CFI : restreindre les branches légitimes »La CFI (Control Flow Integrity, intégrité du flux de contrôle) vérifie que les transferts indirects de contrôle suivent un graphe autorisé. Elle concerne notamment les appels via pointeur de fonction, les retours de fonction et les sauts indirects. Une adresse exécutable n’est plus suffisante : la destination doit être compatible avec le site d’appel ou de retour.
Cette différence est décisive contre ROP. Une chaîne ROP dépend de retours vers des gadgets choisis pour leurs effets de bord, pas pour leur relation légitime avec l’appelant. Une CFI suffisamment précise casse cette chaîne. Les implémentations matérielles, telles que CET (Control-flow Enforcement Technology, technologie de contrôle du flux) sur certaines plateformes x86, peuvent ajouter une pile d’ombre protégée pour valider les retours. Les compilateurs proposent aussi des contrôles logiciels sur les appels indirects.
CFI n’est pas une propriété binaire. Une politique grossière qui autorise toutes les fonctions de même signature laisse un ensemble de destinations parfois très large. Une politique fine coûte davantage en compatibilité, en instrumentation et parfois en performance, mais réduit bien mieux la surface de réemploi. Le compromis central est là : accepter une contrainte de construction et de test pour retirer aux corruptions mémoire le contrôle du chemin d’exécution. Le choix doit être tranché : activer la CFI là où la chaîne de compilation et les dépendances la supportent, puis mesurer les incompatibilités. Désactiver globalement la protection à cause d’un composant exotique est la réponse préférée des incidents reproductibles.
CFI ne garantit pas la confidentialité des données, l’intégrité métier, ni l’absence d’écriture arbitraire. Un attaquant capable de modifier une variable d’autorisation sans détourner le flux de contrôle peut obtenir un résultat exploitable malgré CFI. Les protections de mémoire doivent donc accompagner validation des entrées, langages à sûreté mémoire lorsque le périmètre le permet, cloisonnement des privilèges et correction des défauts. Les pratiques de développement restent le premier terrain de réduction du risque, pas un supplément décoratif.
Compilation durcie : vérifier le binaire, pas seulement les options
Section intitulée « Compilation durcie : vérifier le binaire, pas seulement les options »Les options de compilation doivent produire des artefacts vérifiables. Un indicateur présent dans un fichier de build ne prouve rien si une dépendance, une cible historique ou un drapeau de liaison l’annule. Sur une distribution Linux utilisant GCC ou Clang, l’exemple suivant construit un programme de test avec PIE, canaries, protection de relocalisation et interdiction de pile exécutable.
# Compiler avec code indépendant de la position et canaries forts.cc -O2 -fPIE -pie -fstack-protector-strong \ -D_FORTIFY_SOURCE=3 \ -Wl,-z,relro,-z,now,-z,noexecstack \ -o service service.c
# Vérifier le type PIE et l'absence de pile exécutable.readelf -h service | grep 'Type:'readelf -W -l service | grep GNU_STACK-fstack-protector-strong protège davantage de fonctions que la variante minimale, sans instrumenter aveuglément chaque fonction. -D_FORTIFY_SOURCE=3 ajoute des vérifications de taille lorsque le compilateur dispose des informations nécessaires ; il ne remplace ni les contrôles applicatifs ni l’analyse dynamique. RELRO (RELocation Read-Only, relocalisations en lecture seule) avec résolution immédiate limite la réécriture de tables de liaison, ce qui ferme une primitive classique après une corruption mémoire.
Les options CFI varient selon le compilateur, l’architecture et le mode de liaison. Elles demandent fréquemment une optimisation de liens complète, ou LTO (Link Time Optimization, optimisation au moment de l’édition de liens), car le compilateur doit connaître le graphe de destinations. L’intégrer sans batterie de tests est imprudent : les extensions binaires, bibliothèques chargées dynamiquement et code généré peuvent casser. L’exclure par principe est pire. La méthode saine consiste à segmenter les composants, instrumenter ceux maîtrisés, documenter les exceptions et faire décroître leur nombre.
Exploitation : crashs, supervision et reprise
Section intitulée « Exploitation : crashs, supervision et reprise »Ces protections transforment souvent une compromission potentielle en arrêt de processus. Ce n’est pas un échec de la protection, c’est son résultat attendu. Un dépassement détecté par canari ou une violation NX doit produire un signal exploitable par la supervision : type de faute, image du binaire, version, bibliothèque chargée, identifiant de requête si disponible, sans exposer d’adresses aux journaux accessibles aux utilisateurs.
Les core dumps sont ambivalents. Ils accélèrent le diagnostic, mais peuvent contenir secrets de session, jetons, mots de passe, clés privées chargées en mémoire et données personnelles. Leur collecte exige chiffrement au repos, contrôle d’accès, durée de conservation courte et procédure d’effacement. Désactiver tous les dumps masque le défaut ; les stocker dans un partage banal fabrique un défaut plus rentable. Vingt ans de métier apprennent que le fichier de diagnostic oublié survit souvent mieux que le service qu’il diagnostiquait.
La reprise doit être contrôlée. Un superviseur peut redémarrer un processus après un crash, mais un redémarrage illimité facilite le bruteforce de l’ASLR et transforme une erreur répétable en déni de service permanent. Limiter les redémarrages, appliquer un délai croissant, retirer temporairement l’instance du répartiteur et alerter est plus défendable. La correction du défaut reste obligatoire, avec reconstruction des binaires, revue des dépendances natives et validation que les mitigations sont toujours présentes.
🐝 Ce qui arrive si on s’en passe
Section intitulée « 🐝 Ce qui arrive si on s’en passe »Sans ces briques, une corruption mémoire cesse d’être un incident isolé pour devenir une recette réutilisable : les mêmes adresses valant sur toutes les instances du même binaire, un exploit écrit une fois fonctionne partout, sans étape de reconnaissance ni fuite préalable. Le coût de fabrication s’effondre, le délai entre publication d’un correctif et exploitation de masse aussi. La perte la plus discrète est celle du signal : un processus qui n’est jamais arrêté par un canari ou une violation de page ne produit aucune trace de crash, donc aucune alerte, aucun horodatage, aucun binaire suspect à corréler. La compromission réussit silencieusement et la supervision ne dispose d’aucun événement pour déclencher un confinement. Enfin, sans artefacts vérifiables, l’inventaire de parc ne permet plus de distinguer un composant durci d’un composant nu.
🦫 Explication de la marmotte 🍫
Section intitulée « 🦫 Explication de la marmotte 🍫 »Un programme garde en mémoire ses données, ses variables temporaires et les instructions qu’il doit exécuter. Une faille mémoire apparaît lorsqu’une erreur permet d’écrire ou de lire au mauvais endroit. L’objectif d’une attaque est souvent de modifier l’endroit où le programme va continuer son travail.
La randomisation place les zones mémoire à des emplacements différents à chaque lancement. Une attaque qui connaît une adresse à l’avance échoue alors souvent. Elle échoue moins bien si une autre erreur révèle une adresse réelle, ou si le programme peut être relancé très souvent.
La prévention d’exécution interdit d’exécuter des instructions placées dans les zones prévues pour les données. L’attaque peut alors essayer de réutiliser des morceaux d’instructions déjà présents dans le programme ou dans ses bibliothèques. Les canaris placent une valeur secrète près des informations qui contrôlent le retour d’une fonction. Si une écriture l’écrase, le programme s’arrête avant de continuer sur une adresse modifiée.
L’intégrité du flux de contrôle ajoute une vérification des destinations autorisées lors des appels, sauts et retours. Elle rend beaucoup plus difficile l’utilisation de morceaux de code existants dans un ordre choisi par l’attaquant. Toutes ces protections peuvent arrêter un programme sans réparer son défaut. Il faut donc corriger le code, empêcher les fuites d’adresses, conserver des journaux protégés et limiter les redémarrages automatiques.
À lire aussi
- Vérifier une chaîne de compilation reproductibleFondations de la compilation reproductible, des attaques de build et de la vérification moderne de la chaîne de compilation.
- Rust et la sécurité mémoire par construction, garanties et limitesComment Rust rend une classe entière de fautes mémoire impossible à compiler grâce à la possession et aux emprunts, où cette garantie s'arrête et ce qu'il reste à auditer.
Pages voisines
La citent
- Rust et la sécurité mémoire par construction, garanties et limites
- Évasion de VM et isolation matérielle des hyperviseurs
- Rowhammer, Spectre et Meltdown, mécanismes et contre-mesures
ASLR DEP NX canaries et CFI contre les exploits mémoire
Elle cite
: les deux pages se citent l’une l’autre.
