Emulando um iPhone com QEMU
(eshard.com)- 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
QuartzCorenum 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.plistpara 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
t8030colocava 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
autdaexpacd - 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
- Foram adicionadas instruções de Pointer Authentication (PAC), como
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-Oe 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
- Faz diff entre dois
- 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
- Renderização por software usando o bootarg
- No iOS 14, a opção de bootarg
gpu=0do kernel XNU já não existia mais - Pela análise do framework
QuartzCorecom Ghidra, a renderização por software era chamada como fallback quando não havia renderizadorMetal - Num iPhone real com jailbreak, foi confirmado com patch em
QuartzCoreque a renderização por software podia ser usada- A UI ficou muito mais lenta
- Algumas áreas apresentavam artefatos, possivelmente por exigirem renderização
Metaldireta
- Esse experimento indicou que, na parte que não usa
MetalnemOpenGLdiretamente — 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
Metalsã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
t8030original 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_machfiledo kernel - As bibliotecas do dyld cache eram randomizadas uma vez no boot e depois carregadas no mesmo endereço para todos os executáveis
- Os executáveis foram desativados com patch na função
- O dyld cache em
/System/Library/Caches/com.apple.dyld/dyld_shared_cache_arm64econtém todas as bibliotecas de frameworks como um grande blob binário - Foi criada uma ferramenta em C para fazer
dlopende 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,SpringBoardeQuartzCore - 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
debugserverdo Procursus
Logs do sistema e bypass do lockdownd
- No GDB, o
backboarddparecia 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
idevicesyslogdepois 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
lockdowndverifica a identidade do computador - No ambiente emulado, a interação USB era possível, mas o
lockdowndnão funcionava corretamente - Pela análise no Ghidra, o
lockdowndtentava usar okeybagpara 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
lockdowndtentava buscá-lo nokeybag - 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
QuartzCoreinicializava 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
backboarddmodificado falhava por causa de Pointer Authentication - Pointer Authentication foi adicionada no ARMv8.3, e esse problema apareceu no board
t8030emulado, não not8015usado 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
t8030usam 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
genteregexit - 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
- Instruções específicas da Apple
- 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
backboarddparecia 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
backboarddnão escreve nada - Escreve no endereço errado
- Os dados escritos não são válidos
- O
- 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
ffplaycomo se fosse um frame ARGB, mas sem resultado útil - Depois foi colocado um breakpoint em
iosurface_lockpara obter o endereço da surface mapeada na memória dobackboardde 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
t8015do iPhone X, o DTB do QEMU foi modificado para informarchip-id8015 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
mobileactivationde do frameworkSpringBoardFoundation - 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
ddsimples 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
ddpara 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
PreBoardparece 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 viabackboardd, 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
backboarddmostrou que o frameworkvImageusava 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
vImagetinha 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
UIKitfoi 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
SpringBoardjá estavam prontos, e sua inicialização parecia apenas uma questão de tempo - O próximo texto continua em Part 2
1 comentários
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.
Para rodar apps de 32 bits no iOS 10, o QEMU também precisaria dar suporte ao iPhone 7.
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.
Acho que eu já ficaria satisfeito só com isso, mesmo sem emulação de iOS/Android.
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 que seria necessário para a Apple aceitar o desenvolvimento iOS multiplataforma?
Do ponto de vista da Apple, não há nada a ganhar permitindo esse modelo de desenvolvimento.
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.
Existe algum repositório que permita reproduzir isso?