Retour aux articles

Exploitation du CVE-2018-5093 sur Firefox 56 et 57 – PARTIE 1 : contrôle du pointeur d'instruction

04 juillet 2022

Contexte

L'objectif de ce projet est d'obtenir un accès initial pendant l'exercice Red Team en obtenant une exécution de code à travers l'exploitation d'une vulnérabilité.

Ce premier article décrit les premières étapes du développement de l'exploit concernant le CVE-2018-5093 expliquant comment profiter de la vulnérabilité de l'underflow entier sur Firefox 56 et 57 pour contrôler le pointeur d'instruction lors de l'ouverture d'une page web spécifiquement conçue.

Le développement de la preuve de concept s'est fait dans un environnement Windows 10 version 21H2 et avec l'aide du débogueur WINDBG.
Aucun article relatant un exploit fonctionnel n'a été publié à ce jour. C'est la raison pour laquelle le développement de cet exploit a été réalisé avec l'aide du scénario ExodusIntel.

Pour comprendre cet article, vous devez avoir quelques connaissances de l'assemblage avec l'utilisation de la pile et du tas, ainsi que de certaines techniques célèbres d'exploits de navigateurs, telles que la Pulvérisation de tas.

Analyse de la vulnérabilité

La vulnérabilité avait été découverte dans le code WebAssembly dans la bibliothèque xul.dll et signalée en 2018 à Mozilla. Cette vulnérabilité est corrigée dans Firefox 58 et 59 depuis 2020.

Dans la Figure 1, en guise de preuve de concept, voici une manière simple de déclencher la vulnérabilité.

                                                                                                  Figure 1 : Preuve de concept

Cette vulnérabilité est déclenchée lorsque la fonction get() de l'objet Table est appelée. En fait, lorsque la fonction get() est appelée, un appel de la fonction getImpl() est exécuté. Cependant, dans la fonction getImpl(), il y a une vérification de l'index entré dans l'argument de la fonction get(). Il s'agit de la fonction ToNonWrappingUint32() qui vérifie si l'index est compris entre 0 et table.length() – 1, comme nous pouvons le voir dans la Figure 2.

                                                                                                  Figure 2 : Vulnerabilité underflow
 

Déclenchement du bug

Comme présenté dans la capture d'écran ci-dessus, lorsque nous créons l'objet Table, nous pouvons spécifier une taille de table de 0 avec le composant initial de la Figure 1, donc table.length() peut être égal à 0. Ensuite, lorsque table.length() est égal à 0, la valeur table.length() – 1 est -1, mais la fonction ToNonWrappingUint32() a le type d'argument entier non signé 32, donc la valeur -1 devient la valeur maximale de l'entier non signé 32, c'est un underflow, donc maintenant la fonction ne peut pas vérifier la valeur de l'index et tous les nombres codés en 32 bits peuvent être utilisés comme index.

En fait, un entier signé sur 8 bits, qui est égal à -1, a une représentation binaire comme suit : 1111 1110.

Ainsi, le programme l'interprétera comme l'entier non signé 254, qui correspond à la valeur maximale codée sur 8 bits – 1.

Inversion du composant affecté

Après analyse avec GHIDRA, le code d'assemblage qui permet d'exploiter la vulnérabilité se trouve dans la Figure 3, car c'est la ligne qui permet de récupérer l'objet à l'index de position.

                                                                                              Figure 3 : Code d'assemblage vulnérable

Donc, après cette ligne, nous pouvons contrôler certains registres et nous pouvons aller où nous voulons. Pour cette raison, nous devrions être en mesure de contrôler le registre EIP/RIP si nous pouvons exécuter une instruction d'assemblage qui effectue un appel à un registre que nous contrôlons.

Certaines fonctions possèdent l'instruction qui nous permet d'effectuer un appel sur un registre. Ainsi, après une analyse approfondie, cette instruction est située dans la fonction FUN_107bc8d6 (voir la Figure 4).

                                                                                                         Figure 4 : Fonction ciblée

Avec le contrôle de certains registres, nous devons trouver une chaîne d'appels pour déclencher la fonction et nous avons identifié un moyen de chaîner d'autres fonctions comme présenté ici :

                                                                            Figure 5 : Comment appeler la fonction cible – notre chaîne

 

