Skip to content
KitploitKITPLOIT
OutilsBlog
Soumettre
OutilsBlog
Soumettre

Outils de Hacking, PenTest et Cybersécurité pour votre Arsenal de Sécurité !

Kitploit est un répertoire d'outils de hacking, de cybersécurité et de pentesting. Découvrez les dernières mises à jour des projets pour trouver des vulnérabilités, analyser des systèmes, automatiser les tests et renforcer votre sécurité.

··Flux·Contact·Confidentialité·© 2026 Kitploit

Répertoire d'outils

Catégories

Voir toutes les catégories
Loading categories
CVE-2022-42864 — Preuve de concept pour la condition de concurrence IOHIDFamily CVE-2022-42864 | Kitploit
Outils/GitHubGitHub/muirey03/cve-2022-42864
Analyse des VulnérabilitésExploitationExploitation de Binaires
GitHubmuirey03/cve-2022-42864

CVE-2022-42864

Preuve de concept pour la condition de concurrence IOHIDFamily CVE-2022-42864

Voir le dépôt
6872il y a 3 ansVérifié par Kitploit

Populaires

Voir tout →

Découvrez les outils les plus utilisés par notre communauté.

Explorer tous les outils

Parcourez notre collection d'outils

Voir tous les outils →
Partager

CVE-2022-42864 : Cookies diaboliques

Qu'est-ce que ce dépôt ?

Ceci est mon exploit proof-of-concept (incomplet) pour CVE-2022-42864, une vulnérabilité de type temps de vérification/temps d'utilisation (TOCTOU) dans IOHIDFamily qui a été corrigée dans iOS 16.2 / macOS Ventura 13.1.

Quel est l'état du proof-of-concept ?

L'exploit atteint actuellement la même primitive « arbitrary kfree » (libération arbitraire) que celle utilisée dans l'exploit multicast_bytecopy. Cependant, le déroulement ultérieur de l'exploit de multicast_bytecopy a été fortement atténué, donc il ne s'agit pas d'un exploit complet, mais simplement d'une démonstration de la gravité du problème.

Devrais-je exécuter ceci ?

Si vous devez poser la question, non. Cela ne fait rien d'utile, cela provoque simplement un kernel panic. Je ne peux être tenu responsable d'aucune perte de données ni instabilité que ce code pourrait causer.

Le bug

Le commentaire d'Apple dans le code source lors de la correction de ce problème résume bien la situation :

root@kitploit:~
// Find the number of cookies in the data. The data from elementData is shared with user space and may change at any time.

Examinons la fonction avant le patch (j'ai essayé d'annoter les lignes pertinentes) :

