Retour aux articles

Exploitation du CVE-2018-5093 sur Firefox 56 et 57 – PARTIE 2 : obtention de l’exécution de code

19 septembre 2022

Contexte

L'objectif de ce projet est d'obtenir un accès initial pendant les exercices Red Team ou de simulation d'adversaire en obtenant une exécution de code à travers l'exploitation d'une vulnérabilité de l'overflow entier sur Firefox 56 et 57.
Ce scénario se déroule en deux parties. La première partie décrit comment exploiter la corruption de mémoire pour contrôler le pointeur d'instruction en chaînant plusieurs fonctions.
Ce second volet explique comment vaincre des atténuations en utilisant des primitives R/W arbitraires pour atteindre notre objectif final : un exploit entièrement fonctionnel.
Aucun scénario ni 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 et d'autres ressources fournies dans cet article.
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 et le ROP.

Développement de l'exploit

Le premier défi consiste à utiliser une adresse disposant d'autorisations d'exécution afin de pouvoir activer l'exécution de code.
Toutefois, le tas ne dispose pas de ces autorisations du fait de la protection DEP et nous ne sommes pas en mesure d'effectuer une réutilisation de code directe en raison de la protection ASLR qui randomise les adresses dont nous avons besoin.
Dans notre situation, la protection ASLR randomise toutes les adresses de base telles que celles des bibliothèques, de l'exécutable, de la pile et du tas.
De plus, avec la protection DEP, seules des portions de code de l'exécutable et des bibliothèques disposent d'un indicateur d'exécution. Malheureusement, ces portions n'étant pas accessibles en écriture, nous avons besoin d'obtenir au préalable ces primitives pour vaincre la protection DEP.
En conséquence, une solution permettant d'exécuter le code arbitraire consiste, dans un premier temps, à trouver une façon de contourner la protection ASLR afin de constituer une chaîne ROP.
Puis, à l'aide de cette chaîne ROP, l'étape suivante consiste à contourner la protection DEP en modifiant les autorisations sur le tas puis en exécutant notre shellcode.
 

Contournement de la protection ASLR

Une façon de contourner la protection ASLR consiste à obtenir une fuite de mémoire d'une adresse pour ensuite être en mesure de calculer des décalages et identifier des adresses de fonction qui permettront de mettre en place ultérieurement notre chaîne ROP.

Fuite de mémoire

De prime abord, il est possible de trouver une fuite dans la fonction get() et plus particulièrement dans la sous-fonction putNewInfallibleInternal().
En effet, dans la Figure 1, le programme déplacera la valeur du registre EAX vers une adresse stockée dans le registre ESI.
Par ailleurs, ce qui est également intéressant, c'est que nous contrôlons les registres ESI et EAX qui contiennent une adresse d'objet située dans le tas.
Nous pouvons alors déréférencer une adresse d'objet par l'intermédiaire de notre Pulvérisation de tas avec l'ESI pointant vers une adresse au sein du tas.

Voici les détails de la Figure 1 : fonction putNewInfallibleInternal


Qui plus est, cette adresse pointe vers une adresse de la bibliothèque xul. Par la suite, avec cette adresse, nous pouvons calculer l'adresse de base de la bibliothèque xul, puis utiliser le code xul dans une chaîne ROP.

Pour atteindre cette fonction putNewInfallibleInternal, nous devons constituer la chaîne d'appels suivante.

Voici la Figure 2 : chaîne d'appels jusqu'à la fonction putNewInfallibleInternal

En fait, la fonction putNewInfallibleInternal est appelée par la fonction putNewInfallible comme le montre la Figure 3 avec son code d'assemblage dans la Figure 4.

Voici la Figure 3 : fonction putNewInfallible


Voici la Figure 4 : code d'assemblage putNewInfallible

Ensuite, la fonction putNewInfallible est appelée par la fonction putNew comme le montre la Figure 5 avec son code d'assemblage dans la Figure 6.

