С момента выпуска первой спецификации EFI в двухтысячном году прошло около девятнадцати лет. Десять лет понадобилось интерфейсу, чтобы выйти на пользовательский рынок и закрепиться на нем. На текущий момент редко где можно увидеть современный компьютер без UEFI в прошивке материнской платы. Стандарт интерфейса нарастил «мясо» и несколько тысяч страниц в официальной документации. Для обычного пользователя ничего не поменялось, кроме эпизодических столкновений с включённым Secure Boot. Но если плоскость работы смещается в разработку, всё становится интереснее.
Сама концепция модульной архитектуры UEFI подразумевает, что эти модули можно не только использовать в стандартной конфигурации, но и загрузить что-то своё. Драйвер файловой системы (не ограничиваться же родным efi-шным FAT?), драйвера периферии, приложения, загрузчики – всё можно подгрузить руками, было бы что грузить и немного Shell’а. Можно сделать шаг «вглубь» и обратиться к содержимому прошивки, избавляя себя от танцев с SecureBoot и необходимости писать прослойку из скриптов (на страницах хабра достаточно статей, посвящённых этому).
На этой почве родилась идея создания функциональных модулей, выполняющих разнообразные функции безопасности до загрузки ОС способных в дальнейшем объединиться и стать чем-то вроде интегрированной среды доверенной загрузки, затрагивающей оба сервиса интерфейса boot и runtime таким образом, чтобы между модулями в прошивке и модулями на диске ничего нельзя было «просунуть» без низкоуровневого вмешательства, а после них – только с разрешения администратора безопасности.
Реализация этой идеи познакомила нас с огромным количеством нюансов и тонкостей UEFI – начиная со множества недокументированных или слабодокументированных возможностей, багов, и заканчивая так любимым всеми разработчиками неопределенным поведением. Начнем по порядку.
Платформозависимость
Первое, что необходимо выяснить при интеграции в платформу – сможем ли мы с ней работать? Версия спецификации UEFI важна, и на большинстве устройств она представлена в диапазоне между 2.1 и 2.7. Более новое пока не попадало на исследовательский стенд. Более старое встречается, и его работоспособность может быть ограничена из-за отсутствия нужных протоколов или криво написанных для их реализации драйверов. Например, часто не хватает UnicodeCollation, при обращении к smbios возникают недокументированные ошибки, не работают функции смены языка через SetVariable(). Бывает всякое, в зависимости от вендора и свежести, ведь иногда приходится ставить свои протоколы даже на относительно новые платы.
Ещё в нашей практике посчастливилось наткнуться на два мини-компьютера с Intel Bay Trail D и 32-разрядной прошивкой на борту. Случай редкий, но в своё время вызвал необходимость экстренно перекомпилировать модули. Собственно, как и вопрос: встретимся ли мы с более современной платформой такой же разрядности в будущем? А если встретимся, то где?
Следующий шаг – определение способа интеграции. Модули встраиваются в прошивку, прошивка находится в SPI-микросхеме на плате, там же неподалёку располагается PCH с Intel ME. И тут возникает самый интересный вопрос – а как туда достучаться? Старый добрый программатор с «крокодилом» — это хорошо, это надёжно. Даже если не зацепится до конца, всегда можно посмотреть на горящие светодиоды на плате, питания с программатора для них вполне хватает. Работает почти безотказно, за исключением некоторых старых моделей HP, где микруха SOIC-16 с прошивкой находится в такой доступности, что проще исхитриться и припаять к ее ногам переходник, чем протискивать клипсу.
Знаю, что на Хабре есть люди, сделавшие вклад в написание flashrom’а, им отдельное спасибо.
Но, несмотря на надёжность и достоверность снятия дампа программатором, этот способ плохо подходит, если требуется что-нибудь установить в UEFI на несколько машин, или если целевая платформа для установки не у вас на столе. К счастью для нас, производители оставили нативные утилиты для работы с прошивкой: FPT (Flash programming tool) из комплекта Intel (CS)ME System Tools, и AFU (AMI Firmware Update) для Aptio от American Megatrends. Эти утилиты запускаются как из среды EFI, так из операционных систем Windows, Linux, DOS. Утилиты в чём-то взаимозаменяемые, обе позволяют считать образ если не целиком, то определенные регионы уж точно. И иногда даже позволяют записать обратно.
Пишет и не пишет
Тут и появляется первый серьёзный камень преткновения на пути интеграции. Далеко не все материнские платы позволяют считать прошивку целиком, запрещая доступ к региону ME (ME – почти святое, Интел его читать по-хорошему не разрешит, а по-плохому мы не всегда хотим). Ещё меньше – залить что-то даже в регион BIOS, если только это не подписанная капсула. Вероятность успеха сильно разнится в зависимости от производителя и свежести чипсета. На некоторых моделях материнских плат можно пронаблюдать забавную картину: то, что не записывалось на старых платах вендора, на новых влетает враз.
Иногда бороться с защитой от записи помогает парсер IFR, приоткрывающий нам завесу над скрытыми настройками и переменными. А иногда помогает только хардкор – джампер, открывающий доступ на запись или «выключающий» ME (если таковой предусмотрен, конечно).
Сложный характер систем
Платы Acer, Asus, AsRock и Gigabyte в большинстве случаев пишутся без лишних сложностей. Особняком стоят Intel, HP и серверное железо. HP мало того, что не даёт записать в себя программно, ещё и ругается на любую попытку модификации прошивки (у CodeRush есть статьи по поиску и отключению проверки целостности). Intel более-менее записывался до 87-го чипсета, потом стал глух к просьбам открыть врата региона BIOS.
С Intel’ом первое время было забавно. Импорт модулей в прошивку выполнялся средствами утилиты UEFITool, и мы наткнулись на интересный баг: если вставить модули ffs в конец тома DXE, после всех freeform, то собранный образ «кирпичил» плату. Выходом было добавлять модули после любого родного DXE драйвера. До этого не сразу дошли, и по началу это выглядело, будто Intel контролирует целостность прошивки, как HP. Позже стало понятно, что без автоматической утилиты импорта модулей не обойтись, и проблема сошла на нет после написания оной.
С серверным железом проще и сложнее одновременно. С одной стороны, на серверах всегда существуют дополнительные способы обновлять и модифицировать BIOS’ы, с другой – объем кастомизации в этих самых BIOS’ах зашкаливает, благо на сервера не скупятся и ставят достаточно вместительные микросхемы flash-памяти, зачастую еще и резервируя их.
При установке на сервера всегда неплохо иметь возможность удаленного обновления BIOS через IPMI. Правда, для этого по-хорошему нужна лицензия, само собой платная. Если её не оказывается в нужный момент, вполне возможно угодить в забавную ситуацию, подобную той, которую мы получили, внедряя модули в BIOS сервера Supermicro.
После внедрения модулей загрузка зависала намертво из-за блокирования одним из модулей безопасности (не учли своенравность серверных BIOS’ов, с кем не бывает!). При отсутствии возможности принудительно откатить BIOS через IPMI, рука сама потянулась к программатору, но вот незадача – стандартная SOIC-8 клипса оказалась маловатой для SOIC-16 микросхемы! Ну и ладно, ведь в теории серверная плата имеет возможность резервного восстановления с подключенного носителя, подхватывая SUPER.ROM образ в корне. Но этот механизм не запускается, так как по мнению системы все ОК, все работает, следовательно, и откат BIOS’а не нужен! Что делать?!.. История закончилась беготней по городу в поисках нужной клипсы, экстренной перепайкой проводов, наляпанных китайцами в непонятном для нас порядке, и наконец – перепрошивкой.
С Lenovo вышло ещё интереснее. На полученных от вендора свитчах, под крышкой корпуса была найдена управляющая плата с двумя «микрухами» под прошивку, с SSD под операционку и с жестко зафиксированной батарейкой. BIOS оказался крепким орешком, ни в какую не хотел кушать модифицированный образ, поддаваясь только программатору. В одной из попыток что-то записать, в свитч вставили флешку с консольной убунтой (графику терминал не выдавал) и вполне благополучно загрузились. Сделав то, что требовалось, по старой памяти «выключили» систему командой halt -p. Свитч, по своей природе не приспособленный ни к какому выключению, кроме как по отсутствию питания, оказался не готов к такому и не хотел больше запускаться. Линк на морде горел через раз, вентиляторы тихо шелестели, а все порты выдавали ничего. Перепрошивка не помогала, батарейка сидела как влитая – мы опасались сломать крепление. В итоге, силой упорства и словесного воодушевления под контакты пролезла тонкая пластинка диэлектрика, энергозависимая память сбросилась, свитч ожил.
Исследование снятых с двух чипов дампов показало много интересного. В частности – огромное количество «Invalid» записей в NVRAM основной прошивки и несколько подобных в резервной. Ну и не встречавшуюся ранее мешанину данных в томе с DXE драйверами. О точной причине проблемы старта свитча оставалось только гадать.
Вообще, программная часть редко бывает лишена своих неожиданных нюансов. Многие попавшиеся нам платы до 87-го чипсета (разных производителей) имеют неприятную особенность выдавать бесконечный поток ошибок при вводе команды «dh -v» в консоли shell'a. При ручном вводе это не критично, но при сборе данных в файл это оканчивается досадным зависанием. В обоих случаях приходится перезагружать машину. Радует, что при этом файл с данными не распухает до необъятных размеров.
Очень своенравным показал себя BIOS ПК Kraftway с платой ASRock H81M-DGS. Так, на Ctrl Alt Del он реагирует зависанием, из которого его может вывести только Reset. Были проблемы и с пропуском в Shell’e загрузочного скрипта <startup.nsh> – доли секунды на выбор вместо пяти положенных по умолчанию. Возможно, эти проблемы вызваны модификацией фирменными модулями KSS, возможно, дело в неаккуратно «отвинченном» МЕ.
На плате Asus H97-PLUS в прошивке имеется следующая особенность – со временем переполняется BootOrder. Скорее всего, причина кроется в ошибках в коде. Хотя, может быть, производитель хотел сохранять все загрузочные устройства, когда-либо подключавшиеся в плате, но не рассчитал, что их может быть больше десятка за один день. Так вот, когда BootOrder переполняется, происходит зависание системы в процессе загрузки. Для его очистки необходимо отключить все загрузочные устройства и включить систему. Прошивка очищает себя, и система загружается напрямую в оболочку BIOS Setup. Работоспособность сохраняется до следующего переполнения.
Обобщая опыт работы с платами различных вендоров, приходишь к выводу, что практически невозможно узнать, с какими неожиданностями на уровне EFI будешь иметь дело на следующей плате, даже если у неё уже известная модель. Это своего рода лотерея, ведь иногда и на этапе сбора информации о системе могут возникнуть трудности. Возможно, в этом есть доля неугасающего исследовательского идеализма и веры в производителя, ведь как иначе могут вызывать удивление случаи зависания некоторых наисвежайших плат с ME v11 и v12 при запуске на них FPT или MEInfo старших версий?
Проблематика работы с аппаратными протоколами
Отдельные проблемы всплывают, когда начинаем работать с USB-устройствами – накопителями и токенами. Происходит это зачастую потому, что код BIOS для работы с периферией – это опасный коктейль из драйверов и приложений от Independent Hardware Vendor (IHV) под конкретную периферию, кода от производителя чипсета (в нашем случае – от Intel), кода от производителя BIOS и кода от производителя материнской платы.
Возникали следующие «интересные» ситуации:
Токен «не обнаружен». При этом на нём горит LED. Вероятно, не проходит процедура начального сброса USB-устройства хост-контроллером, то есть питание подано, но сброс через изменение линий D+ и D- не отрабатывает правильно, а без него любые дальнейшие манипуляции с токеном бессмысленны.
Компьютер зависает до загрузки shell (опять же при подключенном токене). При этом без токена ПК нормально стартует. Вживую это выглядит следующим образом: компьютер кажется зависшим сразу после старта, пока токен торчит в разъёме. Вынимаешь его – загрузка внезапно продолжается. Подключаешь – снова висит. Явная проблема в UEFI, и о причинах можно только гадать.
Ситуация, когда невозможно открыть интерфейс USB_IO. Возможно, связана только с интерфейсом для работы со смарт-картами – USB CCID. Некий драйвер AMI уже открыл USB_IO c параметром EFI_OPEN_PROTOCOL_BY_DRIVER. Драйвер имеет протокол с GUID:
#define EFI_AMI_USB_CCID_PROTOCOL_GUID { 0x5FDEE00D, 0xDA40, 0x405A, { 0xB9, 0x2E, 0xCF, 0x4A, 0x80, 0xEA, 0x8F, 0x76} }
// Workaround. Попытаться открыть протокол с флагом EFI_OPEN_PROTOCOL_BY_DRIVER, если ошибка, то открыть с флагом EFI_OPEN_PROTOCOL_GET_PROTOCOL.
//
// Open USB I/O Protocol
//
Status = gBS->OpenProtocol (
ControllerHandle,
&gEfiUsbIoProtocolGuid,
(VOID **) &UsbIo,
This->DriverBindingHandle,
ControllerHandle,
EFI_OPEN_PROTOCOL_BY_DRIVER
);
if (EFI_ACCESS_DENIED == Status)
{ // AMI BIOS workaround (BindingStop will not be invoked)
Status = gBS->OpenProtocol(
ControllerHandle,
&gEfiUsbIoProtocolGuid,
(VOID **)&UsbIo,
This->DriverBindingHandle,
ControllerHandle,
EFI_OPEN_PROTOCOL_GET_PROTOCOL
);
}
Однако при этом не будет вызван BindingStop(), т.е. событие извлечения устройства не отслеживается, и драйвер будет пытаться пользоваться invalid handle. Такое наблюдалось с ПК HP Compaq Elite 8300 SFF и с некоторыми другими. Это или своеобразная защита вендора от нежелательных драйверов, или обычный баг разработки. Возможно, в AMI постоянно что-то делают в направлении USB CCID, но мешающий драйвер не выгрузить, так как он находится в одном модуле «AMI UHCI» вместе с USB HID, USB MassStorage. С UninstallInterface() дела обстоят похожим образом.
Или другая интересная особенность. В одном из UEFI BIOS, где токен не обнаруживался, USB_IO позволял зачитать дескрипторы устройства, но на следующий UsbBulkTransfer() возвращалось EFI_INVALID_PARAMETER. Причем, такое происходило только с некоторыми типами токенов, с абсолютно теми же параметрами другие работали отлично.
Вообще, в протоколе EFI_USB_IO_PROTOCOL интересно реализован метод UsbBulkTransfer(). Он предназначен для гарантированной доставки пакета за неограниченное время, или за время, указанное в параметре Timeout. Но был проведен эксперимент с MassStorage устройством: при копировании большого файла на флешку она извлекалась. ПК зависал намертво. При подключении флешки ПК отвисал и продолжал писать файл как ни в чем не бывало. Та же ситуация была и с токенами, но со своей спецификой. Это проблема архитектурная, в EFI нет прерываний кроме таймера, а устройства работают по опросу. То есть система зависла где-то в опросе USB, но до выхода по таймауту не дошла, при повторном появлении устройства просто продолжила и завершила операцию.
Виртуализация
Отдельно стоит сказать про виртуальные среды. На текущий момент на рынке представлены две основные платформы, поддерживающие эмуляцию EFI-среды: VMware и VirtualBox. Обе имеют свои преимущества и недостатки при взаимодействии с ними как с «реальными» системами. Среда VMware в должной мере предоставляет работу с NVRAM-переменными, но спотыкается при визуальном выводе сообщений во время инициализации DXE-модулей: в лучшем случае, предпочтение отдастся нативным сообщениям о поиске загрузочного носителя, оставив за бортом нужное нам. VirtualBox, наоборот, отлично отрисовывает всё требуемое, но не хочет запоминать длинные переменные.
Ещё один небольшой камень в огород VMware – встроенный в нее драйвер FAT32 поддерживает создание и редактирование файлов только в нотации 8.3. Непонятно, зачем это было сделано, но это ограничение, явно требующее внимания. Вполне вероятно, схожую реализацию драйвера можно пронаблюдать и на реальных платформах, но пока таковые нам не попадались.
С другой стороны, в виртуалках никаких танцев с утилитами прошивки, программаторами, джамперами, неудобными чипами. Отдельный ROM-файл, UEFITool и строчка в конфиг-файле. Почти идиллия.
Напоследок
Кусочек запроса из CHIPSEC. Где учат таким таинствам?
Как уже было сказано, разработка и внедрение в оболочке UEFI – процесс увлекательный и креативный. Всегда можно столкнуться с чем-то новым даже на известном поле. С одной стороны, радует, что стандарт развивается, с другой – огорчает, что конкретные его реализации производителями получаются слишком «творческими».
Основными проблемами были и остаются:
- Отход вендоров от спецификации UEFI при разработке прошивки.
- Ошибки в коде при реализации.
- НДВ в коде, всплывающие при интеграции.
И последнее, но не маловажное – отсутствие многих вещей в официальной (читай, открытой) документации, таких как, например, описаний протокола общения с ME через PCI устройства типа MEI, HECI. Можно найти описание регистров, но не команд. Найти GUID, но не его назначение. Что в очередной раз возвращает работу к длительному анализу, сбору данных и статистики по платформам и помощи дизассемблера.
Следует отметить, что ситуация медленно, но верно исправляется, и хочется верить, что не за горами тот момент, когда разработка под стандарт станет достаточно прогнозируемым и весьма приятным процессом.
Владимир Онипчук,
Руководитель группы программно-аппаратных средств защиты
ООО «Газинформсервис»
Автор: Gazinformservice