Глава 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.

Unified Extensible Firmware Interface

UEFI является некой спецификацией, которая определяет взаимодействие программного обеспечения между некой операционной системой и каким- то встроенным ПО (firmware, прошивки). Первоначально она была разработана Intel для замены широко расходящегося программного обеспечения запуска наследуемого BIOS, которое к тому же было ограничено 16- битным режимом, а следовательно не подходящим для новых аппаратных средств. В наши дни втроенное ПО UEFI доминирует на рынке с ЦПУ Intel, а производители ARM также продвигаются по направлению к нему. Как уже упоминалось, по причинам совместимости, некоторое встроенное ПО на основе UEFI содержит для поддержки неследуемого процесса запуска BIOS ранее разработанных операционных систем Модуль поддержки совместимости (CSM); тем не менее, Безопасный запуск не может поддерживаться под CSM.

Встроенное ПО UEFI походит на миниатюрную операционную систему, которая даже обладает своим собственным стеком сетевой среды. Оно содержит несколько миллионов строк кода, в основном на C, с некоторыми небольшими примесями на языке ассемблера в специфичных для платформы частях. Такое встроенное ПО UEFI, таким образом, является намного более сложным и предоставляет больше функциональных возможностей нежели его предшественники наследуемого BIOS. И, в отличии от такого наследуемого BIOS, его части ядра являются открытым исходным кодом, а это свойство, наряду с утечками кода (к примеру, утечкой исходного кода AMI в 2013), открыло возможности для исследований внешних уязвимостей. И в самом деле, за эти годы были опубликованы обширные сведения относительно уязвимостей UEFI и направлений атак на неё, некоторые из которых обсуждаются в Главе 16.

[Замечание]Замечание

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

Различия между процессами загрузки наследуемого BIOS и 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.

Разбиение диска на разделы: сопоставление MBR и GPT

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.

 

Рисунок 14-1


Основные отличия в потоке запуска между системами с наследуемым BIOS и UEFI

Как вы можете видеть, системы на основе UEFI намного больше выполняют во встроенном ПО прежде чем передать управление запускающему загрузчику соответствующей операционной системы, нежели это делает наследуемый BIOS. Не существует никаких промежуточных этапов подобных коду саморасткрутки MBR/ VBR; сам процесс запуска полностью управляется единственно самим встроенным ПО UEFI, в то время как встроенное ПО BIOS заботится лишь об инициализации платформы, позволяя последующим загрузчикам соответствующей операционной системы (bootmgr и winload.exe) для выполнения всего остального.

Прочие отличия

Другое гигантское изменение, вводимое UEFI состоит в том, чтобы почти весь его код исполнялся в защищённом режиме, за исключением небольшого первоначального корешка, которому передаётся управление самим ЦПУ при его включении или сбросе. Защищённый режим предоставляет поддержку для выполнения 32- или 64- битного кода (хотя он также допускает эмуляцию прочих наследуемых режимов, которые не применяются в современной логике запуска). В противоположность этому наследуемая логика запуска исполняет большую часть кода в 18- битном режиме пока не передаст управление соответствующим загрузчикам ОС.

Другое различие между встроенным ПО UEFI и наследуемым BIOS состоит в том, что основная часть встроенного ПО UEFI написано на C (и даже может компилироваться при помощи компилятора C++, что и делают определённые производители), причём лишь небольшая часть пишется на языке ассемблера. Это обеспечивает лучшее качество кода по сравнению с реализациями целиком на ассемблере наследуемым встроенным ПО BIOS.

Последующие отличия между встроенным ПО наследуемого BIOS и UEFI представлены в Таблице 14-1.

Таблица 14-1. Заголовок GPT
  Наследуемый BIOS Встроенное ПО UEFI

Архитектура

Процесс разработки встроенного ПО не обладает спецификацией; все производители BIOS независимо сопровождают свой собственный базовый код

Унифицированная спецификация для разработки встроенного ПО и эталонный код Intel (EDKI/ EDKII)

Реализация

В основном язык ассемблера

C/C++

Модель памяти

16- битный реальный режим

32/ 64- битный защищённый режим

Код самораскрутки

MBR и VBR

Отсутствует (свой процесс запуска контролирует встроенное ПО)

