4 Punkte von GN⁺ 2025-04-07 | 1 Kommentare | Auf WhatsApp teilen
  • eShard will auf Basis bestehender Open-Source-Arbeiten zur iOS-Emulation iOS 14 in QEMU booten und zielt auf einen Emulator ab, der auch die UI und einige Apps ausführen kann
  • Statt Kernel-Patches direkt in QEMU einzubauen, nutzt das Team PongoOS und checkra1n KPF, um XNU-Patches auszulagern, und macht die Patch-Inhalte mit einem Mach-O-Diff-basierten Tool überprüfbar
  • Da die Emulation der Apple-Silicon-GPU zu umfangreich wäre, fiel die erste Wahl auf Software-Rendering; mit einem QuartzCore-Patch auf einem jailbroken iPhone wurde bestätigt, dass UIKit-UIs zwar langsam, aber darstellbar sind
  • Um das Problem des schwarzen Bildschirms zu lösen, wurden Adressrandomisierung deaktiviert, GDB-Debugging genutzt, lockdownd-Pairing umgangen, PAC deaktiviert, auf QEMU 8.2.1 portiert und die Automatisierung von dyld-cache-Patches vorangetrieben
  • Am Ende wurde auf dem QEMU-Bildschirm die UIKit-Passcode-Eingabeoberfläche angezeigt und per VNC-Tastatureingabe ein Textfeld bedient; damit war die Grundlage geschaffen, auch SpringBoard darzustellen

Ausgangspunkt der iOS-Emulation

  • Bei der Prüfung bestehender Open-Source-Lösungen hatte alephsecurity/xnu-qemu-arm64 bereits Lauferfahrung, das Projekt war jedoch schreibgeschützt
  • Danach wurde TrungNguyen1909/qemu-t8030 als Ausgangspunkt verwendet
    • iOS-Wiederherstellung über USB-Verbindung mit einem zweiten „companion“-QEMU möglich
    • Unterstützung für die Ausführung von iOS 14
    • Basierend auf einer neueren QEMU-Version
    • Wiki mit Anleitungen zum Starten des Emulators
  • Durch Anpassung von System/Library/xpc/launchd.plist wurde schnell Zugriff auf Shell und SSH eingerichtet
  • Das langfristige Ziel ist eine funktionale iOS-Emulation mit UI, die zumindest einige Apps ausführen kann

Kernel-Patches mit PongoOS auslagern

  • Das Projekt t8030 integrierte XNU-Kernel-Patch-Code direkt in QEMU, doch mit Blick auf künftig mehr Patches wurde eine sauberere Struktur nötig
  • Basierend auf Erfahrungen mit realen jailbroken iPhones wurde geprüft, PongoOS zum Anwenden von checkra1n-Patches zu verwenden
  • Im üblichen Jailbreak-Ablauf wird nach einem mit checkm8 kompromittierten Start PongoOS in den SRAM injiziert und das Modul checkra1n-kpf per USB übertragen
    • Um die anfängliche USB-Verarbeitung zu vermeiden, wurde in diesem Projekt der SRAM des emulierten iPhones vergrößert und PongoOS samt checkra1n-KPF-Modul verwendet
  • Beim frühen Start von PongoOS traten Probleme auf, weil Initialisierungscode fehlte, den sonst bootrom oder iBoot ausführen
    • Vor double-/float-Befehlen war eine FPU-Konfiguration nötig
    • Die Lösung erfolgte mithilfe der ARM-Dokumentation und bestehendem QEMU-bezogenem Code
  • Funktionen für Geräte ab A13 wurden von Pongo nicht unterstützt, was Pattern-Matching für einige Patches störte
    • Die Pointer-Authentication-(PAC)-Befehle autda und xpacd kamen hinzu
    • Apple verwendete einen anderen Slide
    • Beim Patch für task_for_pid(tfp0) zeigten sich Unterschiede bei Adressen und Binärmuster zwischen iPhone X und iPhone 11

