- 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
SpringBoarddarzustellen
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.plistwurde 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
t8030integrierte 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-kpfper 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
autdaundxpacdkamen 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
- Die Pointer-Authentication-(PAC)-Befehle
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
- Es diff’t zwei
- 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
- Software-Rendering per
- Unter iOS 14 war die Option
gpu=0in XNU bereits entfernt - Die Analyse des
QuartzCore-Frameworks mit Ghidra zeigte, dass Software-Rendering als Fallback genutzt wird, wenn keinMetal-Renderer vorhanden ist - Auf einem echten jailbroken iPhone wurde
QuartzCoregepatcht, 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
MetaloderOpenGL– 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_machfiledeaktiviert - Bibliotheken im dyld cache werden beim Boot einmal zufällig verschoben und dann von allen ausführbaren Dateien unter derselben Adresse geladen
- Für ausführbare Dateien wurde sie durch Patchen der Kernel-Funktion
- Der dyld cache unter
/System/Library/Caches/com.apple.dyld/dyld_shared_cache_arm64eenthält alle Framework-Bibliotheken als großen Binär-Blob - Mit einem C-Tool wurden alle Framework-Bibliotheken per
dlopengeladen 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,SpringBoardundQuartzCore - 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
debugservervon Procursus
Systemlogs und Umgehung von lockdownd
- In GDB sah es zwar so aus, als starte
backboarddkorrekt, 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
idevicesyslogeinsehen - Der Pairing-Prozess umfasst die Erzeugung eines Schlüsselpaares; der private Schlüssel wird auf dem iPhone gespeichert und
lockdowndprüft die Identität des Computers - In der Emulationsumgebung war USB-Interaktion möglich, aber
lockdowndfunktionierte nicht korrekt - Die Analyse mit Ghidra zeigte, dass
lockdowndzur Speicherung des privaten Schlüssels einkeybagverwenden 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
lockdowndes eigentlich aus demkeybagholen 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
QuartzCorekorrekt 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
backboarddtraten 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
backboarddkam 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
t8030den 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
genterundgexit - Code zur Behandlung von GL exception levels
- Die Portierung war schwierig, weil viele Änderungen im generischen QEMU-Code nötig waren
- Apple-spezifische Befehle
- Nach mehreren XNU-Panics, Kernel-Debugging mit GDB, Debugging von QEMU selbst und
git bisectbootete 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
backboarddnormal 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
backboarddschreibt 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
ffplayals ARGB-Frames lieferte jedoch keine sinnvollen Ergebnisse - Danach wurde ein Breakpoint auf
iosurface_lockgesetzt, um die vonbackboarddin 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
t8030und neuer, schienen Surfaces in komprimierter Form übergeben zu werden, die von der GPU verarbeitet werden kann - Da dies auf dem
t8015des iPhone X nicht geschah, wurde QEMU so geändert, dass im DTB statt 8030 diechip-id8015 ü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
mobileactivationdund im FrameworkSpringBoardFoundation - 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
PreBoardexperimentiert PreBoardscheint dem Benutzer nur bei Problemen wie einem unterbrochenen Update angezeigt zu werden- Als Systemanwendung, die wie
SpringBoarddirekt überbackboarddzeichnet, 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 instructionverwendete - Die Analyse von
backboarddergab, dass das FrameworkvImagefür hardwarebeschleunigte Grafikoperationen wie_vHorizontal_Scale_ARGB_8888_AccelerateAMX-(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
vImagegibt 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
SpringBoardkorrekt 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
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.
Um 32-Bit-Apps unter iOS 10 auszuführen, müsste QEMU auch das iPhone 7 unterstützen.
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.
Wenn nur das klappt, wäre ich schon zufrieden – ganz ohne iOS-/Android-Emulation.
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.
Was wäre nötig, damit Apple multiplattformfähige iOS-Entwicklung akzeptiert?
Aus Apples Sicht gibt es nichts zu gewinnen, wenn man ein solches Entwicklungsmodell zulässt.
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.
Gibt es ein Repository, mit dem man das reproduzieren kann?