Architecture 64 bits/ASLR : quelles conséquences pour les exploits 32 bits ? Étude de cas avec Java et le CVE-2010-0842 – Partie 2/2

En exécutant le binaire plusieurs fois, nous remarquons que le programme java n’a pas été compilé avec l’option PIE (Position Independent Code), donc le binaire est toujours chargé à la même adresse. Et cela bien qu’ASLR soit activé et que les adresses de toutes les librairies et de la pile soient aléatoires. Ce binaire à adresse fixe est une mine d’or (ou plutôt de gadgets) pour l’analyste. Voyons voir si ce binaire contient un jmp rbx (0xFF 0xE3 en hexadécimal). Pour ce faire, nous allons utiliser un des nombreux désassembleurs [4] pour obtenir une chaîne hexadécimale du binaire puis chercher ce qui nous intéresse.

Aucun résultat. Mais d’ailleurs, est-ce bien toujours rbx qui pointe vers le fichier midi en mémoire dans le binaire 64 bits ? Vérifions cela en lançant l’exploit original de Vreugdenhil et en indiquant à gdb de stopper lors d’une erreur de segmentation (SIGSEGV). Cela nous permettra d’arrêter le programme juste au moment où il est censé sauter à l’adresse d’une instruction jmp ebx. Nous avons vu dans la section précédente que cette adresse est 0x6d53cb6d. Comme elle n’est pas mappée dans notre cas, cela produira une erreur de segmentation.

Rappelons que le code de PV_ProcessMidiSequencerSlice est le suivant :

Donc, en regardant le code assembleur un peu avant 0x00007f679e5ae094, nous devrions trouver une instruction d’affectation du pointeur vers le flux d’octets du fichier midi vers un registre suivi d’une instruction incrémentant le pointeur vers le flux. Et effectivement, à l’adresse 0x00007f679e5ae00b, nous avons :

Le registre pointant vers le flux n’est donc pas rbx, mais r12 ! Essayons de trouver un jmp r12 (0x41 0xff 0xe4).

Toujours rien. Nous ne sommes pas plus avancés qu’avant. Mais, nous n’avons pas dit notre dernier mot ! Essayons de trouver une instruction qui saute vers r12 plus un offset :

Nous trouvons donc plus de 50 flux d’octets potentiellement intéressants. En désassemblant ces flux d’octets en instructions [5], nous trouvons trois instructions très intéressantes.

Hourra ! Nous venons de trouver des instructions qui nous permettront de sauter vers notre shellcode ! La prochaine étape est de trouver l’adresse d’une de ces instructions (disons la première vers r12+0x78) :

Ensuite, dans le fichier midi, remplaçons l’ancienne adresse par la nouvelle, et voilà, nous avons réparé l’exploit ! L’exploit d’origine ne fonctionnera sans doute pas sous GNU/Linux, car le code assembleur lance calc.exe, de plus le code assembleur du payload doit être décalé de 0x78 octets. Il est laissé comme exercice au lecteur d’effectuer les modifications nécessaires pour le rendre utile et fonctionnel sous GNU/Linux 64 bits.

4. Exploitation d’un dépassement de tampon et désactivation du SecurityManager

4.1 Débordement de tampon

Dans cette section, nous allons exploiter le dépassement de tampon en version 64 bits pour exécuter notre shellcode. Nous allons tout d’abord tester le comportement du code en 64 bits. Sera-t-il le même qu’en 32 bits ? Pour ce faire, nous utilisons un fichier midi avec le code séquence 0xFF 0x07 suivi de la taille du tableau à copier et des octets à copier (0xAAAAAA…). Comme expliqué dans la section 2, la séquence 0xFF 0x07 servira à déclencher le code qui va faire déborder le tampon et nous permettra d’écrire un zéro n’importe où dans la pile. La fonction vulnérable est PV_MetaEventCallback. Plaçons un breakpoint à l’entrée de la fonction.

Voici l’état de la pile avant la copie dans le tableau :

Nous allons placer un breakpoint à l’unique instruction ret de la fonction pour vérifier l’état de la pile après la copie dans le tableau.

Normalement, comme pour le code 32 bits, le code aura changé la valeur d’un octet en zéro quelque part dans la pile, après le tableau.

Intéressant, il semble que le code 64 bits, contrairement au code 32 bits, copie le tableau source entier sur la pile ! Effectivement, en regardant le code assembleur il est clair que tout le tableau source y est copié.