Deklarative Kernel-Patch-Dateien

  • Pongo ermöglichte zwar die Nutzung vorhandener checkra1n-Patches für mehrere iOS-Versionen, doch das dynamische Anwenden war schwer zu lesen, zu ändern und zu teilen
  • Um damit wie mit echten Code-Patches zu arbeiten, wurde intern ein Tool für deklarative Patch-Dateien erstellt
    • Es diff’t zwei Mach-O-Dateien und erzeugt textbasierte Patch-Dateien auf Basis der Assembler-Unterschiede
    • Zusätzlich wurde ein separates Programm geschrieben, das diese Patch-Dateien auf Binärdateien anwendet
  • Nach dem Boot mit Pongo wurden über den QEMU-Monitor die von Pongo gepatchten Speicherbereiche gedumpt
  • Anschließend wurde der gepatchte Kernel neu zusammengesetzt und eine große Patch-Datei mit allen Änderungen erzeugt
  • Durch Aufteilen und Kommentieren dieser großen Patches wurde Prüfung und Kontrolle darüber möglich, welche Teile des Kernels verändert werden

Strategie zum Zeichnen ohne GPU

  • Das Grafik-Rendering auf modernen iPhones läuft letztlich über Apples Metal-API und erfordert reale GPU-Hardware
  • Da die Emulation einer Apple-Silicon-GPU als zu komplex eingeschätzt wurde, wurden zwei Optionen geprüft
    • Software-Rendering per gpu=0-Bootarg, wie es auf älteren iOS-Versionen möglich war
    • Weiterleitung von Metal-Aufrufen an ein echtes iPhone oder einen Mac mit macOS zur Ausführung des Renderings
  • Unter iOS 14 war die Option gpu=0 in XNU bereits entfernt
  • Die Analyse des QuartzCore-Frameworks mit Ghidra zeigte, dass Software-Rendering als Fallback genutzt wird, wenn kein Metal-Renderer vorhanden ist
  • Auf einem echten jailbroken iPhone wurde QuartzCore gepatcht, um die Nutzung von Software-Rendering zu bestätigen
    • Die UI war deutlich langsamer
    • In manchen Bereichen traten Artefakte auf; dort war vermutlich direktes Metal-Rendering erforderlich
  • Das Experiment legte nahe, dass für Bereiche ohne direkte Nutzung von Metal oder OpenGL – also die meisten UIKit-Apps – Software-Rendering auch in QEMU möglich ist

Experimente mit einem Proxy für Metal-Aufrufe

  • Auch die Alternative, Metal-Aufrufe zu proxien, wurde mit zwei physischen iPhones getestet
    • Parsen aller iOS-Header mit LLVM
    • Darstellung von Objective-C-Objektzeigern des Servers auf Client-Seite als Stub-Pointer
    • Automatische Generierung von Code zum Austausch von Strukturen und Zeigern
    • Hooks für alle Funktionen und Methoden
    • Weiterleitung aller Aufrufe an den Server und Rückgabe der Ausführungsergebnisse
  • Die grundlegenden Roundtrips bei der Metal-Initialisierung funktionierten teilweise
  • Wegen der Komplexität von Objective-C und der Vielzahl an Funktionen der Metal-API war der Aufwand bis zu einer wirklich funktionierenden Lösung jedoch sehr groß
  • Dieser Ansatz wurde deshalb auf später verschoben; stattdessen sollten mit Software-Rendering zunächst andere Probleme gelöst werden, auch wenn das Einschränkungen bedeutet
  • In den iOS-Frameworks waren zudem private APIs sichtbar, die nicht in öffentlichen Headern stehen; sie ließen sich zwar grundsätzlich parsen und als Header erzeugen, waren aber meist schwer direkt zu nutzen und erhöhten die Komplexität weiter

Debugging von IOSurface und Framebuffer

  • Auch mit Software-Rendering wurde mindestens ein einfacher Framebuffer benötigt, der im ursprünglichen t8030-QEMU jedoch nicht implementiert war
  • Für das Display-Debugging wurde ein Fork namens QEMUAppleSilicon mit Arbeiten an IOMFB-Unterstützung verwendet
  • Mit dieser Version waren bei der Wiederherstellung von iOS das Apple-Logo und ein Fortschrittsbalken sichtbar, beim normalen Boot blieb der Bildschirm jedoch vollständig schwarz
  • Die Analyse des IOMFB-kext mit Ghidra und der QEMU-Framebuffer-Implementierung deutete auf zwei Modi hin
    • Ein roher Framebuffer mit fester Hardware-Adresse
    • Eine komplexere API, bei der mehrere Planes über Register konfiguriert werden und Oberflächendaten per DMA geschrieben werden
  • Zwar ließ sich über den rohen Framebuffer eine beliebige ARGB-Oberfläche anzeigen, doch während des Bootvorgangs schrieb das System nicht in diesen Framebuffer
  • Im zweiten Display-Modus war zu sehen, dass der Kernel grafische Planes über Register einrichtete, danach erfolgte aber keine Bildausgabe

