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

Les deux vulnérabilités CVE-2010-0842 exploitées sous Windows en 32 bits restent-elles toujours exploitables en mode 64 bits avec ASLR ? En théorie, il est bien plus difficile, voire presque impossible de les exploiter de manière réaliste. En pratique, nous verrons que c’est toujours possible facilement.

Introduction

Un des objectifs du langage Java est d’éliminer les risques liés à la gestion de la mémoire manuelle (par exemple, débordement de tampon) comme dans un langage tel que le C. En théorie, un accès direct à la mémoire n’est pas possible et Java utilise un ramasse-miette pour récupérer de la mémoire allouée, mais plus utilisée. En pratique, Java repose sur de nombreuses librairies C/C++ dont le code, lui, peut accéder directement à la mémoire. De plus, ce code risque de contenir des vulnérabilités de type débordement de tampon ce qui contredit un des objectifs de sécurité du langage Java. Dans cet article, nous allons dans un premier temps brièvement présenter l’architecture de la sécurité Java. Ensuite, nous détaillerons les deux vulnérabilités du CVE-2010-0842, trouvées dans du code C/C++, qui permettent de contourner le système de sécurité de Java. Puis, nous expliquerons comment les exploiter en mode 64 bits avec ASLR activé.

1. Java et la sécurité

1.1 Classes et permissions

Une application Java est composée de classes qui doivent être chargées dans l’environnement d’exécution (« runtime ») pour être exécutées. Pour ce faire, la plateforme Java contient un ensemble de chargeurs de classe (« classloaders »). Les applications et l’environnement d’exécution lui-même utilisent tous les deux ces chargeurs de classe pour dynamiquement charger des classes provenant de sources diverses telles que le système de fichiers local ou une ressource réseau distante.

Pendant l’initialisation de l’environnement d’exécution Java, la machine virtuelle java, ou JVM, va utiliser un chargeur de classes d’initialisation pour charger les classes nécessaires de la librairie de classes Java (JCL). Les classes de la JCL contiennent toutes les classes qui implémentent l’API standard de Java, comme java.lang.Object ou java.lang.Class. Ces classes sont appelées classes « système ». La JVM va ensuite charger les classes de l’application avec un autre chargeur de classe.

Le processus d’un chargeur de classe qui convertit une représentation binaire d’une classe à une instance de java.lang.Class est appelé « définition de classe ». Lors de chaque nouvelle définition d’une classe, celle-ci est associée à un ensemble de permissions. Les classes « système », chargées par le chargeur de classe d’initialisation, sont des classes de confiance et sont associées avec toutes les permissions. Au contraire, les classes d’une application sont associées avec très peu de permissions voire aucune permission par défaut dans le cas des applets Java.

1.2 La classe SecurityManager

Pour effectuer les contrôles de permissions, l’application Java doit être lancée en définissant un manager de sécurité. En pratique, le champ java.lang.System.security doit faire référence à une instance de la classe java.lang.SecurityManager. Si aucun manager de sécurité n’est défini, les contrôles de permissions ne sont pas effectués.

Dans la plupart des scénarios, l’objectif d’un analyste est de désactiver le manager de sécurité en réinitialisant le champ java.lang.System.security à null. Pour ce faire, il faut qu’il exploite une vulnérabilité dans la base de code de Java. Cette vulnérabilité peut, par exemple, être une erreur d’implémentation au niveau Java, mais aussi, comme nous allons le voir dans les prochaines sections, un débordement de tampon au niveau C/C++. Le lecteur intéressé par les différents types de vulnérabilités Java est invité à lire l’étude de Holzinger et al. [1]. Notez que le manager de sécurité n’effectue que des contrôles de permissions pour le code Java.

2. Description du CVE-2010-0842

Dans cette section, nous allons brièvement décrire les vulnérabilités du CVE-2010-0842 [2] qui ont été trouvées et rendues publiques par Vreugdenhil [3]. Ces vulnérabilités datent un peu, certes, mais le principe d’attaque reste encore le même aujourd’hui. Elles ont été trouvées dans le code qui lit les fichiers midi dans la librairie Java jsound. Ces vulnérabilités sont présentes dans Java version 1.6u18 et ont été corrigées dans la version 1.6u19.

2.1 Écriture d’un octet zéro sur la pile

La première vulnérabilité est un dépassement de tampon dans la fonction PV_MetaEventCallback. La fonction a comme quatrième paramètre un pointeur vers le tampon source (pText) et comme cinquième paramètre (textLength) la taille en octet à copier du tampon source vers le tampon destination situé sur la pile (buffer).

La variable textLength peut-elle être contrôlée par l’analyste ? Il se trouve que c’est bien le cas, car dans la fonction PV_ProcessMidiSequenceSlide, la taille est extraite du fichier midi et ce fichier est contrôlé par l’analyste…

Comme l’indique le code suivant, depuis Java il faut donc : (1) appeler la méthode setSequence qui va appeler GM_SetSongMetaEventCallback qui va, elle, initialiser la variable pSongmetaEventCallabackPtr et (2) lire le fichier midi contenant la séquence 0xFF 0x06 (voir figure 1) pour lancer la fonction PV_MetaEventCallback.

Fig. 1 : Représentation simplifiée de la structure du fichier midi pour déclencher la vulnérabilité de dépassement de tampon. Le fichier contient les octets 0xFF et 0x06 pour déclencher l’appel de la fonction PV_ReadVariableLengthMidi qui va interpréter textLength comme le nombre d’octets à copier dans le tampon, puis l’appel de PV_CallSongMetaEventCallaback qui va copier les octets dans le tampon et provoquer un débordement.

 