Nous pouvons donc remplacer n’importe quel octet sur la pile. Comme le bit NX n’est pas activé par défaut sous notre version de test de Debian testing, nous allons pouvoir mettre notre payload dans le fichier midi et le faire copier sur la pile pour l’exécuter. La technique classique lors de l’exploitation d’un débordement de tampon est de modifier la valeur de l’adresse de retour de la fonction (0x00007f6db5e5ab85 dans notre exemple) pour mettre l’adresse de notre payload. Cependant, comme ASLR est activé, l’adresse de la pile change à chaque exécution et nous ne pouvons donc pas savoir à l’avance quelle sera l’adresse de notre payload. Mais comment diable allons-nous trouver l’adresse de notre payload ? « Mystère et boule de gomme » dirait l’autre. Bon, récapitulons : nous pouvons modifier la pile, mais nous ne connaissons pas l’adresse de notre payload. Peut-être y a-t-il une adresse proche de notre adresse de payload quelque part sur la pile ? Relançons le programme. Voici l’état de la pile avant la copie du tableau.

Un candidat très intéressant est l’adresse 0x00007fae2588a898. En utilisant le débordement de tampon, nous pouvons écraser le dernier octet de l’adresse (les adresses sont représentées en petit-boutiste (little-endian), donc il est possible d’écraser uniquement les deux octets de poids faible sans changer le reste de l’adresse). Nous avons donc l’adresse 0x00007fae2588a800 qui pointe quelque part où l’on contrôle les valeurs dans la pile. Il reste à trouver comment atteindre cette adresse éloignée de rsp qui pointe vers l’adresse 0x7fae2588a668. Une technique, appelée ret2ret [6] consiste à remplacer toutes les adresses de la pile avec l’adresse d’un gadget qui ne fera qu’un ret (voir Figure 3 à gauche). Notez que pour trouver les gadgets nous utilisons la même astuce que dans la section précédente : comme le binaire Java n’est pas compilé en PIE, son adresse en mémoire est toujours la même malgré ASLR, ce qui nous permet de l’utiliser pour trouver les gadgets nécessaires. Ce gruyère d’adresses vers des rets posera problème, car notre adresse modifiée pointera vers ledit gruyère : il y aura probablement un plantage du programme ! Ce qu’il nous faut c’est un emmental d’adresses vers des gadgets qui vont incrémenter rsp pour former les trous vers lesquels 0x00007fae2588a800 pointera. Nous remplirons chaque trou avec un toboggan de nop (nod sled) suivi d’un jmp vers le début de notre shellcode. Cette seconde approche est illustrée Figure 3 à droite.

Fig. 3 : La pile de type ret2ret avec uniquement des adresses vers un gadget ret (gauche) et la pile avec des adresses vers des gadgets incrémentant le pointeur de pile rsp, les toboggans de nop et les jmps vers le shellcode (à droite).

 

Le lecteur attentif se demandera sans doute pourquoi l’astuce de la section 3 consistant à sauter à l’adresse d’ebx, ne fonctionne pas ici. Et il aura raison ! Il est probablement possible d’utiliser cette technique avec un jmp vers le registre rsp+ un offset. L’objectif dans cette section est de montrer une autre approche.

4.2 Désactivation du SecurityManager

Nous sommes maintenant capables d’exécuter notre shellcode. Comme expliqué dans la section 1, aucune vérification de permission n’est effectuée au niveau du code natif. Donc, nous pourrions nous arrêter ici, car nous pouvons déjà exécuter du code avec les droits du processus de la machine virtuelle Java. Cependant, il peut être intéressant de considérer un shellcode qui ira uniquement désactiver le SecurityManager. Cela a plusieurs avantages. Premièrement, le nombre d’instructions en assembleur est faible (une dizaine), ce qui facilitera le portage vers une autre architecture. Deuxièmement, le code de l’analyste peut être directement écrit en Java, langage multiplateforme, ce qui permettra de le réutiliser directement pour différents couples architecture/système d’exploitation.

Le SecurityManager est référencé par un champ privé de la classe System. Pour récupérer la classe System, nous allons utiliser la fonction FindClass de la structure JNIEnv. Cette fonction nous retournera un pointeur vers la classe System. Le code C ressemble à cela :

