4 pontos por GN⁺ 2025-04-07 | 1 comentários | Compartilhar no WhatsApp
  • A eShard, com base em trabalhos anteriores de emulação open source de iOS, tem como objetivo um emulador capaz de inicializar o iOS 14 no QEMU e até exibir a interface e executar alguns apps
  • Em vez de inserir patches do kernel diretamente no QEMU, ela usou PongoOS e checkra1n KPF para separar os patches do XNU e permitir a revisão do conteúdo dos patches com uma ferramenta baseada em diff de Mach-O
  • Como a emulação da GPU do Apple Silicon era ampla demais para o escopo, foi escolhida primeiro a renderização por software, e com patches em QuartzCore num iPhone com jailbreak foi confirmado que a UI do UIKit podia ser desenhada, embora lentamente
  • Para resolver o problema da tela que permanecia preta, o trabalho continuou com desativação da randomização de endereços, depuração com GDB, bypass do pareamento do lockdownd, desativação de PAC, port para o QEMU 8.2.1 e automação de patches no dyld cache
  • No fim, foi possível exibir na tela do QEMU a UI de entrada de senha do UIKit e manipular a caixa de texto com entrada de teclado via VNC, estabelecendo a base necessária para mostrar o SpringBoard

O ponto de partida da emulação de iOS

  • Na análise de soluções open source existentes, alephsecurity/xnu-qemu-arm64 já tinha sido executado antes, mas o projeto estava em modo somente leitura
  • Depois, foi usado como ponto de partida o TrungNguyen1909/qemu-t8030
    • Permite restaurar o iOS por conexão USB via um segundo QEMU “companion”
    • Suporte para executar iOS 14
    • Baseado numa versão mais nova do QEMU
    • Fornece uma wiki explicando como rodar o emulador
  • Foi feita uma modificação em System/Library/xpc/launchd.plist para garantir rapidamente acesso a shell e SSH
  • O objetivo de longo prazo é uma emulação funcional de iOS com interface gráfica e capacidade de executar pelo menos alguns apps

Separando patches do kernel com PongoOS

  • O projeto t8030 colocava código de patch do kernel XNU dentro do próprio QEMU, mas como a quantidade de patches poderia crescer, era necessária uma estrutura mais limpa
  • Com base na experiência prática de jailbreak em iPhone, foi avaliado o uso do PongoOS para aplicar os patches do checkra1n
  • No fluxo normal de jailbreak, após o pwn via checkm8, o PongoOS é injetado na SRAM e o módulo checkra1n-kpf é enviado por USB
    • Neste trabalho, para evitar o tratamento inicial de USB, foi aumentada a SRAM do iPhone emulado e usados o PongoOS e o módulo checkra1n KPF
  • No início da execução do PongoOS, surgiram problemas porque faltava o código de inicialização normalmente executado pelo bootrom ou pelo iBoot
    • Era necessário configurar a FPU antes de instruções double/float
    • O problema foi resolvido com base na documentação ARM e em código anterior relacionado ao QEMU
  • Recursos de aparelhos A13 ou mais novos não eram suportados pelo Pongo, quebrando o pattern matching de alguns patches
    • Foram adicionadas instruções de Pointer Authentication (PAC), como autda e xpacd
    • A Apple usa um slide diferente
    • No patch de task_for_pid(tfp0), foram confirmadas diferenças de endereços e padrões binários entre o iPhone X e o iPhone 11

Arquivos declarativos de patch do kernel

  • O Pongo permitia reutilizar patches existentes do checkra1n para várias versões do iOS, mas a aplicação dinâmica dificultava leitura, edição e compartilhamento
  • Para tratá-los como patches de código reais, foi criada uma ferramenta interna que gera arquivos de patch declarativo
    • Faz diff entre dois Mach-O e gera um arquivo de patch em texto com base nas diferenças de assembly
    • Também foi criado um programa separado para aplicar o arquivo de patch ao binário
  • Depois do boot com Pongo, foi usado o monitor do QEMU para fazer dump das seções de memória modificadas pelo Pongo
  • Em seguida, o kernel patchado foi remontado e foi gerado um grande arquivo de patch com todas as alterações
  • Esse patch grande pôde ser dividido e comentado, permitindo revisão e controle sobre quais partes do kernel estavam sendo alteradas