Deaktivierung der Adressrandomisierung und GDB-Debugging

  • SSH-Zugriff allein reichte nicht aus, um das laufende System zu beobachten; daher wurde GDB-Debugging für Kernel und User Space nötig
  • Die Kernel-Adressrandomisierung war in der Initialisierung des t8030-Boards gesetzt und ließ sich vollständig abschalten
  • Im Userland gab es Randomisierung für ausführbare Dateien sowie für dynamische Bibliotheken im dyld cache
    • Für ausführbare Dateien wurde sie durch Patchen der Kernel-Funktion _load_machfile deaktiviert
    • Bibliotheken im dyld cache werden beim Boot einmal zufällig verschoben und dann von allen ausführbaren Dateien unter derselben Adresse geladen
  • Der dyld cache unter /System/Library/Caches/com.apple.dyld/dyld_shared_cache_arm64e enthält alle Framework-Bibliotheken als großen Binär-Blob
  • Mit einem C-Tool wurden alle Framework-Bibliotheken per dlopen geladen und mit _dyld*-Funktionen die geladenen Images samt Offsets aufgelistet
  • Zusammen mit dem Zurückrechnen von GDB-Adressen ließ sich so Debugging von Bibliotheken im dyld cache durchführen
  • Besonders interessant waren IOMFB-kext, backboardd, SpringBoard und QuartzCore
  • Später wurde auch ein Weg gefunden, den dyld cache per Kernel-Patch zu deaktivieren; um virtuelle Adressen des dyld cache direkt auf dem Host zu finden, kam die Rust-Bibliothek object aus dem Gimli-Projekt zum Einsatz
  • Für User-Space-Debugging wurde ein GDB-Server im Gast benötigt, etwa das Paket debugserver von Procursus

Systemlogs und Umgehung von lockdownd

  • In GDB sah es zwar so aus, als starte backboardd korrekt, doch um die tatsächliche Lage zu verstehen, wurden Systemlogs benötigt
  • Auf einem echten iPhone lassen sich Systemlogs nach USB-Pairing mit einem Computer über idevicesyslog einsehen
  • Der Pairing-Prozess umfasst die Erzeugung eines Schlüsselpaares; der private Schlüssel wird auf dem iPhone gespeichert und lockdownd prüft die Identität des Computers
  • In der Emulationsumgebung war USB-Interaktion möglich, aber lockdownd funktionierte nicht korrekt
  • Die Analyse mit Ghidra zeigte, dass lockdownd zur Speicherung des privaten Schlüssels ein keybag verwenden wollte, was ein nicht vorhandenes SEP voraussetzte
  • Durch Injektion von Shellcode, der Teile bestehender Funktionen ersetzte, wurde ein vorab erzeugtes öffentliches/privates Schlüsselpaar aus dem Dateisystem gelesen und immer dann geladen, wenn lockdownd es eigentlich aus dem keybag holen wollte
  • Weitere Debugging- und Patch-Arbeiten simulierten zusätzlich, dass der Nutzer dem Computer vertraut und das iPhone entsperrt ist
  • Am Ende konnte das emulierte iPhone mit dem companion-QEMU gepairt werden
  • In den Logs war zu sehen, dass QuartzCore korrekt initialisiert wurde, die Displaygröße erkannte und den Software-Rendering-Fallback verwendete
  • Alles schien normal zu laufen, dennoch blieb der Bildschirm leer
  • Ein einzelner Fehler beim Pixel-Format wurde zunächst durch Erzwingen von RGBA umgangen, später aber wieder entfernt

