4 puntos por GN⁺ 2025-04-07 | 1 comentarios | Compartir por WhatsApp
  • 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 QuartzCore en 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.plist para 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 t8030 incorporaba 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-kpf se 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 autda y xpacd
    • 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

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-O para 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
  • 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
  • En iOS 14, la opción de bootarg gpu=0 del kernel XNU ya no existía
  • Al analizar el framework QuartzCore con Ghidra, vieron que el renderizado por software se invocaba como fallback cuando no había renderizador Metal
  • En un iPhone real con jailbreak, parchearon QuartzCore y 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 Metal directamente
  • Con este experimento concluyeron que, para lo que no usa directamente Metal u OpenGL, 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 Metal son 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 t8030 original 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_machfile del 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
  • El dyld cache en /System/Library/Caches/com.apple.dyld/dyld_shared_cache_arm64e contiene todas las bibliotecas de frameworks como un gran blob binario
  • Crearon una herramienta en C que hacía dlopen de 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, SpringBoard y QuartzCore
  • 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 debugserver de Procursus

Logs del sistema y bypass de lockdownd

  • Con GDB, backboardd parecí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 lockdownd verifica la identidad de la computadora
  • En el entorno emulado era posible la interacción USB, pero lockdownd no funcionaba correctamente
  • El análisis con Ghidra mostró que lockdownd intentaba usar keybag para 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 lockdownd intentara obtenerlo desde keybag
  • 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 QuartzCore se 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 backboardd modificado 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 t8030 emulada, 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 t8030 usan 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
  • 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 backboardd funcionaba 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
    • backboardd no 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 ffplay como si fuera un frame ARGB, pero no obtuvieron resultados significativos
  • Luego pusieron un breakpoint en iosurface_lock para obtener e investigar la dirección de la surface mapeada en la memoria de backboardd
  • 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 t8015 del iPhone X, modificaron el DTB de QEMU para pasar chip-id como 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 mobileactivationd y el framework SpringBoardFoundation
  • 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 dd simples 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
  • PreBoard parecí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 que SpringBoard, 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 backboardd mostró que el framework vImage usa 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 vImage incluye 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 SpringBoard se muestre correctamente, y arrancarlo parece cuestión de tiempo
  • El siguiente artículo continúa en Part 2

1 comentarios

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

    • Un iPhone 11 emulado con QEMU podría soportar desde iOS 13.x hasta iOS 18.x: https://github.com/ChefKissInc/QEMUAppleSilicon
      Para ejecutar apps de 32 bits en iOS 10, QEMU también tendría que soportar el iPhone 7.
    • Sería realmente increíble poder volver a usar juegos antiguos o apps viejas geniales para las que hoy no hay alternativa.
  • 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.

    • Es una idea interesante, pero el primer problema es encontrar un teléfono en el que postmarketOS pueda usar la cámara y hacer llamadas telefónicas correctamente.
      Con que solo eso funcione, sin emulación de iOS/Android, ya me daría por satisfecho.
    • Si es solo por diversión, está bien, pero en la práctica sería tremendamente ineficiente, difícilmente se convertiría en un dispositivo usable y requeriría una cantidad enorme de trabajo.
    • ¿Algo así como una especie de paravirtualización? Parece que sería un proyecto interesante.
  • 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.

    • iOS soporta USB Ethernet.
  • ¿Qué haría falta para que Apple aceptara el desarrollo iOS multiplataforma?

    • Apple jamás haría eso. Su comportamiento hasta ahora muestra exactamente lo contrario: control total sobre los dispositivos y el ecosistema, falta de cooperación con otras compañías en estándares y un control estricto de la App Store.
      Desde el punto de vista de Apple, no gana nada permitiendo ese modelo de desarrollo.
    • Apple vende hardware mediante software. Por eso no existe iMessage para Android.
      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.
    • La cultura corporativa tendría que cambiar por completo.
    • Apple es una empresa de hardware. ¿Qué motivo tendría para querer dar soporte a algo en hardware que no vende?
    • Probablemente tendría que verse amenazada con una disolución, como en la época de Internet Explorer.
  • ¿Hay algún repositorio con el que se pueda reproducir esto?