PIE et ASLR sont souvent cités ensemble, au point qu’on finit par les confondre. Il faut pourtant les distinguer nettement : PIE fournit la capacité technique à randomiser un exécutable, mais c’est ASLR qui fournit effectivement la randomisation. L’un sans l’autre ne protège rien.
PIE dépend d’ASLR
ASLR (Address Space Layout Randomization) est la mécanique du noyau qui décide où placer les différentes zones mémoire d’un processus — pile, tas, bibliothèques, parfois l’exécutable lui-même. PIE (Position Independent Executable) transforme, lui, l’exécutable principal en un binaire de type ET_DYN, comme une bibliothèque partagée : son code est adressé de façon relative, ce qui permet au loader de choisir arbitrairement la base à laquelle il charge ses segments PT_LOAD.
D’où une conséquence simple mais souvent mal formulée : sans ASLR (kernel.randomize_va_space=0), un binaire PIE sera toujours chargé à la même base — le ROP ou le ret2text restent alors triviaux dès que cette adresse est connue. Avec ASLR activé, en revanche, la base d’un binaire PIE est randomisée à chaque exécution, exactement comme une bibliothèque partagée.
Ce que PIE + ASLR randomisent, précisément
Il faut ici être exact, car une confusion fréquente mélange le code, le tas et les sections de données. Ce que PIE + ASLR randomisent pour l’exécutable, ce sont les segments PT_LOAD de l’ELF — typiquement .text + .rodata (en R-X) et .data + .bss (en RW). C’est donc bien le code et les données statiques du programme.
Le tas, en revanche, n’est pas un segment de l’exécutable : il provient de brk et/ou de mmap anonymes, et c’est le noyau qui randomise sa base, indépendamment de PIE, via le paramètre kernel.randomize_va_space.
Formulation exacte
Si le binaire est compilé en PIE, l’ASLR peut randomiser la base de l’exécutable lui-même (ses segments de code et de données). La pile, le tas et les bibliothèques partagées sont randomisés par l’ASLR via les mécanismes génériques (
mmap/brk), indépendamment du fait que le binaire principal soit PIE ou non.
Les niveaux de randomize_va_space
Sous Linux, le comportement est gouverné par ce seul paramètre :
0— aucune randomisation ;1— randomisation de la basemmap, de la pile, de la VDSO ; les bibliothèques (chargées parmmap) en bénéficient donc, de même que le code d’un binaire PIE (traité commeET_DYN) ;2— tout ce qui précède, plus la randomisation du tas (brk).
Historiquement, l’ASLR s’est d’abord limitée à la randomisation de la pile ; les déploiements modernes couvrent désormais l’ensemble de ces zones.
Mesurer la qualité d’une ASLR
Toute ASLR ne se vaut pas. Sa robustesse se mesure en bits d’entropie pour chacune des bases randomisées — exécutable, tas, pile, zone mmap/bibliothèques. Plus l’espace de recherche () est grand, plus une attaque par force brute coûte cher. Sur x86-64, l’entropie disponible dépasse très largement celle du 32 bits, où seule une poignée de bits est réellement exploitable ; mais des évolutions récentes — l’alignement sur 2 Mio pour profiter des huge pages — ont parfois réduit l’entropie effective du placement de la libc, jusqu’à passer de 28 à 19 bits utiles selon les configurations. La granularité de la randomisation compte donc autant que sa plage : une randomisation par page de 4 Kio n’offre pas la même robustesse qu’un alignement sur 2 Mio.
La faille structurelle de toute ASLR
L’ASLR repose entièrement sur le fait que l’attaquant ignore les adresses des zones clés. Une seule fuite d’adresse (infoleak) — une adresse de libc, un pointeur de pile ou de tas, un gadget révélé — suffit en général à reconstruire toutes les autres adresses du même fichier, puisque les offsets internes à un ELF ou à une bibliothèque sont fixes. Une ASLR parfaite ne sert à rien si l’application elle-même donne une adresse.
Ce que cela change en pratique
Deux cas de figure structurent la stratégie d’exploitation.
Binaire non-PIE, ASLR active — l’exécutable est de type ET_EXEC : sa base est fixe (souvent 0x400000 en 64 bits). L’ASLR randomise pile, tas, bibliothèques, VDSO. Conséquence : un ret2text (saut direct dans .text) reste trivial, les offsets internes au binaire ne changeant jamais d’une exécution à l’autre. Un ret2libc, en revanche, nécessite généralement un leak d’adresse dans la libc, mais une fois libc_base connue, tous ses offsets internes sont déterministes.
Binaire PIE, ASLR active (le cas durci le plus courant aujourd’hui) — l’exécutable est traité comme une bibliothèque partagée, sa base randomisée à chaque exécution, en même temps que tas, pile et bibliothèques. Conséquence : aucun ret2text direct n’est possible sans fuite préalable — il faut d’abord fuiter au moins une adresse (dans l’ELF ou dans une bibliothèque), puis reconstruire les offsets à partir de là. C’est le schéma que suit systématiquement une chaîne ROP moderne : infoleak, puis calcul, puis exploitation.
Dans les deux cas, l’ASLR n’est jamais traitée, en exploitation réelle, comme un mur absolu : on cherche systématiquement à la contourner par une fuite, ou à l’affaiblir (force brute si le service redémarre en boucle sans nouvelle graine, information partielle glanée ailleurs).