Skip to content
KitploitKITPLOIT
ИнструментыБлог
Отправить
ИнструментыБлог
Отправить

Инструменты для хакинга, пентеста и кибербезопасности — ваш арсенал защиты!

Kitploit — это каталог инструментов для хакинга, кибербезопасности и пентестинга. Находите последние обновления проектов для поиска уязвимостей, анализа систем, автоматизации тестирования и усиления вашей безопасности.

··Ленты·Контакты·Конфиденциальность·© 2026 Kitploit

Каталог инструментов

Категории

Все категории
Loading categories
CVE-2022-42864 — PoC-эксплойт для состояния гонки CVE-2022-42864 в IOHIDFamily | Kitploit
Инструменты/GitHubGitHub/muirey03/cve-2022-42864
Анализ уязвимостейЭксплуатацияЭксплуатация Бинарных Файлов
GitHubmuirey03/cve-2022-42864

CVE-2022-42864

PoC-эксплойт для состояния гонки CVE-2022-42864 в IOHIDFamily

Репозиторий
6873 лет назадПроверено Kitploit

Популярное

Смотреть все →

Откройте для себя самые используемые инструменты нашего сообщества.

Изучить все инструменты

Просмотрите нашу коллекцию инструментов

Смотреть все инструменты →
Поделиться

CVE-2022-42864: Дьявольские куки

Что это за репозиторий?

Это мой (незавершённый) концепт-эксплойт для CVE-2022-42864, уязвимости типа «проверка-в-момент-использования» (TOCTOU) в IOHIDFamily, исправленной в iOS 16.2 / macOS Ventura 13.1.

Каков статус концепт-эксплойта?

Эксплойт в настоящее время достигает того же примитива «произвольный kfree», который используется в эксплойте multicast_bytecopy. Однако последующий эксплойт-флоу multicast_bytecopy был значительно затруднён мерами защиты, так что это не полный эксплойт, а лишь демонстрация серьёзности проблемы.

Стоит ли мне это запускать?

Если вам приходится спрашивать — нет. Этот код не делает ничего полезного, он просто вызывает панику ядра. Я не несу ответственности за потерю данных или нестабильность, которые может вызвать этот код.

Баг

Комментарий Apple из исходного кода, когда эта проблема была исправлена, хорошо подводит итог:

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.

Давайте посмотрим на функцию до патча (я попытался пометить соответствующие строки):

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;
}
  • Цикл на [1] подсчитывает количество IOHIDElementValue в буфере и сохраняет это значение в cookieCount.
  • Проверка на [2] (в сочетании с условием цикла while) гарантирует, что поле length каждого заголовка не выходит за границы буфера elementData (равно как и не может не доставать до конца буфера, хотя это менее важно).
  • После того как все элементы подсчитаны и проверены этим циклом, на [3] в куче выделяется буфер cookies размером cookieCount * 4 (или используется стековый буфер, если cookieCount достаточно мал).
  • Затем второй цикл на [4] проходит по буферу повторно, снова разбирая IOHIDElementValue.
  • Объекты OSData создаются для хранения значения каждого элемента на , используя поле , проверенное в первом цикле.

Так в чём же проблема? Эта функция ведёт себя полностью корректно, когда elementData не является изменяемым; проблема возникает, когда метод вызывается с разделяемой памятью. Переходим к методу 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;
}

Здесь postElementTransaction вызывается с md->getBytesNoCopy() — памятью, разделяемой с пользовательским пространством, что нарушает предположение о неизменяемости elementData. Содержимое буфера elementData может измениться после цикла на [1], но до цикла на [4]. Что это значит для атакующего?

У атакующего есть два способа воспользоваться этим:

  • Первый — заменить length небольшого IOHIDElementValueHeader в конце буфера на гораздо большее значение. Это означает, что при создании OSData на [5] он выйдет далеко за пределы буфера elementData, что позволит атакующему читать данные за границами буфера с помощью IOHIDInterface::GetElementValues_Impl.
  • Второй — заменить length большого IOHIDElementValueHeader в начале буфера на гораздо меньшее значение. Это заставит цикл на [4] разобрать намного больше заголовков, чем было подсчитано в цикле на [1], поэтому при записи cookies в массив cookies на [6] произойдёт переполнение массива, поскольку index никогда не проверяется на соответствие .

