Retour aux articles

Démon Wi-Fi iOS : De la chaîne de format iOS au RCE zéro-clic

07 septembre 2021

Alors, que s'est-il passé avec iOS ?

Vous avez peut-être vu le récent bug dans iOS 14.0 à 14.4, qui a fait planter le service Wi-Fi en nommant un point d'accès d'une manière spécifique. Apple a qualifié ce bug de déni de service sur le service Wi-Fi, mais l'équipe de recherche Zecops [1] a démontré qu'il pouvait être exploité, provoquant un RCE, et plus précisément un RCE zéro-clic. Bien que leur article explique certains des détails de la vulnérabilité, nous avons voulu mener nos propres investigations. Nous nous sommes fait une bonne idée de la nature de la vulnérabilité, que nous détaillerons dans l'article qui suit.

Configuration de l'environnement de débogage

Cet article ne se contentera pas d'expliquer en quoi consiste cette vulnérabilité, il vous permettra également de mener vos propres recherches. Pour vous faciliter la vie, nous allons vous montrer ce qu'il faut faire pour déboguer le service Wi-Fi iOS. Les seules conditions requises sont d'avoir un iPhone (ou peut-être d'en simuler un), et un appareil fonctionnant sous MacOS (j'ai utilisé un Mac mini).

Préparation de l'iPhone

La chose la plus importante ici est de flasher la version iOS de l'iPhone vers une version vulnérable. Bien que le bug de la chaîne de format ne soit pas corrigé dans les versions allant d'iOS 14.0 à iOS 14.6, le formulaire zéro-clic n'existe que dans les versions 14.0 à 14.4, comme l'explique l'équipe de Zecops. Mes investigations ont été menées sous iOS v14.0, sur un iPhone 7+. Les images du firmware peuvent être trouvées facilement en ligne. Après avoir flashé le bon firmware sur l'iPhone, il faut le débrider afin d'établir une connexion SSH entre le dispositif MacOS et l'iPhone, ce qui permet de déboguer ses processus en cours. Le débridage unc0ver [2] a été utilisé pour mes recherches, mais n'importe quel jailbreak devrait faire l'affaire. Après cela, utilisez Cydia et installez les paquets SSH requis pour que SSH fonctionne sur l'iPhone. La connexion peut se faire par le réseau, mais nous avons connecté l'iPhone au Mac mini par USB et utilisé iproxy pour transférer les ports nécessaires. Si vous souhaitez utiliser cette technique, vous aurez besoin d'un port pour la connexion SSH et d'un port pour l'opération de débogage à distance. Mais encore une fois, ce n'est pas obligatoire, et vous pouvez simplement utiliser SSH sur le réseau sans câble.

Session de débogage à distance

Une fois que nous avions ma connexion SSH, nous devions configurer l'environnement de débogage. Pour cela, nous avons utilisé lldb sur le Mac mini, et debugserver sur l'iPhone, qui peut être installé avec Cydia. Ensuite, il nous a suffi d'utiliser debugserver, et d'attacher le bon PID (le nom du processus est wifid) pour démarrer l'écouteur. Notez que si le service wifid reste inactif trop longtemps à cause du processus de débogage, l'iPhone redémarre de lui-même, ce qui m'a parfois obligé à répéter le processus de débridage. Ce qu'il restait à faire, c'était d'exécuter la commande gdb-remote sur lldb pour démarrer la session de débogage.

Débogage à distance - debugserver et lldb

Vulnérabilité et analyse des causes profondes

Inversion du binaire

En utilisant l'article de Ghidra et Zecops, nous avons trouvé la chaîne de format à l'origine du crash :

Lors du deuxième appel à _objc_msgSend(), aucun format n'est défini, ce qui entraîne une vulnérabilité au niveau de la chaîne de format. Du point de vue du débogage, c'est là que vous voulez placer votre point d'arrêt. Cette fonction recherche les réseaux Wi-Fi existants dans la zone, et fonctionne toujours même si le téléphone est verrouillé.

C'est la raison pour laquelle, si le RCE est accompli, il ne nécessite aucune interaction de la part de l'utilisateur (zéro clic). Comme l'explique Zecops dans son article, cette chaîne de format est différente des chaînes habituelles. Puisqu'il utilise [NSString stringWithFormat:], propre à Apple, et puisque Apple a supprimé le support de «%n », nous ne pouvons pas utiliser ce dernier pour écrire dans la mémoire. Cependant, le service est écrit en Objective-C et nous pouvons utiliser «%@ » pour que la fonction essaie d'imprimer un objet Objective-C.

L'équipe de Zecops a eu une excellente idée sur la manière de contrôler le stack et donc de contrôler le flux d'exécution du code. Il s'agit essentiellement d'une attaque par inondation [3] qui diffuse des centaines de points d'accès. Grâce à cela, ces PA apparaîtront sur l'iPhone et éventuellement sur le stack. J'ai reproduit cette technique en utilisant airmon-ng pour configurer l'interface de surveillance, et mdk3 avec une liste de plusieurs SSID pour lancer l'inondation.

Provoquer le crash

Comme il s'agit d'une vulnérabilité de type chaîne de format, il suffit de créer un hotspot avec un SSID contenant une valeur du type «%x%x%x...%@ ». La longueur du SSID est limitée à 32 octets, nous n'en avons donc que la moitié pour nos caractères d'échappement. Avec XCode, vous pouvez analyser les journaux d'applications de l'iPhone en temps réel. Nous avons filtré les journaux avec le processus wifid et la chaîne « crash » pour trouver les journaux qui nous intéressaient, puis nous avons simplement créé un AP avec le nom indiqué ci-dessus.

Console de XCode - journaux de crash wifid

Nous avons ici notre crash, le code d'erreur étant « KERN_INVALID_ADDRESS ». Afin de comprendre ce qui s'est passé, nous avons simplement lancé ma session de débugage. Comme mentionné ci-dessus, nous avons utilisé la session de débogage à distance avec le serveur de déougage fonctionnant sur l'iPhone et lldb sur le Mac mini.

Mise en place d'un point d'arrêt exploitable

Comme le Kernel ASLR (KASLR) est actif, nous avons besoin d'un décalage que nous pouvons ensuite appliquer à une adresse de base afin de calculer l'adresse de l'instruction à laquelle nous voulons placer notre point d'arrêt. Pour obtenir le décalage, nous avons simplement utilisé Ghidra et sommes revenus à l'instruction à l'origine de la vulnérabilité. L'adresse de base de la section __TEXT dans Ghidra est 100000000. Il nous a suffi de soustraire 1000f7fcc de 100000000 (obtenant ainsi f7fcc).

Ghidra - Obtenir le décalage de l'appel vulnérable

Maintenant, pour définir le point d'arrêt, il nous a suffi de démarrer la session de débugage, nous avons utilisé la commande wifid des sections de vidage d'image sur lldb pour obtenir l'adresse de base de la section __TEXT.

lldb - Obtention de l'adresse de base de la section __TEXT

Il ne nous restait plus qu'à ajouter le décalage à cette adresse et à définir notre point d'arrêt.

lldb - Mise en place du point d'arrêt

Pour gagner du temps, nous avons téléchargé le binaire wifid directement sur le Mac mini afin de pouvoir exécuter lldb. La raison en est que si nous définissons le point d'arrêt une fois, même si l'adresse change après le redémarrage du processus, lldb va trouver lui-même la nouvelle adresse en se basant sur le décalage, et nous n'aurons pas à répéter tout le processus à nouveau.

Contrôler le flux d'exécution

Une fois le point d'arrêt fixé, il ne nous restait plus qu'à déclencher le crash pour voir ce qui se passait. Avant cela, nous avons lancé l'attaque par inondation de balises pour essayer de pulvériser des données sur le stack. Zecops a mis au point un script python conçu pour lldb qui vérifie automatiquement le stack pour trouver des traces des SSID pulvérisés. Cependant, nous n'avons pas réussi à faire fonctionner le script, mais cela est dû à ma configuration lldb et nous n'avons pas pu résoudre le problème dans le temps limité dont nous disposions.

Nous avons essayé de scanner manuellement le stack, mais nous n'avons rien trouvé non plus, pas de SSID. Ainsi, au lieu de regarder le stack, nous avons regardé dans le tas, et c'est là que nous avons trouvé mes points d'accès Wi-Fi. La pulvérisation du stack/du tas était censée être un moyen d'essayer de contrôler le flux d'exécution du code en contrôlant les registres. Cela fonctionne mais c'est un peu aléatoire à cause des autres réseaux autour, et à cause de la disposition aléatoire des réseaux dans le stack/le tas. Reportez-vous à l'article de Zecops pour obtenir plus de détails sur la pulvérisation du stack.


quelques AP trouvés dans le tas

Ce que nous contrôlions entièrement, cependant, c'est le registre x15.

lldb - DEADBEEF en x15

Sur la capture d'écran ci-dessus, nous pouvons facilement identifier la raison du crash. En fait, le programme tente d'accéder à l'adresse qui se trouve 28 octets plus loin que l'adresse contenue dans x2, et se plante parce que cette adresse n'existe pas.
Pour contrôler x15, nous avons utilisé la pulvérisation de balise et créé un autre hotspot avec le nom suivant :

FEEBDAED%x%x%x%x%x%x%x%x%x%x%x%@

L'entrée placée avant les caractères d'échappement sera placée systématiquement dans x15. C'est la seule entrée que nous avons réussi à contrôler entièrement. Nous avons également constaté qu'en déplaçant la chaîne FEEBDAED vers la droite, la valeur dans x15 faisait partie des PA existants qui étaient diffusés (pas par la pulvérisation, nous faisons référence aux réseaux Wi-Fi réels). Nous ne savons toujours pas si ces données sont lues depuis le stack ou depuis le tas.

Il y a une possibilité de contrôler également x9 et x10, cependant nous n'avons pas trouvé le moyen de le faire puisque la pulvérisation ne semble pas affecter le stack dans ce cas, et nous l'avons seulement vu à travers les captures d'écran de Zecops (voir ci-dessous). Le contrôle des deux registres semble donner la possibilité de contrôler ensuite une partie de x2.

Zecops - Valeurs de registre après pulvérisation + SSID fabriqué

L'équipe de recherche de Zecops a découvert que x9 pointe vers le premier membre de la structure de données Object.
Pour obtenir le RCE, il faut faire passer un objet Objective-C valide par la pulvérisation pour contrôler x9. Ensuite, l'objet sera passé dans __objc_msgSend() et une exécution de code arbitraire peut être réalisée. (Consultez l'article de Zecops pour voir le schéma du flux d'exécution souhaité).

Atteindre le RCE et découvrir des vulnérabilités supplémentaires (pas encore publié mais déclenchable)

Pour cette recherche, mon temps était limité. Nous n'avons pas pu aller aussi loin que nous l'aurions fait avec plus de temps. Un peu plus d'inversion et de tests doivent être effectués, mais nous sommes assez convaincus que cette vulnérabilité est exploitable.

Cependant, pas par le biais de ROP, puisque nous n'avons pas trouvé de gadgets adaptés à ROP dans le binaire wifid lui-même. JOP serait nécessaire pour exploiter cette vulnérabilité si un contrôle du flux d'exécution est en fait possible, et les charges utiles JOP devraient également faire partie de la pulvérisation AP. KASLR pose toujours problème, mais la seule chose utile que nous ayons trouvée est que lorsque les journaux (disponibles sur XCode) impriment les noms des AP, et lorsqu'ils essaient d'imprimer le SSID malveillant, il remplace les caractères d'échappement «%x » par les adresses réelles, ce qui provoque une fuite de mémoire. Cela ne se produit que lorsque le SSID malveillant fait partie de la pulvérisation et n'est pas un hotspot individuel. Donc, techniquement, si vous avez besoin d'obtenir une adresse, vous pouvez créer plusieurs SSID et les pulvériser pour provoquer une fuite des adresses mémoire.

XCode - Fuite de mémoire

Lorsque nous avons découvert le bug de Format String dans Ghidra, nous pensions avoir la même fonction que celle présentée dans l'article de Zecops. Cependant, lorsque l'on mettait un point d'arrêt, wifid se plantait avant d'atteindre le point d'arrêt.

Cela m'a permis de découvrir que deux autres fonctions étaient vulnérables au même bug de chaîne de format. Lors de la mise à jour de l'iPhone vers iOS 14.7 (où le bug a été corrigé), nous avons remarqué que les trois fonctions (l'originale plus les deux autres) étaient corrigées.

Nous ne sommes pas sûrs de la raison pour laquelle trois fonctions effectuent l'analyse AP, mais Apple a réussi à les patcher toutes, car elles étaient également exploitables.


Références

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

01 février 2024

Se prémunir des attaques DDoS en bloquant le trafic illégitime

De mois en mois, on voit nombre d’attaques dites de déni de service (DDoS) se multiplier au Luxembourg. Des cybercriminels détournent des objets connectés pour envoyer du trafic illégitime vers des organisations et saturer leur connectivité. Ces attaques ont pour effet de dégrader le niveau de service ou de paralyser l’activité. Pour aider les acteurs luxembourgeois à s’en prémunir, POST a mis en place des solutions permettant d’écarter (en temps réel si cela est justifié) le trafic illégitime avant qu’il n’atteigne les systèmes de l’organisation.

Lire cet article

Publié le

19 décembre 2023

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