root@kitploit:~
IOReturn IOHIDDevice::postElementTransaction(const void* elementData, UInt32 dataSize, UInt32 completionTimeout, IOHIDCompletion * completion)
{
    IOReturn ret = kIOReturnError;
    uint32_t   cookies_[kMaxLocalCookieArrayLength];
    uint32_t   *cookies = cookies_;
    uint32_t   cookieCount = 0;
    uint32_t   cookieSize = 0;
    uint32_t   dataOffset = 0;
    uint8_t    *data = (uint8_t*)elementData;
    IOMemoryDescriptor *elementDesc = getMemoryWithCurrentElementValues();
    require(_elementArray && elementDesc, fail);

    WORKLOOP_LOCK;

    // Find the number of cookies in the data. Check that all cookies are valid elements.                   [1]
    while (dataOffset < dataSize) {
        const IOHIDElementValueHeader *headerPtr = (const IOHIDElementValueHeader *)(data + dataOffset);
        IOHIDElementPrivate *element = GetElement(headerPtr->cookie);
        if (!element) {
            HIDDeviceLogError("Could not find element for cookie: %d", headerPtr->cookie);
            ret = kIOReturnAborted;
            goto fail;
        }
        cookieCount++;

        require_noerr_action(os_add3_overflow(dataOffset, headerPtr->length, sizeof(IOHIDElementValueHeader), &dataOffset), fail, HIDDeviceLogError("Overflow iterating cookie data buffer %u %u", dataOffset, headerPtr->length));
    }
    // Data isn't as large as expected, don't overrun, just abort
    if (dataOffset != dataSize) {   //                                                                      [2]
        HIDDeviceLogError("Cookie data buffer is smaller than expected. %u vs. %u",
                        (unsigned int)dataSize, (unsigned int)dataOffset);
        ret = kIOReturnAborted;
        goto fail;
    }
    dataOffset = 0;

    require_noerr_action(os_mul_overflow(cookieCount, sizeof(uint32_t), &cookieSize),
                        fail,
                        HIDDeviceLogError("Overflow calculating cookieSize"));

    cookies = (cookieCount <= kMaxLocalCookieArrayLength) ? cookies : (uint32_t*)IOMallocData(cookieSize); // [3]

    if (cookies == NULL) {
        ret = kIOReturnNoMemory;
        goto fail;
    }

    // Update the elements, this replaced the shared kernel-user shared memory.
    for (size_t index = 0; dataOffset < dataSize; ++index) {    //                                          [4]
        const IOHIDElementValueHeader *headerPtr;
        IOHIDElementPrivate *element;
        OSData *elementVal;

        headerPtr = (const IOHIDElementValueHeader *)(data + dataOffset);
        element = GetElement(headerPtr->cookie);
        dataOffset += headerPtr->length + sizeof(IOHIDElementValueHeader);

        elementVal = OSData::withBytesNoCopy((void*)headerPtr->value,
                                            headerPtr->length); //                                          [5]
        require_action(elementVal, fail, ret = kIOReturnNoMemory);
        element->setDataBits(elementVal);
        elementVal->release();

        cookies[index] = headerPtr->cookie; //                                                              [6]
    }

    // Actually post elements
    ret = postElementValues((IOHIDElementCookie *)cookies, (UInt32)cookieCount, 0, completionTimeout, completion);

fail:
    WORKLOOP_UNLOCK;
    if (cookies != &cookies_[0]) {
        IOFreeData(cookies, cookieSize);
    }

    return ret;
}
  • La boucle [1] compte le nombre de IOHIDElementValues dans le tampon et stocke ce compte dans cookieCount.
  • La vérification [2] (combinée à la condition de la boucle while) garantit que le champ length de chaque en-tête ne dépasse pas les limites du tampon elementData (et ne s'arrête pas non plus avant la fin du tampon, bien que cela soit moins pertinent).
  • Une fois que tous les éléments ont été comptés et validés par cette boucle, un tampon cookies est alloué sur le tas en [3] avec une taille de cookieCount * 4 (ou un tampon sur la pile est utilisé si cookieCount est suffisamment petit).
  • Une seconde boucle en [4] effectue ensuite un deuxième passage dans le tampon, analysant à nouveau les IOHIDElementValues.
  • Des objets OSData sont créés pour contenir la valeur de chaque élément en [5], utilisant le champ length validé dans la première boucle.
  • En [6], le cookie de chaque élément est écrit dans le tableau cookies alloué en [3].

Quel est donc le problème ? Cette fonction se comporte parfaitement correctement lorsque elementData n'est pas volatil ; le problème survient lorsque la méthode est appelée avec de la mémoire partagée. Voici la méthode DriverKit IOHIDInterface::SetElementValues_Impl :

root@kitploit:~
kern_return_t
IMPL(IOHIDInterface, SetElementValues)
{
    IOReturn ret = kIOReturnError;
    UInt8 *values = NULL;
    IOBufferMemoryDescriptor *md = NULL;
    
    md = OSDynamicCast(IOBufferMemoryDescriptor, elementValues);
    require_action(md && count, exit, ret = kIOReturnBadArgument);

    values = (UInt8 *)md->getBytesNoCopy();
    
    // Post the data to the device
    ret = _owner->postElementTransaction(values, (UInt32)md->getLength());
    require_noerr_action(ret, exit, HIDServiceLogError("postElementValues failed: 0x%x", ret));

exit:
    return ret;
}

Ici, postElementTransaction est appelée avec md->getBytesNoCopy(), une mémoire partagée avec l'espace utilisateur, ce qui viole l'hypothèse selon laquelle elementData n'est pas volatil. Le contenu du tampon elementData peut changer après la boucle [1], mais avant la boucle [4]. Qu'est-ce que cela signifie pour un attaquant ?

Un attaquant peut abuser de cela de deux manières :

  • La première consiste à remplacer le length d'un petit IOHIDElementValueHeader à la fin du tampon par une valeur beaucoup plus grande. Cela signifie que lorsque OSData est créé en [5], il s'étendra bien au-delà des limites du tampon elementData, permettant à un attaquant de lire des données hors limites à l'aide de IOHIDInterface::GetElementValues_Impl.
  • La seconde consiste à remplacer le length d'un grand IOHIDElementValueHeader au début du tampon par une valeur beaucoup plus petite. Cela fera analyser par la boucle [4] beaucoup plus d'en-têtes que ceux comptés initialement dans la boucle [1], de sorte que lorsque les cookies sont écrits dans le tableau cookies en [6], ils déborderont du tableau car index n'est jamais validé par rapport à cookieCount.

En pratique, cela permet à un attaquant de lire des données hors limites du tas du noyau d'une taille arbitraire, et d'écrire des données arbitraires (également d'une taille arbitraire) hors limites dans le tas du noyau. Ce sont deux primitives puissantes.

Gagner la course

Avec les conditions de course, nous cherchons toujours des moyens de déterminer si la course a été gagnée avec succès, afin de pouvoir réessayer jusqu'à réussir, rendant notre déclencheur déterministe. Heureusement, dans le cas de cette condition de course, nous pouvons faire exactement cela.

Pour la variante de lecture OOB, je place un IOHIDElementValueHeader supplémentaire après l'en-tête dont je modifie le length, avec sa value définie sur la constante reconnaissable 0xD1AB011CAC1DF00D. Ensuite, lorsque je relis la valeur de l'élément, je sais que j'ai gagné la course si je vois l'en-tête familier 0xD1AB011CAC1DF00D au début des données renvoyées.

Pour la variante d'écriture OOB, je place un IOHIDElementValueHeader après l'en-tête dont je modifie le length, mais avant les en-têtes dont les cookies seront débordés, cette fois avec la value reconnaissable 0xD15EA5ED. Cet en-tête sera encapsulé à l'intérieur de la value de l'élément plus grand dans le cas où nous ne gagnons pas la course, de sorte que l'en-tête ne sera analysé et que la valeur de l'élément ne sera définie sur 0xD15EA5ED que si nous gagnons la course. En relisant la valeur de l'élément, je sais si j'ai réussi.

Le correctif d'Apple

Pour corriger le problème, Apple a choisi d'ajouter une troisième boucle entre la boucle [1] et la boucle [4], validant chaque champ length, puis le mettant en cache dans un nouveau tableau dataLengths, tout en s'assurant que le nombre d'éléments n'avait pas changé. La boucle finale utilise ensuite les longueurs mises en cache pour ses calculs, évitant ainsi de lire à nouveau le tampon.

Problèmes liés à l'exploitation

Le principal obstacle à surmonter lors de l'exploitation de ce problème est que le tampon depuis lequel nous débordons appartient à KHEAP_DATA_BUFFERS, les cibles d'exploitation sont donc limitées. Dans ce proof-of-concept, j'ai choisi de cibler les en-têtes kmsg, car ce sont l'une des rares structures de KHEAP_DATA_BUFFERS contenant des pointeurs du noyau. La primitive « arbitrary kfree » que j'ai obtenue avec cette approche est la même que celle utilisée dans l'exploit multicast_bytecopy. Cependant, le tableau IOSurfaceClient est désormais protégé par PAC et les clients forgés doivent avoir un pointeur valide vers le IOSurfaceRootUserClient qui les a créés, ce qui en fait une cible de lecture/écriture noyau moins intéressante.

Compilation et installation

Apple n'a pas rendu la compilation et l'installation d'extensions DriverKit personnalisées très faciles, surtout sans compte développeur Apple payant, mais c'est possible.

Avant de commencer, je recommande :

  • Désactiver SIP
  • Définir les boot-args amfi_get_out_of_my_way=1 cs_enforcement_disable=1
  • Exécuter systemextensionsctl developer on
  • Redémarrer votre ordinateur

Après cela, vous devriez pouvoir sélectionner votre équipe de développement dans les paramètres du projet Xcode et le projet se compilera avec succès. Vous pouvez ensuite exécuter HIDDriverLoader, utiliser « Install Dext » pour installer l'extension DriverKit et « Trigger Exploit » pour, vous l'aurez deviné, déclencher l'exploit.

Si cela échoue, vous pouvez également essayer de compiler sans signature en utilisant :

root@kitploit:~
xcodebuild build CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO

Puis en signant manuellement à l'aide de :

root@kitploit:~
codesign -fs self-sign-cert --entitlements HIDDriverLoader/HIDDriverLoader.entitlements build/Release/HIDDriverLoader.app/Contents/MacOS/HIDDriverLoader
codesign -fs self-sign-cert --entitlements HIDDriver/HIDDriver.entitlements build/Release/HIDDriverLoader.app/Contents/Library/SystemExtensions/*.dext/*.driver

Remplacez self-sign-cert par le nom d'un certificat auto-signé dans votre trousseau.

Merci de votre lecture :)

Télécharger l’outil