- eShard, tomando como base trabajos open source existentes de emulación de iOS, se propuso arrancar iOS 14 en QEMU y crear un emulador capaz de ejecutar la UI y algunas apps
- En lugar de insertar parches del kernel directamente dentro de QEMU, usaron PongoOS y checkra1n KPF para separar los parches de XNU y hacer posible revisar su contenido con una herramienta basada en diffs de Mach-O
- Como emular la GPU de Apple Silicon era demasiado amplio en alcance, primero eligieron renderizado por software y confirmaron que, parcheando
QuartzCoreen un iPhone con jailbreak, la UI de UIKit podía dibujarse, aunque lentamente - Para resolver el problema de la pantalla negra, continuaron con la desactivación de la aleatorización de direcciones, depuración con GDB, bypass del emparejamiento de
lockdownd, desactivación de PAC, port a QEMU 8.2.1 y automatización de parches del dyld cache - Finalmente, lograron mostrar en la pantalla de QEMU la UI de ingreso de código de UIKit y manipular el cuadro de texto con entrada de teclado vía VNC, dejando lista la base necesaria para mostrar
SpringBoard
Punto de partida de la emulación de iOS
- Al revisar soluciones open source existentes, alephsecurity/xnu-qemu-arm64 ya había sido probado en ejecución, pero el proyecto estaba en modo solo lectura
- Luego usaron TrungNguyen1909/qemu-t8030 como punto de partida
- Permite restaurar iOS mediante una conexión USB a través de un segundo QEMU “companion”
- Soporta ejecutar iOS 14
- Está basado en una versión más reciente de QEMU
- Ofrece una wiki sobre cómo ejecutar el emulador
- Modificaron
System/Library/xpc/launchd.plistpara obtener rápidamente acceso por shell y SSH - El objetivo a largo plazo es una emulación funcional de iOS, con UI y capacidad de ejecutar al menos algunas apps
Separar los parches del kernel con PongoOS
- El proyecto
t8030incorporaba el código de parches del kernel XNU dentro del propio QEMU, pero como probablemente habría más parches en el futuro, hacía falta una estructura más limpia - A partir de la experiencia con un iPhone real con jailbreak, evaluaron usar PongoOS para aplicar parches de checkra1n
- En un flujo normal de jailbreak, después de quedar pwned con checkmate, PongoOS se inyecta en SRAM y el módulo
checkra1n-kpfse transfiere por USB- En este trabajo, para evitar el procesamiento USB inicial, aumentaron la SRAM del iPhone emulado y usaron PongoOS junto con el módulo checkra1n KPF
- Al inicio de la ejecución de PongoOS hubo problemas porque faltaba el código de inicialización que normalmente ejecutaban bootrom o iBoot
- Hacía falta configurar la FPU antes de instrucciones double/float
- Lo resolvieron tomando como referencia la documentación de ARM y código existente relacionado con QEMU
- Pongo no soporta funciones de dispositivos posteriores al A13, lo que rompía el pattern matching de algunos parches
- Se agregaron instrucciones de Pointer Authentication (PAC), como
autdayxpacd - Apple usa un slide distinto
- En el parche de
task_for_pid(tfp0)se observaron diferencias de direcciones y patrones binarios entre iPhone X e iPhone 11
- Se agregaron instrucciones de Pointer Authentication (PAC), como
Archivos declarativos de parches del kernel
- Pongo permitió usar parches existentes de checkra1n para varias versiones de iOS, pero el método de aplicación dinámica era difícil de leer, modificar y compartir
- Para tratarlos como parches de código reales, generaron internamente archivos de parches declarativos
- Hacían diff de dos
Mach-Opara generar un archivo de parche en texto basado en diferencias de assembly - Escribieron un programa separado para aplicar el archivo de parche generado al binario
- Hacían diff de dos
- Tras arrancar con Pongo, usaron el monitor de QEMU para volcar las secciones de memoria parcheadas por Pongo
- Luego reensamblaron el kernel parcheado y generaron un archivo de parche grande con todas las modificaciones
- Al dividir y comentar ese gran parche, pudieron revisar y controlar qué partes del kernel se modificaban
Estrategia para dibujar la pantalla sin GPU
- El renderizado gráfico en iPhones modernos termina pasando por la API Metal de Apple y requiere una GPU real
- Consideraron que emular la GPU de Apple Silicon era demasiado complejo y evaluaron dos opciones
- Renderizado por software usando el bootarg
gpu=0, como era posible en versiones antiguas de iOS - Enviar las llamadas Metal a un iPhone real o a una Mac con macOS para que hiciera el renderizado
- Renderizado por software usando el bootarg
- En iOS 14, la opción de bootarg
gpu=0del kernel XNU ya no existía - Al analizar el framework
QuartzCorecon Ghidra, vieron que el renderizado por software se invocaba como fallback cuando no había renderizadorMetal - En un iPhone real con jailbreak, parchearon
QuartzCorey confirmaron el uso de renderizado por software- La UI era mucho más lenta
- Algunas zonas presentaban artefactos, posiblemente porque esas partes requerían renderizado
Metaldirectamente
- Con este experimento concluyeron que, para lo que no usa directamente
MetaluOpenGL, es decir, la mayoría de las apps UIKit, el renderizado por software también sería posible en QEMU
Experimento de proxy para llamadas Metal
- También probaron la alternativa de proxyear llamadas Metal usando dos iPhones físicos
- Parseo de todos los headers de iOS con LLVM
- Representación de punteros a objetos Objective-C del servidor como punteros stub en el cliente
- Generación automática de código para intercambio de estructuras y punteros
- Hook de todas las funciones y métodos
- Reenvío de todas las llamadas al servidor y devolución de los resultados de ejecución
- Algunas idas y vueltas básicas de llamadas de inicialización de Metal funcionaron
- Pero el lenguaje Objective-C y la API
Metalson complejos y tienen muchas funcionalidades, por lo que la carga de trabajo hasta lograr algo realmente operativo era muy grande - Dejaron este enfoque para más adelante y decidieron resolver primero otros problemas con renderizado por software, aunque tuviera limitaciones
- Los frameworks de iOS también exponían APIs privadas que no están en los headers públicos; había formas de parsearlas y generar headers, pero en su mayoría eran difíciles de usar directamente y aumentaban la complejidad
Depuración de IOSurface y framebuffer
- Incluso después de intentar renderizado por software, hacía falta al menos un dispositivo framebuffer mínimo, pero el QEMU
t8030original no lo implementaba - Encontraron un fork QEMUAppleSilicon con trabajo de soporte para IOMFB y lo usaron para depurar la pantalla
- Con esta versión, al restaurar iOS se veía el logo de Apple y la barra de progreso, pero en un arranque normal la pantalla quedaba completamente negra
- Al revisar el kext de IOMFB con Ghidra y mirar la implementación del framebuffer de QEMU, parecían existir dos modos
- Un raw framebuffer en una dirección fija de hardware
- Una API más compleja que configura varios planes mediante registros y escribe datos de surface por DMA
- Pudieron mostrar una surface ARGB arbitraria con el raw framebuffer, pero durante el arranque el sistema no escribía en ese framebuffer
- En el segundo modo de display, se veía en las trazas que el kernel configuraba un graphical plane mediante registros, pero después no había salida en pantalla
Desactivar la aleatorización de direcciones y depurar con GDB
- Solo con acceso SSH había límites para observar el sistema en ejecución, así que surgió la necesidad de depurar con GDB tanto el kernel como el user space
- La aleatorización de direcciones del kernel estaba configurada en la inicialización de la placa
t8030, por lo que pudieron desactivarla por completo - En userland había aleatorización del ejecutable y de las bibliotecas dinámicas dentro del dyld cache
- Para los ejecutables, la desactivaron parcheando la función
_load_machfiledel kernel - Las bibliotecas del dyld cache tenían su dirección aleatorizada una vez al arrancar y luego se cargaban en la misma dirección en todos los ejecutables
- Para los ejecutables, la desactivaron parcheando la función
- El dyld cache en
/System/Library/Caches/com.apple.dyld/dyld_shared_cache_arm64econtiene todas las bibliotecas de frameworks como un gran blob binario - Crearon una herramienta en C que hacía
dlopende todas las bibliotecas de frameworks y listaba las imágenes cargadas y offsets mediante funciones_dyld* - Combinando ese método con el proceso de revertir direcciones de GDB, pudieron depurar bibliotecas del dyld cache
- Los objetivos de mayor interés eran el kext
IOMFB,backboardd,SpringBoardyQuartzCore - Más adelante también encontraron una forma de desactivar el dyld cache con un parche del kernel, y usaron la biblioteca Rust object del proyecto Gimli para encontrar directamente en el host las direcciones virtuales del dyld cache
- Para depurar user space hacía falta un GDB server en el guest; por ejemplo, usaron el paquete
debugserverde Procursus
Logs del sistema y bypass de lockdownd
- Con GDB,
backboarddparecía iniciarse normalmente, pero para entender la situación real hacían falta logs del sistema - En un iPhone real, después de emparejarlo por USB con una computadora, se pueden ver los logs del sistema con
idevicesyslog - El proceso de emparejamiento incluye la generación de un par de claves; la clave privada se almacena en el iPhone y
lockdowndverifica la identidad de la computadora - En el entorno emulado era posible la interacción USB, pero
lockdowndno funcionaba correctamente - El análisis con Ghidra mostró que
lockdowndintentaba usarkeybagpara almacenar la clave privada, lo cual requería un SEP inexistente - Inyectaron shellcode que reemplazaba algunas funciones existentes para leer del sistema de archivos un par de claves pública/privada pregenerado y cargarlo cada vez que
lockdowndintentara obtenerlo desdekeybag - Con depuración y parches adicionales, simularon que el usuario había confiado en la computadora y que el iPhone estaba desbloqueado
- Finalmente pudieron emparejarse con el iPhone emulado desde el QEMU companion
- Los logs confirmaron que
QuartzCorese inicializaba correctamente, detectaba el tamaño de la pantalla y usaba el fallback de renderizado por software - Todo parecía normal, pero la pantalla seguía sin mostrarse
- Un único error relacionado con pixel format se evitó forzando RGBA, aunque más tarde esa solución se eliminó
Problemas con PAC y port a QEMU 8
- Al intentar corregir el error de pixel format de
backboardd, aparecieron problemas adicionales por una función de seguridad de iOS - Las verificaciones de firma en tiempo de carga y en runtime se resolvieron con parches del kernel, pero durante la ejecución de
backboarddmodificado ocurría una falla de Pointer Authentication - Pointer Authentication es una función agregada en ARMv8.3, y fue un problema nuevo que encontraron en la placa
t8030emulada, a diferencia del t8015 que habían usado antes - Al principio pensaron en reemplazar todas las instrucciones PAC por NOP o por instrucciones equivalentes sin PAC
- Luego confirmaron que los binarios ARM64 con PAC pueden compilarse de dos maneras
- Usando un conjunto de instrucciones PAC dedicado que solo se ejecuta en CPUs ARMv8.3+
- Usando un conjunto de instrucciones “sin uso” que se interpreta como PAC en ARMv8.3+ y como instrucciones equivalentes sin PAC en ARM anteriores
- Lo verificaron probando en buildroot y sistemas Linux ARM64, y confirmaron que los binarios para
t8030usan arm64e, el conjunto de instrucciones backward compatible - Creían que bastaría con desactivar PAC enforcing en QEMU para ejecutar como código sin PAC, pero en QEMU 7 no funcionaba, mientras que QEMU 8 se comportaba distinto
- Portaron la base de código actual a QEMU 8.2.1
- Instrucciones específicas de Apple
genter,gexit - Código de manejo de GL exception levels
- Muchas modificaciones al código genérico de QEMU, lo que dificultó el port
- Instrucciones específicas de Apple
- Tras varios XNU panic, depuración del kernel con GDB, depuración de QEMU y git bisect, lograron volver a arrancar iOS en QEMU 8
- Con esto pudieron desactivar PAC y modificar cualquier código ejecutable en la ubicación deseada
Rastrear la causa de la pantalla negra
- Como los logs del sistema indicaban que
backboarddfuncionaba normalmente, investigaron más a fondo por qué no se mostraba nada - Si escribían directamente un frame ARGB raw en la dirección correspondiente, la pantalla real cambiaba, y podían dibujar en varios graphical planes
- Por lo tanto, la implementación de display parecía estar bien, y quedaban tres posibilidades
backboarddno escribía nada- Escribía en una dirección incorrecta
- Los datos escritos no eran válidos
- Con el monitor de QEMU obtuvieron direcciones físicas no contiguas, y con un script volcaron la memoria DMA física para combinarla en un solo archivo
- La interpretaron con
ffplaycomo si fuera un frame ARGB, pero no obtuvieron resultados significativos - Luego pusieron un breakpoint en
iosurface_lockpara obtener e investigar la dirección de la surface mapeada en la memoria debackboardd - A veces aparecían formas extrañas parecidas al logo de Apple, pero parecía haber un problema en la forma de escribir el frame
- Al hacer lo mismo en un iPhone 10 real, pudieron volcar fácilmente un frame ARGB raw completo de la pantalla actual
- En iPhone 11, es decir, a partir de
t8030, parecía que la surface se entregaba en una forma comprimida que la GPU podía procesar - Como esto no ocurría en el
t8015del iPhone X, modificaron el DTB de QEMU para pasarchip-idcomo 8015 en lugar de 8030 - Como resultado, tras el arranque se mostró el logo de Apple en pantalla
Barra de progreso y parches de activación
- Aunque apareció el logo de Apple, la UI no avanzaba más, y los logs del sistema mostraban muchos mensajes de varios daemons y bibliotecas
- Avanzaron adivinando qué errores podían estar relacionados con el problema de la UI y corrigiéndolos uno por uno
- Identificaron problemas relacionados con la autenticación del usuario, cuyo origen estaba en el daemon
mobileactivationdy el frameworkSpringBoardFoundation - Después de parchearlos, apareció una barra de progreso blanca similar a la que se ve durante la restauración
- La barra de progreso parecía moverse, pero incluso después de varias horas parecía quedarse detenida en 90%
Mejoras iterativas en dyld cache y parches de user space
- Gracias a la desactivación de la aleatorización de direcciones, fue posible parchear user space y frameworks del dyld cache
- Igual que con el kernel, crearon archivos de parche de texto para cada binario/biblioteca y los aplicaron con herramientas internas
- Como el dyld cache pesa unos 2 GB, parchearlo directamente o copiarlo repetidamente por SSH no era práctico
- Como trabajaban en Linux, tampoco podían modificar el NVMe directamente
- Extendieron su herramienta interna de diff/patch para dyld cache, de modo que encontrara los offsets del framework dentro del blob del dyld cache
- Agregaron una opción para generar comandos
ddsimples y comandos de revert que pudieran aplicarse directamente en el iPhone - Después de remontar el sistema de archivos en modo lectura/escritura, podían iterar rápidamente cambios en el dyld cache aplicando comandos
dd - Para reflejar los cambios, solo hacía falta reiniciar iOS
- Para que este método funcionara, hicieron falta algunos parches adicionales a las verificaciones de firma del kernel
Ejecución de PreBoard y visualización de una pantalla UIKit
- Antes de resolver la barra de progreso detenida, experimentaron con el proceso de sistema
PreBoard PreBoardparecía mostrarse al usuario solo cuando había problemas como una actualización interrumpida- Como es una aplicación de sistema que dibuja directamente a través de
backboardd, igual queSpringBoard, podía iniciarse directamente desde la línea de comandos - Al ejecutarlo, apareció una pantalla blanca que pedía “swipe to upgrade”
- Basándose en experiencias anteriores con un servidor VNC en un iPhone físico, agregaron VNC y, tras varios fallos, desbloquearon la pantalla no con un swipe sino con teclas del teclado
- Inmediatamente después del desbloqueo, QEMU detuvo la ejecución porque iOS usó una illegal instruction
- El análisis de
backboarddmostró que el frameworkvImageusa instrucciones AMX (Apple Matrix Coprocessor) para operaciones gráficas aceleradas por hardware como_vHorizontal_Scale_ARGB_8888_Accelerate - AMX es un conjunto de instrucciones propio de Apple que no está implementado en la CPU ARM emulada de QEMU
- El framework
vImageincluye una versión alternativa por software que solo usa instrucciones ARM genéricas, así que volvieron a parchearlo para usarla - Como resultado final, se mostró una ventana real de
UIKit, con pantalla de ingreso de código y un cuadro de texto funcional - Mediante eventos de teclado inyectados por VNC, pudieron escribir en el cuadro de texto
- En este punto quedaron listos los componentes necesarios para que
SpringBoardse muestre correctamente, y arrancarlo parece cuestión de tiempo - El siguiente artículo continúa en Part 2
1 comentarios
Opiniones en Hacker News
Ojalá https://github.com/devos50/qemu-ios evolucione hasta soportar iPhone OS 3.x, para poder experimentar las primeras apps de iPhone como parte de su preservación digital.
https://github.com/touchHLE/touchHLE también es excelente, pero salvo apps muy básicas, requiere parches específicos para cada app.
Para ejecutar apps de 32 bits en iOS 10, QEMU también tendría que soportar el iPhone 7.
Hace tiempo emulé la NumWorks N0100[1] y la HP Prime G1[2] con QEMU, hasta el punto de lograr que el firmware oficial se ejecutara de verdad.
[1] https://github.com/boricj/qemu/tree/numworks_calculators
[2] https://github.com/boricj/qemu/tree/s3c2416-boricj
Una aplicación interesante de este proyecto sería instalar una imagen mínima de pmOS y esta versión de QEMU en un teléfono con muy buen soporte de hardware en postmarketOS, y arrancar iOS en un teléfono Android.
Quizá incluso sea posible personalizar más QEMU para pasarle a la máquina virtual de iOS hardware del teléfono como el módem o Bluetooth.
Con que solo eso funcione, sin emulación de iOS/Android, ya me daría por satisfecho.
Versión archivada: https://archive.ph/l1CwO
¿Esto significa que ahora se podrían hacer cosas como pruebas en Safari o compilación para iOS en un sistema Linux sin hardware de Apple?
https://github.com/ChefKissInc/QEMUAppleSilicon
Es un dispositivo Apple Silicon emulado en QEMU, y actualmente solo soporta el iPhone 11.
Video de demostración: https://nitter.poast.org/eshard/status/1908162866609311962
Seguí las instrucciones de ejecución: https://github.com/TrungNguyen1909/qemu-t8030/wiki/Bringing-...
Se me crasheó más veces de las que quisiera admitir, pero igual está bastante genial.
No se menciona nada sobre la conectividad de red. Parece que no emulan Wi-Fi ni el chipset del módem celular.
Me pregunto cómo conectarían este dispositivo emulado a internet. Podría ser algo como Ethernet vía USB.
¿Qué haría falta para que Apple aceptara el desarrollo iOS multiplataforma?
Desde el punto de vista de Apple, no gana nada permitiendo ese modelo de desarrollo.
Para que algo así ocurriera, Apple tendría que cambiar su forma misma de ver el mundo, un cambio tan grande como cuando Microsoft aceptó Linux hasta cierto punto con WSL y .NET para Linux.
¿Hay algún repositorio con el que se pueda reproducir esto?