Voici la Figure 5 : fonction putNew


Voici la Figure 6 : code d'assemblage putNew


Enfin, la fonction putNew est appelée par la fonction getExportedFunction comme le montre la Figure 7 avec son code d'assemblage dans la Figure 8.

Voici la Figure 7 : fonction getExportedFunction


Voici la Figure 8 : code d'assemblage getExportedFunction


Adaptation de la Pulvérisation de tas

Toutefois, nous devons contourner certaines conditions pour atteindre la fonction cible que nous pouvons voir par exemple dans la Figure 7. Pour ce faire, nous devons appliquer la même méthode que dans la première partie du scénario. Ensuite, nous avons effectué la Pulvérisation de tas suivante dans la Figure 9, ce qui nous permet d'exécuter la fonction cible et de récupérer l'adresse d'objet.

Voici la Figure 9 : Pulvérisation de tas pour provoquer une fuite de mémoire dans les adresses d'objet


Néanmoins, nous devons avoir une adresse qui se termine par 0xFFFF0 et 0xFF000 pour remplir certaines conditions et poursuivre l'exécution du programme sans provoquer de crash. C'est la raison pour laquelle nous devons créer un autre type de Pulvérisation de tas, moins fiable, mais qui nous permettra d'obtenir ce genre d'adresse.
Pour réaliser cette nouvelle Pulvérisation de tas, nous avons créé un tableau de chaînes contenant nos données (cf. Figure 10).

Voici la Figure 10 : Pulvérisation de tas avec tableau pour obtenir une adresse se terminant par 0xFF000


Dans la mesure où nous devons ensuite récupérer l'adresse dans JavaScript pour la prochaine étape et obtenir l'adresse de base xul en calculant les décalages, cette fonction get doit être menée à bien sans crash.
Lorsque la fonction get est achevée, nous pouvons voir qu'une adresse d'objet est inscrite dans notre Pulvérisation de tas dans la Figure 11. Dans la mesure où cette adresse est dans notre Pulvérisation de tas, nous pouvons extraire l'adresse d'objet dans JavaScript en employant une méthode semblable à celle décrite dans la Figure 12.

Voici la Figure 11 : Pulvérisation de tas avec adresse d'objet


Voici la Figure 12 : Extraction d'adresse d'objet avec JavaScript


Nous disposons maintenant d'une adresse d'objet et lorsque nous regardons ce que contient l'adresse dans la Figure 13, nous constatons qu'une adresse 0x7AE5BBE0 se situe dans l'adresse 0x0858907C. Après analyse, nous concluons que cette adresse correspond à une adresse d'objet statique dans la bibliothèque xul. Donc, si nous pouvons extraire cette adresse avec JavaScript, nous serons ensuite en mesure de calculer l'adresse de base de la bibliothèque xul.

Voici la Figure 13 : objet contenant une adresse intéressante


Une fois que nous avons correctement achevé la fonction get, nous pouvons attribuer l'objet restitué par la fonction get à une variable. Nous nommerons cette variable tabexp et l'utiliserons ultérieurement avec la fonction set.
En fait, la fonction set présente la même vulnérabilité que la fonction get comme le montre la Figure 14.

Voici la Figure 14 : fonction setImpl


Contrairement à la fonction get, la fonction set vise à ajouter une nouvelle fonction à l'objet Table. Donc, si nous donnons comme arguments le même index que la fonction get et la variable tabexp, nous verrons que la fonction nous permettra entre autres d'extraire l'adresse de l'objet statique.

Pour utiliser la fonction set sans provoquer de crash, nous devons modifier la Pulvérisation de tas existante (Figure 15) pour réussir les contrôles et ensuite trouver l'adresse afin de pouvoir contourner la protection ASLR.

Voici la Figure 15 : Adaptation de la Pulvérisation de tas pour utiliser la fonction set