Pour appeler FindClass, il nous faut un pointeur vers un JNIEnv. Où le trouver ? En mettant un breakpoint dans la première fonction native (runNative, voir la trace d’appels de la section 3), nous regardons la valeur de rsi qui n’est autre que l’adresse de JNIEnv : 0x7f648c140190 (en 64 bits/Linux le premier paramètre est passé via rsi). Regardons ensuite dans la pile, après le débordement du tampon et le saut vers notre shellcode, pour trouver une valeur similaire :

L’adresse de JNIEnv est donc 0x00007f648c140000 plus 0x190. Le code assembleur de notre shellcode pour récupérer l’adresse de la classe System est :

Nous avons donc maintenant dans rbx l’adresse 0x7f6443c2ec90 qui pointe vers la classe System. Il ne reste plus qu’à initialiser le champ contenant le pointeur vers le SecurityManager à zéro (null). Pour ce faire, nous allons exécuter un bout de code Java avec sun.misc.Unsafe pour connaître l’adresse du SecurityManager : c’est 0x7f6473af9870. Essayons de retrouver 0x7f6473af9870 en mémoire. Dans l’implémentation de la machine virtuelle d’OpenJDK, les champs statiques d’une classe Java se trouvent juste au-dessus de la classe, c’est-à-dire à des adresses mémoire plus petites que l’adresse de la classe.

Nous voyons que le pointeur vers le SecurityManager se trouve à -24 octets de la classe System. Voici le code pour l’initialiser à 0 :

Finalement, nous allons retourner en Java pour exécuter le code de l’analyste. Malheureusement, comme la pile a été détruite et que certains registres ont été modifiés, il n’est pas possible de continuer l’exécution du code natif sans planter le programme. Cependant, comme la librairie jsound tourne dans son propre thread, nous allons simplement boucler indéfiniment en utilisant l’instruction suivante :

Le thread principal de l’application Java va, lui, boucler jusqu’à ce que le SecurityManager soit désactivé :

Ensuite il aura le champ libre, car plus aucun contrôle de permission ne sera effectué…

Conclusion

L’exploitation des deux vulnérabilités du CVE-2010-0842 sous un système Debian testing 64 bits reste relativement facile malgré la randomisation des adresses des segments de code en mémoire (ASLR). Cela est dû au fait que le binaire java ne soit pas compilé en PIE, ce qui fournit un nombre important de gadgets pour contourner ASLR. La présence de ce binaire à adresse fixe facilitera aussi le contournement d’autres moyens de défense comme le bit NX qui rend la pile non exécutable. Malheureusement, la dernière version de Java (8) n’est toujours pas compilée avec PIE.

Le nombre d’exploits Java au niveau du code natif a tendance à augmenter, car de nombreuses vulnérabilités y sont découvertes et les attaquants ont un niveau technique suffisant pour les exploiter [7]. Par exemple, CVE-2013-1491 [8] exploite Java 7 sous Windows 8.

Alexandre Bartel
alexandre.bartel@uni.lu

Remerciements

Je tiens à remercier Andreas Follner et Philipp Holzinger pour les discussions fructueuses à propos des exploits Java ainsi que Patrick Ventuzelo pour la relecture de l’article.

Références

[1] Philipp Holzinger, Stefan Triller, Alexandre Bartel and Eric Bodden : An In-Depth Study of More Than Ten Years of Java Exploitation, Proceedings of the 23rd ACM Conference on Computer and Communications Security (CCS’16) : http://www.abartel.net/static/p/ccs2016-10yearsJavaExploits.pdf
[2] MITRE, CVE-2010-0842, 2010 : http://cve.mitre.org/cgi-bin/cvename.cgi?name=cve-2010-0842
[3] Peter Vreugdenhil, Java parse vulnerabilities, May 21st 2010 : http://vreugdenhilresearch.nl/java-midi-parse-vulnerabilities/
[4] PSHAPE : https://sites.google.com/site/exploitdevpshape/
[5] Defuse online Disassembler : https://defuse.ca/online-x86-assembler.htm
[6] Izik Kotler, Smack the Stack, 2005 : http://web.textfiles.com/hacking/smackthestack.txt
[7] Jack Tang, Java Native Layer Exploits Going Up, 2013 : http://blog.trendmicro.com/trendlabs-security-intelligence/java-native-layer-exploits-going-up/
[8] Yuki Chen, 2013 : https://github.com/guhe120/CVE20131491-JIT

Retrouvez cet article (et bien d’autres) dans MISC n°89, disponible sur la boutique et sur la plateforme de lecture en ligne Connect !