
Exploit de preuve de concept qui contourne l'ASLR sur les processeurs Intel en abusant du Branch Target Buffer et de l'exécution spéculative pour divulguer des adresses randomisées via un canal auxiliaire.
L'Address Space Layout Randomization est une mitigation utilisée pour rendre plus difficile l'exploitation des attaques par corruption mémoire. Dans un scénario de vulnérabilité de débordement de tampon, par exemple, un attaquant qui tente de monter une exploitation par Return Oriented Programming a besoin de connaître les adresses des gadgets de la chaîne. Si le segment de code du binaire exploité est randomisé, il est alors beaucoup plus difficile pour un attaquant de choisir la bonne adresse pour l'exploitation, rendant celle-ci irréalisable.
L'exemple suivant montre comment une adresse est randomisée :
#include <stdio.h>
void DoNothing();
void (*codePtr)() = DoNothing;
void DoNothing(){}
int main(int argc,char **argv){
printf("Destination %p\n",codePtr);
DoNothing();
}
À chaque exécution, la valeur est randomisée :
Destination 0x563714256149
Destination 0x556d8e2f1149
Destination 0x5618c8bdd149
Destination 0x55ee623b0149
Les 12 derniers bits 149 sont toujours les mêmes, mais la fonction peut se trouver à peu près n'importe où entre 0x550000000000 et 0x570000000000, ce qui signifie que 29 bits sont randomisés, occupant un espace d'adressage possible de 0x200 0000 0000 ou 2,2 To.
Le traitement de chaque instruction est une tâche complexe. Certaines étapes du traitement d'une seule instruction sont :
Afin d'augmenter le débit d'instructions du CPU, chaque tâche de l'instruction est effectuée par une unité spécifique du processeur. Avec toutes les unités travaillant en parallèle, le CPU peut fonctionner à des fréquences d'horloge bien plus élevées ; c'est l'idée du pipeline.
Exécution des instructions A, B et C sur les cycles 1 à 5. Au cycle 3, par exemple, les unités d'extraction, de décodage et d'exécution sont simultanément actives
Cependant, les instructions ne sont pas complètement indépendantes les unes des autres. Par exemple, la séquence suivante :
A. add ax,[bx]
B. jz $+1
C. mov dl,[rsi]
D. nop
Dans ce cas, l'instruction A, dans le meilleur des cas, ne se terminera qu'au cycle 3 en exécution. Cependant, l'unité d'extraction doit décider quelle est la prochaine instruction à charger depuis la mémoire, à savoir si l'instruction C (mov dl,[rsi]) doit être ignorée.
Dans ce scénario, le CPU a la possibilité d'attendre la fin de l'instruction A, qui n'aura lieu qu'au troisième cycle d'horloge, pour ensuite charger la bonne instruction en mémoire, si par exemple l'opération add retourne 0 :
Ceci implique un retard dans le pipeline, car le CPU doit attendre que l'instruction soit exécutée. Dans cet exemple, le retard est d'un seul cycle d'horloge, mais l'instruction add ax,[bx] nécessite une opération mémoire qui, comme vu précédemment, peut prendre jusqu'à des centaines de cycles pour se terminer, engendrant ainsi un coût de performance significatif sur le processeur.
Une option plus rapide consisterait à essayer de « deviner » le bon chemin d'exécution. Le CPU peut spéculer sur la prise ou non de la branche. Après ce point, l'exécution continue sur le chemin spéculé et les valeurs ne sont validées que si le chemin s'avère correct après la fin de l'instruction A. Si le chemin s'avère incorrect, les résultats sont rejetés et l'état est restauré à celui d'avant le point de spéculation.
Le seul problème lors de l'annulation du chemin emprunté est que l'état microarchitectural du CPU ne peut pas être restauré. Ainsi, si le CPU spécule pour exécuter l'instruction C (mov dl,[rsi]), les données pointées par rsi seront déplacées dans le cache. Cet effet peut être mesuré ultérieurement à l'aide d'une attaque par canal auxiliaire (side channel).