На практике это позволяет атакующему читать данные кучи ядра за пределами границ произвольного размера, а также записывать произвольные данные (опять же произвольного размера) за пределы границ в кучу ядра. Это два мощных примитива.

Выигрываем гонку

В условиях состояния гонки мы всегда ищем способы определить, была ли гонка выиграна успешно, чтобы можно было продолжать попытки до успеха, делая наш триггер детерминированным. К счастью, в случае с этим состоянием гонки мы можем сделать именно это.

Для варианта чтения за пределами границ (OOB read) я помещаю ещё один IOHIDElementValueHeader после заголовка, у которого меняю length, с value, установленным в узнаваемую константу 0xD1AB011CAC1DF00D. Затем, читая значение элемента обратно, я понимаю, что выиграл гонку, если вижу знакомый заголовок 0xD1AB011CAC1DF00D в начале возвращённых данных.

Для варианта записи за пределами границ (OOB write) я помещаю один IOHIDElementValueHeader после заголовка, у которого меняю length, но перед заголовками, чьи cookie будут переполнены, на этот раз с узнаваемым value 0xD15EA5ED. Этот заголовок будет инкапсулирован внутри value более крупного элемента в случае, если мы не выиграем гонку, поэтому заголовок будет разобран и значение элемента будет установлено в 0xD15EA5ED, только если мы выиграем гонку. Считывая значение элемента, я узнаю, был ли я успешен.

Исправление Apple

Чтобы исправить проблему, Apple решила добавить третий цикл между циклом [1] и циклом [4], проверяющий каждое поле length и затем кэширующий его в новом массиве dataLengths, одновременно гарантируя, что количество элементов не изменилось. Затем финальный цикл использует закэшированные длины для своих вычислений, избегая повторного чтения из буфера.

Проблемы с эксплуатацией

Основное препятствие, которое нужно преодолеть при эксплуатации этой уязвимости, заключается в том, что буфер, из которого мы переполняемся, принадлежит зоне KHEAP_DATA_BUFFERS, поэтому цели для эксплуатации ограничены. В этом концепт-эксплойте я решил нацелиться на заголовки kmsg, так как это одна из немногих структур в KHEAP_DATA_BUFFERS, содержащих указатели ядра. Примитив «произвольный kfree», полученный таким подходом, — это тот же примитив, который используется в эксплойте multicast_bytecopy, однако массив IOSurfaceClient теперь защищён PAC, и поддельные клиенты должны иметь корректный указатель обратно на IOSurfaceRootUserClient, который их создал, из-за чего это больше не является желаемой целью для чтения/записи в ядре.

Сборка и установка

Apple не сделали сборку и установку пользовательских расширений DriverKit очень простой, особенно без платной учётной записи Apple Developer, но это возможно.

Перед началом я рекомендую:

  • Отключить SIP
  • Установить boot-args amfi_get_out_of_my_way=1 cs_enforcement_disable=1
  • Выполнить systemextensionsctl developer on
  • Перезагрузить компьютер

После этого вы сможете выбрать свою команду разработчика в настройках проекта Xcode, и проект успешно соберётся. Затем вы можете запустить HIDDriverLoader, использовать "Install Dext" для установки расширения DriverKit и "Trigger Exploit", чтобы — как вы догадались — запустить эксплойт.

Если это не сработает, вы также можете попробовать собрать без подписи, используя:

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

А затем подписать вручную с помощью:

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

Замените self-sign-cert на имя самоподписанного сертификата в вашей связке ключей.

Спасибо за чтение :)

Скачать инструмент
[5]
length
  • На [6] cookie каждого элемента записывается в массив cookies, выделенный на [3].
  • cookieCount