Comment Pegasus vole... les données

Apple n’a cessé de vanter et de vendre ses systèmes d’exploitation comme les plus sûrs au monde. L’affaire Pegasus démontre, au contraire, que loin d’être invulnérables, iOS et MacOS peuvent être attaqués, avec des conséquences lourdes sur la vie privée de leurs utilisateurs. Si la plupart de ces failles sont demeurées secrètes, certains éléments publiés permettent de comprendre comment Pegasus réussit à s’infiltrer jusqu’au cœur de smartphones réputés imprenables. Article paru dans L'Informaticien n°199 (septembre 2021).

Quand on demande aux utilisateurs avertis quels sont les systèmes d’exploitation les plus sûrs qu’ils connaissent, on peut parier qu’ils répondront soit MacOS, soit Linux. Rares sont ceux qui songerait à citer Windows, a priori. Il faut dire que le produit phare de Microsoft n’a cessé, au long des années, de donner une piètre image de ses performances, rythmées par les nombreux bugs et autres écrans bleus qui furent – et sont parfois encore – le quotidien de ses utilisateurs.

Il faut cependant faire attention, car nous avançons ici en terrain miné. La mauvaise réputation de Windows résulte avant tout de son instabilité. Certes, le système d’exploitation compte de nombreuses failles; mais ce que les utilisateurs subissent au quotidien relève plus du syndrome du crash aléatoire que de la cyberattaque – même si, malheureusement, ces dernières – les ransomware, entre autres – tendent à se multiplier. Il convient de soigneusement distinguer instabilité et sécurité. Un système peut crasher toutes les cinq minutes, tout en garantissant une sécurité absolue. La réciproque est également possible : un système qui ronronne se révèlera n’être qu’une vulgaire passoire.

Aux yeux du profane, la plupart du temps ces deux notions se confondent : stabilité rime avec sécurité. Cette opinion ne repose sur aucun fondement factuel, juste une sensation. Le discours marketing d’Apple se nourrit de cette sensation de sécurité faussement éprouvée par les utilisateurs de Mac et d’iPhone, qu’il n’hésite pas à qualifier de « machines les plus sûres du monde ». Le même sentiment d’impunité affecte également, pour d’autres raisons, les utilisateurs de Linux. Il est vrai que ces deux systèmes ont jusqu’ici été relativement peu ciblés par les attaques; il ne faudrait cependant pas voir dans cette immunité une conséquence de la qualité de leur code, mais plutôt le constat d’une réalité économique : avec des parts de marché assez faibles, le bénéfice tiré d’éventuelles cyberattaques sur ces plates-formes ne compense pas l’investissement économique, assez lourd, nécessaire à l’identification puis l’exploitation des vulnérabilités. Mais les choses changent, et l’affaire Pegasus en est un bon exemple.

L’exemple de la vulnérabilité CVE-2016-4656

On peut se demander comment, en l’espace de quelques cycles CPU, une application malveillante arrive à prendre le contrôle total d’un téléphone tant vanté pour sa sécurité et sa robustesse, pour le transformer en parfaite petite «balance»? Si le mécanisme de pénétration des versions les plus récentes de Pegasus n’a pas été rendu public, certains groupes de white hackers ont analysé des failles par lesquelles ce même logiciel avait déjà réussi, il y a cinq ans, à forcer les barrières érigées par les développeurs d’Apple. L’exemple le mieux documenté est lié à la mise à jour iOS 9.3.5 de 2016, correspondant à la faille dénommée CVE-2016-4656. Apple ne publie jamais les détails de ses patches, pour éviter que des groupes malveillants ne profitent des informations avant que tous les utilisateurs aient eu le temps d’installer un correctif. A posteriori cependant, il est possible de savoir quels éléments du noyau ont été modifiés en comparant les deux versions binaires successives. En principe, le binaire du noyau ne devrait pas être lisible. Effectivement, celui d’iOS 9 était protégé par un triple mécanisme : le binaire était crypté, et seul le firmware savait à quel endroit se trouvait la clef de décryptage (1). De plus, à chaque mise en route du téléphone, le noyau se plaçait à une adresse RAM aléatoirement choisie : le décalage entre l’adresse théorique du noyau et son adresse réelle est appelée «KASLR». Enfin, le noyau résidait évidemment en mémoire superviseur, à laquelle les applications n’ont normalement pas accès.