Prédicteur conditionnel à 2 bits. https://en.wikipedia.org/wiki/Branch_predictor
Non seulement les instructions conditionnelles doivent être prédites, mais aussi les branchements indirects. Le CPU doit disposer d'un mécanisme pour deviner les destinations d'une instruction telle que call [rdi].
La vulnérabilité Spectre v2 montre qu'il est possible d'exploiter le prédicteur indirect pour obtenir une exécution transitoire dans d'autres processus :
Extrait de https://spectreattack.com/spectre.pdf
En plaçant une instruction call dans le contexte A à la même adresse virtuelle qu'un autre call du contexte B, l'attaquant peut entraîner le CPU à exécuter du code à une position choisie par l'attaquant dans le contexte B, dans une attaque par réutilisation de code, similaire au Return Oriented Programming (ROP).
La victime ciblée doit disposer d'un morceau de code connu sous le nom de « spectre gadget » capable de fuiter un secret à l'aide d'une attaque par canal auxiliaire. Pour qu'une attaque Spectre réussisse, l'attaquant doit également connaître l'emplacement du spectre gadget. Par conséquent, dans les attaques utilisateur-utilisateur, protéger la victime avec l'ASLR était une mitigation pour ce type d'attaque. Cependant, il existe aussi des techniques pour extraire l'ASLR à l'aide d'attaques microarchitecturales comme Jump Over ASLR. Néanmoins, cette technique présente certaines limites quant à la quantité de bits divulgués, car elle repose sur une collision dans le prédicteur direct pour contourner l'ASLR.
Les mécanismes internes de ce prédicteur sont présentés ci-dessous :

Extrait de https://spectreattack.com/spectre.pdf
Certains de ces composants sont :
La configuration classique d'une attaque Spectre v2 ressemble à ceci :

getenv + secret[0]*4096 peut fuiter la valeur du secret à la position 0 en utilisant la libc comme mémoire partagée.Exec ASLR (également connu sous le nom de Reverse Branch Target Buffer Poisoning) est une nouvelle technique pour contourner l'ASLR en utilisant la vulnérabilité Spectre-BTI. Cette attaque exploite le fait que non seulement l'attaquant peut polluer le BTB dans un scénario Spectre-BTI classique, mais que les victimes peuvent aussi déclencher une mauvaise prédiction de branche dans le processus de l'attaquant, amenant l'attaquant à sauter spéculativement vers une adresse protégée par l'ASLR. En utilisant ensuite un canal auxiliaire qui révèle quelle adresse est en cours d'exécution, un attaquant peut récupérer l'adresse de destination complète, contournant ainsi l'ASLR pour le processus ciblé.
La configuration de l'attaque Exec ASLR ressemble à ceci :