Estratégia para desenhar a tela sem GPU

  • A renderização gráfica nos iPhones modernos acaba passando pela API Metal da Apple e exige uma GPU real
  • Como emular a GPU do Apple Silicon foi considerado complexo demais, duas alternativas foram avaliadas
    • Renderização por software usando o bootarg gpu=0, como era possível em versões antigas do iOS
    • Encaminhar chamadas Metal para um iPhone real ou para um Mac com macOS para fazer a renderização
  • No iOS 14, a opção de bootarg gpu=0 do kernel XNU já não existia mais
  • Pela análise do framework QuartzCore com Ghidra, a renderização por software era chamada como fallback quando não havia renderizador Metal
  • Num iPhone real com jailbreak, foi confirmado com patch em QuartzCore que a renderização por software podia ser usada
    • A UI ficou muito mais lenta
    • Algumas áreas apresentavam artefatos, possivelmente por exigirem renderização Metal direta
  • Esse experimento indicou que, na parte que não usa Metal nem OpenGL diretamente — ou seja, na maioria dos apps UIKit — a renderização por software poderia funcionar também no QEMU

Experimento de proxy para chamadas Metal

  • Também foi testada a alternativa de fazer proxy das chamadas Metal com dois iPhones físicos
    • Parsing de todos os headers de iOS com LLVM
    • Ponteiros para objetos Objective-C do servidor representados no cliente como ponteiros stub
    • Geração automática de código para troca de structs e ponteiros
    • Hook de todas as funções e métodos
    • Encaminhamento de todas as chamadas ao servidor, com retorno dos resultados da execução
  • Algumas idas e voltas básicas da inicialização do Metal chegaram a funcionar
  • Mas a linguagem Objective-C e a API Metal são complexas e extensas, então o volume de trabalho necessário até chegar a algo realmente funcional era muito grande
  • Essa abordagem foi deixada para depois, e decidiu-se resolver primeiro outros problemas com renderização por software, mesmo com limitações
  • Os frameworks do iOS também expunham APIs privadas ausentes dos headers públicos; havia maneiras de parseá-las e gerar headers, mas isso era difícil de usar na prática e aumentava a complexidade

Depuração de IOSurface e framebuffer

  • Mesmo tentando renderização por software, ainda era necessário pelo menos um dispositivo mínimo de framebuffer, mas o QEMU t8030 original não implementava isso
  • Foi encontrado o fork QEMUAppleSilicon, que tem trabalho em suporte a IOMFB, e ele foi usado para depurar a exibição
  • Ao restaurar o iOS com essa versão, apareciam o logo da Apple e a barra de progresso, mas no boot normal a tela continuava totalmente preta
  • Ao inspecionar a kext IOMFB no Ghidra e a implementação de framebuffer do QEMU, parecia haver dois modos
    • Um framebuffer raw em endereço fixo de hardware
    • Uma API mais complexa que configura vários planes por registradores e escreve os dados das surfaces via DMA
  • Era possível exibir uma surface ARGB arbitrária no framebuffer raw, mas durante o boot o sistema não escrevia nesse framebuffer
  • No segundo modo de display, era possível ver traços de o kernel configurando um plane gráfico via registradores, mas depois disso não havia saída de tela

Desativando randomização de endereços e depuração com GDB

  • Só com acesso por SSH havia limites para observar o sistema em execução, então surgiu a necessidade de depurar o kernel e o user space com GDB
  • A randomização de endereço do kernel era configurada na inicialização da placa t8030, então pôde ser totalmente desativada
  • No userland havia randomização de executáveis e de bibliotecas dinâmicas dentro do dyld cache
    • Os executáveis foram desativados com patch na função _load_machfile do kernel
    • As bibliotecas do dyld cache eram randomizadas uma vez no boot e depois carregadas no mesmo endereço para todos os executáveis
  • O dyld cache em /System/Library/Caches/com.apple.dyld/dyld_shared_cache_arm64e contém todas as bibliotecas de frameworks como um grande blob binário
  • Foi criada uma ferramenta em C para fazer dlopen de todas as bibliotecas de framework e listar as imagens carregadas e seus offsets com funções _dyld*
  • Com essa técnica e o processo de desfazer endereços no GDB, foi possível depurar bibliotecas dentro do dyld cache
  • Os principais alvos eram a kext IOMFB, backboardd, SpringBoard e QuartzCore
  • Depois também foi encontrada uma forma de desativar o dyld cache por patch de kernel, e para localizar diretamente no host os endereços virtuais do dyld cache foi usada a biblioteca Rust object do projeto Gimli
  • Para depuração de user space, era necessário um servidor GDB no guest; um exemplo usado foi o pacote debugserver do Procursus