Схема разбиения разделов

Таблица разделов MBR

Отсутствует (свой процесс запуска контролирует встроенное ПО)

Дисковый ввод/ вывод

Системные прерывания

Службы UEFI

Загрузчики запуска

bootmgr и winload.exe

bootmgfw.efi и winload.efi

Взаимодействие с ОС

Прерывания BIOS

Службы UEFI

Сведения о конфигурации запуска

Память CMOS, отсутствует понятие переменных NVRAM

Хранилище переменных NVRAM UEFI

Пержде чем мы перейдём к подробностям процесса запуска UEFI и загрузчика запуска её операционной системы, мы пристальнее взглянем на особенности GPT. Понимание имеющихся отличий между схемами разделов MBR и GPT, существенными для изучения самого процесса запуска UEFI.

Отличительные особенности таблицы разделов GUID

Когда вы просматриваете некий первичный жёсткий диск Windows, отфоратированный с ПЗЕ в каком- то шестнадцатеричном редакторе, вы не обнаружите никакого кода запуска MBR или VBR в его первых двух секторах (1 сектор = 512 байт). Это пространство, которое в наследуемом BIOS содержало бы код MBR, почти полностью заполнено нулями. Вместо него, в самом начале его второго сектора вы можете обнаружить некую подпись EFI PART по смещению 0x200 (Рисунок 14-2 ), сразу после знакомого нам тега конца MBR 55 AA. Именно эта подпись заголовка соответствующего GPT таблицы разделов EFI и указывает на него как таковой.

 

Рисунок 14-2


Вывод подписи Таблицы разделов GUID дампа из \\.\PhysicalDrive0

Однако, таблица разделов 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. В частности, командой Get-Disk (Листинг 14-1) возвратит некую таблицу, самая последняя колонка которой носит название Partition Style, отображает значение поддерживаемого типа таблицы разделов. Когда он совместим с GPT, вы обнаружите в колонке Partition Style GPT; в противном случае в этой колонке вы увидите MBR.

 

Листинг 14-1. Вывод от Get-Disk



PS C:\> Get-Disk Number Friendly Name  Operational Status  Total Size  Partition Style ------ -------------  ------------------  ----------  --------------- 0      Microsoft      Online                   127GB  GPT        Virtual Disk

Таблица 14-2 перечисляет описания тех значений, которые обнаруживаются в заголовке GPT.

Таблица 14-1. Сопоставление наследуемого BIOS и встроенного ПО UEFI
Название Смещение Длина

Подпись "EFI PART"

0x00

8 байт

Ревизия версии GPT

0x08

4 байта

Размер заголовка

0x0С

4 байтf

CRC32 заголовка

0x10

4 байта

Зарезервировано

0x14

4 байта

Текущая LBA (logical block addressing, адресация логического блока)

0x18

8 байт

Резервная копия LBA

0x20

8 байт

Первая используемая LBA для разделов

0x28

8 байт

Последняя используемая LBA для разделов

0x30

8 байт

GUID диска

0x38

16 байт

Начальная LBA массива записей разделов

0x48

8 байт

Число записей разделов в массиве

0x50

4 байта

Размер отдельной записи раздела

0x54

4 байта

СКС32 массива разделов

0x58

4 байта

Зарезервировано

0x5C

*

Как вы можете видеть, такой заголовок 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, каждая запись в данной таблице разделов предоставляет сведения по свойствам и местоположению раздела на его жёстком диске.

 

Рисунок 14-4


Таблица разделов GPT

Два 64- битных поля Первый LBA и Последний LBA определяют, соответственно, значения адреса самого первого и самого последнего секторов раздела. Поле GUID типа раздела содержит некое значение GID, которое указывает значение типа этого раздела. Например, для системного раздела EFI, который мы упоминали ранее в части Разбиение диска на разделы: сопоставление MBR и GPT, значением типа выступает C12A7328-F81F-11D2-BA4B-00A0C93EC93B.

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

 
[Замечание]Синтаксический анализ GPT при помощи SweetScape