Dans ce type d'attaque, il n'est pas nécessaire de trouver un spectre gadget ni de disposer d'une mémoire partagée pour le canal auxiliaire ; la seule exigence est d'exploiter une branche indirecte. Tous les gadgets requis pour un Spectre v2 sont placés dans le processus de l'attaquant. La seule nouvelle exigence pour cette attaque est de pouvoir mapper l'adresse de destination de la victime dans votre propre processus ; par conséquent, cette attaque ne peut pas fonctionner contre KASLR, par exemple. Ce n'est pas non plus une attaque par force brute : en un seul essai, il est possible de tester plusieurs adresses en même temps. Cependant, il existe une limite quant au nombre de « leak gadgets » que la mémoire peut contenir simultanément. Cela réduit considérablement le temps nécessaire pour réaliser l'attaque par rapport à Jump Over ASLR, passant d'environ 100 adresses par seconde à quelques centaines de milliards d'adresses par seconde.
Le leak gadget utilise probeArray pour indiquer à l'attaquant où le gadget lui-même est exécuté. Il reçoit probeArray et l'index du RIP à fuiter comme arguments et effectue une sorte de programmation sans branche pour décider si probeArray[0] ou probeArray[4096] doit être accédé.
lea rax,[rip - 7] ;load current address
shr rax,cl ;selects the bit using cl arg
and rax,1
shl rax,12 ;loads probearray
mov dl,[rsi+rax] ;or probearray+4096
Comme vu précédemment, le BHB est utilisé pour sélectionner une entrée du BTB. Afin de trouver une collision dans le BTB et d'exploiter cette vulnérabilité, un attaquant doit connaître les N dernières branches prises (29 si < skylake). Lors de nos tests, nous avons utilisé une boucle for pour définir l'état du BHB à une valeur connue avant l'appel indirect. Voici un exemple vulnérable de code de victime :
#include <stdio.h>
void DoNothing();
void (*codePtr)() = DoNothing;
void DoNothing(){
return;
}
int main(){
printf("Destination = %p\n",codePtr);
while(1){
for(int i=0;i<200;i++){}
codePtr();
}
}
Pour garantir que l'état du BHB est le même dans les deux contextes, l'attaquant copie les octets correspondant à la boucle for et à l'appel de la victime sous forme de shellcode. Le shellcode est copié à toutes les 256 positions possibles pouvant correspondre à l'alignement sur 20 LSBs requis pour que l'état du BHB soit identique.
Victim Code
0x5594c566a152 <+28>: mov eax,0xc8
0x5594c566a157 <+33>: dec eax
0x5594c566a159 <+35>: jne 0x1157 <main+33>
0x5594c566a15b <+37>: nop
0x5594c566a15c <+38>: nop
0x5594c566a15d <+39>: nop
0x5594c566a15e <+40>: lea rdi,[rip+0x2ecb]
0x5594c566a165 <+47>: call QWORD PTR [rdi]
--> Executes to
0x5594c5669135: ret
Attacker Code
… //eax=200 rsi=probeArray, cl=0
0x6a157 dec eax
0x6a159 jne 0x455555500157
…
0x6a165 jmp QWORD PTR [rdi]
--> Misspredicts to
0x5594c566a135: lea rax,[rip - 7]
0x5594c566a13c: shr rax,cl
0x5594c566a13f: and rax,1
0x5594c566a133: shl rax,12
0x5594c566a137: mov dl,[rsi+rax]
Il y a un problème lorsqu'on essaie de placer un gadget à toutes les positions possibles en même temps. Dans nos tests, l'ASLR place la destination quelque part entre 0x550000000000 et 0x570000000000. Cela signifie qu'il y a 2,2 To d'adresse virtuelle possible à mapper, soit 537 millions de leak gadgets. Mais le système ne dispose que de 8 Go de RAM. Bien qu'il soit possible de mapper 2 To de RAM en utilisant COW, nous n'avons pas eu beaucoup de succès avec cette approche. Je suppose que cela crée trop de pression sur le Translation Lookaside Buffer (TLB), rendant la spéculation vers une adresse non traduite beaucoup trop lente. Lors des tests, nous avons créé une page de 1 Go en mémoire et l'avons remplie avec les gadgets de fuite. Ensuite, nous avons décalé la page sur la plage de 2 To à l'aide de l'appel système remap. Un autre problème observé était le fait que l'adresse spéculée n'était probablement pas présente dans le TLB, car elle n'avait jamais réellement été exécutée. Mais le manuel Intel indique :
Par conséquent, afin d'augmenter les chances d'une « pagewalk induite par la spéculation », nous avons essayé de rendre la résolution de l'adresse correcte aussi difficile que possible. Cela est fait en utilisant une chaîne de pointeurs pour l'adresse de destination. L'idée est que le front-end du CPU va spéculer sur la destination de la branche et que l'unité de réorganisation exécutera le leak gadget avant de terminer la lecture de la chaîne de pointeurs.
Improved Caller - Frontend fetched instructions
mov rcx,%1 ;mask arg for gadget
lea rsi,[%2] ;probe array ptr arg
lea rdx,[%0]
mov rdx,[rdx] ;pointer chain
mov rdx,[rdx]
mov rdx,[rdx]
...
mov rdx,[rdx]
mov rdx,[rdx]
check [rdx] ;mispredicts to gadget
lea rax,[rip - 7];speculative execution
shr rax,cl
and rax,1
shl rax,12
mov dl,[rsi+rax]
Improved Caller - Reorder unit scheduled instructions
mov rcx,%1 ;mask arg for gadget
lea rsi,[%2] ;probe array ptr arg
lea rdx,[%0]
mov rdx,[rdx] ;pointer chain
mov rdx,[rdx]
; <pagewalk occurs some where here>
... ;Out of order + speculation
lea rax,[rip - 7]
shr rax,cl
and rax,1
shl rax,12
mov dl,[rsi+rax]
...
mov rdx,[rdx]
mov rdx,[rdx]
check [rdx] ;the execution path reverted
Cette technique d'exécution hors ordre + spéculative a montré une amélioration des taux de mauvaise prédiction souhaités dans l'attaque.
Pour réaliser une attaque Spectre V2, l'attaquant doit exécuter du code sur le même cœur que la victime, afin de partager la même unité de prédiction de branche (BPU). Sur les CPU <= Skylake, nous avons observé qu'il est possible d'obtenir une co-résidence de cœur en utilisant l'hyperthreading. Les CPU Icelake et Cascade Lake implémentent une mitigation appelée Single Thread Indirect Branch Predictor, qui sépare la BPU entre les threads. Il est donc nécessaire d'exécuter la victime et l'attaquant sur le même thread et d'utiliser usleep pour alterner entre le processus victime et le processus attaquant, ce qui rendrait l'attaquant plus lent sur ces CPU sans le fait que les caches (et probablement aussi le TLB) sont très bons sur ces générations, permettant de mettre en cache beaucoup plus de gadgets en même temps.
Cette technique a été testée sur tous les CPU Intel disponibles sur Google Cloud, à la fois sur les générations N1 et N2 :
Mis à part quelques différences dans les exploits pour Cascade Lake et Ice Lake, tous les tests parviennent à récupérer les adresses avec une précision > 99 % en moins de 10 s.
Les mitigations sont les mêmes que pour Spectre V2 afin d'atténuer les attaques utilisateur-utilisateur.
La barrière de prédiction de branche indirecte (IBPB) permet de vider la BPU et peut être utilisée lors des changements de contexte. Sous Linux, l'IBPB peut être utilisée via l'appel système prctl avec l'option PR_SET_SPECULATION_CTRL.
Je n'ai aucune idée de la mitigation équivalente pour Windows, merci de me le dire.
https://cos.ufrj.br/uploadfile/publicacao/3061.pdf
https://www.youtube.com/watch?v=Qj4z-KvnkxU
https://docs.google.com/presentation/d/10t-oo-c26x9ydx1_FYgmhy204rxfmQ92eboPlCnA2y4/edit?usp=sharing
https://googleprojectzero.blogspot.com/2018/01/reading-privileged-memory-with-side.html https://eprint.iacr.org/2013/448.pdf https://spectreattack.com/spectre.pdf https://www.cs.ucr.edu/~nael/pubs/micro16.pdf http://download.vusec.net/papers/bhi-spectre-bhb_sec22.pdf https://www.kernel.org/doc/html/latest/userspace-api/spec_ctrl.html Intel® 64 and IA-32 Architectures Software Developer’s Manual Volume 3. Santa Clara, USA: Intel Corporation, 2016, iSBN 325384-060US
| Opération \ cycle d'horloge | 1 | 2 | 3 | 4 | 5 |
|---|
| Extraction | A | B | C | ||
| Décodage | A | B | C | ||
| Exécution | A | B | C |
| Opération \ cycle d'horloge | 1 | 2 | 3 | 4 | 5 | 6 |
|---|
| Extraction | A | B | D | |||
| Décodage | A | B | D | |||
| Exécution | A | B | D |
| Opération \ cycle d'horloge | 1 | 2 | 3 | 4 | 5 | 6 |
|---|
| Extraction | A | B | (S) C | |||
| Décodage | A | B | (S) C | |||
| Exécution | A | B | (S) C |