Logs do sistema e bypass do lockdownd

  • No GDB, o backboardd parecia iniciar normalmente, mas para entender a situação real eram necessários logs do sistema
  • Num iPhone real, é possível ver logs do sistema com idevicesyslog depois do pareamento USB com um computador
  • O processo de pareamento inclui geração de par de chaves, a chave privada é armazenada no iPhone e o lockdownd verifica a identidade do computador
  • No ambiente emulado, a interação USB era possível, mas o lockdownd não funcionava corretamente
  • Pela análise no Ghidra, o lockdownd tentava usar o keybag para armazenar a chave privada, o que exigia o SEP, ausente nesse ambiente
  • Foi injetado um shellcode que substituía parte de funções existentes, lendo um par de chaves pública/privada pré-gerado do sistema de arquivos e carregando-o sempre que o lockdownd tentava buscá-lo no keybag
  • Com depuração e patches adicionais, foi simulada a situação em que o usuário confiou no computador e o iPhone estava desbloqueado
  • No fim, foi possível parear com o iPhone emulado a partir do QEMU companion
  • Nos logs, foi confirmado que o QuartzCore inicializava corretamente, detectava o tamanho da tela e usava o fallback de renderização por software
  • Tudo parecia normal, mas a tela continuava sem aparecer
  • Um erro isolado relacionado a formato de pixel foi contornado forçando RGBA, mas depois esse workaround foi removido

Problema com PAC e port para QEMU 8

  • Ao tentar corrigir o erro de formato de pixel do backboardd, surgiram problemas adicionais por causa de recursos de segurança do iOS
  • As verificações de assinatura no carregamento e em runtime foram resolvidas com patches de kernel, mas a execução do backboardd modificado falhava por causa de Pointer Authentication
  • Pointer Authentication foi adicionada no ARMv8.3, e esse problema apareceu no board t8030 emulado, não no t8015 usado anteriormente
  • No começo, foi considerada a ideia de trocar todas as instruções PAC por NOP ou por equivalentes sem PAC
  • Depois foi confirmado que binários ARM64 com PAC podem ser compilados de duas formas
    • Usando um conjunto dedicado de instruções PAC que só roda em CPUs ARMv8.3+
    • Usando um conjunto de instruções “não utilizadas” que em ARMv8.3+ são interpretadas como PAC e em ARMs anteriores como equivalentes sem PAC
  • Testes com buildroot e sistemas Linux ARM64 validaram esse comportamento, e foi confirmado que os binários para t8030 usam o conjunto backward compatible arm64e
  • A expectativa era que, ao desativar apenas o enforcement de PAC no QEMU, o código rodasse como se fosse sem PAC, mas isso não funcionou no QEMU 7; no QEMU 8 o comportamento era diferente
  • A base de código atual foi portada para o QEMU 8.2.1
    • Instruções específicas da Apple genter e gexit
    • Código de tratamento para níveis de exceção GL
    • O port foi difícil por causa de muitas mudanças no código genérico do QEMU
  • Depois de vários panics do XNU, depuração do kernel com GDB, depuração do próprio QEMU e um git bisect, o iOS voltou a inicializar no QEMU 8
  • Isso permitiu desativar o PAC e modificar qualquer código executável no ponto desejado