Alors, comment faire ? En pratique, le mécanisme de décryptage du noyau d’iOS 9 avait une faiblesse : il décryptait toujours le code au même endroit en mémoire superviseur (kernel cache), puis copiait le résultat à l’adresse définitive une fois le KASLR déterminé. Il suffisait donc de gagner accès à l’espace superviseur pour récupérer non seulement le code du noyau, mais les symboles qui allaient avec et qui indiquaient où résidait chaque fonction. Gagner cet accès, quitte à lire le noyau octet par octet, n’était pas très complexe, comme le prouve la suite. Une liste de failles plus récentes, dont certaines permettent de calculer le KASLR, est disponible sur le blog du projet Zéro de Google : https://googleprojectzero. blogspot.com.

La plupart de ces failles utilisent des mécanismes de bas niveau du noyau, une tâche simplifiée par le fait que Apple publie une grande partie de ses noyaux en Open Source sous le nom XNU. Comme toute arme à double tranchant, la publication du code permet également à des auditeurs externes de repérer des failles avant que celles-ci ne soient exploitées.

UNE PEGASUS 01

Extrait du blog du projet groupezero de Google. Ce dernier publie régulièrement de nouvelles failles de sécurité sur les différents systèmes et la manière de les exploiter ou de les corriger.

Deux failles différentes dans une seule fonction

Dans le cas de la CVE-2016-4656, le mécanisme de bas niveau utilisé exploite deux erreurs dans la fonction OSUnserializeBinary. Cette dernière sert à créer en mémoire une instance d’un objet de type conteneur (liste, dictionnaire…) à partir d’une représentation binaire – objet dit sérialisé. La première vulnérabilité réside dans l’absence de contrôle de la longueur des valeurs de type kOSSerializeNumber, autrement dit, de la longueur utilisée pour stocker un entier dans l’objet sérialisé. Cette omission permet de construire une instance sérialisée malveillante comportant une entrée kOSSerializeNumber de longueur quelconque.

Il suffit d’utiliser cette pseudo-instance dans un appel noyau pour ouvrir une fenêtre d’accès à la mémoire superviseur qui va permettre de casser le KASLR. On utilise pour cela la fonction io_service_open_extended. Elle n’est pas documentée par Apple (API privée), mais apparaît dans le code source de XNU, qui, rappelons-le, est open sourced. Ces API privées n’implémentent généralement pas le même niveau de sécurité que les API publiques.

Une fois l’instance frauduleuse créée et enregistrée par le noyau, il suffit de lire ses propriétés à l’aide d’une autre fonction privée (io_registry_entry_get_property_bytes).

Cette fonction se fie aux tailles des sous-instances spécifiées dans l’instance : si celles-ci sont anormalement grandes, la fonction renverra un nombre quelconque d’octets de la pile, où résident les variables dynamiques… mais aussi les adresses de retour des appels aux fonctions. Or, puisque nous avons accès à la table des symboles du noyau, nous connaissons l’adresse de base de chaque fonction. Récupérons, dans les données «fuitées», l’adresse de la fonction appelante modifiée par le KASLR, et nous en déduisons par une simple soustraction la valeur de ce dernier et « bingo !»

Il reste maintenant à prendre le contrôle du système. Facile ! C’est l’objet de la deuxième partie de la vulnérabilité CVE-2016- 4656. Celle-ci exploite une faille appelée « utilisation après libération » : autrement dit, un accès illicite à la mémoire utilisée par une instance alors que cette dernière est censée avoir été effacée. Ce bug va permettre de créer une instance truquée qui va servir à effectuer un su root sans vérification.

Dans le détail, le code d’OSUnserializeBinary devrait libérer les espaces mémoire provisoires utilisés pour construire au fil de l’eau les instances des sous-objets du conteneur et invalider toutes les références à ces zones. Mais dans le cas d’un sous-objet de type chaîne, si le code libère correctement la mémoire (free), il garde une référence valide sur la zone libérée (pas d’appel à release). Là-dessus, la politique de l’allocateur de mémoire de MacOS/ iOS (appelé zalloc) consiste à ré-allouer prioritairement les derniers blocs libérés. En créant une fausse instance sérialisée contenant une chaîne quelconque de taille ad-hoc, puis juste derrière une instance de type raw data remplie de zéros, nous allons conduire iOS à créer une fausse instance d’objet chaîne vide, sur laquelle nous garderons un pointeur valide.

UNE PEGASUS 02