Contrôle du pointeur d'instruction

En exécutant le code JavaScript présenté comme preuve de concept dans la Figure 1, nous voyons que le programme tente de récupérer un objet à la position 0x2000000 de l'objet Table. Nous pouvons voir ce qui se passe en utilisant WINDBG à la Figure 6.

                                                                                                        Figure 6 : Violation d'accès

Dans cette Figure, le programme veut récupérer un objet à l'adresse 0x169a40b0, mais il n'existe pas car la position est plus grande que la taille de Table allouée.

Pulvérisation précise de tas

À ce stade, si nous utilisons une Pulvérisation précise de tas, nous pouvons placer une adresse valide que nous pouvons contrôler à l'endroit où le flux veut accéder afin de nous permettre d'utiliser notre faux objet et d'avoir le contrôle sur certains registres utiles.

La charge utile Pulvérisation précise de tas est conçue sur la base de l'article de Corelan[4] démontrant cette technique et comment pulvériser le tas de manière plus précise, ce qui donne la charge utile présentée ci-dessous.

                                                                                     Figure 7 : Charge utile Pulvérisation précise de tas

En utilisant cette technique, nous allons pulvériser le tas avec l'adresse 0x10101010. Ensuite, lorsque le programme prend l'adresse de l'objet, il prendra la valeur 0x10101010 comme nous pouvons le voir à la Figure 8.

                                                                                                    Figure 8 : Adresse corrompue

Avec la Pulvérisation précise de tas, l'adresse 0x10101010 ne contient que des octets nuls jusqu'à l'adresse 0x10101090, comme souhaité à la Figure 9 et illustré à la Figure 10.

                                    Figure 9 : Pulvérisation précise de tas avec des octets nuls à l'adresse 0x10101008 jusqu'à l'adresse 0x101010A0

                                                                                                       Figure 10 : Contenu du tas

Chaînage d'appels de fonction

À partir de maintenant, nous voulons exécuter le programme d'assemblage jusqu'à l'appel de la fonction FUN_1234294b.

Pour ce faire, nous devons modifier certaines adresses dans la Pulvérisation précise de tas afin de contourner tous les contrôles qui vérifient si le format de l'objet récupéré est correct, donc si l'objet a le type d'objet demandé dans la fonction getImpl() (voir Figure 2) avant l'appel de la fonction ciblée.

Cependant, pour identifier le contenu et la localisation des données, nous allons modifier pas à pas les données dans la Pulvérisation de tas avec WINDBG jusqu'à ce que le flux d'exécution atteigne la fonction ciblée.

Lorsque le programme est exécuté avec le débogueur, nous avons une violation d'accès (Figure 11), car le programme veut accéder à l'adresse 0x00000008, mais celle-ci n'existe pas.

                                                                                                   Figure 11 : Violation d'accès

Le programme tente d'accéder à cette adresse car il y a 0x00000000 à 0x10101018.

                                                                                                   Figure 12 : Valeur ESI affectée

C'est la raison pour laquelle nous changeons la Pulvérisation de tas comme présenté dans la Figure 13 pour avoir une bonne adresse à 0x10101018 que nous pouvons contrôler. À ce stade, nous pouvons choisir l'adresse 0x10101040.

                                                                             Figure 13 : Pulvérisation précise de tas avec une modification

En suivant ce principe, nous continuons à modifier la Pulvérisation de tas pour exécuter le code d'assemblage jusqu'à ce que la fonction FUN_1234294b soit appelée.

Donc, à partir de maintenant, voici à quoi ressemble notre charge utile Pulvérisation de tas :

                                       Figure 14 : Pulvérisation précise de tas avec quelques modifications pour contourner tous les contrôles

Maintenant, nous sommes dans la fonction FUN_1234294b et nous voulons appeler la fonction FUN_104e3000 :

                                                                                                   Figure 15 : Appel FUN_104e3000