Rastreando a causa da tela preta

  • Como os logs do sistema indicavam que o backboardd parecia funcionar normalmente, a investigação sobre a ausência de imagem foi aprofundada
  • Ao escrever frames ARGB raw diretamente no endereço, o display de fato mudava, e era possível desenhar em vários planes gráficos
  • Portanto, a implementação da tela em si parecia correta, restando três possibilidades
    • O backboardd não escreve nada
    • Escreve no endereço errado
    • Os dados escritos não são válidos
  • Com o monitor do QEMU, foram obtidos endereços físicos não contíguos, e um script fez dump da memória DMA física e a reuniu em um único arquivo
  • O arquivo foi interpretado com ffplay como se fosse um frame ARGB, mas sem resultado útil
  • Depois foi colocado um breakpoint em iosurface_lock para obter o endereço da surface mapeada na memória do backboardd e investigá-la
  • Às vezes apareciam formas estranhas que lembravam o logo da Apple, mas parecia haver problema na forma como o frame era escrito
  • Ao repetir o mesmo procedimento num iPhone 10 real, foi fácil fazer dump do frame ARGB raw completo da tela atual
  • No iPhone 11, ou seja, a partir do t8030, a surface parecia ser entregue em formato comprimido para processamento pela GPU
  • Como isso não acontecia no t8015 do iPhone X, o DTB do QEMU foi modificado para informar chip-id 8015 em vez de 8030
  • Com isso, o logo da Apple passou a aparecer após o boot

Barra de progresso e patch de autenticação

  • Mesmo depois de o logo da Apple aparecer, a UI não avançava, e os logs do sistema mostravam muitas mensagens de diversos daemons e bibliotecas
  • O trabalho seguiu tentando corrigir um erro por vez, com base em hipóteses sobre quais mensagens estavam ligadas ao problema de UI
  • Foi identificado um problema relacionado à autenticação do usuário, vindo do daemon mobileactivationd e do framework SpringBoardFoundation
  • Depois dos patches nesses componentes, passou a aparecer uma barra de progresso branca parecida com a mostrada na etapa de restauração
  • A barra parecia se mover, mas mesmo após algumas horas continuava aparentemente travada em 90%

Melhorando o ciclo de patches em dyld cache e user space

  • Com a randomização de endereços desativada, tornou-se possível aplicar patches ao user space e aos frameworks do dyld cache
  • Como no kernel, foram criados arquivos de patch em texto para cada binário/biblioteca e aplicados com ferramenta interna
  • O dyld cache tem cerca de 2 GB, então aplicar patch diretamente ou copiar repetidamente por SSH era inviável
  • Como o trabalho era em Linux, também não era possível modificar diretamente o NVMe
  • A ferramenta interna de diff/patch foi expandida para o dyld cache, passando a localizar o offset do framework dentro do blob do dyld cache
  • Também foi adicionada uma opção para gerar comandos dd simples e comandos de revert que podiam ser executados diretamente no iPhone
  • Depois de remontar o sistema de arquivos em modo leitura/escrita, bastava aplicar os comandos dd para iterar rapidamente alterações no dyld cache
  • Para refletir as mudanças, só era necessário reiniciar o iOS
  • Para esse fluxo funcionar, ainda foram necessários alguns patches adicionais nas verificações de assinatura do kernel

Execução do PreBoard e exibição da tela UIKit

  • Antes de resolver a barra de progresso travada, foi feito um experimento com o processo de sistema PreBoard
  • O PreBoard parece ser exibido ao usuário apenas quando há problemas como interrupção de atualização
  • Como o SpringBoard, ele é um aplicativo de sistema que desenha diretamente via backboardd, então podia ser iniciado diretamente pela linha de comando
  • O resultado foi a exibição de uma tela branca pedindo “swipe to upgrade”
  • Com base numa experiência anterior usando servidor VNC em iPhone físico, foi adicionado VNC e, após várias tentativas frustradas, a tela foi desbloqueada não com swipe, mas com teclas do teclado
  • Logo após o desbloqueio, o QEMU parou de executar porque o iOS usou uma illegal instruction
  • A análise do backboardd mostrou que o framework vImage usava instruções AMX (Apple Matrix Coprocessor) para operações gráficas aceleradas por hardware, como _vHorizontal_Scale_ARGB_8888_Accelerate
  • O AMX é um conjunto de instruções proprietário da Apple que não está implementado na CPU ARM emulada pelo QEMU
  • O framework vImage tinha versões alternativas em software usando apenas instruções ARM genéricas, então novos patches forçaram o uso dessas versões
  • Como resultado final, uma janela real do UIKit foi exibida, com a tela de entrada de senha e uma caixa de texto funcional
  • Foi possível digitar na caixa de texto por meio de eventos de teclado injetados via VNC
  • Nesse ponto, os componentes necessários para exibir corretamente o SpringBoard já estavam prontos, e sua inicialização parecia apenas uma questão de tempo
  • O próximo texto continua em Part 2

