Глава 14. Сопоставление процессов загрузки UEFI и MBR/ VBR
Содержание
Как мы уже наблюдали, разработка бутитов следует за развитием самого процесса запуска. С введением Windows 7 Политики подписи кода режима ядра, который затруднил загрузку произвольного кода в имеющееся ядро, возродились буткиты, имевшие целью запуск логики процесса до применения каких бы то ни было проверок подписей (например, имея целью VBR, которая не может быть защищена в этот момент). Точно также, по той причине, что новый стандарт UEFI, поддерживаемый в Windows 8, заменяет устаревшие процессы загрузки, подобные потокам запуска MBR/ VBR, она также превращается в следующую цель инфицирования запуска.
Современная UEFI очень отличается от наследуемых подходов. Наследуемые BIOS разрабатывались совместно с самым первым встраиваемым программным обеспечением (ПО) PC- совместимых компьютеров и, всвои первые дни, представляли собой просто фрагмент кода, который настраивался самим оборудованием ПК в процессе первоначальной установки для запуска прочего ПО. Однако по мере того, как аппаратные средства росли в сложности, для их настройки требовался всё более сложный код встроенного ПО, а потому и был разработан стандарт UEFI для контроля над расползающейся сложностью в некой единообразной структуре. В наши дни, почти все современные вычислительные системы рассматриваются как применяющие встроенное ПО UEFI для своей настройки; наследуемый процесс BIOS понижается в ранге до простейших встраиваемых систем.
До введения стандарта UEFI реализации BIOS различныз производителей не применяли совместно никакой общей структуры. Такое отсутствие согласованности создавало препятствия для злоумышленников, которым приходилось иметь целью каждую реализацию BIOS по отдельности, но это также являлось и проблемой для защиты, которая не обладала никаким единнообразным механизмом защиты имеющейся целостности процеса своего запуска и управления потоком. Созданный стандарт UEFI позволил защитникам создать такой механизм, который получил название Безопасного запуска (Secure Boot) UEFI.
Частичная поддержка UEFI началась с Windows 7, однако поддержка Безопасного запуска UEFI не была введена вплоть до Windows 8. Совместно с Безопасным запуском Microsoft продолжает сопровождение наследуемого процесса запуска на основании MBR через Модуль поддержки совместимости (CSM, Compatibility Support Module) UEFI, который не совместим с Безопасным запуском и не предлагает своих гарантий целостности, как это уже вкратце обсуждалось. Вне зависимости от того, будет или нет такая поддержка наследуемости через CSM отключена в будущем, ясно что UEFI следующийважный шаг в развитии самого процесса запуска и, следовательно, основная арена, на которой будет происходить совместная разработка буткитов и обороны процесса запуска.
В этой главе мы сосредоточимся на отличительных особенностях процесса запуска UEFI, в частности, на его отличиях от имеющихся подходов инфицирования запуска MBR/ VBR.
UEFI является некой спецификацией, которая определяет взаимодействие программного обеспечения между некой операционной системой и каким- то встроенным ПО (firmware, прошивки). Первоначально она была разработана Intel для замены широко расходящегося программного обеспечения запуска наследуемого BIOS, которое к тому же было ограничено 16- битным режимом, а следовательно не подходящим для новых аппаратных средств. В наши дни втроенное ПО UEFI доминирует на рынке с ЦПУ Intel, а производители ARM также продвигаются по направлению к нему. Как уже упоминалось, по причинам совместимости, некоторое встроенное ПО на основе UEFI содержит для поддержки неследуемого процесса запуска BIOS ранее разработанных операционных систем Модуль поддержки совместимости (CSM); тем не менее, Безопасный запуск не может поддерживаться под CSM.
Встроенное ПО UEFI походит на миниатюрную операционную систему, которая даже обладает своим собственным стеком сетевой среды. Оно содержит несколько миллионов строк кода, в основном на C, с некоторыми небольшими примесями на языке ассемблера в специфичных для платформы частях. Такое встроенное ПО UEFI, таким образом, является намного более сложным и предоставляет больше функциональных возможностей нежели его предшественники наследуемого BIOS. И, в отличии от такого наследуемого BIOS, его части ядра являются открытым исходным кодом, а это свойство, наряду с утечками кода (к примеру, утечкой исходного кода AMI в 2013), открыло возможности для исследований внешних уязвимостей. И в самом деле, за эти годы были опубликованы обширные сведения относительно уязвимостей UEFI и направлений атак на неё, некоторые из которых обсуждаются в Главе 16.
Замечание | |
---|---|
Внутренне присущая сложность встроенного ПО UEFI является одной из основных причин некоторого числа уязвимостей и направлений атак UEFI, о которых появлялись сообщения на протяжении лет. Собственно доступность его исходного кода и величайшая открытость подробностей реализации встроенного ПО UEFI, тем не менее, таковыми не являются. Доступность исходного кода не должна обладать отрицательным воздействием на безопасность и на самом деле обладает обратным эффектом. |
С точки зрения безопасности, основные отличия в процессе запуска UEFI происходят из основной цели поддержки Безопасного запуска: логика основного потока наследуемого MBR/ VBR целиком прекращается и полностью заменяется компонентами UEFI. Мы уже несколько раз упоминали Безопасный запуск, а таеперь мы более пристально рассмотрим как мы изучаем сам процесс UEFI.
Давайте прежде всего просмотрим примеры вредоносных изменений запуска ОС, которые мы наблюдали до сих пор, а также которые причиняют ему соответствующие буткиты:
-
Изменение кода запуска MBR (TDL4)
-
Изменение таблицы разделов MBR (Olmasco)
-
Блокирование параметра VBR BIOS (Gapz)
-
Изменение кода самораскрутки IPL (Rovnix)
Из этого перечня мы можем видеть, что все основные техники инфицирования имеющегося процесса запуска зависят от осквернения имеющейся целостности следующей загружаемой стадии. Безопасный запуск UEFI имеет целью изменение данного шаблона устанавливая некую цепочку доверительных отношений, на протяжении которой имеющаяся целостность каждого этапа в общем потоке проверяется до загрузки этого этапа и передачи ему управления.
Основная задача имевшегося наследуемого BIOS на основе MBR состояла единственно в применении всех необходимых
настроек аппаратных средств и затем перенесения управления в каждую последующую стадию соответствующего кода
запуска - от кода запуска к MBR, далее к VBR и, наконец, к некому загрузчику запуска ОС (например, к
bootmgr
и winload.exe
в случае Windows); вся прочая логика этого потока выходила за рамки её ответственности.
Процесс запуска в UEFI по большей части отличен. MBR и VBR более не существуют; вместо этого собственный
единственный фрагмент кода запуска UEFI отвечает за загрузку необходимого
bootmgr
.
UEFI также отличается от наследуемого BIOS в представляемом виде таблицы разделов, который она применяет. В отличии от наследуемого BIOS, который пользуется таблицей разделов в стиле MBR, UEFI поддерживает GPT (GUID Partition Table). Такие GPT сильно отличны от MBR. Таблицы MBR поддерживают только четыре первичных или расширенных слотов разделов (со множеством логических разделов в неком расширенном разделе, если это необходимо), с то время как GPT поддерживате намного большее число разделов, причём каждый из них идентифициркется неким уникальным 18- байтовым GUID (Globally Unique Identifer, Глобально уникальным идентификатором). В целом правилы разбиения на разделы MBR намного сложнее правил в GPT; имеющийся стиль GPT допускает размеры разделов большего размера и обладает плоской структурой таблицы за счёт применения меток GUID вместо небольших целых чисел для идентификации разделов. Такая плоская струткура таблицы упрощает определённые стороны управления разделами в UEFI.
Для поддержки имеющегося процесса запуска UEFI такая новая схема разбиения на разделы GPT определяет некий выделенный раздел из которого загружается UEFI запуск ОС (в наследуемой таблице MBR эта роль проигрывалась неким "активным" битом настройки флага в какой-то из первичных разделов). Этот особый раздел именуется как системный раздел EFI и он форматируется файловой системой FAT32 (хотя также возможны FAT12 и FAT15). Устнаовленный путь к такому загрузчику запуска внутри этой файловой системы раздела определяется некой переменной энергонезависимой памяти с произвольным доступом (NVRAN,nonvolatile random access memory), также именуемой переменной UEFI. NVRAM является небольшим модулем хранения, располагающимся н материнской плате ПК, который используется для хранения самого BIOS и настроек конфиигурации операционной системы.
Для Microsoft Windows значение пути к загрузчику запуска в системе UEFI выглядит как
\EFI\Microsoft\Boot\bootmgfw.efi
. Основная цель этого модуля
состоит в определении местоположения загрузчика ядра соответствующей операционной системы -
winload.efi
для современных версий Windows с поддержкой UEFI - и
передавать ему управление. Основная функциональность winload.efi
в целом та же самая что и у winload.exe
: загрузить и выполнить
инициализацию образа ядра своей операционной системы.
Рисунок 14-1 отображает поток процесса запуска для сопоставления наследуемого BIOS и UEFI, пропускающего такие шаги как MBR и VBR.
Как вы можете видеть, системы на основе UEFI намного больше выполняют во встроенном ПО прежде чем передать управление
запускающему загрузчику соответствующей операционной системы, нежели это делает наследуемый BIOS. Не существует никаких
промежуточных этапов подобных коду саморасткрутки MBR/ VBR; сам процесс запуска полностью управляется единственно
самим встроенным ПО UEFI, в то время как встроенное ПО BIOS заботится лишь об инициализации платформы, позволяя
последующим загрузчикам соответствующей операционной системы (bootmgr
и winload.exe
) для выполнения всего остального.
Другое гигантское изменение, вводимое UEFI состоит в том, чтобы почти весь его код исполнялся в защищённом режиме, за исключением небольшого первоначального корешка, которому передаётся управление самим ЦПУ при его включении или сбросе. Защищённый режим предоставляет поддержку для выполнения 32- или 64- битного кода (хотя он также допускает эмуляцию прочих наследуемых режимов, которые не применяются в современной логике запуска). В противоположность этому наследуемая логика запуска исполняет большую часть кода в 18- битном режиме пока не передаст управление соответствующим загрузчикам ОС.
Другое различие между встроенным ПО UEFI и наследуемым BIOS состоит в том, что основная часть встроенного ПО UEFI написано на C (и даже может компилироваться при помощи компилятора C++, что и делают определённые производители), причём лишь небольшая часть пишется на языке ассемблера. Это обеспечивает лучшее качество кода по сравнению с реализациями целиком на ассемблере наследуемым встроенным ПО BIOS.
Последующие отличия между встроенным ПО наследуемого BIOS и UEFI представлены в Таблице 14-1.
Наследуемый BIOS | Встроенное ПО UEFI | |
---|---|---|
Архитектура |
Процесс разработки встроенного ПО не обладает спецификацией; все производители BIOS независимо сопровождают свой собственный базовый код |
Унифицированная спецификация для разработки встроенного ПО и эталонный код Intel (EDKI/ EDKII) |
Реализация |
В основном язык ассемблера |
C/C++ |
Модель памяти |
16- битный реальный режим |
32/ 64- битный защищённый режим |
Код самораскрутки |
MBR и VBR |
Отсутствует (свой процесс запуска контролирует встроенное ПО) |
Схема разбиения разделов |
Таблица разделов MBR |
Отсутствует (свой процесс запуска контролирует встроенное ПО) |
Дисковый ввод/ вывод |
Системные прерывания |
Службы UEFI |
Загрузчики запуска |
|
|
Взаимодействие с ОС |
Прерывания BIOS |
Службы UEFI |
Сведения о конфигурации запуска |
Память CMOS, отсутствует понятие переменных NVRAM |
Хранилище переменных NVRAM UEFI |
Пержде чем мы перейдём к подробностям процесса запуска UEFI и загрузчика запуска её операционной системы, мы пристальнее взглянем на особенности GPT. Понимание имеющихся отличий между схемами разделов MBR и GPT, существенными для изучения самого процесса запуска UEFI.
Когда вы просматриваете некий первичный жёсткий диск Windows, отфоратированный с ПЗЕ в каком- то шестнадцатеричном
редакторе, вы не обнаружите никакого кода запуска MBR или VBR в его первых двух секторах (1 сектор = 512 байт).
Это пространство, которое в наследуемом BIOS содержало бы код MBR, почти полностью заполнено нулями. Вместо него,
в самом начале его второго сектора вы можете обнаружить некую подпись EFI PART
по смещению 0x200
(Рисунок 14-2 ),
сразу после знакомого нам тега конца MBR 55 AA
. Именно эта подпись заголовка
соответствующего GPT таблицы разделов EFI и указывает на него как таковой.
Однако, таблица разделов MBR не исчезла совсем. Для совместимости с наследуемыми процессами запуска и такими инструментами как дисковый редактор нижнего уровня до GPT, сам GPT эмулирует свою старую таблицу MBR пи своём запуске. Такая эмулируемая таблица разделов MBR содержит теперь всего одну запись для всего диска GPT целиком, показанную на Рисунке 14-3. Такой вид схемы MBR носит название Защитной MBR.
Рисунок 14-3
Наследуемый заголовок MBR, синтаксически разобранный в редакторе 010 по шаблону
Drive.bt
Данная Защитная MBR (Protective MBR) препятствует наследуемому ПО, такому как дисковые утилиты, разрушать разделы GUID помечая всё дисковое пространство целиком как заявленное под единственный раздел; не осведомлённые о GPT наследуемые инструменты не примут ошибочно свои разделы разбитые под GPT в качестве свободного пространства. Такая защитная MBR обладает тем же самым форматом что и обычная MBR, несмотря на то что это всего лишь заглушка. Имеющееся встроенное ПО UEFI распознает эту Защитную MBR на предмет того чем она является и не будет пытаться выполнять из ней какой бы то ни было код.
Самое основное отступление от наследуемого процесса запуска BIOS состоит в том, что весь тот код, который отвечает за самые начальные этапы запуска своей системы теперь содержится в самом встроенном ПО UEFI, располагающемся в своей микросхеме флеша, вместо его размещения на своём диске. Это означает, что методы инфицирования MBR, которые заражают соответствующие MBR или VBR данного диска (применяемые подобные TDL4 или Olmasco ПО, как это обсуждалось, соответственно в Главе 4 и Главе 10), не окажут воздействия на системы, на поток запуска систем на основе GPT, причём даже без включённого Безопасного запуска.
Проверка поддержки GPT | |
---|---|
Вы можете проверить содержит ли ваша система Windows поддержку GPT воспользовавшись командами PowerShell
Microsoft. В частности, командой |
Таблица 14-2 перечисляет описания тех значений, которые обнаруживаются в заголовке GPT.
Название | Смещение | Длина |
---|---|---|
Подпись "EFI PART" |
|
8 байт |
Ревизия версии GPT |
|
4 байта |
Размер заголовка |
|
4 байтf |
CRC32 заголовка |
|
4 байта |
Зарезервировано |
|
4 байта |
Текущая LBA (logical block addressing, адресация логического блока) |
|
8 байт |
Резервная копия LBA |
|
8 байт |
Первая используемая LBA для разделов |
|
8 байт |
Последняя используемая LBA для разделов |
|
8 байт |
GUID диска |
|
16 байт |
Начальная LBA массива записей разделов |
|
8 байт |
Число записей разделов в массиве |
|
4 байта |
Размер отдельной записи раздела |
|
4 байта |
СКС32 массива разделов |
|
4 байта |
Зарезервировано |
|
* |
Как вы можете видеть, такой заголовок GPT содержит только поля констант вместо кода. С точки зрения криминалистики, наиболее важным из этих полей выступают Начальная LBA массива записей разделов (Starting LBA of array of partition entries) и Число записей разделов в массиве (Number of partition entries in array). Эти записи определяют, соответственно, значение местоположения и размер данной таблицы разделов на самом жёстком диске.
Другим интересным полем в этом заголовке GPT является Резервная копия LBA (Backup LBA), которая предоставляет значение местоположения резервной копии данного заголовка GPT. Это делает возможным для вас восстанавливать свой первичный заголовок GPT в случае его разрушения. Мы затронем такую резервную копию заголовка GPT в Главе 13, когда будем обсуждать вымогательское ПО Petya, которое шифрует и первичный и резервный заголовки GPT для усложнения процесса восстановления системы.
Как это показано на Рисунке 14-4, каждая запись в данной таблице разделов предоставляет сведения по свойствам и местоположению раздела на его жёстком диске.
Два 64- битных поля Первый LBA и
Последний LBA определяют, соответственно, значения адреса самого первого и самого
последнего секторов раздела. Поле GUID типа раздела содержит некое значение
GID, которое указывает значение типа этого раздела. Например, для системного раздела EFI, который мы упоминали ранее в
части Разбиение диска на разделы: сопоставление MBR и GPT,
значением типа выступает C12A7328-F81F-11D2-BA4B-00A0C93EC93B
.
Отсутсвие какого бы то ни было кода в схеме GPT представляет некую проблему для инфицирований буткитами: как разработчики вредоносного ПО смогут передавать управление самим процессом запуска своему вредоносному коду в такой схеме GPT? Одна из идей состоит в изменении загрузчиков запуска EFI до передачи ими управления в надлежащее ядро ОС. Прежде чем мы изучим это, давайте рассмотрим основы собственно архитектуры встроенного ПО и процесса запуска.
Синтаксический анализ GPT при помощи SweetScape | |
---|---|
Для синтаксического разбора некого устройства GPT в запущенной машине или вывода дампа разделов вы можете
воспользоваться совместно используемым Редактором SweetScape 010 с шаблоном |
Изучив установленную схему разбиения на разделы GPT, мы теперь понимаем где располагается необходимая загрузка запуска ОС и как находит его на жёстком диске встроенное ПО UEFI. Далее, давайте рассмотрим наше встроенное ПО UEFI заливает и исполняет необходимый загрузчик ОС. Мы предоставим справочные сведения по тем этапам общего процесса запуска UEFI проходящего с целью подготовки необходимой среды для выполнения самой загрузки.
Имеющееся встроенное ПО UEFI, которое интерпретирует приведённую выше структуру данных из таблицы GPT для определения местоположения загрузчика ОС, хранящегося в микросхеме флеша материнской платы (также имеющего названия флеша SPI, где "SPI" это название того интерфейса шины {последовательного}, который соединяет эту микросхемы с остальным набором микросхем). Когда данная система запускается, установленная логика набора микросхем устанавливает соответствие его содержимого памяти микросхемы этого флеша в определённую область ОЗУ, чьи начальный и завершающий адреса настраиваются в самом наборе микросхем оборудования и зависят от конфигурации конкретного ЦПУ. После того как этот код отображённого микросхема флеша SPI получает управление по включению питаня, он выполняет инициализацию имеющегося оборудования и загружает различные драйверы, необходимый диспетчер запуска ОС, надлежащий загрузчик ОС, а затем, наконец, само соответствующее ядро ОС. Основные этапы этой последовательности можно суммировать следующим образом:
-
Встроенное ПО выполняет инициализацию UEFI платформы, осуществляет инициализацию ЦПУ и набора микросхем и загружает модули UEFI платформы (также носящих название драйверов UEFI; они отличаются от загружаемого на нашем следующем этапе специфичного для устройства кода).
-
Диспетчер запуска UEFI выполняет нумерацию устройств в его внешних шинах (таких как шина PCI), загружает драйверы устройств и затем заливает приложения запуска.
-
Диспетчер запуска Windows (
bootmgfw.efi
) заливает необходимый Загрузчик запуска Windows. -
Загрузчик запуска Windows (
winload.efi
) заливает надлежащую ОС Windows.
Тот код, который отвечает за этапы 1 и 2 располагается в самом флеше SPI; код, необходимый для этапов 3 и 4 выделяется из той файловой системы, которая располагается в особом разделе UEFI на загрузочном жёстком диске, после того как 1 и2 сделали возможным считывание с этого жёсткого диска. Установленная спецификация UEFI и дальге делит своё встроенное ПО на компоненты, отвечающие за различные части инициализации аппаратных средств или действий процесса запуска, как это проиллюстрировано на Рисунке 14-5.
Заливаемый загрузчик ОС по существу полагается на имеющиеся службы EFI и службы времени исполнения EFI, предоставляемые встроенным ПО UEFI для запуска и управления самой системы. Как мы поясним в разделе Внутри загрузчика операционной системы, такой загрузчик ОС полагается на эти службы для установления некой среды в которой он сможет заливать необходимое ядро ОС. После того как загрузчик ОС получает управление рассматриваемым потоком запуска от своего встроенного ПО UEFI, эти службы запуска удаляются и более не доступны загружаемой операционной системе. Службы времени исполнения, тем не менее, остаются доступными для своей операционной системы на время её работы и предоставляют некий интерфейс для считывания и записи переменных NVRAM UEFI, выполнения обновлений встроенного ПО (через Капсульное обновление), а также для перезагрузки или выключения этой системы.
Капсульное обновление встроенного ПО | |
---|---|
Капсульное обновление является некой технологией для для безопасного обновления встроенного ПО UEFI. Установленная операционная система заливает капсулу образа обновления встроенного ПО в память и выставляет сигналы для своего встроенного ПО UEFI через некую службу времени исполнения, которые представляют эту капсулу. В результате само встроенное ПО UEFI перезапускает свою систему и обрабатывает эту капсулу обновления на протяжении следующего запуска. Капсульное обновление предпринимает попытку стандартизации и улучшения имеющейся безопасности самого процесса обновления встроенного ПО UEFI. Мы обсудим его более подробно в Главе 15. |
В противоположность запуску наследуемой BIOS, спецификация UEFI описывает каждый этап с самого начала инициализации оборудования и далее. До появления этой спецификации производители аппаратных средств обладали большей свободой в процессе разработки своего встроенного ПО, однако такая свобода также допускала и беспорядок, а следовательно и уязвимости. Данная спецификация описывает четыре основные последовательные этапа самого процесса запуска, причём каждый из них со своими обязанностями:
- Security (SEC)
Инициализирует временную память при помощи кэшей ЦПУ и определяет местоположения соответствующего загрузчика для следующей фазы PEI. Выполняемый на этом этапе SEC запускается из памяти флеша SPI.
- Pre-EFI Initialization (PEI)
Настраивает имеющийся контроллер памяти, инициализирует свой набор микросхем и обрабатывает сответствующий процесс возобновления S3. Выполняемый на этом этапе код исполняется во временной памяти пока не инициализируется имеющийся контроллер памяти. После её выполнения данный код PEI исполняется в постоянной памяти.
- Driver Execution Environment (DXE)
Инициализирует службы Режима управления системой (SMM, System Management Mode) и DXE (своих ядра, диспетчера, драйверов и тому подобного), а аткже слуюж запуска и времени исполнения.
- Boot Device Selection (BDS)
Обнаруживает необходимые аппаратные устройства с который может быть запущена ОС, например, через перенумерацию периферийных устройств на имеющейся шине PCI, которые могут содержать совместимые с UEFI загрузчики запуска (например, некие загрузчики ОС).
Все те компоненты, которые применяются в данном процессе запуска, располагаются в самом флеше SPI, за исключением необходимого загрузчика ОС, который располагается в файловой системе соответствующего диска и обнаруживаемого этим флешем SPI - на основании кода этапов DXE/ BDSчерез некий путь файловой системы, сохраняемый в какой- то переменной UEFI NVRAM (как это обсуждалось ранее).
Соответствующие этапы SMM и DXE являются чем- то, представляющим наибольшую область интереса для вживляемых руткитов. Режим SMM, в кольце -2, является наиболее привилегированным режимом системы - более привилегированным чем гипервизоры в кольце -1 (Обратите внимание на врезку Режим управления системой для получения дополнительных сведений по SMM и кольцам уровней полномочий.) В этом режиме вредоносный код может воспользоваться полным контролем над данной системой.
Аналогично, драйверы DXE предлагают другой мощный момент для реализации функциональности буткитов. Хорошим примером вредоносного ПО на основе DXE является встроенное ПО руткита Hacking Team, обсуждаемое в Главе 15.
Режим управления системой | |
---|---|
Режим управления системой является особым режимом ЦПУ x86, выполняемый со специальными более высокими привилениями "кольца -2" (то есть "минус два", который ниже и более мощно чем "кольцо -1", которое в свою очередь более мощное нежели "кольцо 0", исторически наиболее доверительные полномочия - разве это не удача, что у нас имеется бесконечный запас целых меньше нуля?). SMM был введён в процессорах Intel 386 первоначально как некое средство добавления мощного управления, но к тому же вырос в современных ЦПУ как в сложности, так и в важности. SMM теперь некая встроенная часть самого встроенного ПО, отвечающий за всё отделение настройки инициализации и памяти в своём процессе запуска. Код SNN выполняется в неком обособленном адресном пространстве, предназначенном быть изолированным от схемы адресного пространства обычной операционной системы (включая пространства ядра самой ОС). В Главе 15 и Главе 16 мы рассмотрим подробнее то, как руткиты UEFI пользуются SMM. |
Теперь мы изучим этот последний этап и тот процесс, посредством которого получает управление соответствующее ядро операционной системы. Подробности DXE и SMM мы рассмотрим подробнее в Главе 15.
Теперь, когда хранимый в SPI код встроенного ПО UEFI выполнил свою работу, он передаёт управление хранимому на диске соответствующему загрузчику ОС. Код этого загрузчика также 64- или 32- битный (в зависимости от версии своей операционной системы); в этом процессе запуска нет никакого места для кода загрузки MBR или VBR.
Сам загрузчик ОС состоит из нескольких файлов, хранимых в соответствующем разделе системы EFI, включая модули
bootmgfw.efi
и winload.efi
.
Первый именуется как Windows Boot Manager, а второй как
Windows Boot Loader. Местоположение этих модулей также определяется
переменными NVRAM. В частности, значение пути UEFI этого устройства (определяемого тем как установленный стандарт UEFI
перенумеровывает имеющиеся в материнской плате порты и шины) содержащего значение ESP (EFI system partition) хранится
в переменной BOOT_ORDER
NVRAM порядка запуска (которую её пользователь, как
правило, может изменять через настройки BIOS); значение пути внутри самой файловой системы ESP хранится в другой
переменной, BOOT
(которая обычно в
\EFI\Microsoft\Boot\
).
Доступ к Диспетчеру запуска Windows
Диспетчер запуска встроенного ПО UEFI обращается к переменным NVRAM UEFI для поиска ESP, а затем, в случае
Windows, из него диспетчер запуска конкретной ОС, bootmgfw.efi
.
Этот диспетчер запуска затем создаёт некий образ времени исполнения этого файла в памяти. Для осуществления этого
он полагается на считывание значения жёсткого диска старта и разборки его файловой системы встроенным ПО UEFI.
В других ОС эта переменная NVRAM будет содержать путь к загрузчику этой ОС; например, для Linux он указывает на
его загружающий запуск GRUB (grub.efi
).
После того как bootmgfw.efi
загружен, наш диспетчер запуска
встроенного ПО UEFI передаёт управление в соответствующую точку записи
bootmgfw.efi
, EfiEntry
.
Именно она запускает процесс запуска ОС и она указывает как хранимое во флеше SPI встроенное ПО передаёт управление
хранимому на жёстком диске коду.
Установка некой среды выполнения
Соответствующая запись EfiEntry
, прототип которой показан в
Листинге 14-2, вызывает Диспетчер запуска Windows,
bootmgfw.efi
, и применяется для настройки необходимых обратных
вызовов встроенного ПО UEFI для Загрузчика запуска Windows, winload.efi
,
который вызывается сразу после него. Этот обратный вызов соединяет код winload.efi
со службами времени исполнения встроенного ПО UEFI, в которых он нуждается для действий с периферией, например
считывания своего жёсткого диска. Эти службы продолжат применяться Windows даже когда она полностью загружена, через
обёртки уровня абстракции от аппаратных средств (HAL, hardware abstraction layer), которые мы вскорости увидим
настроенными.
Листинг 14-2. Прототип подпрограммы EfiEntry
(EFI_IMAGE_ENTRY_POINT
)
EFI_STATUS EfiEntry (
1 EFI_HANDLE ImageHandle, // UEFI image handle for loaded application
2 EFI_SYSTEM_TABLE *SystemTable // Pointer to UEFI system table
Первый параметр
EfiEntry
(1)
указывает на модуль bootmgfw.efi
, который отвечает за продолжение
общего процесса запуска и вызова winload.efi
.
Второй параметр(2)
содержит значение указателя на таблицу конфигурации UEFI (EFI_SYSTEM_TABLE
),
которая является ключом к доступу большинства сведений о конфигурации служб среды EFI
(Рисунок 14-6).
Загрузчик winload.efi
пользуется службами UEFI для загрузки
необходимого ядра операционной системы со стеком драйвера своего устройсва запуска и для инициализации
EFI_RUNTIME_TABLE
в пространстве этого ядра для последующего доступа самим ядром
через модуль кода библиотеки HAL (hal.dll
). HAL употребляет значение
EFI_SYSTEM_TABLE
и экспортирует те функции, которые обёртывают необходимые
функции времени исполнения UEFI для всего остального ядра. Данное ядро вызывает эти функции для выполнения таких
задач, как считывание переменных NVRAM и обработки обновлений BIOS через так называемое Капсульное обновление,
передаваемое во встроенное ПО своей UEFI.
Обратите внимание на шаблон множества обёрток, создаваемых через тот специфичный для оборудования UEFI код, настраиваемый на самых ранних этапах запуска каждым из последующих уровней. Вы никогда не знаете насколько глубоко в кроличью нору UEFI способны проваливаться некие системные вызовы ОС!
Пример структуры EFI_SYSTEM_TABLE
применяемого модулем
hal.dll
HAL отображён на
Рисунке 14-7.
HalEfiRuntimeServiceTable
удерживает указатель к
EFI_RUNTIME_SERVICES
, который в свою очередь содержит значение адресов точек
входа подпрограмм служб, которые будут делать такие вещи как получать или устанавливать переменные NVRAM,
выполнять Капсульное обновление и тому подобное.
В своих следующих главах мы проанализируем эти структуры в контексте уязвимостей, эксплуатации и руткитов
встроенного ПО. На данный же момент мы просто хотим выделить лишь что
EFI_SYSTEM_TABLE
и (в особенности)
EFI_RUNTIME_SERVICES
внутри него являются основными ключами для поиска тех
структур, которые отвечают за доступ к седениям о конфигурации UEFI и что к некоторым из этих сведений имеется
доступ из режима ядра самой операционной системы.
Рисунок 14-8 показывает дизассемблированную
подпрограмму EfiEntry
. Одна из её первых инструкций включает некий вызов
к функции EfiInitCreateInputParametersEx()
, которая преобразовывает
значения параметров EfiEntry
в тот формат, который ожидается в
bootmgfw.efi
. Внутри
EfiInitCreateInputParametersEx()
некая подпрограмма с названием
EfiInitpCreateApplicationEntry()
создаёт некую запись для
bootmgfw.efi
в надлежащих Сведениях конфигурации запуска (BCD,
Boot Confguration Data), неком двоичном хранилище параметров настройки для загрузчика запуска Windows. После
возврата из EfiInitCreateInputParametersEx()
, получает управление
подпрограмма BmMain
(выделенная на
Рисунке 14-8).
Обратите внимание что в этот момент для надлежащего доступа к операциям аппаратного устройства, включая
всякий ввод и вывод жёсткого действия, а также для инициализации памяти, соответствующий Диспетчер запуска
Windows обязан применять только службы EFI, поскольку необходимые основные стеки драйверов Windows ещё пока
не загружены, а потому недоступны.
Считывание Сведений конфигурации запуска
В качестве следующего этапа BmMain
вызывает следующие подпрограммы:
BmFwInitializeBootDirectoryPath
Подпрограмма применяется для инициализации значения пути запуска приложений (
\EFI\Microsoft\Boot
)
BmpLaunchBootEntry
Подпрограмма используется для монтирования и считывания файла базы данных BCD (
\EFI\Microsoft\Boot\BCD
) через службы UEFI (дисковый ввод/ вывод)BmOpenDataStore
иImgArchEfiStartBootApplication
Подпрограммы применяются для выполнения запуска приложения (
winload.efi
)
Листинг 14-3
отображает Данные конфигурации запуска в качестве вывода через инструмент
bcdedit.exe
стандартной командной строки, который содержится
во всех последних версиях Microsoft Windows. Значения путей к модулям Диспетчеру запуска Windows и Загрузчика
запуска Windows, соответственно, помечены как (1)
и (2).
Листинг 14-3. Вывод команды консоли bcdedit
PS C:\WINDOWS\system32> bcdedit
Windows Boot Manager
--------------------
identifier {bootmgr}
device partition=\Device\HarddiskVolume2
1 path \EFI\Microsoft\Boot\bootmgfw.efi
description Windows Boot Manager
locale en-US
inherit {globalsettings}
default {current}
resumeobject {c68c4e64-6159-11e8-8512-a4c49440f67c}
displayorder {current}
toolsdisplayorder {memdiag}
timeout 30
Windows Boot Loader
-------------------
identifier {current}
device partition=C:
2 path \WINDOWS\system32\winload.efi
description Windows 10
locale en-US
inherit {bootloadersettings}
recoverysequence {f5b4c688-6159-11e8-81bd-8aecff577cb6}
displaymessageoverride Recovery
recoveryenabled Yes
isolatedcontext Yes
allowedinmemorysettings 0x15000075
osdevice partition=C:
systemroot \WINDOWS
resumeobject {c68c4e64-6159-11e8-8512-a4c49440f67c}
nx OptIn
bootmenupolicy Standard
Диспетчер запуска Windows (bootmgfw.efi
) также отвечает за
проверку своей политики запуска и за инициализацию устанавливаемых компонентов Кода целостности и Безопасного
запуска, обсуждаемых в наших последующих главах.
На следующем этапе общего процесса запуска bootmgfw.efi
заливает и проверяет Загрузчик запуска Windows (winload.efi
).
Перед запуском загрузки winload.efi
его Диспетчер запуска Windows
инициализирует карту памяти для переноса в соответствующий защищённый режим, который предоставляет как виртуальную
память, так и организацию страниц. Что важно, он выполняет эту настройку через службы UEFI времени исполнения вместо
выполнения этого напрямую. Это создаёт некий сильный уровень абстракции для структур данных виртуальной памяти
самой ОС, таких как GDT, которые изначально обрабатывались наследуемым BIOS в 16- битном коде ассемблера.
Передача управления в Winload
На самом последнем этапе Диспетчера запуска Windows подпрограмма
BmpLaunchBootEntry()
заливает и исполняет
winload.efi
, Загрузчик запуска Windows.
Рисунок 14-9
представляет полный граф вызовов от EfiEntry()
к BmpLaunchBootEntry()
, как вырабатываемый дизассемблером
Hex-Rays IDA Pro со сценарием IDAPathFinder (http://www.devttys0.com/tools/).
Предшествующий функции BmpLaunchBootEntry()
поток управления выбирает
верную запись запуска на соновании установленных в хранилище BCD значений. Когда включено Полное шифрование тома
(Full Volume Encryption, BitLocker), Диспетчер запуска дешифрует этот раздел системы до того как он передаст
управление своему Загрузчику запуска. Следующая за BmpTransferExecution()
функция BmpLaunchBootEntry()
проверяет установленные варианты запуска и
передаёт управление в BlImgLoadBootApplication()
, который затем вызывает
ImgArchEfiStartBootApplication()
. Подпрограмма
ImgArchEfiStartBootApplication()
отвечает за инициализацию защищённого
режима памяти для winload.efi
. После этого управление
передаётся в функцию Archpx64TransferTo64BitApplicationAsm()
, которая и
завершает подготовку для запуска winload.efi
(Рисунок 14-10).
После этого критически важного момента весь поток управления передаётся в winload.efi
,
который отвечает за заливку и инициализацию своего ядра Windows. До этого момента исполнение происходит в среде
UEFIповерх служб запуска и операций под плоской моделью физической памяти.
Замечание | |
---|---|
Когда Безопасный запуск отключён, вредоносный код может на этом этапе процесса запуска выполнять любые изменения памяти, так как модули режима ядра ещё не защищенытехнологией Windows KPP (Kernel Patch Protection, Защитой от исправлений кода ядра, также носящей название PatchGuard). PatchGuard проинициализируется только на самых последних шагах общего процесса запуска. Однако, когда PatchGuard активирован он существенно затруднит внесение видоизменений в модулях ядра. |
Загрузчик запуска Windows выполняет следующие действия настроек:
-
Инициализирует отладчик своего ядра если эта ОС запускается в режиме отладки (включая и режим отладки своего гипервизора).
-
Обёртывает Службы запуска UEFI в абстракциях HAL для последующего использования кодом режима ядра Windows и вызывает Службы запуска выхода.
-
Проверяет свой ЦПУ на предмет поддержки гипервизором Hyper-V функций и устанавливает их включение когда они поддерживаются.
-
Проверяет политики для Virtual Secure Mode (VSM) и DeviceGuard policies (только для Windows 10).
-
Запускает проверки целостности своего ядра самого по себе и всех компонентов Windows и затем передаёт управление этому ядру.
Загрузчик запуска Windows начинает выполнение с подпрограммы OslMain()
, как
это показано в Листинге 14-4, которая выполняет
все описанные выше действия.
Листинг 14-4. Декомпилированная функция OslMain()
(Windows 10)
__int64 __fastcall OslpMain(__int64 a1)
{
__int64 v1; // rbx@1
unsigned int v2; // eax@3
__int64 v3; //rdx@3
__int64 v4; //rcx@3
__int64 v5; //r8@3
__int64 v6; //rbx@5
unsigned int v7; // eax@7
__int64 v8; //rdx@7
__int64 v9; //rcx@7
__int64 v10; //rdx@9
__int64 v11; //rcx@9
unsigned int v12; // eax@10
char v14; // [rsp+20h] [rbp-18h]@1
int v15; // [rsp+2Ch] [rbp-Ch]@1
char v16; // [rsp+48h] [rbp+10h]@3
v1 = a1;
BlArchCpuId(0x80000001, 0i64, &v14);
if ( !(v15 & 0x100000) )
BlArchGetCpuVendor();
v2 = OslPrepareTarget (v1, &v16);
LODWORD(v5) = v2;
if ( (v2 & 0x80000000) == 0 && v16 )
{
v6 = OslLoaderBlock;
if ( !BdDebugAfterExitBootServices )
BlBdStop(v4, v3, v2);
1 v7 = OslFwpKernelSetupPhase1(v6);
LODWORD(v5) = v7;
if ( (v7 & 0x80000000) == 0 )
{
ArchRestoreProcessorFeatures(v9, v8, v7);
OslArchHypervisorSetup(1i64, v6);
2 LODWORD(v5) = BlVsmCheckSystemPolicy(1i64);
if ( (signed int)v5 >= 0 )
{
if ( (signed int)OslVsmSetup(1i64, 0xFFFFFFFFi64, v6) >= 0
3 || (v12 = BlVsmCheckSystemPolicy(2i64), v5 = v12, (v12 & 0x80000000) == 0 ) )
{
BlBdStop(v11, v10, v5);
4 OslArchTransferToKernel(v6, OslEntryPoint);
while ( 1 )
;
}
}
}
}
}
Загрузчик запуска Windows начинается с настройки пространства адресов памяти своего ядра, вызывая функцию
OslBuildKernelMemoryMap()
(Рисунок 14-11). Затем она выполняет подготовку
загрузки своего ядра с вызовом к функции
OslFwpKernelSetupPhase1()
(1).
Эта функция OslFwpKernelSetupPhase1()
вызывает
EfiGetMemoryMap()
для получения указателя к настроенной ранее структуре
EFI_BOOT_SERVICE
, а после этого сохраняет её в некой глобальной переменной
для последующих операций из режима ядра через надлежащие службы HAL.
После этого наша подпрограмма OslFwpKernelSetupPhase1()
вызывает
соответствующую функцию EFI ExitBootServices()
. Эта функция уведомляет
свою операционную систему что именно она готова получить полное управление; этот обратный вызов делает возможным
выполнение любых настроек последней минуты предле чем передать управление своему ядру.
Проверки политики запуска VSM реализуются в подпрограмме BlVsmCheckSystemPolicy
(2)
(3),
которые проверят свою среду относительно установленных политик Безопасного запуска и считывает значение переменной
UEFI VbsPolicy
в память, заполняя соответствующую струткуру
BlVsmpSystemPolicy.exe
в памяти.
Наконец, поток выполнения достигает ядра своей операционной системы (которым в нашем случае выступает
ntoskrnl.exe
)(4)
через OslArchTransferToKernel()
(Листинг 14-5).
Листинг 14-5. Дизассемблер функции OslArchTransferToKernel()
text:0000000180123C90 OslArchTransferToKernel proc near
.text:0000000180123C90 xor esi, esi
.text:0000000180123C92 mov r12, rcx
.text:0000000180123C95 mov r13, rdx
.text:0000000180123C98 wbinvd
.text:0000000180123C9A sub rax, rax
.text:0000000180123C9D mov ss, ax
.text:0000000180123CA0 mov rsp, cs:OslArchKernelStack
.text:0000000180123CA7 lea rax, OslArchKernelGdt
.text:0000000180123CAE lea rcx, OslArchKernelIdt
.text:0000000180123CB5 lgdt fword ptr [rax]
.text:0000000180123CB8 lidt fword ptr [rcx]
.text:0000000180123CBB mov rax, cr4
.text:0000000180123CBE or rax, 680h
.text:0000000180123CC4 mov cr4, rax
.text:0000000180123CC7 mov rax, cr0
.text:0000000180123CCA or rax, 50020h
.text:0000000180123CD0 mov cr0, rax
.text:0000000180123CD3 xor ecx, ecx
.text:0000000180123CD5 mov cr8, rcx
.text:0000000180123CD9 mov ecx, 0C0000080h
.text:0000000180123CDE rdmsr
.text:0000000180123CE0 or rax, cs:OslArchEferFlags
.text:0000000180123CE7 wrmsr
.text:0000000180123CE9 mov eax, 40h
.text:0000000180123CEE ltr ax
.text:0000000180123CF1 mov ecx, 2Bh
.text:0000000180123CF6 mov gs, ecx
.text:0000000180123CF8 assume gs:nothing
.text:0000000180123CF8 mov rcx, r12
.text:0000000180123CFB push rsi
.text:0000000180123CFC push 10h
.text:0000000180123CFE push r13
.text:0000000180123D00 retfq
.text:0000000180123D00 OslArchTransferToKernel endp
Эта функция упоминалась в предыдущих главах, так как некоторые буткиты (такие как Gapz) цепляют её для вставки своих собственных специальных точек входа в установленном образе ядра.
Как мы уже видели, буткиты на основе наследуемых MBR и VBR, к нас нет возможности получения управления от схемы запуска UEFI, так как тот код самораскрутки, который они инфицируют больше не выполняется в общем потоке процесса запуска UEFI. Ещё большее воздействие на безопасность UEFI происходит по причине поддержки технологии Безопасного запуска. Безопасный запуск изменяет всю игру инфицирования руткитов и буткитов, поскольку он противодействует изменению каких бы то ни было компонентов предзагрузки ОС для злоумышленника - а именно пока тот не обнаружит способа обхода Безопасного запуска.
Более того, самая последняя технология Защитника запуска (Boot Guard), выпущенная Intel отмечает ещё один этап в развитии Безопасного запуска. Защитник запуска является интегрироанной с аппаратными средствами технологией защиты, которая пытается защищать свою систему до старта Безопасного запуска. Говоря кратко, Защитник запуска позволяет производителю платформы устанавливать криптографические ключи, которые поддерживают наличие целостности Безопасного запуска.
Другой технологией последних дней, предоставляемой начиная с ЦПУ Intel Skylake (поколения ЦПУ Intel) является выпуск Защитника BIOS, который вооружает платформы против изменений хранилища встроенного ПО флэша. Даже когда злоумышленник получает доступ к памяти этого флеша, Защитник BIOS способен защищать её от установки вредоносных вставое, тем самым также препятствуя выполнению вредоносного кода в момент запуска.
Эти технологии безопасности напрямую повлияли на саму рижесуру современных буткитов, понуждая разработчиков вредоносного ПО развивать свои подходы противостояния такой обороне.
Само переключение современных ПУ на встроенное ПО UEFI начиная с Windows 7 было первым шагом к изменению общего потока процесса запуска и перестройки экологии буткитов. Те методы, которые полагались на прерывание наследуемого BIOS для передачи управления вредоносному коду вышли из строя, ибо такие структуры исчезли из загружающихся через UEFI систем.
Технология Безопасного запуска полностью изменила эту игру, потому как больше стало невозможным изменять
компоненты загрузки запуска, такие как bootmgfw.efi
и
winload.efi
, напрямую.
Теперь весь поток запуска и проверки со стороны встроенного ПО получает доверие со стороны поддержки аппаратными средствами. Злоумышленникам требуется глубже проникать во встроенное ПО в поиске уязвимостей BIOS и их эксплуатации для обхода таких функциональных возможностей безопасности UEFI. Глава 16 предоставит некий обзор ландщафта таких уязвимостей современного BIOS, но вначале Глава 15 затронет развитие угроз руткитов и буткитов в свете атак на встроенное ПО.