PAC-Probleme und Portierung auf QEMU 8

  • Bei der Behebung des Pixel-Format-Fehlers in backboardd traten wegen iOS-Sicherheitsfunktionen weitere Probleme auf
  • Signaturprüfungen beim Laden und zur Laufzeit wurden per Kernel-Patch gelöst, doch während der Ausführung des modifizierten backboardd kam es zu Fehlern bei der Pointer Authentication
  • Pointer Authentication wurde mit ARMv8.3 eingeführt und war auf dem emulierten t8030-Board neu, im Unterschied zum zuvor genutzten t8015
  • Zunächst wurde erwogen, alle PAC-Befehle in NOPs oder äquivalente Befehle ohne PAC umzuwandeln
  • Später zeigte sich, dass ARM64-PAC-Binärdateien auf zwei Arten gebaut werden können
    • Mit einem dedizierten PAC-Befehlssatz, der nur auf ARMv8.3+-CPUs läuft
    • Mit einem Satz „unbenutzter“ Befehle, die auf ARMv8.3+ als PAC interpretiert werden und auf älteren ARM-Versionen als äquivalente Befehle ohne PAC
  • Tests mit buildroot und einem ARM64-Linux-System bestätigten dieses Verhalten; dabei zeigte sich, dass die Binärdateien für t8030 den rückwärtskompatiblen arm64e-Befehlssatz verwenden
  • Es lag nahe, in QEMU nur das PAC-Enforcing abzuschalten, damit der Code wie Code ohne PAC läuft; unter QEMU 7 funktionierte das jedoch nicht, unter QEMU 8 war das Verhalten anders
  • Deshalb wurde die aktuelle Codebasis auf QEMU 8.2.1 portiert
    • Apple-spezifische Befehle genter und gexit
    • Code zur Behandlung von GL exception levels
    • Die Portierung war schwierig, weil viele Änderungen im generischen QEMU-Code nötig waren
  • Nach mehreren XNU-Panics, Kernel-Debugging mit GDB, Debugging von QEMU selbst und git bisect bootete iOS schließlich wieder unter QEMU 8
  • Damit ließ sich PAC deaktivieren und beliebiger ausführbarer Code an gewünschten Stellen modifizieren

Ursachenforschung für den schwarzen Bildschirm

  • Da die Systemlogs darauf hindeuteten, dass backboardd normal arbeitete, wurde tiefer verfolgt, warum dennoch nichts angezeigt wurde
  • Beim direkten Schreiben roher ARGB-Frames an die betreffende Adresse änderte sich die tatsächliche Anzeige, und es ließ sich auf mehrere grafische Planes zeichnen
  • Die Display-Implementierung selbst schien also in Ordnung zu sein; übrig blieben drei Möglichkeiten
    • backboardd schreibt gar nichts
    • Es schreibt an die falsche Adresse
    • Die geschriebenen Daten sind ungültig
  • Über den QEMU-Monitor wurden nicht zusammenhängende physische Adressen ermittelt, per Skript physische DMA-Speicherbereiche gedumpt und zu einer Datei zusammengefügt
  • Die Interpretation mit ffplay als ARGB-Frames lieferte jedoch keine sinnvollen Ergebnisse
  • Danach wurde ein Breakpoint auf iosurface_lock gesetzt, um die von backboardd in den Speicher gemappte Surface-Adresse zu erhalten und zu untersuchen
  • Gelegentlich tauchten merkwürdige Formen auf, die dem Apple-Logo ähnelten, doch die Art des Frame-Schreibens schien problematisch zu sein
  • Dieselbe Methode auf einem echten iPhone 10 lieferte dagegen problemlos einen vollständigen Dump des aktuellen rohen ARGB-Frames
  • Ab dem iPhone 11, also t8030 und neuer, schienen Surfaces in komprimierter Form übergeben zu werden, die von der GPU verarbeitet werden kann
  • Da dies auf dem t8015 des iPhone X nicht geschah, wurde QEMU so geändert, dass im DTB statt 8030 die chip-id 8015 übergeben wird
  • Danach erschien beim Booten das Apple-Logo auf dem Bildschirm