1 comentários

 
GN⁺ 2025-04-07
Comentários no Hacker News
  • Seria bom se https://github.com/devos50/qemu-ios evoluísse para dar suporte até o iPhone OS 3.x, permitindo experimentar os primeiros apps do iPhone como forma de preservação digital.
    https://github.com/touchHLE/touchHLE também é excelente, mas, fora apps muito básicos, exige patches específicos por app.

    • Um iPhone 11 emulado com QEMU pode dar suporte do iOS 13.x ao iOS 18.x: https://github.com/ChefKissInc/QEMUAppleSilicon
      Para rodar apps de 32 bits no iOS 10, o QEMU também precisaria dar suporte ao iPhone 7.
    • Seria realmente incrível poder voltar a usar jogos antigos e apps legais dos primórdios que hoje não têm substitutos.
  • Antigamente, cheguei a emular a NumWorks N0100[1] e a HP Prime G1[2] com QEMU, chegando ao ponto de o firmware oficial realmente rodar.
    [1] https://github.com/boricj/qemu/tree/numworks_calculators
    [2] https://github.com/boricj/qemu/tree/s3c2416-boricj

  • Uma aplicação interessante deste projeto seria instalar uma imagem mínima do pmOS e esta versão do QEMU em um celular com suporte de hardware razoavelmente bom no postmarketOS, e então inicializar iOS em um telefone Android.
    Talvez até seja possível customizar mais o QEMU para repassar hardware do celular, como modem e Bluetooth, para a máquina virtual iOS.

    • É uma ideia interessante, mas o primeiro problema é encontrar um celular em que a câmera funcione no postmarketOS e as chamadas telefônicas também funcionem direito.
      Acho que eu já ficaria satisfeito só com isso, mesmo sem emulação de iOS/Android.
    • Só por diversão, tudo bem, mas na prática seria extremamente ineficiente, dificilmente viraria um dispositivo utilizável, e o volume de trabalho seria enorme.
    • Seria algo com uma sensação meio de paravirtualização? Parece que daria um projeto interessante.
  • Versão arquivada: https://archive.ph/l1CwO

  • Isso quer dizer que agora dá para fazer coisas como testes no Safari ou compilar para iOS em um sistema Linux, mesmo sem hardware Apple?

  • https://github.com/ChefKissInc/QEMUAppleSilicon
    É um dispositivo Apple Silicon emulado no QEMU e, no momento, só dá suporte ao iPhone 11.
    Vídeo de demonstração: https://nitter.poast.org/eshard/status/1908162866609311962

  • Tentei seguir as instruções de execução: https://github.com/TrungNguyen1909/qemu-t8030/wiki/Bringing-...
    Travou mais vezes do que eu gostaria de admitir, mas é bem legal.

  • Não há menção à conectividade de rede. Parece que Wi‑Fi ou chipsets de modem celular não são emulados.
    Fico curioso para saber como conectar esse dispositivo emulado à internet. Talvez seja algo como Ethernet via USB.

    • O iOS oferece suporte a USB Ethernet.
  • O que seria necessário para a Apple aceitar o desenvolvimento iOS multiplataforma?

    • A Apple nunca faria isso. O comportamento dela até agora mostra exatamente o oposto: controle total sobre os dispositivos e o ecossistema, falta de cooperação com outras empresas em padrões e controle rígido da App Store.
      Do ponto de vista da Apple, não há nada a ganhar permitindo esse modelo de desenvolvimento.
    • A Apple vende hardware por meio de software. É por isso que não existe iMessage para Android.
      Para algo assim acontecer, a própria forma como a Apple enxerga o mundo teria que mudar; seria uma mudança tão grande quanto a Microsoft ter adotado o Linux em certa medida com o WSL e o .NET para Linux.
    • A cultura corporativa teria que mudar completamente.
    • A Apple é uma empresa de hardware. Por que ela teria interesse em dar suporte a algo em hardware que ela não vende?
    • Provavelmente só se houvesse uma ameaça de desmembramento, como na época do Internet Explorer.
  • Existe algum repositório que permita reproduzir isso?