Nous pouvons voir dans la Figure 16 que la fonction set recherchera une valeur dans notre Pulvérisation de tas à l'adresse 0x10101094. C'est la raison pour laquelle nous avons ajouté l'adresse d'objet à l'adresse 0x10101094 dans notre Pulvérisation de tas (Figure 15).
Après chaque exécution, l'adresse d'objet changera et sera différente, mais elle référencera le même objet.

Voici la Figure 16 : aperçu WINDBG, obtention de l'adresse d'objet

De plus, le programme recherchera l'adresse d'objet statique à l'adresse d'objet plus 4 (Figure 17). Toutefois, l'adresse d'objet statique se trouve à l'adresse d'objet plus 0xC. C'est la raison pour laquelle nous modifions l'adresse d'objet avant de l'ajouter à la Pulvérisation de tas en remplaçant 0 par 8 au niveau du dernier chiffre de l'adresse. Après cela, le programme sera en mesure d'obtenir l'adresse d'objet statique comme indiqué à la Figure 17.

Voici la Figure 17 : Aperçu WINDBG, obtention de l'adresse d'objet statique


Ensuite, la fonction set placera l'adresse d'objet statique dans notre Pulvérisation de tas (Figure 18).

Voici la Figure 18 : Aperçu WINDBG, placement de l'adresse d'objet statique dans la Pulvérisation de tas


Ensuite, nous extrayons l'adresse de la Pulvérisation de tas, puis calculons l'adresse de base de la bibliothèque xul comme dans la Figure 19.

Voici la Figure 19 : Calcul de l'adresse de base de la bibliothèque xul


Nous sommes donc en mesure de trouver l'adresse de base xul qui nous permet d'utiliser toutes les fonctions dans la bibliothèque xul et, au final, de vaincre la protection ASLR.

Vaincre la protection DEP

Une façon de contourner la protection DEP consiste à assembler une chaîne ROP pour appeler la fonction VirtualProtect afin de modifier l'indicateur d'autorisations sur le tas, ce qui nous permettra d'exécuter ensuite un shellcode à l'aide de notre Pulvérisation de tas.
Toutefois, avant d'assembler une chaîne ROP, nous devons contrôler la pile et donc, le pointeur ESP. Nous devons donc contrôler le pointeur EIP, puis nous en servir pour atterrir quelque part dans la bibliothèque xul, ce qui nous permettra d'exécuter un code d'assemblage afin de contrôler le pointeur ESP. Pour ce faire, nous avons utilisé l'outil ROPgadget pour générer les gadgets de la bibliothèque xul. Grâce à cela, nous sommes en mesure de trouver une adresse correspondant au code d'assemblage que nous souhaitons exécuter.

Falsification de la pile

Dans la première partie de ce scénario, nous montrons comme contrôler le registre EIP en chaînant des appels de fonction. Nous utiliserons cette technique ici pour effectuer une réutilisation de code « magique » afin de falsifier la pile et de prendre le contrôle du pointeur ESP.
Nous avons trouvé un gadget qui envoie la valeur d'EAX – un registre que nous pouvons contrôler – dans le pointeur ESP (Figure 20). Cette adresse présente un décalage de 0x17E946C et l'adresse de base de la bibliothèque xul est 0x78870000.

Voici la Figure 20 : Code d'assemblage pour contrôler le pointeur ESP


Pour atteindre cet emplacement, il nous suffit de remplacer le pointeur ESP par sa valeur. Ensuite, nous pouvons commencer à assembler la chaîne ROP et enfin contourner la protection DEP.
Afin que la pile ait suffisamment d'espace, nous utilisons un gadget (Figure 21) pour modifier le pointeur ESP de 0x101010E0.

Voici la Figure 21 : Gadget ROP pour modifier le pointeur ESP


Chaîne ROP

À l'aide de GHIDRA, nous avons identifié où la fonction VirtualProtect est appelée dans la bibliothèque xul. Nous avons donc trouvé une adresse où le programme envoie les arguments sur la pile puis appelle la fonction VirtualProtect (Figure 22).

