
empty_list - exploit pour le problème p0 1564 (CVE-2018-4243) iOS 11.0 - 11.3.1 lecture/écriture du noyau
empty_list - exploit pour p0 issue 1564 (CVE-2018-4243) iOS 11.0 - 11.3.1 kernel r/w @i41nbeer
BUG : getvolattrlist prend un argument bufferSize contrôlé par l'utilisateur via l'appel système fgetattrlist.
Lors de l'allocation d'un tampon noyau pour sérialiser la liste d'attributs, on trouve le commentaire suivant :
/*
Le problème est que le code ne gère pas correctement le cas où la taille de tampon fournie par l'utilisateur est inférieure à la taille d'en-tête demandée. Si on passe ATTR_CMN_RETURNED_ATTRS, on atteindra le code suivant :
/* Return attribute set output if requested. / if (return_valid) { ab.actual.commonattr |= ATTR_CMN_RETURNED_ATTRS; if (pack_invalid) { / Only report the attributes that are valid */ ab.actual.commonattr &= ab.valid.commonattr; ab.actual.volattr &= ab.valid.volattr; } bcopy(&ab.actual, ab.base + sizeof(uint32_t), sizeof (ab.actual)); }
Il n'y a pas de vérification que le tampon alloué est suffisamment grand pour contenir au moins cela.
Exploitation : J'espère publier une analyse plus longue de ce problème ; voici quelques notes approximatives sur le fonctionnement de l'exploit :
Le bug permet d'écrire 8 octets nuls au-delà de la fin d'une allocation kalloc.16. Bien qu'il semble possible de contrôler quelques bits dans ces octets, je ne suis pas sûr que ce soit réalisable, j'ai donc concentré mes efforts sur l'exploitation comme s'il s'agissait d'écrire un pointeur NULL au-delà de la fin.
Il s'agit d'une primitive assez limitée, donc la première étape consiste à essayer d'énumérer les choses possibles :
Finalement, j'ai choisi la première option. Il y a ensuite deux conditions supplémentaires :
J'ai choisi de cibler struct ipc_port, qui a un champ de compteur de références comme deuxième mot double, remplissant ainsi la première condition. Cependant, elle n'est pas allouée dans kalloc.16 ; elle réside dans sa propre zone (ipc_ports).
Cela signifie que nous devons aligner un bloc de zone kalloc.16 juste avant un bloc ipc_ports, puis déborder de la dernière allocation kalloc.16 du bloc kalloc.16 vers la première allocation dans ipc_ports.
Il y a deux astuces pour faciliter cela :
Inversion de la liste libre : Les allocations de zone proviendront d'abord des pages intermédiaires (partiellement pleines). Cela signifie que si nous commençons à libérer et allouer des objets k.16 au milieu du « grooming », ils ne seront pas réutilisés tant que la page intermédiaire actuelle n'est ni pleine ni vide.
Cela pose un défi car les listes libres des pages fraîches sont remplies de manière semi-aléatoire, de sorte que leurs allocations iront de l'intérieur vers l'extérieur :
| 9 8 6 5 2 1 3 4 7 10 | <-- exemple d'ordre d'allocation « aléatoire » depuis une page fraîche entièrement libre
Cela signifie que nos pages intermédiaires finales k.16 et ports ressembleront à ceci :
| - - - 5 2 1 3 4 - - | - - - 4 1 2 3 5 - - | kalloc.16 ipc_ports
Si nous utilisons le débordement pour corrompre une entrée de la liste libre, le système paniquera si elle est allouée, nous devons donc l'éviter.
L'astuce est qu'en contrôlant l'ordre d'allocation et de libération, nous pouvons inverser les listes libres de sorte que les pages intermédiaires finales ressemblent plutôt à ceci : | 1 4 - - - - - 5 3 2 | 2 5 - - - - - 4 3 1 | kalloc.16 ipc_ports
À ce stade, il est bien plus probable de pouvoir libérer un kalloc.16 et le réallouer pour le débordement, de manière à toucher le premier qword d'un ipc_port.
Allocations sûres vis-à-vis du débordement : Comme il y aura probablement de nombreuses allocations candidates à déborder avant d'atteindre la cible (qui se trouve juste à la fin, avant l'ipc_port), nous devons nous assurer que les objets alloués sur la page kalloc.16 peuvent être corrompus sans danger avec un pointeur NULL.
J'utilise pour cela des descripteurs ool_port de messages mach, car NULL est une valeur valide.
Déroulement de l'exploit : Nous effectuons le « grooming » pour inverser les listes libres kalloc.16 et commençons à essayer de déborder dans un ipc_port.
Nous connaissons la plage approximative des noms de ports mach qui contiennent le port à corrompre ; après chaque tentative de débordement, nous vérifions chacun de ces ports pour voir si le port a été corrompu. Un effet secondaire d'une corruption réussie est que le drapeau io_active du port sera mis à zéro. Nous pouvons détecter cela sans effets secondaires en utilisant la méthode MIG mach_port_kobject.
Une fois le port corrompu trouvé, nous devons provoquer la prise et le relâchement d'une référence sur celui-ci ; et plus important encore, nous avons besoin que le chemin de code qui fait cela ne vérifie pas le drapeau io_active. mach_port_set_attributes fera cela pour nous.
Maintenant, nous avons transformé notre écriture de pointeur NULL au-delà de la fin d'un kalloc.16 en un port mach pendant :)
Nous provoquons un gc de zone, dans le but que la mémoire du port soit réutilisée comme une page kalloc.4096. Nous la faisons d'abord réutiliser comme un descripteur ool_ports où le champ ip_context chevauche un droit d'envoi que nous nous envoyons à un port canari. Cela nous permet d'apprendre l'adresse approximative de nos objets dans le noyau. Nous remplaçons ensuite le ool_desc par un tampon de pipe, et avec un peu de bricolage, nous parvenons à déterminer où se trouve le port mach pendant en mémoire.
Nous y forgeons un faux port de tâche noyau, puis nettoyons.
Fiabilité : L'exploit fonctionne, c'était mon objectif :) La fiabilité est d'environ 30 % peut-être, tout dépend de la rapidité avec laquelle vous pouvez effectuer le débordement initial et la boucle de test. Si un autre processus alloue ou libère dans kalloc.16, vous augmentez la probabilité de corrompre une entrée de la liste libre ou autre chose et provoquerez une panique.
Je suis sûr que l'exploit peut être rendu plus fiable ; je l'ai seulement amené au point où j'ai démontré que ce bug est exploitable. Si vous voulez prendre ceci comme point de départ et montrer comment améliorer la fiabilité, j'adorerais lire un article de blog ! J'imagine que cela impliquerait de surveiller réellement les allocations kalloc.16 et de comprendre quels sont les cas d'échec et comment les éviter.
Les taux de réussite semblent être les plus élevés lorsque l'appareil a été redémarré et laissé inactif un moment.
Nettoyage : Si l'exploit fonctionne, il devrait se nettoyer tout seul et ne pas faire paniquer l'appareil. Le faux port de tâche noyau restera actif.
Utilisez les fonctions dans kmem.h pour lire et écrire la mémoire du noyau. Persistez un droit d'envoi vers tfp0 si vous souhaitez conserver l'accès à la mémoire du noyau après la sortie de ce processus.
J'ai testé sur : iPod Touch 6G, iPhone 6S, iPhone SE, iPhone 7, iPhone 8 Cela devrait fonctionner sur iOS 11 jusqu'à iOS 11.3.1