- eShard s’appuie sur les travaux existants d’émulation iOS open source pour démarrer iOS 14 dans QEMU et vise un émulateur capable d’afficher l’interface utilisateur et d’exécuter certaines apps
- Au lieu d’intégrer directement des patchs du noyau dans QEMU, le projet utilise PongoOS et le KPF de checkra1n pour séparer les patchs XNU et rendre leur contenu inspectable via des outils fondés sur des diff Mach-O
- L’émulation du GPU Apple Silicon étant trop vaste comme chantier, le choix initial s’est porté sur le rendu logiciel, avec confirmation via un patch de
QuartzCoresur un iPhone jailbreaké qu’une UI UIKit peut être affichée, lentement, mais effectivement - Pour résoudre le problème de l’écran noir, le travail s’est poursuivi avec la désactivation de l’aléa d’adressage, le débogage GDB, le contournement de l’appairage
lockdownd, la désactivation du PAC, le portage vers QEMU 8.2.1 et l’automatisation des patchs du cache dyld - Au final, l’équipe a réussi à afficher sur QEMU l’interface UIKit de saisie du code d’accès et à manipuler le champ de texte via le clavier VNC, avec désormais les fondations nécessaires pour afficher
SpringBoard
Point de départ de l’émulation iOS
- Lors de l’examen des solutions open source existantes, alephsecurity/xnu-qemu-arm64 avait déjà une expérience d’exécution, mais le projet était passé en lecture seule
- Ensuite, TrungNguyen1909/qemu-t8030 a été utilisé comme point de départ
- Restauration d’iOS possible via une connexion USB avec un second QEMU « companion »
- Prise en charge de l’exécution d’iOS 14
- Basé sur une version plus récente de QEMU
- Wiki expliquant comment lancer l’émulateur
- En modifiant
System/Library/xpc/launchd.plist, l’équipe a rapidement obtenu un accès shell et SSH - L’objectif à long terme est une émulation iOS fonctionnelle avec interface graphique et capacité à exécuter au moins certaines apps
Séparer les patchs noyau avec PongoOS
- Le projet
t8030injectait du code de patch du noyau XNU directement dans QEMU, mais comme le nombre de patchs allait probablement augmenter, une structure plus propre était nécessaire - Fort de l’expérience acquise sur de vrais iPhone jailbreakés, le projet a étudié l’usage de PongoOS pour appliquer les patchs checkra1n
- Dans le flux de jailbreak habituel, après un pwn via checkm8, PongoOS est injecté en SRAM et le module
checkra1n-kpfest envoyé via USB- Ici, pour éviter la gestion initiale de l’USB, la SRAM de l’iPhone émulé a été agrandie et PongoOS ainsi que le module KPF de checkra1n ont été utilisés
- Au démarrage de PongoOS, des problèmes sont apparus faute du code d’initialisation normalement exécuté par le bootrom ou iBoot
- Une configuration de la FPU était nécessaire avant les instructions double/float
- Le problème a été résolu en s’appuyant sur la documentation ARM et sur du code QEMU existant
- Comme Pongo ne prend pas en charge les fonctions des appareils A13 et ultérieurs, cela cassait certains pattern matching de patchs
- Les instructions de Pointer Authentication (PAC)
autda,xpacdavaient été ajoutées - Apple utilisait un slide différent
- Des écarts d’adresses et de motifs binaires ont été observés dans le patch
task_for_pid(tfp0)entre l’iPhone X et l’iPhone 11
- Les instructions de Pointer Authentication (PAC)
Fichiers de patch noyau déclaratifs
- Pongo permettait de réutiliser les patchs checkra1n existants pour plusieurs versions d’iOS, mais leur application dynamique était difficile à lire, modifier et partager
- Pour les traiter comme de vrais patchs de code, un outil interne a été créé pour générer des fichiers de patch déclaratifs
- Diff de deux
Mach-Opour produire un fichier texte de patch fondé sur les différences d’assembleur - Développement d’un programme séparé pour appliquer ce fichier de patch au binaire
- Diff de deux
- Après démarrage avec Pongo, le moniteur QEMU a été utilisé pour dumper les sections mémoire patchées par Pongo
- Le noyau patché a ensuite été reconstruit, puis un gros fichier de patch contenant toutes les modifications a été généré
- En découpant ce gros patch et en y ajoutant des commentaires, il est devenu possible de réviser et contrôler précisément quelles parties du noyau étaient modifiées
Stratégie d’affichage sans GPU
- Le rendu graphique sur les iPhone récents passe au final par l’API Metal d’Apple et nécessite un vrai GPU
- L’émulation du GPU Apple Silicon étant jugée trop complexe, deux pistes ont été étudiées
- Le rendu logiciel via l’argument de boot
gpu=0, comme c’était possible sur d’anciennes versions d’iOS - Le transfert des appels Metal vers un vrai iPhone ou un Mac sous macOS pour effectuer le rendu
- Le rendu logiciel via l’argument de boot
- Sous iOS 14, l’option
gpu=0dans les bootargs du noyau XNU avait disparu - L’analyse du framework
QuartzCoreavec Ghidra a montré que le rendu logiciel était appelé comme mécanisme de repli quand aucun rendererMetaln’était disponible - Sur un vrai iPhone jailbreaké, un patch de
QuartzCorea confirmé l’usage du rendu logiciel- L’interface était nettement plus lente
- Des artefacts apparaissaient dans certaines zones, probablement là où un rendu
Metaldirect était requis
- Cette expérience a conduit à penser que, dans QEMU, le rendu logiciel restait possible pour la plupart des apps UIKit, tant qu’elles n’utilisent pas directement
MetalouOpenGL
Expérimentation autour d’un proxy d’appels Metal
- Une alternative consistant à proxifier les appels Metal a aussi été testée avec deux iPhone physiques
- Parsing de tous les headers iOS avec LLVM
- Représentation des pointeurs d’objets Objective-C du serveur comme pointeurs stub côté client
- Génération automatique du code d’échange des structures et pointeurs
- Hook de toutes les fonctions et méthodes
- Transmission de tous les appels au serveur et retour des résultats d’exécution
- Les allers-retours de base pour l’initialisation de Metal ont partiellement fonctionné
- Mais la complexité du langage Objective-C et de l’API
Metal, ainsi que leur richesse fonctionnelle, rendaient la charge de travail énorme avant d’obtenir un vrai fonctionnement - Cette approche a donc été reportée, et le projet a préféré résoudre d’abord d’autres problèmes avec le rendu logiciel malgré ses limites
- Les frameworks iOS exposaient aussi des API privées absentes des headers publics ; il existait des moyens de les parser et d’en générer des headers, mais elles étaient en pratique difficiles à exploiter et augmentaient encore la complexité
Débogage d’IOSurface et du framebuffer
- Même après avoir tenté le rendu logiciel, un périphérique framebuffer minimal restait nécessaire, mais il n’était pas implémenté dans le QEMU
t8030d’origine - Le fork QEMUAppleSilicon, qui contenait un travail de prise en charge d’IOMFB, a été trouvé et utilisé pour déboguer l’affichage
- Avec cette version, le logo Apple et la barre de progression apparaissaient pendant la restauration d’iOS, mais lors d’un boot normal l’écran restait entièrement noir
- En examinant la kext IOMFB dans Ghidra et l’implémentation framebuffer de QEMU, deux modes semblaient exister
- Un framebuffer brut à adresse matérielle fixe
- Une API plus complexe configurant plusieurs plans via des registres et écrivant les données de surface en DMA
- Il était possible d’afficher une surface ARGB arbitraire via le framebuffer brut, mais durant le boot le système n’écrivait pas dans ce framebuffer
- Dans le second mode d’affichage, des traces montraient que le noyau configurait bien un plan graphique via des registres, mais aucun affichage n’apparaissait ensuite
Désactivation de l’aléa d’adressage et débogage GDB
- L’accès SSH seul ne suffisait pas pour observer le système en cours d’exécution ; il a fallu déboguer le noyau et l’espace utilisateur avec GDB
- L’aléa d’adressage du noyau était configuré lors de l’initialisation de la carte
t8030, ce qui a permis de le désactiver complètement - Côté userland, il existait à la fois l’aléa des exécutables et celui des bibliothèques dynamiques dans le cache dyld
- Pour les exécutables, cela a été désactivé en patchant la fonction noyau
_load_machfile - Les bibliothèques du cache dyld étaient randomisées une seule fois au boot, puis chargées à la même adresse dans tous les exécutables
- Pour les exécutables, cela a été désactivé en patchant la fonction noyau
- Le cache dyld de
/System/Library/Caches/com.apple.dyld/dyld_shared_cache_arm64econtient toutes les bibliothèques des frameworks sous forme d’un gros blob binaire - Un outil C a été créé pour
dlopentoutes les bibliothèques des frameworks et lister, via les fonctions_dyld*, les images chargées et leurs offsets - Cette méthode, combinée au recalcul inverse des adresses vues dans GDB, a permis de déboguer les bibliothèques présentes dans le cache dyld
- Les cibles particulièrement suivies étaient la kext
IOMFB,backboardd,SpringBoardetQuartzCore - Plus tard, une méthode a aussi été trouvée pour désactiver le cache dyld via un patch noyau, et la bibliothèque Rust object du projet Gimli a servi à retrouver directement les adresses virtuelles du cache dyld sur l’hôte
- Le débogage user space nécessitait un serveur GDB dans l’invité ; par exemple, le paquet
debugserverde Procursus a été utilisé
Logs système et contournement de lockdownd
- Avec GDB,
backboarddsemblait démarrer correctement, mais il fallait des logs système pour comprendre ce qui se passait réellement - Sur un vrai iPhone, il est possible de consulter les logs système avec
idevicesyslogaprès appairage USB avec un ordinateur - Le processus d’appairage implique la génération d’une paire de clés, la clé privée étant stockée sur l’iPhone, et
lockdowndvérifie l’identité de l’ordinateur - Dans l’environnement émulé, les interactions USB étaient possibles, mais
lockdowndne fonctionnait pas correctement - L’analyse Ghidra a montré que
lockdowndessayait d’utiliserkeybagpour stocker la clé privée, ce qui supposait la présence d’un SEP absent ici - En injectant du shellcode remplaçant certaines fonctions, une paire de clés publique/privée pré-générée a été lue depuis le système de fichiers et chargée chaque fois que
lockdowndtentait de la récupérer depuiskeybag - Des sessions supplémentaires de débogage et de patch ont permis de simuler le fait que l’utilisateur faisait confiance à l’ordinateur et que l’iPhone était déverrouillé
- Il a finalement été possible d’appairer l’iPhone émulé avec le QEMU companion
- Les logs ont confirmé que
QuartzCores’initialisait correctement, détectait la taille de l’écran et utilisait bien le fallback de rendu logiciel - Tout semblait fonctionner, mais rien ne s’affichait encore à l’écran
- Une erreur isolée liée au pixel format a été contournée en forçant le RGBA, avant d’être supprimée plus tard
Problèmes de PAC et portage vers QEMU 8
- En corrigeant l’erreur de pixel format dans
backboardd, d’autres problèmes liés aux mécanismes de sécurité d’iOS sont apparus - Les vérifications de signature au chargement et à l’exécution avaient été contournées via des patchs noyau, mais l’exécution du
backboarddmodifié échouait sur une Pointer Authentication - La Pointer Authentication a été ajoutée dans ARMv8.3 et constituait un problème nouveau sur la carte
t8030émulée, contrairement aut8015utilisé auparavant - Une première idée consistait à remplacer toutes les instructions PAC par des NOP ou des instructions équivalentes sans PAC
- Il a ensuite été constaté que les binaires ARM64 avec PAC pouvaient être construits de deux façons
- En utilisant un jeu d’instructions PAC dédié, exécutable uniquement sur des CPU ARMv8.3+
- En utilisant un jeu d’instructions « inutilisées » interprétées comme PAC sur ARMv8.3+, mais comme instructions équivalentes sans PAC sur les ARM plus anciens
- Des tests sur buildroot et un système Linux ARM64 ont validé ce comportement, et confirmé que les binaires
t8030utilisent l’ensemble d’instructions rétrocompatible arm64e - Il semblait donc qu’en désactivant seulement l’application stricte du PAC dans QEMU, le code s’exécuterait comme du code sans PAC, mais cela ne fonctionnait pas avec QEMU 7, alors que QEMU 8 se comportait différemment
- La base de code a donc été portée vers QEMU 8.2.1
- Instructions Apple spécifiques
genter,gexit - Code de gestion des niveaux d’exception GL
- Le portage a été difficile à cause de nombreuses modifications du code générique de QEMU
- Instructions Apple spécifiques
- Après plusieurs panic XNU, du débogage GDB côté noyau, du débogage interne de QEMU et un
git bisect, iOS a finalement redémarré sous QEMU 8 - Cela a permis de désactiver le PAC et de modifier n’importe quel code exécutable à l’endroit voulu
Remonter à la cause de l’écran noir
- Comme les logs système indiquaient que
backboarddsemblait fonctionner normalement, les recherches se sont approfondies sur la raison de l’absence d’affichage - En écrivant directement une frame ARGB brute à l’adresse concernée, l’affichage réel changeait bien, et il était possible de dessiner sur plusieurs plans graphiques
- L’implémentation de l’affichage elle-même semblait donc correcte, et trois possibilités restaient
backboarddn’écrit rien- Il écrit à une mauvaise adresse
- Les données écrites ne sont pas valides
- Le moniteur QEMU a servi à récupérer des adresses physiques discontinues, puis un script a dumpé la mémoire DMA physique avant de la regrouper dans un seul fichier
- L’outil
ffplaya été utilisé pour l’interpréter comme une frame ARGB, mais sans résultat significatif - Ensuite, un breakpoint a été placé sur
iosurface_lockafin de récupérer et examiner l’adresse des surfaces mappées en mémoire parbackboardd - Par moments, des formes étranges rappelant le logo Apple ont été observées, mais la manière dont les frames étaient écrites semblait poser problème
- En répétant la même opération sur un vrai iPhone 10, il a été facile de dumper la frame ARGB brute complète de l’écran courant
- Sur l’iPhone 11, donc à partir du
t8030, les surfaces semblaient être transmises dans un format compressé exploitable par le GPU - Comme cela n’arrivait pas sur l’iPhone X
t8015, le DTB de QEMU a été modifié pour fournirchip-id8015 au lieu de 8030 - Résultat : le logo Apple s’est affiché après le boot
Barre de progression et patchs d’authentification
- Après l’affichage du logo Apple, l’interface n’allait toujours pas plus loin, alors que les logs système produisaient de nombreux messages provenant de divers daemons et bibliothèques
- La progression s’est faite en corrigeant les erreurs une par une, au jugé, pour déterminer lesquelles étaient réellement liées au problème d’interface
- Des soucis liés à l’authentification utilisateur ont été identifiés, les erreurs provenant du daemon
mobileactivationdet du frameworkSpringBoardFoundation - Après patch de ces éléments, une barre de progression blanche semblable à celle observée pendant la restauration s’est affichée
- La barre semblait animée, mais même après plusieurs heures elle paraissait bloquée à 90 %
Amélioration itérative des patchs user space et du cache dyld
- Grâce à la désactivation de l’aléa d’adressage, il est devenu possible de patcher l’espace utilisateur et les frameworks du cache dyld
- Comme pour le noyau, des fichiers de patch texte ont été créés pour chaque binaire ou bibliothèque puis appliqués avec les outils internes
- Le cache dyld faisant environ 2 Go, le patcher directement ou le recopier en boucle via SSH n’était pas réaliste
- Comme le travail se faisait sous Linux, il n’était pas non plus possible de modifier directement le NVMe
- Les outils internes de diff/patch ont donc été étendus au cache dyld afin de retrouver les offsets des frameworks dans le blob du cache dyld
- Une option supplémentaire a été ajoutée pour générer de simples commandes
ddapplicables directement sur l’iPhone, ainsi que des commandes de revert - Après remontage du système de fichiers en lecture/écriture, l’application de ces commandes
dda permis d’itérer rapidement sur les modifications du cache dyld - Un simple redémarrage d’iOS suffisait pour prendre en compte les changements
- Pour que cette méthode fonctionne, quelques patchs supplémentaires du noyau sur les vérifications de signature ont été nécessaires
Exécution de PreBoard et affichage d’écrans UIKit
- Avant de résoudre la barre de progression bloquée, des expériences ont été menées avec le processus système
PreBoard PreBoardsemble n’être affiché à l’utilisateur qu’en cas de problème, comme une interruption de mise à jour- Comme
SpringBoard, c’est une application système qui dessine directement viabackboardd, et elle pouvait donc être lancée depuis la ligne de commande - Son exécution a fait apparaître un écran blanc demandant un « swipe to upgrade »
- En s’appuyant sur une expérience passée avec un serveur VNC sur un iPhone physique, le VNC a été ajouté ; après plusieurs échecs, l’écran a finalement été déverrouillé non pas par glissement, mais via des touches clavier
- Juste après le déverrouillage, QEMU s’arrêtait car iOS utilisait une illegal instruction
- L’analyse de
backboardda révélé que le frameworkvImageutilisait des instructions AMX (Apple Matrix Coprocessor) pour des opérations graphiques accélérées matériellement, comme_vHorizontal_Scale_ARGB_8888_Accelerate - AMX est un jeu d’instructions propriétaire d’Apple non implémenté dans le CPU ARM émulé par QEMU
- Le framework
vImagecontenait heureusement une version logicielle alternative n’utilisant que des instructions ARM génériques, et un nouveau patch a permis de la forcer - Résultat final : une véritable fenêtre
UIKita été affichée, avec l’écran de saisie du code d’accès et un champ de texte fonctionnel - Il a été possible de saisir du texte dans ce champ via des événements clavier injectés par VNC
- À ce stade, tous les éléments nécessaires pour afficher correctement
SpringBoardétaient en place, et son lancement semblait n’être plus qu’une question de temps - La suite se trouve dans Part 2
1 commentaires
Avis sur Hacker News
Ce serait bien que https://github.com/devos50/qemu-ios évolue jusqu’à prendre en charge iPhone OS 3.x, afin de pouvoir découvrir les premières apps iPhone dans une optique de préservation numérique.
https://github.com/touchHLE/touchHLE est excellent aussi, mais à part pour des apps très basiques, il faut des correctifs spécifiques à chaque app.
Pour exécuter des apps 32 bits sous iOS 10, QEMU devrait aussi prendre en charge l’iPhone 7.
Il y a quelque temps, j’ai émulé avec QEMU les NumWorks N0100[1] et HP Prime G1[2], jusqu’à faire tourner réellement leur firmware officiel.
[1] https://github.com/boricj/qemu/tree/numworks_calculators
[2] https://github.com/boricj/qemu/tree/s3c2416-boricj
Une application amusante de ce projet serait d’installer une image pmOS minimale et cette version de QEMU sur un téléphone avec une assez bonne prise en charge matérielle par postmarketOS, puis de démarrer iOS sur un téléphone Android.
En personnalisant davantage QEMU, il serait peut-être aussi possible de transmettre à la machine virtuelle iOS du matériel de téléphone comme le modem ou le Bluetooth.
Rien que ça, sans émulation iOS/Android, me satisferait déjà.
Version archivée : https://archive.ph/l1CwO
Est-ce que cela veut dire qu’on peut désormais faire des choses comme des tests Safari ou de la compilation pour iOS sur un système Linux, sans matériel Apple ?
https://github.com/ChefKissInc/QEMUAppleSilicon
Ce sont des appareils Apple Silicon émulés dans QEMU ; pour l’instant, seul l’iPhone 11 est pris en charge.
Vidéo de démo : https://nitter.poast.org/eshard/status/1908162866609311962
J’ai essayé de suivre les instructions d’exécution : https://github.com/TrungNguyen1909/qemu-t8030/wiki/Bringing-...
Ça a planté plus de fois que je n’ai envie de l’admettre, mais c’est quand même assez impressionnant.
Il n’y a aucune mention de la connexion réseau. Il semble que les chipsets Wi‑Fi ou modem cellulaire ne soient pas émulés.
Je me demande comment connecter cet appareil émulé à Internet. Ce pourrait être via une méthode du genre Ethernet sur USB.
Que faudrait-il pour qu’Apple accepte le développement iOS multiplateforme ?
Du point de vue d’Apple, autoriser un tel modèle de développement ne leur apporte rien.
Pour que cela arrive, il faudrait qu’Apple change sa manière même de voir le monde, un changement aussi important que lorsque Microsoft a fini par accepter Linux dans une certaine mesure avec WSL et .NET pour Linux.
Existe-t-il un dépôt permettant de reproduire ça ?