Команда The Binarly Research Team представляет первое публичное исследование по Cross-Silicon Exploitation в экосистеме UEFI: от x86 до ARM.
Традиционно, раскрытия уязвимостей в прошивках UEFI были были преймущественно сфокусированны на х86 экосистемах, в частности - на устр-вах от Intel и AMD. Тем не менее с ростом рынка ARM устр-в возникло стремление к расширению и унификации, которое привело к созданию UEFI прошивок для ARM. Это неизбежно привело к первому публичному раскрытию в истории UEFI информации, относящейся к спецификации экосистем ARM устр-в. В январе 2023 BINARLY обнародовали несколько уязвимостей, влияющих на референс-код Qualcomm, а так же и на других производителей устройств и IBV: Multiple Vulnerabilities in Qualcomm and Lenovo ARM based Devices
Это было результатом исследования, целью которого было определить - станут ли такие же атаки / классы багов, представленные на x86 устр-вах влиять на ARM девайсы. Наша работа включала в себя реконструкцию boot flow на ARM устр-вах с UEFI прошивкой, а так же изучить средства защиты, и способы смягчения последствий атаки, в сравнении с х86 системами. Мы обсуждаем общий охват и недостатки средств безопасности, а так же достижение arbitrary code execution во время загрузки, как результат эксплойтации уязвимости в фазе DXE с помощью техники ROP (для обхода DEP).
В прошлом мае, на OffensiveCon команда The Binarly Research охватила различные аспекты унификации разработки прошивок с такими фреймворками, как UEFI и последствия их безопасности, с точки зрения атаки/защиты. В этом блоге мы подробно погрузимся в основные идеи, возникшие в ходе этого исследовательского проекта.
запись разговора
слайды доступны
линк
Помимо перехода на новую архитектуру CPU, самое явное отличие была - имплементация изолированной среды исполнения для SMM-подобных и runtime services внутри TrustZone ARM. В конце концов, каждое устр-во будет иметь bootROM (не обновляемый) SoC-specific firmware, которое типично для процессоров ARM. Системная прошивка будет состоять из 3-ех компонентов: UEFI прошивка для загрузочной среды, hypervisor и прошивку TrustZone, которая обычно построенна на ARM Trusted Firmware (TF) и Trusted Execution Environment (TEE), и дополненна большим кол-вом кода для поддержки функциональности UEFI management mode (MM) и других раличных функций, включая функции безопасности.
Держа в уме то, как идеальный концепт может отличаться от реальности, мы решили проверить рельную UEFI прошивку на реальном ARM устройстве.
Единственным недостатком этого устройства является то, что имеет трудности с тем, что бы быть Linux-Friendly, исходя из этого мы решили проводить наши эксперименты с Windows, и с UEFIShell (используя наши собственные EFI драйверы и приложения).
До проведения любых экспериментов - лучше всего заиметь образ UEFI пршивки для текущего устройства, с целью изучить её на предмет уязвимостей, изучить boot flow, проверит статические конфигурации, и.т.д..
В целом - есть 3 способа это сделать:
⦁ Скачать со страницы поддержки OEM.
⦁ Провести software дамп.
⦁ Провести физический (Hardware) дамп.
Мы решили исключить аппаратное вмешательство везде, где это возможно, и получить дамп прошивки с помощью программного обеспечения. C устройствами Microsoft мы можем извлечь образы прошивки UEFI из recovery packages. К примеру, в случае с девайсом Microsoft Surface Pro X, мы можем найти Surface_UEFI_*.bin файл внутри install.swm файла:
К сожалению - мы не смогли найти образ UEFI прошивки в recovery package на Windows Dev Kit 2023. Тем не менее, после загрузки в UEFIShell мы обнаружили следующее:
⦁ Прошивка расположена в физической памяти (только регион BIOS);
⦁ Некоторые FD подключаются во время загрузки, это вывод команды map в UEFIShell:
С целью считать контент региона BIOS с физической память, нам нужно только обладать стартовым и начальным адресами. Возможно так же получить эту информацию, с помощью команды device в UEFIShell.
Если мы будем читать 200 байтов от стартового адреса, используя команду dmem, мы можем удостовериться, что регион BIOS находится по адресу 0x9f000000 (поскольку данные соответствуют структуре EFI Firmware Volume Header):
Как мы видим из вывода команды device - размер региона BIOS 0x3e0000. Тем временем, мы смогли сдампить 0x5d0000 байтов -- максимально возможный объем для чтения подряд.
Так как невозможно сдампить содержимое памяти в файл, используя стандартные команды UEFIShell, мы имплементировали простой инструмент (похожий на dmem, но с дополнительным функционалом): DumpMem
После этого мы сдампили прошивку, при помощи следующих команд:
DumpMem.efi 0x9f000000 0x5d0000 uefi.bin
Ниже - часть дампа прошивки, пропаршенная при помощи UEFITool
Читаем содержимое прошивки из смонтированной файловой системыКак уже было упомянуто, мы обнаружили, что некоторые части прошивки (специфичные файлы/разделы) были смонтированны к разным FS в момент запуска. Ниже описание что и где было смонтированно:
⦁ fs2: EFI файлы с образа прошивки
⦁ fs0: декомпрессированное содержание - 1FD93-9C72-4C15-8C4B-E77F1DB2D792;
⦁ fs1: декомпрессированное содержание - ACF1180-6AF9-436D-A8EE-F7043C19AF18;
⦁ fs7: статические ACPI таблицы;
⦁ fs8: TZAPPS (trusted applications);
⦁ fs9: прошивка для Qualcomm Hexagon (Audio DSP);
⦁ Другие fs содержат ACPI таблицы, SMBIOS шаблоны и TZAPPS бэкапы.
Таким образом SEC или DXE модули, конфигурационные файлы, приложения TZ и другие компоненты прошивки могут быть просто скопированны на USB, без какого либо memory дампинга.
Визуальная репрезентация различных FS демонстрируются ниже:
Следовательно, любое исследование UEFI должно начинаться с загрузки в UEFIShell.
Другим подарком для нас стал файл uefiplat.cfg. Этот файл содержит следующие конфигурационные предметы:
⦁ Config
⦁ MemoryMap
⦁ RegisterMap
⦁ ConfigParameters
Этот файл защищен от записи, и невозможно изменить конфигурацию платформы, путешм перезаписи этого файла. Однако, этот файл содержит много полезной информации, которая упростит наше исследование, и поможет реконструировать boot flow.
⦁ Primary Boot Loader (PBL), содержащий имплементацию Worlds-switch и Secure monitor для поддержки Secure Monitor Calls (SMCs) из Normal и Security worlds;
⦁ Extended Boot Loader (XBL), который проверяет и запускает :
⦁ CDT образ - display support;
⦁ AOP(Always-On Processor) прошивка - переводит CPU в режим пониженного энергопотребления;
⦁ XBL конфиг - описание следующих стадий загрузки.
Здесь представленна схема распределения частей цепочки Trusted Boot
Загрузка Secure Worlds продолжается с подключением TrustZone (TZ) окружения, которая проверяет и запускает:
⦁ QSEE Dev Config;
⦁ Qualcomm Universal Peripheral (QUP) - TZ drivers;
⦁ Qualcomm Secure Execution Environment (QSEE) - TZ kernel :
⦁ Trustlets - TZ apps.
Целью Normal World является подготовка:
⦁ UEFI SEC фазы;
⦁ Qualcomm Hypervisor Execution Enviroment (QHEE);
⦁ UEFI DXE фазы;
Вся trusted загрузочная цепочка Secure World и Normal World продемонстрирована на схеме:
Так как все раскрытые уязвимости относятся к неправильному обращению переменных EFI NVRAM - мы так же должны разобраться с тем, как получить доступ к переменным EFI, которые имплементированы на ARM.
На х86 это было более очевидно:
Data-Only attacks Against UEFI BIOS
Учитывая особенности архитектуры - на ARM способ их чтения и записи отличен:
Знание этого позволит нам подтвердить, что как минимум один из изученных классов уязвимостей полностью может быть портирован от х86 на ARM, с точки зрения эксплойтации.
Это совершенно новый вектор атаки: чтение OOB (memory leak) с помощью шаблонов GetVariable/SetVariable. Часть уязвимого кода в целом выглядит так:
⦁ Data link обновлен gRT->GetVariable() до фактического размера запрашиваемой переменной EFI;
⦁ DataSize не инициализируется заново до gRT->SetVariable();
⦁ Следовательно, когда размер существующей переменной NVRAM Variable1 больше, чем оригинальное значение DataSize (N), дополнительные байты (DataSize - N) будут записываться в переменную Variable2 NVRAM при вызове gRT->SetVariable().
Что бы проэксплуатировать данную уязвимость, атакующему лишь надо поменять первую переменную (для замены DataSize). Эксплуатация таких уязвимостей будет одинаково работать на х86 и AArch64, и не требует навыков работы с двоичными файлами.
Демонстрация PoC для этого класса уязвимостей показана тут:
Средства защиты, применяемые для UEFI прошивок на ARM, в сравнении с х86 прошивками.Так как мы плавно сместились к обсуждению обнаруженных уязимостей и способов их вызова (переменные доступа на ARM), будет так же неплохо подумать о пути к эксплойтации и проверить, какие средства защиты включены на ARM.
Модель безопасности на х86 описана в одном из наших предыдущих исследовательских проектов:
github.com
Короче говоря - много механизмов защиты продемонстрированно, но практически ни один из них не включен в "боевом" режиме.
Однако в прошивке ARM UEFI, которую мы рассматривали, были применены меры по предотвращению эксплойтации stack buffer overflow. Во время этого исследования мы впервые обнаружили прошивку с имплементацией stack canary в производстве UEFI!
Тут сравнение применяемых средств защиты(mitigation) на х86 и ARM
ASLR не применяется на х86 и ARM(основываясь на нашем опыте). Более того, ASLR не поддерживается даже на референс-коде EDK2. Работа в этом направлении идёт уже долгое время:
https://github.com/jyao1/SecurityEx/tree/master/AslrPkg, https://edk2-docs.gitbook.io/a-tour...t_randomization/enable_aslr_for_uefi_in_edkii
Ранее мы демонстрировали, что уязвимости в DXE на х86 могут быть проэксплойтированы легче, чем вы можете себе предствить. Вот один из сценариев атаки, который опирается на факт, что код может быть исполнен в MMIO:
⦁ Запишите шелкод в переменную NVRAM: код
⦁ Найдите адрес этой переменной в projection прошивки в физической памяти: код
⦁ Передача контроля тут - результат эксплойтации (шелл код переживет перезагрузку с тем же адресом)
На нашем целевом ARM устр-ве такая атака не сработает. На основе результатов наших экспериментов, код не может быть исполнен нигде, кроме сегментов кода модулей UEFI. Любая попытка исполнить код в регионе NX - вызывает Synchronous Exception. А так же любая попытка доступа к памяти, защищена от чтения:
Stack canary
Как мы упоминали ранее, мы видели применение stack canary на прошивке цели. Мы думаем, что это имплементация Qualcomm (так как мы видели это на устройствах Qualcomm от различных OEM).
Давайте взглянем как это работает:
⦁ Каждый модуль, находящийся под защитой stack canary будет иметь спецефичную процедуру инициализации в начале функции ModuleEntryPoint
⦁ Если Configuration Table с GUID b898d8dc-080a-40f7-99e3-31627b806a5a существует, значение gCanary (которое указывается gCanaryPtr) будет получено из этой таблицы;
⦁ Другими словами - значение gCanary будет получено из функции UpdateCanary() и установлено в EFI Configuration Table с GUID b898d8dc-080a-40f7-99e3-31627b806a5a:
⦁ Функция UpdateCanary() вызовет функцию
GetRndValue(), которая вернет случайное
значение, полученное через один из SMC
(разновидность службы RNG)
⦁ Затем в начале каждой покрываемой функции значение gCanary будет сохранено в локальной переменной(до возврата адресе) и в конце каждой функции оно будет проверянно :
⦁ Stack variables:
⦁ Проверка Canary:
Эта имплементация выглядит просто и прямолинейно. Однако сразу же возникает вопрос: с того момента, когда значение canary обновлено только, когда Configuration Table с GUID b898d8dc-080a-40f7-99e3-31627b806a5a еще не была создана, будет ли значение canary одинаково для всех модулей? Дабы проверить это - мы сдампили два модуля с инициализированным значением canary. Как оказалось, значения одинаковы.
SemmMenu.efi:
DfciMenu.efi:
Другая проблема, которую мы обнаружили в этом разборе. Мы обнаружили, что не все функции покрыты проверкой canary (Защищены функции, которые используют небезопасные функции из MemLib/PrintLib) к примеру - 9/382 покрыты в DXE модуле QcomChargerDxeW:
Мы так же обнаружили, что не все модули из прошивки были покрыты при помощи инициализации/проверки canary. К примеру - DXE модуль MsNetwork вообще не содержит инициализационную рутину canary
Аналогичная проблема с покрытием была описана на блоге ресёрчеров из Quarkslab (так же на устр-вах Qualcomm):
Таким образом, следующие заключения к условиям эксплойтации могут быть сделаны:
⦁ Базовые адреса драйверов, stack и heap предсказуемы и могут быть получены из UEFIShell
⦁ Даже, если значения canary одинаковы для всех модулей, это все равно усложняет эксплуатацию таких уязвимостей, как stack overflow, оставляя следующие варианты:
⦁ Найти уязвимость в функции, не покрываемой
защитой Stack Canary
⦁ Найти уязвимое место, где мы сможем добиться
исполнения кода, путем переписи локальной
переменной в той же самой функции
(без переписи значения canary)
⦁ DEP работает корректно, поэтому при управлении ПК (из-за переполнения стека) приходится использовать ROP/JOP или переходить к полезному primitive с помощью одного гаджета.
GetVariable Stack Overflow (QcomChargerDxeWp)
Уязвимость в QcomChargerDxeWp, это классическй двойной GetVariable() уязвимость, когда DataSize переменная буфера (в этом случае буфера heap) не ре-инициализированна, после первого запроса к gRT->GetVariable():
Но, как мы можем видеть из псевдокода уязвимой функции, в этом случае 5 значений переменных в строке. Это значит, что атакующий может использовать любую пару переменных(всего 10 вариантов). К примеру, если PrintChargerAppDbgMsg, ChargerPDLogLevel и ChargerPDLogTimer переменные не установлены - атакующий может эксплуатировать уязвимость через переменные DISABLEBATTERY и ForcePowerTesting.
Как мы можем видеть из псевдокода функции - эта функция не покрыта средствами защиты Stack Canary. Однако, эскплойтация этой уязвимость осложнена тем фактом, что return address выше в stack (с меньшим stack offset), чем значение, которое мы можем переписать в буфер произвольной длинны:
Картинка слева показывает локацию return address, и перезаписываемых данных(мы получили этот дамп, используя крюк к gRT->GetVariable()). Картинка справа показывает локацию показывает локацию переменных stack в представлении stack IDA для уязвимой функции.
Подобные случаи могут быть проэксплойтированы, используя следующие подходы:
⦁ Переписывание значений стэка родительской функции(которая инициализируется до вызова уязвимой функции)
⦁ Перезаписывание return address родительской функции(конкретный случай):
⦁ Родительская функция в нашем случае
защищена canary check
Уязвимость в модуле PILDxe очень похожа на предыдущую:
Атакующий может переполнить стэк (через буффер Value) через любую пару переменых, где VariableName будет иметь следующий формат: {Section}.{Setting}.
Cекция - это любая секция из файла uefipil.cfg (ADSPPD, SPSS, ACPI, QUPV3FW, и.т.д.):
Возможные значения Settings перечислены снизу:
Type, FwName, PartiLabel, PartiRootGuid, PartiGuid, ImagePath, SubsysID, ResvRegionStart, ResvRegionSize, ImageLoadInfo, Unlock, OverrideElfAddr, ProxyGuid, BackupPartiLabel, BackupPartiRootGuid, BackupPartiGuid
Уязвимые функции позволяют нам перезаписать PIL конфигурацию, в момент загрузки (первоначально указаная в конфигурационном файле uefipil.cfg)
Смешная вещь в том, что разработчики забыли инициализировать DataSize.
Таким образом, значение DataSize будет заполнена мусорными значениями из стека:
В нашем случае DataSize будет заполнен 0x9F3CF100(указатель стека). Это значение слишком большое для размера переменных NVRAM, так gRT->GetVariable() будет постоянно возвращать EFI_OUT_OF_RESOURCES. Таким образом, на других устр-вах или версиях драйверов это значение может быть достаточно мало, для того, что бы позволить этой уязвимости проэксплуатироваться.
Так же должно быть отмечено, что эта функция так же покрыта Canary Check.
Так она выглядит:
Уязвимость снова кроется в том, что после первого вызова к gRT->GetVariable() DataSize может быть переписан, если актуальный размер переменной будет больше, чем изначальное значение DataSize(в этом случае DataSize будет инициализирован со значением 1).
Затем, после второго вызова к gRT->GetVariable(), значение переменной стека PortValuе может подвержено overflow.
Написание "proof-of-concept" для DOS довольно просто:
⦁ Нам нужно поменять значения UsbConfigPrimaryPort и UsbConfigSecondaryPort, и сделать эти значения, достаточно большие, что бы повредить стек
⦁ Нам нужен сигнал ивента UsbConfigStartUsbLoopback(к сожалению, небыл просигнален по стандарту во время загрузки, так как это часть дигностической функции). Что бы вызвать это событие, мы решили написать простой модуль. Этот модуль будет сигналить ивент UsbConfigStartUsbLoopback, путем Event Handle:
Тем не менее - наша цель показать как мы можем исполнить произвольный код в DXE. Ввиду этого, мы решили написать простую цепочку ROP.
⦁ https://github.com/binarly-io/Research_Publications/tree/main/OffensiveCon_2022/UsbRt_ROP_PoC
⦁ https://github.com/binarly-io/Research_Publications/tree/main/BHUS_2022/BRLY-2022-016-PoC
Преймущества х86 в рамках эксплойтинга уязвимостей в SMM, с использованием ROP/JOP заключается в большом разнообразии гаджетов
Мы не видели такого разнообразия гаджетов в коде прошивки для ARM.
Однако, для того что бы выполнить simple primitive - вызов функции с несколькими аргументами - колличество гаджетов было достаточным.
Мы решили показать сообщение на экране, используя следующую функцию: gST->ConOut->OutputString(gST->ConOut,
Message).
Где:
⦁ gST->ConOut - Известный адрес (0x9439f320)
⦁ gST->ConOut->OutputString - Известный адрес ((0x92fd8cc8)
Цепочка ROP, которую мы построили выглядит так:
Код для сборки значениея переменной UsbConfigSecondaryPort показан ниже (линк)
После исполнения PoC, значение переменной UsbConfigSecondaryPort будет выглядить так:
Финальное значение второй переменной будет 0xD9. То есть переменная UsbConfigPrimaryPort может забрать любое значение с длинной в 0xD9 байтов.
Демо этого PoC тут
⦁ Уязвимости в UEFI на ARM труднее эксплойтировать, но все равно ничего особенного;
⦁ UEFI NVRAM API до сих пор неправильно используется во многих случаях;
⦁ Стандарт UEFI расширяет площадь атак на ARM TrustZone;
⦁ Ограниченное использование средств защиты: stack canary не применяется ко всем функциям, делая возможным использование ROP без дополнительных Memory Leak;
⦁ UEFI на ARM кажется более безопасной архитектурно, чем на х86.
Binarly предлагает правида FwHunt для детектирования уязвимосте в масштабе. чтобы помочь индустрии восстановиться посде многочисленных сбоев в прошивке.
FwHunt Community Scanner:
github.com
FwHunt detection rules:
github.com
Команда Binarly постоянно работает, ради защиты цепочек поставок прошивок и снидения площадей атаки для наших общеотраслевых клиентов, путем поставки инновационных технологий на рынок. Основываясь на нашем опыте, мы понимаем, что исправление уязвимостей для одного производителя - недостаточно. Как результат сложности цепочки поставок прошивок, возникают пробелы, которые трудно устранить на этапе производства, поскольку это связанно с проблемами, не зависящами от поставщиков устройств.
Перевёл : H0unT
Оригинал : https://binarly.io/posts/the_dark_s...ve_into_cross_silicon_exploitation/index.html
Традиционно, раскрытия уязвимостей в прошивках UEFI были были преймущественно сфокусированны на х86 экосистемах, в частности - на устр-вах от Intel и AMD. Тем не менее с ростом рынка ARM устр-в возникло стремление к расширению и унификации, которое привело к созданию UEFI прошивок для ARM. Это неизбежно привело к первому публичному раскрытию в истории UEFI информации, относящейся к спецификации экосистем ARM устр-в. В январе 2023 BINARLY обнародовали несколько уязвимостей, влияющих на референс-код Qualcomm, а так же и на других производителей устройств и IBV: Multiple Vulnerabilities in Qualcomm and Lenovo ARM based Devices
Это было результатом исследования, целью которого было определить - станут ли такие же атаки / классы багов, представленные на x86 устр-вах влиять на ARM девайсы. Наша работа включала в себя реконструкцию boot flow на ARM устр-вах с UEFI прошивкой, а так же изучить средства защиты, и способы смягчения последствий атаки, в сравнении с х86 системами. Мы обсуждаем общий охват и недостатки средств безопасности, а так же достижение arbitrary code execution во время загрузки, как результат эксплойтации уязвимости в фазе DXE с помощью техники ROP (для обхода DEP).
В прошлом мае, на OffensiveCon команда The Binarly Research охватила различные аспекты унификации разработки прошивок с такими фреймворками, как UEFI и последствия их безопасности, с точки зрения атаки/защиты. В этом блоге мы подробно погрузимся в основные идеи, возникшие в ходе этого исследовательского проекта.
запись разговора
слайды доступны
Общий взгляд на UEFI экосистему в ARM
AMI поделились первыми идеями разработки UEFI прошивок для ARM устр-в на UEFI Plugfest в 2016:
линк
Помимо перехода на новую архитектуру CPU, самое явное отличие была - имплементация изолированной среды исполнения для SMM-подобных и runtime services внутри TrustZone ARM. В конце концов, каждое устр-во будет иметь bootROM (не обновляемый) SoC-specific firmware, которое типично для процессоров ARM. Системная прошивка будет состоять из 3-ех компонентов: UEFI прошивка для загрузочной среды, hypervisor и прошивку TrustZone, которая обычно построенна на ARM Trusted Firmware (TF) и Trusted Execution Environment (TEE), и дополненна большим кол-вом кода для поддержки функциональности UEFI management mode (MM) и других раличных функций, включая функции безопасности.
Держа в уме то, как идеальный концепт может отличаться от реальности, мы решили проверить рельную UEFI прошивку на реальном ARM устройстве.
Играем с тестовым ARM девайсом (Windows Dev kit 2023)
Как было упомянуто выше, целевым устройством мы выбрали Microsoft Windows Dev Kit 2023 (так же известная, как проект "Volterra") на процессоре Qualcomm Snapdragon 8cx Gen 3. Мы выбрали это устр-во, потому что были уверенны в наличии интересных нам уязвимостей на его прошивке. Более того - этот девайс один из самых доступных на рынке.Единственным недостатком этого устройства является то, что имеет трудности с тем, что бы быть Linux-Friendly, исходя из этого мы решили проводить наши эксперименты с Windows, и с UEFIShell (используя наши собственные EFI драйверы и приложения).
До проведения любых экспериментов - лучше всего заиметь образ UEFI пршивки для текущего устройства, с целью изучить её на предмет уязвимостей, изучить boot flow, проверит статические конфигурации, и.т.д..
В целом - есть 3 способа это сделать:
⦁ Скачать со страницы поддержки OEM.
⦁ Провести software дамп.
⦁ Провести физический (Hardware) дамп.
Мы решили исключить аппаратное вмешательство везде, где это возможно, и получить дамп прошивки с помощью программного обеспечения. C устройствами Microsoft мы можем извлечь образы прошивки UEFI из recovery packages. К примеру, в случае с девайсом Microsoft Surface Pro X, мы можем найти Surface_UEFI_*.bin файл внутри install.swm файла:
К сожалению - мы не смогли найти образ UEFI прошивки в recovery package на Windows Dev Kit 2023. Тем не менее, после загрузки в UEFIShell мы обнаружили следующее:
⦁ Прошивка расположена в физической памяти (только регион BIOS);
⦁ Некоторые FD подключаются во время загрузки, это вывод команды map в UEFIShell:
Дамп прошивки с физической памяти
С целью считать контент региона BIOS с физической память, нам нужно только обладать стартовым и начальным адресами. Возможно так же получить эту информацию, с помощью команды device в UEFIShell.
Если мы будем читать 200 байтов от стартового адреса, используя команду dmem, мы можем удостовериться, что регион BIOS находится по адресу 0x9f000000 (поскольку данные соответствуют структуре EFI Firmware Volume Header):
Как мы видим из вывода команды device - размер региона BIOS 0x3e0000. Тем временем, мы смогли сдампить 0x5d0000 байтов -- максимально возможный объем для чтения подряд.
Так как невозможно сдампить содержимое памяти в файл, используя стандартные команды UEFIShell, мы имплементировали простой инструмент (похожий на dmem, но с дополнительным функционалом): DumpMem
После этого мы сдампили прошивку, при помощи следующих команд:
DumpMem.efi 0x9f000000 0x5d0000 uefi.bin
Ниже - часть дампа прошивки, пропаршенная при помощи UEFITool
Читаем содержимое прошивки из смонтированной файловой системы
⦁ fs2: EFI файлы с образа прошивки
⦁ fs0: декомпрессированное содержание - 1FD93-9C72-4C15-8C4B-E77F1DB2D792;
⦁ fs1: декомпрессированное содержание - ACF1180-6AF9-436D-A8EE-F7043C19AF18;
⦁ fs7: статические ACPI таблицы;
⦁ fs8: TZAPPS (trusted applications);
⦁ fs9: прошивка для Qualcomm Hexagon (Audio DSP);
⦁ Другие fs содержат ACPI таблицы, SMBIOS шаблоны и TZAPPS бэкапы.
Таким образом SEC или DXE модули, конфигурационные файлы, приложения TZ и другие компоненты прошивки могут быть просто скопированны на USB, без какого либо memory дампинга.
Визуальная репрезентация различных FS демонстрируются ниже:
Следовательно, любое исследование UEFI должно начинаться с загрузки в UEFIShell.
Конфигурационный файл платформы UEFI
Другим подарком для нас стал файл uefiplat.cfg. Этот файл содержит следующие конфигурационные предметы:
⦁ Config
⦁ MemoryMap
⦁ RegisterMap
⦁ ConfigParameters
Этот файл защищен от записи, и невозможно изменить конфигурацию платформы, путешм перезаписи этого файла. Однако, этот файл содержит много полезной информации, которая упростит наше исследование, и поможет реконструировать boot flow.
Реконструируем System Design.
С этой информацией и чутка реверс-инжинирингом, мы смогли реконструировать всю цепочку trusted boot. Как упоминалось ранее - включение ARM процессора приводит к запуску его bootROM, который является Root of Trust. Уровень исключений является самым низким(а привелегии - самыми высокими) на этом моменте: EL3. Без переключения к любому другому уровню исключений он проверяет и запускает:
⦁ Primary Boot Loader (PBL), содержащий имплементацию Worlds-switch и Secure monitor для поддержки Secure Monitor Calls (SMCs) из Normal и Security worlds;
⦁ Extended Boot Loader (XBL), который проверяет и запускает :
⦁ CDT образ - display support;
⦁ AOP(Always-On Processor) прошивка - переводит CPU в режим пониженного энергопотребления;
⦁ XBL конфиг - описание следующих стадий загрузки.
Здесь представленна схема распределения частей цепочки Trusted Boot
Загрузка Secure Worlds продолжается с подключением TrustZone (TZ) окружения, которая проверяет и запускает:
⦁ QSEE Dev Config;
⦁ Qualcomm Universal Peripheral (QUP) - TZ drivers;
⦁ Qualcomm Secure Execution Environment (QSEE) - TZ kernel :
⦁ Trustlets - TZ apps.
Целью Normal World является подготовка:
⦁ UEFI SEC фазы;
⦁ Qualcomm Hypervisor Execution Enviroment (QHEE);
⦁ UEFI DXE фазы;
Вся trusted загрузочная цепочка Secure World и Normal World продемонстрирована на схеме:
Так как все раскрытые уязвимости относятся к неправильному обращению переменных EFI NVRAM - мы так же должны разобраться с тем, как получить доступ к переменным EFI, которые имплементированы на ARM.
На х86 это было более очевидно:
Data-Only attacks Against UEFI BIOS
Учитывая особенности архитектуры - на ARM способ их чтения и записи отличен:
Знание этого позволит нам подтвердить, что как минимум один из изученных классов уязвимостей полностью может быть портирован от х86 на ARM, с точки зрения эксплойтации.
Это совершенно новый вектор атаки: чтение OOB (memory leak) с помощью шаблонов GetVariable/SetVariable. Часть уязвимого кода в целом выглядит так:
⦁ Data link обновлен gRT->GetVariable() до фактического размера запрашиваемой переменной EFI;
⦁ DataSize не инициализируется заново до gRT->SetVariable();
⦁ Следовательно, когда размер существующей переменной NVRAM Variable1 больше, чем оригинальное значение DataSize (N), дополнительные байты (DataSize - N) будут записываться в переменную Variable2 NVRAM при вызове gRT->SetVariable().
Что бы проэксплуатировать данную уязвимость, атакующему лишь надо поменять первую переменную (для замены DataSize). Эксплуатация таких уязвимостей будет одинаково работать на х86 и AArch64, и не требует навыков работы с двоичными файлами.
Демонстрация PoC для этого класса уязвимостей показана тут:
Средства защиты, применяемые для UEFI прошивок на ARM, в сравнении с х86 прошивками.
Модель безопасности на х86 описана в одном из наших предыдущих исследовательских проектов:
Research_Publications/H2HC_2022/[H2HC2022] Data-Only Attacks Against UEFI BIOS.pdf at main · binarly-io/Research_Publications
Contribute to binarly-io/Research_Publications development by creating an account on GitHub.
Короче говоря - много механизмов защиты продемонстрированно, но практически ни один из них не включен в "боевом" режиме.
Однако в прошивке ARM UEFI, которую мы рассматривали, были применены меры по предотвращению эксплойтации stack buffer overflow. Во время этого исследования мы впервые обнаружили прошивку с имплементацией stack canary в производстве UEFI!
Тут сравнение применяемых средств защиты(mitigation) на х86 и ARM
ASLR
ASLR не применяется на х86 и ARM(основываясь на нашем опыте). Более того, ASLR не поддерживается даже на референс-коде EDK2. Работа в этом направлении идёт уже долгое время:
https://github.com/jyao1/SecurityEx/tree/master/AslrPkg, https://edk2-docs.gitbook.io/a-tour...t_randomization/enable_aslr_for_uefi_in_edkii
DEP
DEP частично применяется на х86, и полноценно применяется на ARM. Мы видели случаи на платформах х86 где исполнение кода на stack/heap было и возможно, и не возможно. Есть и другие способы сохранить shellcode, который переживет перезагрузку. К примеру - через NVRAM.Ранее мы демонстрировали, что уязвимости в DXE на х86 могут быть проэксплойтированы легче, чем вы можете себе предствить. Вот один из сценариев атаки, который опирается на факт, что код может быть исполнен в MMIO:
⦁ Запишите шелкод в переменную NVRAM: код
⦁ Найдите адрес этой переменной в projection прошивки в физической памяти: код
⦁ Передача контроля тут - результат эксплойтации (шелл код переживет перезагрузку с тем же адресом)
На нашем целевом ARM устр-ве такая атака не сработает. На основе результатов наших экспериментов, код не может быть исполнен нигде, кроме сегментов кода модулей UEFI. Любая попытка исполнить код в регионе NX - вызывает Synchronous Exception. А так же любая попытка доступа к памяти, защищена от чтения:
Stack canary
Как мы упоминали ранее, мы видели применение stack canary на прошивке цели. Мы думаем, что это имплементация Qualcomm (так как мы видели это на устройствах Qualcomm от различных OEM).
Давайте взглянем как это работает:
⦁ Каждый модуль, находящийся под защитой stack canary будет иметь спецефичную процедуру инициализации в начале функции ModuleEntryPoint
⦁ Если Configuration Table с GUID b898d8dc-080a-40f7-99e3-31627b806a5a существует, значение gCanary (которое указывается gCanaryPtr) будет получено из этой таблицы;
⦁ Другими словами - значение gCanary будет получено из функции UpdateCanary() и установлено в EFI Configuration Table с GUID b898d8dc-080a-40f7-99e3-31627b806a5a:
⦁ Функция UpdateCanary() вызовет функцию
GetRndValue(), которая вернет случайное
значение, полученное через один из SMC
(разновидность службы RNG)
⦁ Затем в начале каждой покрываемой функции значение gCanary будет сохранено в локальной переменной(до возврата адресе) и в конце каждой функции оно будет проверянно :
⦁ Stack variables:
⦁ Проверка Canary:
Эта имплементация выглядит просто и прямолинейно. Однако сразу же возникает вопрос: с того момента, когда значение canary обновлено только, когда Configuration Table с GUID b898d8dc-080a-40f7-99e3-31627b806a5a еще не была создана, будет ли значение canary одинаково для всех модулей? Дабы проверить это - мы сдампили два модуля с инициализированным значением canary. Как оказалось, значения одинаковы.
SemmMenu.efi:
DfciMenu.efi:
Другая проблема, которую мы обнаружили в этом разборе. Мы обнаружили, что не все функции покрыты проверкой canary (Защищены функции, которые используют небезопасные функции из MemLib/PrintLib) к примеру - 9/382 покрыты в DXE модуле QcomChargerDxeW:
Мы так же обнаружили, что не все модули из прошивки были покрыты при помощи инициализации/проверки canary. К примеру - DXE модуль MsNetwork вообще не содержит инициализационную рутину canary
Аналогичная проблема с покрытием была описана на блоге ресёрчеров из Quarkslab (так же на устр-вах Qualcomm):
Таким образом, следующие заключения к условиям эксплойтации могут быть сделаны:
⦁ Базовые адреса драйверов, stack и heap предсказуемы и могут быть получены из UEFIShell
⦁ Даже, если значения canary одинаковы для всех модулей, это все равно усложняет эксплуатацию таких уязвимостей, как stack overflow, оставляя следующие варианты:
⦁ Найти уязвимость в функции, не покрываемой
защитой Stack Canary
⦁ Найти уязвимое место, где мы сможем добиться
исполнения кода, путем переписи локальной
переменной в той же самой функции
(без переписи значения canary)
⦁ DEP работает корректно, поэтому при управлении ПК (из-за переполнения стека) приходится использовать ROP/JOP или переходить к полезному primitive с помощью одного гаджета.
Уязвимости
На устр-ве с Windows Dev Kit 2023 мы обнаружили 3 уязвимости, которые могут быть интересны в рамках эксплойтации. Следующее - обсуждение каждой из них:GetVariable Stack Overflow (QcomChargerDxeWp)
Уязвимость в QcomChargerDxeWp, это классическй двойной GetVariable() уязвимость, когда DataSize переменная буфера (в этом случае буфера heap) не ре-инициализированна, после первого запроса к gRT->GetVariable():
Но, как мы можем видеть из псевдокода уязвимой функции, в этом случае 5 значений переменных в строке. Это значит, что атакующий может использовать любую пару переменных(всего 10 вариантов). К примеру, если PrintChargerAppDbgMsg, ChargerPDLogLevel и ChargerPDLogTimer переменные не установлены - атакующий может эксплуатировать уязвимость через переменные DISABLEBATTERY и ForcePowerTesting.
Как мы можем видеть из псевдокода функции - эта функция не покрыта средствами защиты Stack Canary. Однако, эскплойтация этой уязвимость осложнена тем фактом, что return address выше в stack (с меньшим stack offset), чем значение, которое мы можем переписать в буфер произвольной длинны:
Картинка слева показывает локацию return address, и перезаписываемых данных(мы получили этот дамп, используя крюк к gRT->GetVariable()). Картинка справа показывает локацию показывает локацию переменных stack в представлении stack IDA для уязвимой функции.
Подобные случаи могут быть проэксплойтированы, используя следующие подходы:
⦁ Переписывание значений стэка родительской функции(которая инициализируется до вызова уязвимой функции)
⦁ Перезаписывание return address родительской функции(конкретный случай):
⦁ Родительская функция в нашем случае
защищена canary check
BRLY-2022-030 : GetVariable Stack Overflow (PILDxe)
Уязвимость в модуле PILDxe очень похожа на предыдущую:
Атакующий может переполнить стэк (через буффер Value) через любую пару переменых, где VariableName будет иметь следующий формат: {Section}.{Setting}.
Cекция - это любая секция из файла uefipil.cfg (ADSPPD, SPSS, ACPI, QUPV3FW, и.т.д.):
Возможные значения Settings перечислены снизу:
Type, FwName, PartiLabel, PartiRootGuid, PartiGuid, ImagePath, SubsysID, ResvRegionStart, ResvRegionSize, ImageLoadInfo, Unlock, OverrideElfAddr, ProxyGuid, BackupPartiLabel, BackupPartiRootGuid, BackupPartiGuid
Уязвимые функции позволяют нам перезаписать PIL конфигурацию, в момент загрузки (первоначально указаная в конфигурационном файле uefipil.cfg)
Смешная вещь в том, что разработчики забыли инициализировать DataSize.
Таким образом, значение DataSize будет заполнена мусорными значениями из стека:
В нашем случае DataSize будет заполнен 0x9F3CF100(указатель стека). Это значение слишком большое для размера переменных NVRAM, так gRT->GetVariable() будет постоянно возвращать EFI_OUT_OF_RESOURCES. Таким образом, на других устр-вах или версиях драйверов это значение может быть достаточно мало, для того, что бы позволить этой уязвимости проэксплуатироваться.
Так же должно быть отмечено, что эта функция так же покрыта Canary Check.
BRLY-2022-033 :GetVariable Stack overflow (UsbConfigDxe)
Уязвимость в модуле UsbConfigDxe - да, это очередная проблема с двойной GetVariable(). Она доказанно является легчайшей для эксплойтации. Паттерн уязвимости представлен в функции UsbConfigStartUsbLoopback event callback:
Так она выглядит:
Уязвимость снова кроется в том, что после первого вызова к gRT->GetVariable() DataSize может быть переписан, если актуальный размер переменной будет больше, чем изначальное значение DataSize(в этом случае DataSize будет инициализирован со значением 1).
Затем, после второго вызова к gRT->GetVariable(), значение переменной стека PortValuе может подвержено overflow.
Написание "proof-of-concept" для DOS довольно просто:
⦁ Нам нужно поменять значения UsbConfigPrimaryPort и UsbConfigSecondaryPort, и сделать эти значения, достаточно большие, что бы повредить стек
⦁ Нам нужен сигнал ивента UsbConfigStartUsbLoopback(к сожалению, небыл просигнален по стандарту во время загрузки, так как это часть дигностической функции). Что бы вызвать это событие, мы решили написать простой модуль. Этот модуль будет сигналить ивент UsbConfigStartUsbLoopback, путем Event Handle:
Тем не менее - наша цель показать как мы можем исполнить произвольный код в DXE. Ввиду этого, мы решили написать простую цепочку ROP.
Эксплойтация Stack Overflow с использованием цепочки ROP.
До этого мы показывали как использовать ROP/JOP в SMM для обхода средств безопасности SMM_Code_Chk_En.⦁ https://github.com/binarly-io/Research_Publications/tree/main/OffensiveCon_2022/UsbRt_ROP_PoC
⦁ https://github.com/binarly-io/Research_Publications/tree/main/BHUS_2022/BRLY-2022-016-PoC
Преймущества х86 в рамках эксплойтинга уязвимостей в SMM, с использованием ROP/JOP заключается в большом разнообразии гаджетов
Мы не видели такого разнообразия гаджетов в коде прошивки для ARM.
Однако, для того что бы выполнить simple primitive - вызов функции с несколькими аргументами - колличество гаджетов было достаточным.
Мы решили показать сообщение на экране, используя следующую функцию: gST->ConOut->OutputString(gST->ConOut,
Message).
Где:
⦁ gST->ConOut - Известный адрес (0x9439f320)
⦁ gST->ConOut->OutputString - Известный адрес ((0x92fd8cc8)
Цепочка ROP, которую мы построили выглядит так:
Код для сборки значениея переменной UsbConfigSecondaryPort показан ниже (линк)
После исполнения PoC, значение переменной UsbConfigSecondaryPort будет выглядить так:
Финальное значение второй переменной будет 0xD9. То есть переменная UsbConfigPrimaryPort может забрать любое значение с длинной в 0xD9 байтов.
Демо этого PoC тут
Заключения
Результатом этого исследования стали следуюшие выводы:⦁ Уязвимости в UEFI на ARM труднее эксплойтировать, но все равно ничего особенного;
⦁ UEFI NVRAM API до сих пор неправильно используется во многих случаях;
⦁ Стандарт UEFI расширяет площадь атак на ARM TrustZone;
⦁ Ограниченное использование средств защиты: stack canary не применяется ко всем функциям, делая возможным использование ROP без дополнительных Memory Leak;
⦁ UEFI на ARM кажется более безопасной архитектурно, чем на х86.
Binarly предлагает правида FwHunt для детектирования уязвимосте в масштабе. чтобы помочь индустрии восстановиться посде многочисленных сбоев в прошивке.
FwHunt Community Scanner:
GitHub - binarly-io/fwhunt-scan: Tools for analyzing UEFI firmware and checking UEFI modules with FwHunt rules
Tools for analyzing UEFI firmware and checking UEFI modules with FwHunt rules - binarly-io/fwhunt-scan
FwHunt detection rules:
FwHunt/rules at main · binarly-io/FwHunt
The Binarly Firmware Hunt (FwHunt) rule format was designed to scan for known vulnerabilities in UEFI firmware. - binarly-io/FwHunt
Команда Binarly постоянно работает, ради защиты цепочек поставок прошивок и снидения площадей атаки для наших общеотраслевых клиентов, путем поставки инновационных технологий на рынок. Основываясь на нашем опыте, мы понимаем, что исправление уязвимостей для одного производителя - недостаточно. Как результат сложности цепочки поставок прошивок, возникают пробелы, которые трудно устранить на этапе производства, поскольку это связанно с проблемами, не зависящами от поставщиков устройств.
Перевёл : H0unT
Оригинал : https://binarly.io/posts/the_dark_s...ve_into_cross_silicon_exploitation/index.html