Для синтаксического разбора некого устройства GPT в запущенной машине или вывода дампа разделов вы можете воспользоваться совместно используемым Редактором SweetScape 010 с шаблоном Drive.bt от Бенджамина Верну, находящегося на площадке SweetScape в репозитории Drive.bt в соответвующем разделе Downloads. Этот редактор 010 обладает действительно мощным механизмом синтаксического разбора на основе шаблона, базирующемся на C- подобной структуре (см. Рисунок 14-3).

Как работает встроенное ПО UEFI

Изучив установленную схему разбиения на разделы 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.

 

Рисунок 14-5


Обзор инфраструктуры UEFI

Заливаемый загрузчик ОС по существу полагается на имеющиеся службы EFI и службы времени исполнения EFI, предоставляемые встроенным ПО UEFI для запуска и управления самой системы. Как мы поясним в разделе Внутри загрузчика операционной системы, такой загрузчик ОС полагается на эти службы для установления некой среды в которой он сможет заливать необходимое ядро ОС. После того как загрузчик ОС получает управление рассматриваемым потоком запуска от своего встроенного ПО UEFI, эти службы запуска удаляются и более не доступны загружаемой операционной системе. Службы времени исполнения, тем не менее, остаются доступными для своей операционной системы на время её работы и предоставляют некий интерфейс для считывания и записи переменных NVRAM UEFI, выполнения обновлений встроенного ПО (через Капсульное обновление), а также для перезагрузки или выключения этой системы.

 
[Замечание]Капсульное обновление встроенного ПО

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

Спецификация UEFI

В противоположность запуску наследуемой 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).

 

Рисунок 14-6


Структура EFI_SYSTEM_TABLE на верхнем уровне

Загрузчик 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.

 

Рисунок 14-7


EFI_RUNTIME_SERVICES в представлении hal.dll

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 ещё пока не загружены, а потому недоступны.

 

Рисунок 14-8


Дизассемблированная подпрограмма EfiEntry

 

Считывание Сведений конфигурации запуска

В качестве следующего этапа 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/).

 

Рисунок 14-9


Поток графа вызовов из EfiEntry() к BmpLaunchBootEntry()

Предшествующий функции BmpLaunchBootEntry() поток управления выбирает верную запись запуска на соновании установленных в хранилище BCD значений. Когда включено Полное шифрование тома (Full Volume Encryption, BitLocker), Диспетчер запуска дешифрует этот раздел системы до того как он передаст управление своему Загрузчику запуска. Следующая за BmpTransferExecution() функция BmpLaunchBootEntry() проверяет установленные варианты запуска и передаёт управление в BlImgLoadBootApplication(), который затем вызывает ImgArchEfiStartBootApplication(). Подпрограмма ImgArchEfiStartBootApplication() отвечает за инициализацию защищённого режима памяти для winload.efi. После этого управление передаётся в функцию Archpx64TransferTo64BitApplicationAsm(), которая и завершает подготовку для запуска winload.efi (Рисунок 14-10).

 

Рисунок 14-10


Поток графа вызовов из BmpLaunchBootEntry() к Archpx64TransferTo64BitApplicationAsm()

После этого критически важного момента весь поток управления передаётся в winload.efi, который отвечает за заливку и инициализацию своего ядра Windows. До этого момента исполнение происходит в среде UEFIповерх служб запуска и операций под плоской моделью физической памяти.

[Замечание]Замечание

Когда Безопасный запуск отключён, вредоносный код может на этом этапе процесса запуска выполнять любые изменения памяти, так как модули режима ядра ещё не защищенытехнологией Windows KPP (Kernel Patch Protection, Защитой от исправлений кода ядра, также носящей название PatchGuard). PatchGuard проинициализируется только на самых последних шагах общего процесса запуска. Однако, когда PatchGuard активирован он существенно затруднит внесение видоизменений в модулях ядра.

Начальный загрузчик Windows

Загрузчик запуска 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.

 

Рисунок 14-11


Поток графа вызовов из OslMain() к OslBuildKernelMemoryMap()

После этого наша подпрограмма 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) цепляют её для вставки своих собственных специальных точек входа в установленном образе ядра.

Преимущества безопасности встроенного ПО UEFI

Как мы уже видели, буткиты на основе наследуемых 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 затронет развитие угроз руткитов и буткитов в свете атак на встроенное ПО.