Сегодня, 27 декабря 2023 года, мы (Борис Ларин, Леонид Безвершенко и Георгий Кучерин) выступили с докладом Operation Triangulation: What You Get When Attack iPhones of Researchers (Операция «Триангуляция»: что произойдет, если атаковать iPhone специалистов по безопасности) на 37-м всемирном конгрессе хакеров Chaos Communication Congress (37C3), проходившем в Congress Center Hamburg. В этой презентации мы подвели итоги нашего продолжительного исследования «Операция Триангуляция», которое мы проводили совместно с нашими коллегами Игорем Кузнецовым, Валентином Пашковым и Михаилом Виноградовым.
На 37C3 мы также впервые публично раскрыли подробности касательно всех уязвимостей и эксплойтов, использовавшихся в атаке. Мы ежедневно выявляем и анализируем новые эксплойты, а также атаки с их использованием. Мы обнаружили в реальной среде уже более тридцати уязвимостей нулевого дня в продуктах Adobe, Apple, Google и Microsoft и уведомили об этом производителей, однако в данном случае речь идет, безусловно, о самой изощренной цепочке атак, которую мы когда-либо наблюдали.
Цепочка атаки «Операция Триангуляция»
Это была атака через iMessage, не требующая от пользователя каких-либо действий, с использованием четырех уязвимостей нулевого дня для устройств iOS до версии 16.2.
- Злоумышленники отправляли через iMessage вредоносное вложение, которое обрабатывается приложением без ведома пользователя.
- Для удаленного выполнения кода вложение эксплуатирует уязвимость CVE-2023-41990 в недокументированной инструкции ADJUST, предназначенной для шрифтов TrueType, которая присутствовала только на устройствах Apple. Эта инструкция существовала с начала 90-х годов и была удалена в вышедшем исправлении.
- В атаке была использована техника возвратно- и переходно-ориентированного программирования. Проникновение происходило в несколько этапов с использованием языка запросов NSExpression/NSPredicate, при этом в среду библиотеки JavaScriptCore вносились изменения, позволявшие выполнить эксплойт на JavaScript для повышения привилегий.
- Эксплойт обфусцирован с целью сделать его полностью нечитаемым и минимизировать его размер. Тем не менее, в нем около 11 000 строк кода, которые в основном посвящены манипуляциям с памятью JavaScriptCore и памятью ядра.
- Эксплойт использует DollarVM ($vm), отладочный функционал JavaScriptCore, чтобы манипулировать памятью JavaScriptCore из скрипта и исполнять нативные функции API.
- Эксплойт был разработан для запуска как на старых, так и новых iPhone и предусматривал обход Pointer Authentication Code (PAC) для работы на более новых моделях.
- В нем использовалась уязвимость CVE-2023-32434, связанная с целочисленным переполнением в системных вызовах для работы с памятью ядра XNU (mach_make_memory_entry и vm_map). Она позволяла получить доступ на чтение-запись ко всей физической памяти устройства на уровне пользователя.
- Для обхода Page Protection Layer (PPL) используются аппаратные регистры ввода-вывода через память (MMIO). Эта проблема была устранена в исправлении CVE-2023-38606.
- В результате эксплуатации всех уязвимостей JavaScript-эксплойт получает полный контроль над устройством и может запускать шпионские программы, но злоумышленники пошли иным путем: а) запустили процесс IMAgent и внедрили в него полезную нагрузку, стирающую с устройства следы атаки; b) запустили процесс Safari в фоновом режиме и перенаправили его на веб-страницу для перехода к следующему этапу.
- Веб-страница содержит скрипт, верифицирующий жертву, и, если проверка пройдена, происходит переход к следующему этапу — загрузке эксплойта для Safari.
- Эксплойт для Safari использует уязвимость CVE-2023-32435, связанную с выполнением шелл-кода.
- Шелл-код запускает еще один эксплойт ядра в виде объектного файла Mach. Он использует те же уязвимости CVE-2023-32434 и CVE-2023-38606, также впечатляет своими размерами и функциональностью, но совершенно не похож на эксплойт для ядра, написанный на JavaScript. Совпадают лишь некоторые части, связанные с эксплуатацией вышеупомянутых уязвимостей. Тем не менее, большая часть его кода также предназначена для чтения памяти ядра и манипуляций с ней. В нем присутствуют различные утилиты для работы с уже скомпрометированными устройствами, которые в основном не используются.
- Эксплойт получает привилегии root и запускает другие этапы, отвечающие за загрузку шпионского ПО. Мы уже рассказывали об этих этапах в наших предыдущих статьях.
Мы практически завершили обратный инжиниринг всех аспектов этой цепочки атак и в новом году выпустим серию статей с подробным описанием каждой уязвимости и ее эксплуатации.
Однако некоторые аспекты одной конкретной уязвимости нам не удалось до конца понять.
Загадка и уязвимость CVE-2023-38606
Речь пойдет об уязвимости, известной как CVE-2023-38606. Новые модели iPhone имеют дополнительную аппаратную защиту чувствительных областей памяти ядра. Она не позволяет злоумышленникам получить полный контроль над устройством, даже если у них есть доступ на чтение-запись в память ядра, как в случае этой атаки с использованием уязвимости CVE-2023-32434. Мы выяснили, что для обхода этой аппаратной защиты злоумышленники используют другую аппаратную функцию SoC компании Apple.
С ее помощью злоумышленники могут записать необходимые данные по нужному физическому адресу в обход аппаратной защиты памяти, для этого необходимо записать данные, адрес назначения и хэш данных в недокументированные, не используемые прошивкой аппаратные регистры чипа.
Мы предполагаем, что эта недокументированная аппаратная функция, скорее всего, предназначалась для отладки или тестирования инженерами Apple или на заводе-изготовителе, либо была включена по ошибке. Поскольку эта функция не активна в прошивке, мы понятия не имеем, как злоумышленники догадались, как ее использовать.
Мы публикуем технические подробности для того, чтобы другие специалисты по безопасности iOS могли подтвердить наши выводы и выдвинуть возможные идеи о том, как злоумышленники могли узнать об этой аппаратной функции.
Технические подробности
Разные периферийные устройства, присутствующие в SoC, могут предоставлять специальные аппаратные регистры, которые могут использоваться ЦПУ для управления этими периферийными устройствами. Для этого аппаратные регистры отображаются в память, доступную ЦПУ, и поэтому их называют регистрами MMIO (ввода-вывода через память).
Диапазоны адресов MMIO периферийных устройств на устройствах Apple (iPhone, Mac и т. д.) хранятся в файле специального формата — DeviceTree. Файлы дерева устройств можно извлечь из прошивки и затем просмотреть их содержимое с помощью утилиты dt.
Пример того, как диапазоны MMIO хранятся в дереве устройств
Например, на этом скриншоте можно увидеть начало (0x210f00000) и размер (0x50000) диапазона MMIO acc-impl для cpu0.
Анализируя эксплойт, используемый в атаке «Операция Триангуляция», я обнаружил, что большинство MMIO, применяемых для обхода аппаратной защиты памяти ядра, не принадлежат ни одному из диапазонов MMIO, определенных в дереве устройств. Эксплойт предназначен для SoC Apple A12-A16 Bionic и использует недокументированные блоки регистров MMIO, расположенные по адресам: 0x206040000, 0x206140000, 0x206150000.
Это подсказало мне взглянуть на ситуацию с другой стороны. Я изучил разные файлы дерева устройств, предназначенные для разных устройств и разных прошивок, но безуспешно. Затем я просмотрел общедоступный исходный код XNU — тоже безрезультатно. В поисках прямого обращения к этим адресам я исследовал образы ядра, расширения ядра, iboot, прошивки сопроцессоров — никакого результата.
Как такое может быть, что эксплойт использует регистры MMIO, которые не используются прошивкой? Откуда злоумышленники узнали о них? Какому периферийному устройству (устройствам) принадлежат эти адреса MMIO?
У меня возникла идея проверить, какие еще документированные MMIO расположены по соседству с этими неизвестными блоками регистров MMIO. Этот подход оказался успешным.
Рассмотрим дамп записи в дереве устройств для gfx-asc, сопроцессора GPU.
Дамп записи в дерево устройств для gfx-asc
Мы видим два диапазона MMIO: 0x206400000–0x20646C000 и 0x206050000–0x206050008. Теперь посмотрим, как они соотносятся с областями, используемыми эксплойтом.
Соотнесение диапазонов MMIO gfx-asc с адресами, используемыми эксплойтом
Если точнее, то эксплойт использует эти неизвестные адреса: 0x206040000, 0x206140008, 0x206140108, 0x206150020, 0x206150040, 0x206150048. Как видим, большинство из них находится между двумя областями gfx-asc, а оставшийся находится рядом с началом первой области gfx-asc. Это позволяет предположить, что все эти регистры MMIO, скорее всего, относятся к сопроцессору GPU!
После этого я присмотрелся к эксплойту повнимательнее и обнаружил еще один момент, подтверждающий это предположение. В первую очередь при инициализации эксплойт записывает данные в другой регистр MMIO, который в каждой SoC располагается по разным адресам.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
if (cpuid == 0x8765EDEA): # CPUFAMILY_ARM_EVEREST_SAWTOOTH (A16) base = 0x23B700408 command = 0x1F0023FF elif (cpuid == 0xDA33D83D): # CPUFAMILY_ARM_AVALANCHE_BLIZZARD (A15) base = 0x23B7003C8 command = 0x1F0023FF elif (cpuid == 0x1B588BB3): # CPUFAMILY_ARM_FIRESTORM_ICESTORM (A14) base = 0x23B7003D0 command = 0x1F0023FF elif (cpuid == 0x462504D2): # CPUFAMILY_ARM_LIGHTNING_THUNDER (A13) base = 0x23B080390 command = 0x1F0003FF elif (cpuid == 0x07D34B9F): # CPUFAMILY_ARM_VORTEX_TEMPEST (A12) base = 0x23B080388 command = 0x1F0003FF if ((~read_dword(base) & 0xF) != 0): write_dword(base, command) while(True): if ((~read_dword(base) & 0xF) == 0): break |
Псевдокод для управления менеджером питания GFX из эксплойта
С помощью дерева устройств и утилиты pmgr за авторством Siguza мне удалось выяснить, что все эти адреса соответствуют регистру GFX в диапазоне MMIO менеджера питания.
И наконец, я получил третье подтверждение, когда решил попробовать получить доступ к регистрам, расположенным в этих недокументированных областях. Почти сразу же сопроцессор GPU выдал сообщение о критической ошибке: GFX SERROR Exception class=0x2f (SError interrupt), IL=1, iss=0 — power(1).
Таким образом я смог убедиться, что все эти незнакомые регистры MMIO, используемые эксплойтом, принадлежат сопроцессору GPU. Это побудило меня внимательнее изучить его прошивку, которая также скомпилированна под архитектуру ARM и не зашифрована, но я не нашел там ничего, связанного с этими регистрами.
Я решил выяснить, как эксплойт управляет этими неизвестными регистрами MMIO. Регистр 0x206040000 отличается от всех остальных тем, что он расположен в отдельном блоке MMIO. Он также используется только на этапах инициализации и финализации работы эксплойта — это первый регистр, устанавливаемый при инициализации, и последний — при финализации. Исходя из своего опыта, я пришел к выводу, что этот регистр либо включает-выключает аппаратную функцию, используемую эксплойтом, либо управляет прерываниями. Я начал копать в направлении прерываний и довольно скоро смог определить этот неизвестный регистр 0x206040000, а также выяснил, что именно соответствует диапазону адресов 0x206000000–0x206050000. Ниже представлен код эксплойта, который мне удалось воссоздать в ходе обратного инжиниринга. Я уже дал ему подходящее имя.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
def ml_dbgwrap_halt_cpu(): value = read_qword(0x206040000) if ((value & 0x90000000) != 0): return write_qword(0x206040000, value | 0x80000000) while (True): if ((read_qword(0x206040000) & 0x10000000) != 0): break def ml_dbgwrap_unhalt_cpu(): value = read_qword(0x206040000) value = (value & 0xFFFFFFFF2FFFFFFF) | 0x40000000 write_qword(0x206040000, value) while (True): if ((read_qword(0x206040000) & 0x10000000) == 0): break |
Псевдокод для использования регистра 0x206040000 эксплойтом
Мне удалось сопоставить функцию ml_dbgwrap_halt_cpu из приведенного выше псевдокода с одноименной функцией в файле dbgwrap.c исходного кода XNU. Этот файл содержит код для работы с отладочными регистрами MMIO ARM CoreSight основного ЦПУ. Согласно исходному коду, существует четыре области MMIO, связанные с CoreSight, с названиями ED, CTI, PMU и UTT; каждая из них занимает 0x10000 байт, и все они расположены по соседству друг с другом. Функция ml_dbgwrap_halt_cpu использует область UTT; при этом в исходном коде указано, что, в отличие от трех остальных, она не имеет отношения к ARM, а является собственной разработкой Apple, которая была помещена туда просто для удобства.
Записав ARM_DBG_LOCK_ACCESS_KEY в соответствующее место, мне удалось подтвердить, что 0x206000000–0x206050000 действительно является блоком отладочных регистров MMIO CoreSight для сопроцессора GPU. Каждое ядро основного ЦПУ имеет свой блок отладочных регистров MMIO CoreSight, но, в отличие от сопроцессора GPU, их адреса присутствуют в дереве устройств.
Примечательно, что автор(ы) этого эксплойта также в курсе, как использовать эту проприетарную область UTT Apple для вывода процессора из режима ожидания — этого нет в исходном коде XNU. Однако справедливости ради стоит отметить, что это можно легко выяснить опытным путем.
Однако то, что делают злоумышленники с регистрами во второй неизвестной области, таким образом объяснить невозможно. Я не знаю, какие блоки отладочных регистров MMIO там расположены и как злоумышленники узнали способ их задействовать, если те не используются прошивкой.
Рассмотрим остальные неизвестные регистры, используемые эксплойтом.
Регистры 0x206140008 и 0x206140108 отвечают за включение-выключение и запуск аппаратной функции, используемой эксплойтом.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 |
def dma_ctrl_1(): ctrl = 0x206140108 value = read_qword(ctrl) write_qword(ctrl, value | 0x8000000000000001) sleep(1) while ((~read_qword(ctrl) & 0x8000000000000001) != 0): sleep(1) def dma_ctrl_2(flag): ctrl = 0x206140008 value = read_qword(ctrl) if (flag): if ((value & 0x1000000000000000) == 0): value = value | 0x1000000000000000 write_qword(ctrl, value) else: if ((value & 0x1000000000000000) != 0): value = value & ~0x1000000000000000 write_qword(ctrl, value) def dma_ctrl_3(value): ctrl = 0x206140108 value = value | 0x8000000000000000 write_qword(ctrl, read_qword(ctrl) & value) while ((read_qword(ctrl) & 0x8000000000000001) != 0): sleep(1) def dma_init(original_value_0x206140108): dma_ctrl_1() dma_ctrl_2(False) dma_ctrl_3(original_value_0x206140108) def dma_done(original_value_0x206140108): dma_ctrl_1() dma_ctrl_2(True) dma_ctrl_3(original_value_0x206140108) |
Псевдокод для использования регистров 0x206140008 и 0x206140108 эксплойтом
Регистр 0x206150020 используется только для Apple A15/A16 Bionic SoC. Он принимает значение 1 на этапе инициализации эксплойта и возвращается к исходному значению на этапе финализации.
Регистр 0x206150040 используется для хранения определенных флагов и нижней половины физического адреса назначения.
Последний регистр 0x206150048 используется для хранения данных, которые необходимо записать, а также верхней половины физического адреса назначения, объединенного с хешем данных и еще каким-то значением (возможно, командой). Эта аппаратная функция записывает данные выровненными блоками по 0x40 байт, причем все должно быть записано в регистр 0x206150048 за девять последовательных проходов.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
if (cpuid == 0x8765EDEA): # CPUFAMILY_ARM_EVEREST_SAWTOOTH (A16) i = 8 mask = 0x7FFFFFF elif (cpuid == 0xDA33D83D): # CPUFAMILY_ARM_AVALANCHE_BLIZZARD (A15) i = 8 mask = 0x3FFFFF elif (cpuid == 0x1B588BB3): # CPUFAMILY_ARM_FIRESTORM_ICESTORM (A14) i = 0x28 mask = 0x3FFFFF elif (cpuid == 0x462504D2): # CPUFAMILY_ARM_LIGHTNING_THUNDER (A13) i = 0x28 mask = 0x3FFFFF elif (cpuid == 0x07D34B9F): # CPUFAMILY_ARM_VORTEX_TEMPEST (A12) i = 0x28 mask = 0x3FFFFF dma_init(original_value_0x206140108) hash1 = calculate_hash(data) hash2 = calculate_hash(data+0x20) write_qword(0x206150040, 0x2000000 | (phys_addr & 0x3FC0)) pos = 0 while (pos < 0x40): write_qword(0x206150048, read_qword(data + pos)) pos += 8 phys_addr_upper = ((((phys_addr >> 14) & mask) << 18) & 0x3FFFFFFFFFFFF) value = phys_addr_upper | (hash1 << i) | (hash2 << 50) | 0x1F write_qword(0x206150048, value) dma_done(original_value_0x206140108) |
Псевдокод для использования регистров 0x206150040 и 0x206150048 эксплойтом
Если все сделано правильно, устройство должно выполнить операцию прямого доступа к памяти (DMA) и записать данные в указанное место.
Эксплойт использует эту аппаратную функцию для обхода Page Protection Layer (PPL). В основном она используется эксплойтом для внесения изменений в записи таблицы страниц. Она также может использоваться для изменения данных в защищенном сегменте __PPLDATA. Эксплойт не использует ее для изменения кода ядра, но один раз во время тестирования мне удалось перезаписать инструкцию в сегменте __TEXT_EXEC ядра и добиться сообщения о критической ошибке ядра Undefined Kernel Instruction с ожидаемым адресом и значением. Но это сработало только один раз — в остальных случаях я получал критическую ошибку AMCC. У меня уже есть мысли о том, что я сделал в тот момент, когда это сработало, и я собираюсь более тщательно изучить этот вопрос. Было бы замечательно применить уязвимость, которая была использована для причинения нам вреда, для чего-то полезного, например для включения отладки ядра в новых iPhone.
Теперь, когда работа со всеми регистрами MMIO завершена, рассмотрим последний момент: как вычисляются хеши. Алгоритм показан ниже.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
sbox = [ 0x007, 0x00B, 0x00D, 0x013, 0x00E, 0x015, 0x01F, 0x016, 0x019, 0x023, 0x02F, 0x037, 0x04F, 0x01A, 0x025, 0x043, 0x03B, 0x057, 0x08F, 0x01C, 0x026, 0x029, 0x03D, 0x045, 0x05B, 0x083, 0x097, 0x03E, 0x05D, 0x09B, 0x067, 0x117, 0x02A, 0x031, 0x046, 0x049, 0x085, 0x103, 0x05E, 0x09D, 0x06B, 0x0A7, 0x11B, 0x217, 0x09E, 0x06D, 0x0AB, 0x0C7, 0x127, 0x02C, 0x032, 0x04A, 0x051, 0x086, 0x089, 0x105, 0x203, 0x06E, 0x0AD, 0x12B, 0x147, 0x227, 0x034, 0x04C, 0x052, 0x076, 0x08A, 0x091, 0x0AE, 0x106, 0x109, 0x0D3, 0x12D, 0x205, 0x22B, 0x247, 0x07A, 0x0D5, 0x153, 0x22D, 0x038, 0x054, 0x08C, 0x092, 0x061, 0x10A, 0x111, 0x206, 0x209, 0x07C, 0x0BA, 0x0D6, 0x155, 0x193, 0x253, 0x28B, 0x307, 0x0BC, 0x0DA, 0x156, 0x255, 0x293, 0x30B, 0x058, 0x094, 0x062, 0x10C, 0x112, 0x0A1, 0x20A, 0x211, 0x0DC, 0x196, 0x199, 0x256, 0x165, 0x259, 0x263, 0x30D, 0x313, 0x098, 0x064, 0x114, 0x0A2, 0x15C, 0x0EA, 0x20C, 0x0C1, 0x121, 0x212, 0x166, 0x19A, 0x299, 0x265, 0x2A3, 0x315, 0x0EC, 0x1A6, 0x29A, 0x266, 0x1A9, 0x269, 0x319, 0x2C3, 0x323, 0x068, 0x0A4, 0x118, 0x0C2, 0x122, 0x214, 0x141, 0x221, 0x0F4, 0x16C, 0x1AA, 0x2A9, 0x325, 0x343, 0x0F8, 0x174, 0x1AC, 0x2AA, 0x326, 0x329, 0x345, 0x383, 0x070, 0x0A8, 0x0C4, 0x124, 0x218, 0x142, 0x222, 0x181, 0x241, 0x178, 0x2AC, 0x32A, 0x2D1, 0x0B0, 0x0C8, 0x128, 0x144, 0x1B8, 0x224, 0x1D4, 0x182, 0x242, 0x2D2, 0x32C, 0x281, 0x351, 0x389, 0x1D8, 0x2D4, 0x352, 0x38A, 0x391, 0x0D0, 0x130, 0x148, 0x228, 0x184, 0x244, 0x282, 0x301, 0x1E4, 0x2D8, 0x354, 0x38C, 0x392, 0x1E8, 0x2E4, 0x358, 0x394, 0x362, 0x3A1, 0x150, 0x230, 0x188, 0x248, 0x284, 0x302, 0x1F0, 0x2E8, 0x364, 0x398, 0x3A2, 0x0E0, 0x190, 0x250, 0x2F0, 0x288, 0x368, 0x304, 0x3A4, 0x370, 0x3A8, 0x3C4, 0x160, 0x290, 0x308, 0x3B0, 0x3C8, 0x3D0, 0x1A0, 0x260, 0x310, 0x1C0, 0x2A0, 0x3E0, 0x2C0, 0x320, 0x340, 0x380 ] def calculate_hash(buffer): acc = 0 for i in range(8): pos = i * 4 value = read_dword(buffer + pos) for j in range(32): if (((value >> j) & 1) != 0): acc ^= sbox[32 * i + j] return acc |
Псевдокод хеш-функции, используемой этой недокументированной аппаратной функцией
Как можно заметить, это кастомный алгоритм, а хэш вычисляется с помощью предопределенной таблицы sbox. Я пытался найти его в большой коллекции бинарных файлов, но безуспешно.
Возможно, вы обратите внимание, что этот хэш выглядит не очень безопасным: он занимает всего 20 бит (10+10, так как вычисляется дважды), но он справляется со своей задачей до тех пор, пока никто не знает, как его вычислить и использовать. Лучше всего этот подход выражается термином «безопасность через неясность«.
Как злоумышленники смогли обнаружить и использовать эту аппаратную функцию, если она не задействована и в прошивке нет никаких инструкций по ее применению?
Я провел еще один тест и выяснил, что чип M1 в Apple Mac также имеет эту неизвестную аппаратную функцию. Затем я воспользовался чудесным инструментом m1n1, чтобы провести эксперимент. В этом инструменте реализована функция trace_range, позволяющая отследить все обращения к заданному диапазону регистров MMIO. С ее помощью я настроил трассировку для диапазона памяти 0x206110000–0x206400000, но она также не сообщила об использовании этих регистров macOS.
По удивительному совпадению на 37-м конгрессе Chaos Communication Congress (37C3) и в этой статье я рассказывал об уязвимости, очень похожей на ту, которой был посвящен мой доклад на предыдущем 36-м конгрессе Chaos Communication Congress (36C3).
В презентации Hacking Sony PlayStation Blu-ray Drives (Взлом приводов Blu-ray Sony PlayStation) я рассказываю, как мне удалось сделать дамп прошивки и добиться выполнения кода на приводах Blu-ray Sony PlayStation 3 и 4 с помощью регистров MMIO DMA, доступ к которым осуществлялся с помощью SCSI-команд.
Мне удалось обнаружить и использовать эту уязвимость, потому что ранние версии прошивки использовали эти регистры для всех операций с DRAM, однако затем Sony перестала их использовать и начала обращаться к DRAM напрямую, поскольку вся DRAM также отображалась на адресное пространство ЦПУ. Поскольку никто больше не обращался к этим регистрам, а я знал, как с ними работать, я и воспользовался ими. Тут не нужен был никакой секретный хэш-алгоритм.
Не могло ли что-то подобное произойти и в этом случае? Я не могу ответить на этот вопрос, но этот сопроцессор GPU впервые появился только в новых SoC от Apple. Судя по информации, приведенной выше, я очень сомневаюсь, что эта аппаратная функция ранее использовалась для чего-то в прошивках устройств, предназначенных для розничной продажи. Однако не исключено, что ранее она была случайно раскрыта в каком-то выпуске прошивки или исходного кода XNU, а затем информация о ней была удалена.
Я надеялся узнать, что находится во второй неизвестной области благодаря исправлению этой уязвимости, реализованному в iOS 16.6. Мне удалось выяснить, как Apple устранила эту проблему, но они обфусцировали исправление.
Apple устранила эту уязвимость, добавив используемые эксплойтом диапазоны MMIO 0x206000000–0x206050000 и 0x206110000–0x206400000 в pmap-io-ranges, хранящиеся в дереве устройств. XNU использует хранящуюся там информацию для принятия решения о том, разрешать ли отображение определенных физических адресов. Все записи, хранящиеся там, имеют осмысленные имена-метки, поясняющие, к какому типу памяти относится диапазон.
Пример записей, хранящихся в pmap-io-ranges
Здесь PCIe означает Peripheral Component Interconnect Express, DART — Device Address Resolution Table, DAPF — Device Address Filter и так далее.
А вот названия меток для областей, используемых эксплойтом. Они выделяются на фоне остальных.
Записи для областей, используемых эксплойтом
Заключение
Это не обычная уязвимость, и у нас осталось много нерешенных вопросов. Нам неизвестно, как злоумышленники узнали способ использовать недокументированную аппаратную функцию и каково было ее первоначальное назначение. Мы также не знаем, была ли она разработана Apple или это компонент стороннего производителя, как, например CoreSight от ARM.
Но мы знаем точно, и данная уязвимость это доказывает, что даже самые современные аппаратные средства защиты бессильны перед лицом изощренного злоумышленника, пока существуют аппаратные функции, позволяющие обойти эти средства защиты.
При защите аппаратного обеспечения производители очень часто полагаются на принцип «безопасность через неясность», ведь аппаратное обеспечение гораздо сложнее подвергнуть обратному инжинирингу, чем программное. Но это неправильный подход, потому что рано или поздно все тайное становится явным. Системы, опирающиеся на принцип «безопасность через неясность», никогда не будут по-настоящему безопасными.
Обновление 09.01.2024
Известный хардварный хакер Гектор Мартин (marcan) смог выяснить, что предполагаемый кастомный хэш из нашего исследования на самом деле нечто другое. Это код коррекции ошибок (ECC), а точнее, Код Хэмминга с кастомной таблицей поиска – в тексте выше мы называем ее «таблица sbox».
Это открытие дает нам ответ на вопрос об изначальном предназначении неизвестной аппаратной функции. Мы считали, что это отладочная функция, предоставляющая прямой доступ к памяти и защищенная простеньким хэшем для дополнительной безопасности. Однако тот факт, что в работе этой функции задействован код коррекции ошибок (в дополнение к нестабильному поведению, которое наблюдалось при попытке использования этой функции для изменения кода ядра), позволяет сделать вывод, что эта аппаратная функция обеспечивает прямой доступ к кэшу.
Это открытие также повышает вероятность того, что эта неиспользуемая аппаратная функция могла быть обнаружена посредством экспериментов с чипом, хотя для этого злоумышленникам пришлось бы найти большое количество неизвестных переменных. Они могли найти значения в кастомной таблице поиска посредством перебора всех возможных значений, но им также было необходимо знать о существовании в чипе настолько мощной функции отладки для кэша, что для ее работы необходим Код Хэмминга, а также, что наиболее важно, им было необходимо знать расположение и назначение всех задействованных регистров MMIO, а также понимать, как и в каком порядке с ними взаимодействовать. Смогли ли злоумышленники найти все эти неизвестные переменные самостоятельно или эта информация была где-то раскрыта по ошибке? Это до сих пор остается загадкой.
Операция «Триангуляция»: последняя (аппаратная) загадка