Le code fautif de la fonction OSUnserializeBinary. Comme on le voit, à aucun moment le paramètre len ne fait l’objet d’un contrôle de pertinence – il peut donc prendre n’importe quelle valeur. [Source : https://jndok.github.io]

Prise de contrôle de la page 0

Qu’allons-nous faire de cette instance vide ? Les ABI des compilateurs C++ indiquent que les adresses des méthodes virtuelles se trouvent dans un espace appelé vtable, qui réside en tête de la mémoire dédié à l’instance. Le cas qui nous intéresse ici est celui de la méthode virtuelle retain (nous verrons pourquoi plus loin), héritée de OSObject, et utilisée pour incrémenter le compteur de référencement d’une instance. Il est facile de déterminer à quel offset de la vtable se trouve l’adresse de la méthode retain (il suffit pour cela d’examiner une instance de la classe de base). Ici, on trouve 32. Comme notre instance ne contient que des zéros, cela signifie que, sur appel de la méthode retain, le système d’exploitation se branchera à l’adresse indiquée à la position mémoire 0 (celle de la vtable) + 32 (offset de retain) = 32. À nous d’écrire à cette adresse un pointeur vers le code que nous souhaitons voir exécuter par le noyau.

En règle générale, ce scénario d’attaque ne fonctionne pas, car la plupart des systèmes d’exploitation interdisent l’accès à la « page 0 », qui contient les adresses virtuelles de 0 à 4096(2). Mais la version 32-bit d’iOS n’implémente pas cette politique, pour des raisons de compatibilité. Un code très simple permet donc d’écrire n’importe quelle valeur dans la page 0, à condition de le compiler en mode 32-bit et d’utiliser l’option de compilation -pagezero_size,0.

À l’adresse 32, nous aurons 0. Nous allons donc placer à l’adresse 0 un minuscule bout de code (gadget) qui ira écraser le pointeur de pile (RSP), puis exécutera l’instruction ret et ainsi transférera l’exécution dans une zone de mémoire plus vaste contenant la suite de notre code. Celui-ci élèvera les privilèges de la tâche courante, simulant ainsi un su root. Cette tactique de transfert d’exécution s’appelle «attaque ROP» (Return-Oriented Programming).

Dernière étape : faire en sorte que notre piège soit déclenché par un code exécuté en mode superviseur – requis si nous voulons modifier la structure de description de la tâche, qui contient, entre autres, son UID. OSUnserializeBinary nous offre cette possibilité : les sous-objets d’un conteneur peuvent en effet contenir des références à d’autres sous-objets du même conteneur. Il va donc suffire d’ajouter une telle référence sur l’instance chaîne vérolée pour déclencher automatiquement un appel à sa méthode retain. Bingo de nouveau! Il ne reste plus qu’à appeler une fonction du noyau qui prendra comme argument l’objet sérialisé forgé, par exemple io_service_get_matching_ services_bin, pour activer le piège. Au retour de l’appel, le processus courant aura son UID égal à 0 (root), donc la faculté d’accéder librement à toutes les ressources système.

Un marketing ennemi de la sécurité ?

Comme on le voit, il suffit d’un minuscule trou de souris pour ouvrir les portes du système en grand ! Prétendre qu’un système d’exploitation, c’est-à-dire souvent plusieurs dizaines, voire centaines de milliers de lignes de code, ne contient aucune vulnérabilité de ce type, c’est aussi prétentieux que d’affirmer ne jamais avoir conduit au-dessus de la vitesse limite. Ces vulnérabilités ne sont pas évidentes à identifier, mais il semble que NSO, la société israélienne qui édite le spyware Pegasus, n’a pas lésiné sur les moyens pour constituer une équipe d’auditeurs compétents capable de les localiser. Plus compétents que ceux d’Apple, en tout cas, si l’on en croit l’historique de la correction de cette vulnérabilité : le premier patch publié ne réglait que partiellement le problème, il a dû être suivi dans la foulée d’un second correctif. Examiner un code pour en déceler les faiblesses nécessite du temps et de l’expertise. Si Apple a les moyens de s’offrir l’expertise, depuis la mort de Steve Jobs le géant californien a fait le choix marketing regrettable de sortir une version majeure de ses systèmes d’exploitation tous les ans, au lieu de tous les deux ans auparavant.

Résultat : non seulement les nouveautés proposées sont souvent cosmétiques et décevantes, mais les équipes de développeurs font face à une pression accrue, ont moins de temps pour tester et relire le code, dont la qualité se détériore. En face de cela, rien ne semble indiquer que Apple a renforcé ses équipes d’auditeurs sécurité, et la société ne peut pas toujours compter sur la bonne volonté d’auditeurs externes pour jouer les pompiers.

On pourra aussi incriminer le choix douteux d’incorporer dans le noyau du code C++ (IOKit) au milieu d’une base robuste écrite en C. La vulnérabilité que nous venons de discuter, qui exploite directement la vtable, n’aurait pas existé si le code de l’IOKit avait été écrit en C, qui n’est pas un langage orienté objet (3).

UNE PEGASUS 03

Exemple de stockage en mémoire de deux objets C++, B et C, le second héritant du premier. La recherche du code des méthodes s’effectue au travers d’une table de pointeurs de fonctions appelée vTable. La fonction qux, héritée mais non-modifiée, n’est pas dupliquée : les pointeurs des vTables de B et de C sont identiques.

Des contre-mesures inefficaces

La découverte de traces de Pegasus dans des iPhones tournant la dernière version d’iOS indique clairement que toutes les failles n’ont pas été corrigées, ou que d’autres ont été ouvertes ailleurs, possiblement dans les applications pré-installées. On ne pourra qu’admirer – d’un point de vue théorique – le travail des analystes de NSO, car, contrairement au code du noyau, qui est public, celui des applications n’est pas divulgué, ce qui complique singulièrement la découverte des vulnérabilités.

Face à ces « effractions » répétées, les contre-mesures prises par Apple relèvent plus du « sparadrap sur la fracture » que du véritable travail de fond. La mise en place du système csr/sip sur MacOS, par exemple, permet d’éviter, entre autres, qu’un processus root ne puisse effacer des fichiers jugés essentiels au système. Malheureusement, d’une part il n’est pas certain que ce dispositif ne soit pas contournable, par exemple en ré-écrivant des routines d’E/S disque : est-ce la raison pour laquelle Apple n’a pas communiqué sur le format de son nouveau file system APFS, rendant tous les utilitaires disques tiers inopérants, et, d’autre part, certains utilisateurs pourraient souhaiter effacer des applications (Stocks, Home, Stickies, Photo Booth, Automator…) dont ils n’ont pas besoin, ce qui n’est plus possible sauf à désactiver le mécanisme par une procédure lourde et peu documentée.

Autrement dit, il en va ici comme dans la vie courante : plutôt que d’investir dans une véritable politique de sécurité – audit fouillé et correction du code –, on préfère restreindre la liberté des utilisateurs. Tout cela afin de préserver un sentiment de confiance propice à la marque.

Du côté de l’iPhone, le système BlastDoor, inauguré avec iOS 14, et qui était censé protéger les utilisateurs d’iMessage contre des intrusions via des SMS malveillants, n’aura pas empêché NSO de s’engouffrer dans une faille « zero day » (initiale), suffisamment grave pour une nouvelle fois permettre à Pegasus de prendre le contrôle du téléphone et ce, apparemment, sans même que l’utilisateur n’ait à ouvrir le message incriminé. BlastDoor serait-il lui même à l’origine de cette vulnérabilité ?

Que faut-il retenir de tout cela? Qu’il n’y a pas de panacée en cybersécurité. Plus un système est complexe, plus il est exposé, et plus les données qu’on lui confie seront en danger, quand bien même son fabricant vous affirme farouchement le contraire. Si vous souhaitez passer des communications et des SMS en toute confidentialité, faites comme les espions : achetez un petit téléphone à clapet vendu quelques euros, surtout sans accès à la data, accompagné d’un abonnement téléphonique prépayé et d’un carnet d’adresses papier. Et revendez votre smartphone sur un site spécialisé.


(1) Ce mécanisme de chiffrement du noyau a été abandonné avec iOS 10.

(2) Ceci évite, par exemple, une vulnérabilité potentielle lors du dé-référencement d’un pointeur NULL.

(3) Probablement la raison pour laquelle les noyaux des systèmes UNIX de type BSD sont toujours écrits en pur C.


Cet article s’inspire directement de l’–unique– article du blog https://jndok.github.io. On y trouvera également une liste assez complète de lien permettant d’obtenir de plus amples informations techniques. On pourra aussi consulter le blog du groupe allemand «Sektion Eins » à l’adresse https://www.sektioneins.de/en/categories/blog.html