Tous les ingrédients sont donc réunis pour exploiter ce débordement de tampon. Sauf que Vreugdenhil indique que le code assembleur ne copie pas la totalité du tampon source dans le tampon destination. Seul l’octet zéro final peut être écrit n’importe où sur la pile en fonction de la variable textLength contrôlée par l’analyste. Cela rend l’attaque plus complexe qu’un classique débordement de tampon. Vreugdenhil ne fournit pas d’exemple concret d’exploit pour cette vulnérabilité, mais l’exploitation en 32 bits (c’est-à-dire l’exécution de code arbitraire) est toujours possible selon lui. Nous verrons dans la section 4 comment exploiter cette vulnérabilité dans un environnement 64 bits avec ASLR.

2.2 Pointeur de fonction fourni par l’analyste

La seconde vulnérabilité permet à l’analyste de contrôler un pointeur de fonction. Dans la fonction PV_CallControlCallbacks, le pointeur de fonction callback est initialisé en fonction de l’indice controler. Nous voyons dans la fonction PV_ProcessMidiSequencerSlice que la valeur de cet indice est, comme pour la vulnérabilité précédente, extraite du fichier midi. Elle est donc directement sous le contrôle de l’analyste.

Mais comment contrôler callback pour pouvoir appeler notre propre code à exécuter ? L’astuce consiste à avoir une valeur pour l’indice controler plus grande que le nombre d’éléments dans le tableau callbackProc, car l’analyste peut contrôler ce qui est écrit dans ce tableau. Voyons d’abord à quoi ressemble la définition de la structure controllerCallback pour connaître la taille du tableau.

La taille est de 128. Il faut donc que l’indice controller contienne une valeur supérieure ou égale à 128 (0x80) pour lire dans le tableau callbackReference. Voyons maintenant comment l’analyste peut contrôler ce qui est écrit dans callbackProc. Dans la fonction nOpenRmfSequencer, ci-dessous, l’entier id est d’abord initialisé avec la fonction getMidiSongCount qui ne fait qu’incrémenter un compteur pour chaque MidiSong.

Notez que le nom de la fonction nOpenRmFSequencer commence par un nom de paquet Java, ce qui indique que la fonction native peut être appelée depuis une classe Java.

Ensuite, l’adresse d’id est passée en paramètre à la fonction XGetIndexedResource qui va le passer à la fonction XgetIndexedFileResource. Cette fonction va ensuite aller extraire un entier du fichier midi et le placer dans la variable id. Finalement, l’entier id est affecté au champ pSonguserReference.

Mais comment le tableau pSongcontrollerCallback->callbackReference[controler] est-il affecté par l’entier pSonguserReference ? Cela se passe dans la fonction GM_ SetControllerCallback qui est elle-même appelée par nAddControllerEventCallback. Il reste à comprendre comment combiner toutes ces opérations, qui semblent indépendantes les unes des autres, depuis l’application Java.

Comme l’indique le code suivant, depuis Java il faut appeler des méthodes qui vont déclencher les évènements suivants : (1) lire un entier du fichier midi et le placer dans pSonguserReference via la variable id, (2) copier la valeur de pSonguserReference dans le tableau callbackReference et (3) utiliser le pointeur de fonction avec la valeur copiée dans callbackReference.

Il reste à savoir quelle valeur mettre dans le tableau callbackReference pour que le pointeur de fonction aille exécuter le payload. Vreugdenhil note que le registre ebx pointe vers la suite du fichier midi. Il suffit donc de (a) placer le payload à cet endroit dans le fichier midi et (b) de trouver l’adresse d’une instruction qui va sauter à l’adresse contenue par ebx. À l’époque de la découverte de cette vulnérabilité, ASLR n’était pas grandement déployé. Trouver l’adresse d’une instruction jmp ebx était donc une solution viable. Une telle instruction se trouve en effet, à l’adresse 0x6d53cb6d de la librairie jsound.

Fig. 2 : Représentation simplifiée de la structure du fichier midi pour déclencher la vulnérabilité. Le fichier contient le Song id qui sera utilisé comme adresse vers un jmp ebx, et les octets 0xB0 et 0x80 pour écrire l’id dans le tableau callbackReference.

 

Cet exploit permet de contourner le SecurityManager via l’exécution de code en natif où aucun contrôle de permission n’est effectué. Nous allons voir dans la section suivante si c’est toujours le cas en mode 64 bits avec ASLR.

3. La vulnérabilité qui n’en est plus une ?

La vulnérabilité de la section précédente reste-t-elle exploitable sous Debian en mode 64 bits avec ASLR ? Commençons par regarder le code de plus près pour identifier le nombre d’octets du pointeur de fonction que l’analyste contrôle en mode 64 bits.

La fonction XgetLong, contrairement à ce que son nom indique, ne va pas lire un long (8 octets en java), mais un int (4 octets). La valeur est ensuite convertie en XlongResourceID qui est une valeur sur 32 bits. L’analyste ne contrôle maintenant plus que les 32 bits de poids faible de chaque élément du tableau callbackReference (les pointeurs sont maintenant 64 bits). Cela suffira-t-il pour trouver l’instruction jmp rbx (rbx est l’extension 64 bits d’ebx) comme expliqué dans la section précédente ? Voyons un peu à quoi ressemble la cartographie de la mémoire. Peut-être allons-nous trouver quelque chose d’intéressant malgré la présence d’ASLR ?

Alexandre Bartel
alexandre.bartel@uni.lu

La seconde partie de cet article sera publiée prochainement sur le blog, restez connectés 😉

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

Laisser un commentaire