4 points par GN⁺ 2025-04-07 | 1 commentaires | Partager sur WhatsApp
  • 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 QuartzCore sur 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 t8030 injectait 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-kpf est 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, xpacd avaient é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

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-O pour 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
  • 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
  • Sous iOS 14, l’option gpu=0 dans les bootargs du noyau XNU avait disparu
  • L’analyse du framework QuartzCore avec Ghidra a montré que le rendu logiciel était appelé comme mécanisme de repli quand aucun renderer Metal n’était disponible
  • Sur un vrai iPhone jailbreaké, un patch de QuartzCore a confirmé l’usage du rendu logiciel
    • L’interface était nettement plus lente
    • Des artefacts apparaissaient dans certaines zones, probablement là où un rendu Metal direct é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 Metal ou OpenGL

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 t8030 d’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
  • Le cache dyld de /System/Library/Caches/com.apple.dyld/dyld_shared_cache_arm64e contient toutes les bibliothèques des frameworks sous forme d’un gros blob binaire
  • Un outil C a été créé pour dlopen toutes 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, SpringBoard et QuartzCore
  • 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 debugserver de Procursus a été utilisé

Logs système et contournement de lockdownd

  • Avec GDB, backboardd semblait 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 idevicesyslog aprè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 lockdownd vérifie l’identité de l’ordinateur
  • Dans l’environnement émulé, les interactions USB étaient possibles, mais lockdownd ne fonctionnait pas correctement
  • L’analyse Ghidra a montré que lockdownd essayait d’utiliser keybag pour 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 lockdownd tentait de la récupérer depuis keybag
  • 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 QuartzCore s’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 backboardd modifié é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 au t8015 utilisé 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 t8030 utilisent 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
  • 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 backboardd semblait 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
    • backboardd n’é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 ffplay a été utilisé pour l’interpréter comme une frame ARGB, mais sans résultat significatif
  • Ensuite, un breakpoint a été placé sur iosurface_lock afin de récupérer et examiner l’adresse des surfaces mappées en mémoire par backboardd
  • 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 fournir chip-id 8015 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 mobileactivationd et du framework SpringBoardFoundation
  • 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 dd applicables 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 dd a 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
  • PreBoard semble 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 via backboardd, 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 backboardd a révélé que le framework vImage utilisait 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 vImage contenait 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 UIKit a é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

 
GN⁺ 2025-04-07
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.

    • Un iPhone 11 émulé avec QEMU pourrait prendre en charge d’iOS 13.x à iOS 18.x : https://github.com/ChefKissInc/QEMUAppleSilicon
      Pour exécuter des apps 32 bits sous iOS 10, QEMU devrait aussi prendre en charge l’iPhone 7.
    • Ce serait vraiment formidable de pouvoir réutiliser d’anciens jeux des débuts ou de vieilles apps sympa qui n’ont aujourd’hui aucun substitut.
  • 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.

    • Idée amusante, mais le premier problème est de trouver un téléphone sous postmarketOS où l’appareil photo fonctionne et où les appels téléphoniques marchent correctement.
      Rien que ça, sans émulation iOS/Android, me satisferait déjà.
    • Pour le fun, pourquoi pas, mais en pratique ce serait extrêmement inefficace, difficile d’en faire un appareil utilisable, et la charge de travail serait énorme.
    • Une sorte de paravirtualisation, en quelque sorte ? Ça ferait un projet intéressant.
  • 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.

    • iOS prend en charge USB Ethernet.
  • Que faudrait-il pour qu’Apple accepte le développement iOS multiplateforme ?

    • Apple ne fera jamais ça. Tout ce qu’ils ont fait jusqu’ici montre l’inverse : contrôle total des appareils et de l’écosystème, absence de coopération avec les autres entreprises sur les standards, contrôle strict de l’App Store.
      Du point de vue d’Apple, autoriser un tel modèle de développement ne leur apporte rien.
    • Apple vend du matériel grâce au logiciel. C’est pour ça qu’iMessage pour Android n’est jamais sorti.
      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.
    • Il faudrait que la culture d’entreprise change complètement.
    • Apple est une entreprise de matériel. Quelle raison auraient-ils de vouloir prendre en charge quelque chose sur du matériel qu’ils ne vendent pas ?
    • Il faudrait probablement qu’ils soient menacés d’un démantèlement, comme à l’époque d’Internet Explorer.
  • Existe-t-il un dépôt permettant de reproduire ça ?