Fortschrittsbalken und Aktivierungs-Patches

  • Auch nach dem Erscheinen des Apple-Logos ging die UI nicht weiter; in den Systemlogs erschienen viele Meldungen verschiedener Daemons und Bibliotheken
  • Um die wirklich relevanten Fehler für das UI-Problem zu finden, wurde pragmatisch Fehler für Fehler geprüft und behoben
  • Dabei wurden Probleme rund um die Benutzerauthentifizierung erkannt; die Fehlerquelle lag im Daemon mobileactivationd und im Framework SpringBoardFoundation
  • Nach Patches daran erschien ein weißer Fortschrittsbalken, ähnlich wie im Wiederherstellungsmodus
  • Der Fortschrittsbalken wirkte zwar aktiv, schien aber auch nach Stunden bei 90 % stehenzubleiben

Verbesserte Schleife für dyld-cache- und User-Space-Patches

  • Durch die deaktivierte Adressrandomisierung wurden Patches für User Space und Frameworks im dyld cache möglich
  • Wie beim Kernel wurden textbasierte Patch-Dateien pro Binärdatei bzw. Bibliothek erzeugt und mit internen Tools angewandt
  • Der dyld cache ist etwa 2 GB groß, daher waren direktes Patchen oder wiederholtes Kopieren per SSH unpraktisch
  • Da in einer Linux-Umgebung gearbeitet wurde, ließ sich auch NVMe nicht direkt bearbeiten
  • Das interne Diff-/Patch-Tool wurde für den dyld cache erweitert, sodass Framework-Offsets innerhalb des dyld-cache-Blobs gefunden werden konnten
  • Es kam eine Option hinzu, einfache dd-Befehle zum direkten Anwenden auf dem iPhone sowie Revert-Befehle zu erzeugen
  • Nach dem Remount des Dateisystems im Lese-/Schreibmodus konnten dyld-cache-Änderungen mit diesen dd-Befehlen schnell iteriert werden
  • Zum Übernehmen der Änderungen war nur ein Neustart von iOS nötig
  • Damit dies funktionierte, waren zusätzliche Kernel-Patches für die Signaturprüfung erforderlich

Start von PreBoard und Anzeige einer UIKit-Oberfläche

  • Noch bevor der hängende Fortschrittsbalken gelöst war, wurde mit dem Systemprozess PreBoard experimentiert
  • PreBoard scheint dem Benutzer nur bei Problemen wie einem unterbrochenen Update angezeigt zu werden
  • Als Systemanwendung, die wie SpringBoard direkt über backboardd zeichnet, ließ sie sich direkt über die Kommandozeile starten
  • Das Ergebnis war ein weißer Bildschirm mit der Aufforderung „swipe to upgrade“
  • Auf Basis früherer Erfahrungen mit einem VNC-Server auf einem physischen iPhone wurde VNC hinzugefügt; nach mehreren Fehlschlägen wurde der Bildschirm nicht per Wischgeste, sondern mit einer Tastaturtaste entsperrt
  • Direkt danach stoppte QEMU, weil iOS eine illegal instruction verwendete
  • Die Analyse von backboardd ergab, dass das Framework vImage für hardwarebeschleunigte Grafikoperationen wie _vHorizontal_Scale_ARGB_8888_Accelerate AMX-(Apple Matrix Coprocessor)-Befehle nutzt
  • AMX ist ein proprietärer Apple-Befehlssatz, der auf der von QEMU emulierten ARM-CPU nicht implementiert ist
  • Im Framework vImage gibt es alternative Software-Implementierungen, die nur generische ARM-Befehle verwenden; per weiterem Patch wurden diese stattdessen genutzt
  • Als Endergebnis erschien ein echtes UIKit-Fenster mit Passcode-Eingabe und funktionsfähigem Textfeld
  • Über per VNC eingespeiste Tastaturereignisse konnte in das Textfeld eingegeben werden
  • Damit waren die nötigen Komponenten vorhanden, damit auch SpringBoard korrekt angezeigt werden kann, und der Start schien nur noch eine Frage der Zeit zu sein
  • Der nächste Artikel folgt in Teil 2