Malheureusement, comme nous pouvons le constater à la Figure 17, en déboguant pas à pas avec WINDBG, le programme sautera à 0x7a682a35 car ESI est égal à 0x00000000. Cependant, si le programme saute à cette adresse, il exécutera une instruction POP RET (Figure 16) alors que nous voulons exécuter les instructions de LAB_123429b2 dans la même fonction, donc nous ne voulons pas faire de saut.

                                                                           Figure 16 : Fonction LAB_123429b2 avec instructions POP RET
 

                                                                                               Figure 17 : Saut vers LAB_12342A35

Par conséquent, nous modifions la valeur à l'adresse 0x10101044 pour que le registre ESI soit différent de 0x00000000 afin d'éviter que le flux d'exécution n'exécute le saut non souhaité.

Par conséquent, nous pouvons constater à la Figure 18 que le programme veut accéder à l'adresse 0x101ffff0. Il nous faut donc adapter la Pulvérisation de tas comme illustré à la Figure 19 pour l'y autoriser et obtenir la valeur 0x10101010 à 0x101ffff0.

                                                                                             Figure 18 : Accès à l'adresse 0x101ffff0

 

                                                        Figure 19 : Adaptation de la pulvérisation de tas pour contrôler l'adresse 0x101ffff0


Maintenant, si nous inspectons le tas à l'aide de notre débogueur préféré, nous avons les données présentées dans la capture d'écran ci-dessous qui permettent de valider que notre Pulvérisation de tas est bien corrigée, car nous avons ajouté 101010101111111111 à la fin de la charge utile Pulvérisation précise de tas.

                                                                                                         Figure 20 : Contenu du tas

Nous sommes dans la fonction FUN_104e3000 et la fonction FUN_104e33c0 est à présent appelée. Notre chaîne fonctionne désormais !

Enfin, la fonction cible FUN_107bc8d6 est appelée et une violation d'accès est déclenchée.

                                                                                                      Figure 21 : Violation d'accès

En fait, la cause de la violation d'accès ci-dessus est déclenchée parce que le programme tente d'appeler une fonction à l'adresse 0x00000010.

Réalisation du contrôle du pointeur d'instruction

Ce que nous devons faire pour notre étape finale, c'est remplacer cette adresse par une autre que nous pouvons contrôler en adaptant notre charge utile.
Pour cette dernière modification (mais la dernière !), nous devons adapter la valeur de l'adresse 0x10101068 car le registre EAX contient ce dont nous avons besoin à cette adresse.

Dans la Figure 22, nous remplaçons cette valeur par 0x10101080 sachant que 0x10101080 + 0x10 = 0x10101090 (appel EAX+0x10) et nous avons ajouté 0x41414141 à cette adresse pour obtenir la valeur 0x41414141 dans notre registre de pointeur d'instruction.

                                                                                Figure 22 : Charge utile finale Pulvérisation précise de tas

À la Figure 23, nous pouvons voir que le pointeur d'instruction est désormais égal à 0x41414141. Nous avons donc réussi à écraser l'EIP avec notre valeur 0x41414141.

                                                                                         Figure 23 : Contrôle du pointeur d'instruction

À ce stade, techniquement, nous pouvons exécuter ce que nous voulons en contrôlant le flux d'exécution. Cependant, comme il s'agit de la vie réelle, le programme implémente certaines protections comme ASLR, DEP/NX...

Par conséquent, la deuxième partie de cet article expliquera comment contourner ces instructions pour obtenir l'exécution de code.

Nos experts répondent à vos questions

Des questions sur un article ? Besoin de conseils pour trouver la solution qui répondra à vos problématiques ICT ?

Autres articles de la catégorie Cybersécurité

Attaques DDoS au Luxembourg en 2023

Découvrez les statistiques des attaques DDoS détectées au Luxembourg en 2023 par POST Cyberforce.

Lire cet article

Publié le

15 février 2023

Attaques DDoS au Luxembourg en 2022

Découvrez les statistiques des attaques DDoS détectées au Luxembourg en 2022 par POST Cyberforce.

Lire cet article

Publié le

11 octobre 2022

Cybersécurité : à bord du SOC de POST, l’esprit serein

Recourir à un Security Operations Center (SOC) en tant qu’organisation permet de s’assurer d’avoir un œil en permanence sur l’activité opérée au niveau de ses systèmes d’information dans l’optique de réagir efficacement et rapidement à toute attaque ou anomalie.

Lire cet article

Publié le

12 juillet 2022