Voici la Figure 22 : appel de la fonction VirtualProtect dans la bibliothèque xul


La fonction VirtualProtect requiert quatre arguments : lpAddress, dwSize, flNewProtect, lpflOldProtect.
lpAddress, le premier argument (EDI), correspond à l'adresse initiale. La fonction modifiera les protections depuis cette adresse jusqu'à l'adresse plus dwSize (ECX).

Le second argument porte sur la taille des octets que la fonction doit modifier. Le troisième argument flNewProtect (EAX) constitue l'indicateur de protection, tandis que le dernier argument (EDX) correspond à une adresse avec droits d'écriture, car le programme écrira l'ancienne protection à cette adresse.

À l'aide du premier gadget, nous avons assigné la valeur 0x10101010 à ECX (Figure 23) afin de modifier la valeur EDX en 0x10101010 (Figure 24).

Voici la Figure 23 : Gadget ROP 1 pour assigner 0x10101010 à ECX


Voici la Figure 24 : Gadget ROP 2 pour assigner 0x10101010 à EDX et 0x641FF000 à ESI


Avec le gadget présenté à la Figure 24, lpflOldProtect est égal à 0x10101010. Ensuite, nous avons assigné 0x641FF000 à ESI afin d'attribuer cette valeur à EDI (Figure 25).
Le registre EDI dans la Figure 25 reprendra 0x641FF000 qui correspond à l'argument lpAddress. Nous nous servons de ce gadget pour assigner en même temps 0x1000 à ECX qui correspond à dwSize.

Voici la Figure 25 : Gadget ROP 3 pour assigner 0x641FF000 à EDI et 0x1000 à ECX


Les deux gadgets suivants (Figures 26 et 27) servent à assigner 0x40 (PAGE_EXECUTE_READWRITE) à EAX qui permet à l'indicateur flNewProtect d'avoir un droit d'exécution, de lecture et d'écriture.

Voici la Figure 26 : Gadget ROP 4 pour assigner 0x4000 à EAX


Voici la Figure 27 : Gadget ROP 5 pour assigner 0x40 à EAX et 0x1010111C à EBP


En résumé, tous ces gadgets servent à déterminer les arguments de la fonction VirtualProtect, puis à appeler celle-ci.
Ensuite, nous modifions la Pulvérisation de tas pour y inclure notre chaîne ROP comme le montre la Figure 28.

Nous ajoutons l'adresse du shellcode au bout de la chaîne ROP, l'exécutons et voilà, le tour est joué ?

Voici la Figure 28 : Pulvérisation avec la chaîne ROP


En fait, nous avions précédemment ajouté le shellcode à notre Pulvérisation de tas pour le positionner à l'adresse 0x641FF200 (Figure 29).

Voici la Figure 29 : Shellcode ajouté à la Pulvérisation de tas finale


Prochaines étapes

Le shellcode utilisé dans cet exploit est un reverse shell au format « js_le » (merci msfvenom ?).
Comme vous pouvez le voir à la Figure 30, l'exploit fonctionne et la reconnexion à l'hôte est effectuée, mais la connexion est rapidement interrompue.

Voici la Figure 30 : Connexion interrompue


En réalité, le shellcode ne peut pas être correctement exécuté à cause du bac à sable de Firefox qui restreint la création du processus. Nous devons alors pouvoir la contourner avec quelques primitives SBX...

Voici la Figure 31 : pwned


Enfin, nous pouvons exécuter un code arbitraire et obtenir un exploit qui fonctionne sans SBX à ce stade – il faut le désactiver dans le menu « about:config ».
Pour finaliser cet exploit et obtenir un accès initial à Firefox 57 dans tous les cas, nous devons le chaîner à une autre vulnérabilité qui permet finalement d'échapper au bac à sable, comme le CVE-2022-1529

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 2024

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

Lire cet article

Publié le

31 mars 2024

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