1 Kommentare

 
GN⁺ 2025-04-07
Meinungen auf Hacker News
  • Es wäre schön, wenn sich https://github.com/devos50/qemu-ios so weiterentwickeln würde, dass es bis iPhone OS 3.x unterstützt, damit man frühe iPhone-Apps im Sinne der digitalen Bewahrung erleben kann.
    https://github.com/touchHLE/touchHLE ist ebenfalls großartig, benötigt aber außer bei sehr einfachen Apps app-spezifische Patches.

    • Ein mit QEMU emuliertes iPhone 11 kann iOS 13.x bis iOS 18.x unterstützen: https://github.com/ChefKissInc/QEMUAppleSilicon
      Um 32-Bit-Apps unter iOS 10 auszuführen, müsste QEMU auch das iPhone 7 unterstützen.
    • Es wäre wirklich großartig, frühe Spiele, für die es derzeit keinen Ersatz gibt, oder tolle alte Apps wieder nutzen zu können.
  • Ich habe früher mit QEMU den NumWorks N0100[1] und den HP Prime G1[2] emuliert und es bis zu dem Punkt gebracht, dass die offizielle Firmware tatsächlich läuft.
    [1] https://github.com/boricj/qemu/tree/numworks_calculators
    [2] https://github.com/boricj/qemu/tree/s3c2416-boricj

  • Eine interessante Anwendung dieses Projekts wäre, auf einem Smartphone mit ziemlich guter postmarketOS-Hardwareunterstützung ein minimales pmOS-Image und diese QEMU-Version zu installieren und iOS auf einem Android-Handy zu booten.
    Vielleicht ließe sich QEMU noch weiter anpassen, um Smartphone-Hardware wie Modem oder Bluetooth an die iOS-VM durchzureichen.

    • Interessante Idee, aber das erste Problem wäre, ein Smartphone zu finden, auf dem unter postmarketOS auch die Kamera funktioniert und Telefonanrufe zuverlässig laufen.
      Wenn nur das klappt, wäre ich schon zufrieden – ganz ohne iOS-/Android-Emulation.
    • Nur zum Spaß wäre es okay, aber in der Praxis wäre es extrem ineffizient, würde kaum ein benutzbares Gerät ergeben und enorm viel Arbeit erfordern.
    • Ist das so etwas wie eine Art Paravirtualisierung? Könnte ein interessantes Projekt werden.
  • Archivierte Version: https://archive.ph/l1CwO

  • Bedeutet das, dass man jetzt Safari-Tests oder das Kompilieren für iOS auf einem Linux-System machen kann, ohne Apple-Hardware zu besitzen?

  • https://github.com/ChefKissInc/QEMUAppleSilicon
    Dabei handelt es sich um in QEMU emulierte Apple-Silicon-Geräte; derzeit wird nur das iPhone 11 unterstützt.
    Demo-Video: https://nitter.poast.org/eshard/status/1908162866609311962

  • Ich habe die Ausführungsanleitung ausprobiert: https://github.com/TrungNguyen1909/qemu-t8030/wiki/Bringing-...
    Es ist öfter abgestürzt, als ich zugeben möchte, aber ziemlich cool ist es schon.

  • Es gibt keine Erwähnung von Netzwerkverbindung. Wi-Fi- oder Mobilfunkmodem-Chipsätze scheinen nicht emuliert zu werden.
    Ich frage mich, wie man dieses emulierte Gerät mit dem Internet verbinden würde. Vielleicht über so etwas wie Ethernet via USB.

    • iOS unterstützt USB Ethernet.
  • Was wäre nötig, damit Apple multiplattformfähige iOS-Entwicklung akzeptiert?

    • Apple wird das niemals tun. Das bisherige Verhalten zeigt genau das Gegenteil: vollständige Kontrolle über Geräte und Ökosystem, keine Zusammenarbeit mit anderen Unternehmen bei Standards und strenge App-Store-Kontrolle.
      Aus Apples Sicht gibt es nichts zu gewinnen, wenn man ein solches Entwicklungsmodell zulässt.
    • Apple verkauft Hardware über Software. Deshalb gibt es iMessage für Android nicht.
      Damit so etwas passiert, müsste sich Apples Sicht auf die Welt selbst ändern; das wäre wohl ein ähnlich großer Wandel wie Microsofts teilweise Hinwendung zu Linux mit WSL und .NET für Linux.
    • Die Unternehmenskultur müsste sich komplett ändern.
    • Apple ist ein Hardwareunternehmen. Warum sollten sie etwas auf Hardware unterstützen wollen, die sie nicht verkaufen?
    • Vermutlich müsste ihnen eine Zerschlagung drohen, wie damals bei Internet Explorer.
  • Gibt es ein Repository, mit dem man das reproduzieren kann?