Глава 6. Безопасность процесса запуска

В этой главе мы рассмотрим два важных механизма безопасности, реализованных в ядре Microsoft Windows: модуле ELAM (Early Launch Anti-Malware, Противодействия вредоносному ПО раннего запуска), введённый в Windows 8 и Политика подписи кода режима ядра (Kernel-Mode Code Signing Policy), введённая в Windows Vista. Оба механизма были разработаны для препятствия выполнению кода без авторизации в адресном пространстве своего ядра чтобы упрочить его против компрометации системы со стороны руткитов. Мы рассмотрим как реализуются эти механизмы, обсудим их преимущества и слабые моменты и изучим их эффективность против руткитов и буткитов.

Модуль раннего запуска противодействия вредоносному ПО

Модуль ELAM (Early Launch Anti-Malware, Противодействия вредоносному ПО раннего запуска) является механизмом выявления для систем Windows, который позволяет программному обеспечению безопасности третьих сторон, например антивирусному программному обеспечению регистрировать некий драйвер режима ядра,который гарантированно выполняется на очень раннем этапе в общем процессе запуска, до загрузки прочих драйверов сторонних разработчиков. Таким образом, когда некий злоумышленник пытается загружать какой- то вредоносный компонент в адресное пространство своего ядра Windows, это программное обеспечение безопасности способно проинспектировать вредоносный драйвер и предотвратить его загрузку, так как подобный драйвер ELAM уже в работе.

API процедур обратного вызова

Наш драйвер ELAM регистрирует подпрограммы обратного вызова, которые имеющееся ядро применяет для оценки данных в своём улее реестра и драйверах старта запуска. Эти обратные вызовы выявляют вредоносные данные и модули и предотвращают их загрузку и инициализацию со стороны Windows.

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

CmRegisterCallbackEx и CmUnRegisterCallback

Заносят в реестр и исключают из него обратные вызовы для отслеживания данных реестра.

IoRegisterBootDriverCallback и IoUnRegisterBootDriverCallback

Заносят в реестр и исключают из него драйверы старта запуска.

Эти подпрограммы обратного вызова применяют установленный прототип EX_CALLBACK_FUNCTION, отображаемый в Листинг 6-1:

 

Листинг 6-1. Прототип обратного вызова ELAM


NTSTATUS EX_CALLBACK_FUNCTION(
1 IN PVOID CallbackContext,
2 IN PVOID Argument1,         // callback type
3 IN PVOID Argument2          // system-provided context structure
);
 	   

Первый параметр CallbackContext1 получает некий контекст из своего драйвера ELAM после того как этот драйвер исполнил один из упомянутых выше обратных вызовов подпрограмм для регистрации какого- то обратного вызова. Значение контекста это указатель на некий буфер в памяти, удерживающий относящиеся к драйверу ELAM параметры, доступ к которым может выполняться одной из таких подпрограмм обратного вызова. Такой контекст является неким указателем, который также применяется для хранения значения текущего состояния самого драйвера ELAM. Значение аргумента2 предоставляет тот тип обратного вызова, который может быть одним из перечисленных ниже для своих драйверов старта запуска:

BdCbStatusUpdate

Предоставляет состояние обновлений для какого- то драйвера ELAM в зависимости от зависимостей самого загружаемого драйвера или драйверов старта запуска.

BdCbInitializeImage

Применяется этим драйвером ELAM для классификации драйверов старта запуска и их зависимостей.

 

Классификация драйверов старта запуска

Значение аргумента3 предоставляет сведения, которые сама операционная система использует для классификации такого драйвера старта запуска как заведомого хорошего (known good, драйвера, о котором известно что он легитимен и чист), неизвестного (unknown good, драйвера, который ELAM не способен классифицировать) и заведомо плохого (known bad, драйвера, о котором известно что он вредоносен).

К сожалению, наш драйвер ELAM должен основывать это решение на ограниченных сведениях относительно подлежащего классификации драйвера, а именно:

  • Названии его образа

  • Местоположения в своём реестре, в котором этот образ зарегистрирован в качестве драйвера старта запуска

  • Издателя и эмитента сертификата его образа

  • Хэша данного образа и значения названия алгоритма такого хэширования

  • Некого отпечатка сертификата и значения алгоритма снятия такого отпечатка

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

 

Политика ELAM

Windows принимает решение о том стоит ли загружать заведомо плохой или неизвестный драйверы на основании той политики ELAM, которая определяется в следующем ключе реестра: HKLM\System\CurrentControlSet\Control\EarlyLaunch\DriverLoadPolicyю

Таблица 6-1 перечисляет те значения политики ELAM, которые определяют какие драйверы могут загружаться.

Таблица 6-1. Значения политики ELAM
Название политики Значение политики Описание

PNP_INITIALIZE_DRIVERS_DEFAULT

0x00

Загружать исключительно заведомо хорошие драйверы.

PNP_INITIALIZE_UNKNOWN_DRIVERS

0x01

Загружать исключительно заведомо хорошие и неизвестные драйверы.

PNP_INITIALIZE_BAD_CRITICAL_DRIVERS

0x03

Загружать исключительно заведомо хорошие, неизвестные и критически важные заведомо плохие драйверы. (Это установленная по умолчанию настройка.)

PNP_INITIALIZE_BAD_DRIVERS

0x07

Загружать все драйверы.

Как вы можете видеть, установленная по умолчанию политика, PNP_INITIALIZE_BAD_CRITICAL_DRIVERS, допускает загрузку плохих критически важных драйверов. Это означает, что когда некий критически важны драйвер классифицируется ELAM как заведомо плохой, его система загрузит его вне зависимости от чего бы то ни было. Рациональное зерно, стоящее за этой политикой состоит в том, что критически важные системные драйверы являются существенной частью загружаемой операционной системы, а потому любой отказ в их инициализации превратит данную операционную систему в не загружаемую; то есть такая система не будет способна запускаться пока все её критически важные драйверы не загрузятся и не проинициализируются. Такая политика ELAM, следовательно, выступает компромиссом в некой безопасности в угоду доступности и обслуживаемости.

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

Как буткиты обходят ELAM

ELAM предоставляет программному обеспечению безопасности некое преимущество перед угрозами руткитов, но не против буткитов - да и не предназначен для этого. ELAM способен отслеживать лишь законно загружаемые драйверы, однако большинство буткитов загружает драйверы режима ядра, которые пользуются недокументированными функциональными возможностями операционной системы. Это означает, что некий буткит способен обходить принуждения безопасности и внедрять свой код в адресное пространство ядра несмотря на ELAM. Кроме того, как это показано на Рисунке 6-1, некий код вредоносного буткита запускается до инициализации ядра своей операционной системы и до загрузки каких бы то ни было драйверов режима ядра, включая ELAM. Это означает, что некий буткит способен уклоняться от защиты ELAM.

 

Рисунок 6-1


Поток процесса запуска с ELAM

Большинство буткитов загружает свой код режима ядра в середине инициализации ядра, когда все подсистемы ОС (подсистема ввода/ вывода, диспетчер объектов, диспетчер подключаемых модулей и тому подобное) уже проинициализировано, но до выполнения ELAM. ELAM, естественно, не способен препятствовать исполнению того вредоносного кода, который загружается до него, а потому он не обладает никакой защитой против технологий буткитов.

Политика подписи кода режима ядра Microsoft

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

Объект драйверов режима ядра для проверок целостности

Политика подписи была введена в Windows Vista и применялась во всех последующих версиях Windows, хотя и использовалась по- разному в 32- битных и 64- битных операционных системах. Она запускается при загрузке своих драйверов режима ядра с тем чтобы была способна проверять их целостность до отображения образов драйверов в адресное пространство ядра. Таблица 6-2 отображает какие из драйверов режима ядра в 64- и 32- битных системах выступают предметом для какой из проверок целостности.

Таблица 6-2. Требования политики подписи кода режима ядра
Тип драйвера Предмет проверки целостности?
64- битная 32- битная

Драйверы старта запуска

Да

Да

Драйверы PnP, не относящиеся к старту запуска

Да

Нет

Не PnP драйверы , не относящиеся к старту запуска

Да

Нет (за исключением драйверов носителей защищённого потока)

Как показывает эта таблица, в 64- битных операционных системах все модули режима ядра, вне зависимости от их типа, выступают предметом для проверки целостности. В 32- битных системах, имеющиеся политики подписи применяются лишь к драйверам старта запуска и драйверам носителей; прочие драйверы не проверяются (установка PnP устройства обеспечивает требование подписи вовремя установки).

Для соответствия требованиям целостности кода, драйверы обязаны обладать либо встроенной цифровой подписью SPC (Software Publisher Certifcate, Сертификата издателя ПО) или неким файлом каталога с подписью SPC. Драйверы старта запуска, однако, могут обладать только встроенными подписями, потому как в момент запуска необходимый стек драйвера устройства хранения не инициализирован, что делает недоступным каталог таких драйверов.

Расположение подписей драйвера

Встроенная подпись драйвера внутри некого файла PE, такого как драйвер старта запуска, определяется в соответствующей записи IMAGE_DIRECTORY_DATA_SECURITY в каталогах данных заголовка самого PE. Microsoft предоставляет API для перечисления и получения сведений по всем сертификатам, содержащимся в неком образе, как это показано в Листинге 6-2.

 

Листинг 6-2. API Microsoft для перечисления и утверждения сертификатов


BOOL ImageEnumerateCertificates(
   _In_     HANDLE FileHandle,
   _In_     WORD TypeFilter,
   _Out_    PDWORD CertificateCount,
   _In_out_ PDWORD Indices,
   _In_opt_ DWORD IndexCount
);
BOOL ImageGetCertificateData(
   _In_    HANDLE FileHandle,
   _In_    DWORD CertificateIndex,
   _Out_   LPWIN_CERTIFICATE Certificate,
   _Inout_ PDWORD RequiredLength
);
 	   

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

 
[Замечание]Политика подписи установки подключаемого устройства

Дополнительно к Политике подписи кода режима ядра, Microsoft Windows обладает и другим типом политики подписи: Политикой подписи установки подключаемого устройства (Plug and Play Device Installation Signing Policy). Важно не путать их между собой.

Устанавливаемые Политикой подписи установки подключаемого устройства требования применяются только для драйверов подключаемых (PnP) устройств и принуждаются к исполнению для проверки и идентификации самого издателя и наличия целостности самого пакета установки драйвера подключаемого устройства. Проверка требует, чтобы имеющийся файл каталога этого пакета драйвера был подписан либо сертификатом WHQL (Windows Hardware Quality Labs, Лабораторией по сертификации аппаратных средств для работы в среде Windows) или неким сторонним SPC. Когда рассматриваемый пакет драйвера не соответствует установленным для такой политики PnP требованиям, некий предупредительный диалог предлагает пользователям принять решение для разрешения этому пакету драйвера быть установленным в его системе.

Администраторы системы могут отключать данную политику PnP, позволяя пакетам драйверов подлежащими установке в системе без надлежащих подписей. Кроме того, обратите внимание, что данная политика применяется исключительно при установке самого драйвера, а не при загрузке такого драйвера. Хотя это и может рассматриваться как слабость TOCTOU (time of check to time of use, момента проверки времени применения), это не так; это просто означает, что некий успешно установленный в системе драйвер нет необходимости проверять при загрузке, ибо эти драйверы также являются предметом проверки для Политики подписи кода режима ядра при запуске.

Уязвимость целостности наследуемого кода

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

В сердцевине этого механизма в Microsoft Windows Vista и 7 лежит единственная переменная в самом образе ядра и она определяет будет ли осуществляться принуждение к проверкам целостности. Она выглядит так:


BOOL nt!g_CiEnabled
 	   

Эта переменная инициализируется в момент запуска в процедуре образа своего ядра NTSTATUS SepInitializeCodeIntegrity(). Запускаемая операционная система проверяет запускается ли она в своём режиме WinPE (Windows preinstallation) и, если это так, величина переменной nt!g_CiEnabled инициализируется значением FALSE (0x00), что отключает проверки целостности.

Тем самым, ечтественно, злоумышленники обнаружили, что они запросто могут увернуться от необходимой проверки целостности просто устанавливая nt!g_CiEnabled в FALSE и именно это и произошло в семействе вредоносного ПО Uroburos (также имеющего названия Snake и Turla) в 2011. Uroburos обходит установленную политику подписи кода вводя, а затем и эксплуатируя некую уязвимость в драйвере стороннего разработчика. Законным подписанным сторонним драйвером был VBoxDrv.sys (драйвер VirtualBox), а эксплуатировавший его очищал значение переменной nt!g_CiEnabled после получения выполнения кода в режиме ядра, после чего мог загружаться любой вредоносный драйвер без подписи на данной подвергшейся атаке машине.

 
[Замечание]Уязвимость Linux

Этот вид слабого места не уникален для Windows, злоумышленники отключали обязательное принуждение управление доступом в SELinux аналогичными способами. В частности, когда злоумышленник знает значение адреса той переменной, которая содержит состояние принуждения к SELinux, всё что требуется такому злоумышленнику сделать, это переписать значение такой переменной. Поскольку логика принуждения SELinux проверяет значение этой переменной до выполнения каких бы то ни было проверок, эта логика сама по себе превратится в незадействованную. Подробный анализ этой уязвимости и код её эксплуатации можно найти в https://grsecurity.net/~spender/exploits/exploit2.txt.

Когда Windows пребывает не в режиме WinPE, она далее проверяет значения параметров запуска DISABLE_INTEGRITY_CHECKS и TESTSIGNING. Как и подразумевает её название, DISABLE_INTEGRITY_CHECKS отключает проверки целостности. Некий пользователь в любой версии Windows способен установить этот вариант вручную при запуске при помощи параметра меню Запуска (Boot) Disable Driver Signature Enforcement (Отключить принуждение к подписи драйвера). Пользователи Windows Vista также могут применять инструмент bcdedit.exe для установки значения параметра nointegritychecks в TRUE; более поздние версии игнорируют этот параметр в BCD (Boot Confguration Data, Данных настройки запуска) при включённом Безопасном запуск (Secure Boot, для дополнительных сведений о безопасном запуске обратитесь к Главе 17).

Параметр TESTSIGNING изменяет тот способ, которым его операционная система проверяет наличие целостности модулей режима ядра. Когда он установлен в TRUE, удостоверение сертификата не требуется для цепочки по всему пути вплоть до доверенного корня CA (Certifcate Authority, Центра сертификации). Иначе говоря, любой драйвер с любой цифровой подписью может быть загружен в адресное пространство ядра. Руткит Necurs злоупотребляет этим параметром TESTSIGNING, устанавливая его в TRUE и загружая свой драйвер режима ядра, подписанный своим индивидуальным сертификатом.

На протяжении долгих лет имелись ошибки в коде браузеров, которые отказывались следовать по промежуточным ссылкам в доверительных цепочках сертификатов X.509 к законному CA (См. Moxie Marlinspike, “Internet Explorer SSL Vulnerability”), однако схемы подписей модулей не избегают ярлыков (shortcuts) когда речь заходит о цепочках доверительных отношений.

Модуль ci.dll

Библиотека модуля ядра ci.dll, который отвечает за принуждение к политике целостности кода, содержит следующие подпрограммы:

CiCheckSignedFile

Проверяет значение свёртки и удостоверяет значение цифровой подписи

CiFindPageHashesInCatalog

Проверяет содержит каталог проверяемой системы значение свёртки самой первой страницы своего образа PE

CiFindPageHashesInSignedFile

Проверяет значение свёртки и удостоверяет значение цифровой подписи самой первой страницы своего образа PE

CiFreePolicyInfo

Высвобождает память, выделяемую функциями CiVerifyHashInCatalog, CiCheckSignedFile, CiFindPageHashesInCatalog и CiFindPageHashesInSignedFile

CiGetPEInformation

Создаёт некий шифруемый канал взаимодействия между самой вызывающей стороной и имеющимся модулем ci.dll

CiInitialize

Инициализирует возможность ci.dll для удостоверения целостности файла образа PE.

CiVerifyHashInCatalog

Удостоверяет значение свёртки образа рассматриваемого PE, содержащейся внутри каталога проверяемой системы.

Подпрограмма CiInitialize является наиболее важной для наших целей, так как она инициализирует необходимую библиотеку и создаёт её контекст данных. Мы можем рассмотреть её прототип, относящийся к Windows 7 в Листинге 6-3:

 

Листинг 6-3. Прототип подпрограммы CiInitialize


NTSTATUS CiInitialize(
1 IN ULONG CiOptions;
   PVOID Parameters;
2 OUT PVOID g_CiCallbacks;
);
);
 	   

Такая подпрограмма CiInitialize получает в качестве параметров параметры самого кода целостности (CiOptions)1 и указатель на некий массив обратных вызовов (OUT PVOID g_CiCallbacks)2, подпрограммы которых он заполнит в процессе вывода. Само ядро применяет эти обратные вызовы для проверки наличия целостности модулей режима ядра.

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

После завершения инициализации этой библиотеки ci.dll, основное ядро применяет обратные вызовы в своём буфере g_CiCallbacks для проверки наличия целостности соответствующих модулей. В Windows Vista и 7 (но не в Windows 8), подпрограмма SeValidateImageHeader принимает решение прошёл ли определённый образ эту проверку целостности. Листинг 6-4 отображает лежащий в основе этой подпрограммы алгоритм.

 

Листинг 6-4. Псевдокод подпрограммы SeValidateImageHeader


NTSTATUS SeValidateImageHeader(Parameters) {
   NTSTATUS Status = STATUS_SUCCESS;
   VOID Buffer = NULL;
1 if (g_CiEnabled == TRUE) {
         if (g_CiCallbacks[0] != NULL)
         2 Status = g_CiCallbacks[0](Parameters);
         else
            Status = 0xC0000428
   }
   else {
      3 Buffer = ExAllocatePoolWithTag(PagedPool, 1, 'hPeS');
         *Parameters = Buffer
         if (Buffer == NULL)
            Status = STATUS_NO_MEMORY;
   }
   return Status;
}
 	   

SeValidateImageHeader проверяет того что значение переменной nt!g_CiEnabled установлено в TRUE1. Если это не так, она пытается выделить буфер длиною в байт2, и, если это успешно, возвращает значение STATUS_SUCCESS.

Если nt!g_CiEnabled равно TRUE, тогда SeValidateImageHeader выполняет самый первый обратный вызов из своего буфера g_CiCallbacks, g_CiCallbacks[0]3, который установлен в подпрограмму CiValidateImageData. Этот последний обратный вызов CiValidateImageData проверяет целостность подлежащего загрузке образа.

Защитные изменения в Windows 8

В Windows 8, Microsoft сделал несколько изменений в архитектуре для ограничения тех видов атак, которые возможны в данном сценарии. Прежде всего, Microsoft признал устаревшей свою переменную ядра nt!g_CiEnabled, не оставляя никакой единой точки управления своей политикой целостности в собственном образе ядра, как это имело место в более ранних версиях Windows/ Windows 8 также изменил схему своего буфера g_CiCallbacks.

Листинг 6-5 (Windows 7 и Vista) и Листинг 6-6 (Windows 8) отображает как именно отличается схема g_CiCallbacks.

 

Листинг 6-5. Схема буфера g_CiCallbacks в Windows Vista и Windows 7


typedef struct _CI_CALLBACKS_WIN7_VISTA {
 PVOID CiValidateImageHeader;
 PVOID CiValidateImageData;
 PVOID CiQueryInformation;
} CI_CALLBACKS_WIN7_VISTA, *PCI_CALLBACKS_WIN7_VISTA;
 	   

Как выможете видеть в Листинге 6-5, схема Vista и Windows 7 содержит лишь необходимую основу. Схема Windows 8 (Листинг 6-6), с другой стороны, обладает большим числом полей для дополнительных функций обратных вызовов с целью проверки цифровой подписи образа PE.

 

Листинг 6-6. Схема буфера g_CiCallbacks в Windows 8.x


typedef struct _CI_CALLBACKS_WIN8 {
    ULONG ulSize;
    PVOID CiSetFileCache;
    PVOID CiGetFileCache;
 1 PVOID CiQueryInformation;
 2 PVOID CiValidateImageHeader;
 3 PVOID CiValidateImageData;
    PVOID CiHashMemory;
    PVOID KappxIsPackageFile;
} CI_CALLBACKS_WIN8, *PCI_CALLBACKS_WIN8;
 	   

Дополнительно к указателям этой функции CiQueryInformation1, CiValidateImageHeader2 и CiValidateImageData3, которые присутствуют в обеих структурах, и в CI_CALLBACKS_WIN7_VISTA, и в CI_CALLBACKS_WIN8, CI_CALLBACKS_WIN8 также обладает полями, которые воздействуют на то как выполняется принуждение к целостности кода в Windows 8.

 
[Замечание]Дополнительное знакомство с CI.DLL

Дополнительные сведения по имеющимся подробностям реализации модуля ci.dll можно найти в https://github.com/airbus-seclab/warbirdvm. Эта статья погружается в подробности шифрованного хранения в памяти, применяемого в модуле ci.dll, которое можно применять прочими компонентами ОС для сохранения в секрете определённых подробностей и сведений о конфигурации. Это хранилище защищается строго скрытой виртуальной машиной (ВМ), что делает намного более сложной обратное проектирование алгоритмов шифрования/ дешифрации такого хранилища. Авторы этой статьи предоставляет подробный анализ данного метода сокрытия ВМ и они делятся своим подключаемым модулем Windbg для дешифрации и шифрования этого хранилища на лету.

Технология безопасного запуска

Технология Безопасного запуска была предложена в Windows 8 для защиты самого процесса запуска против внедрений буткитов. Безопасный запуск применяет имеющийся UEFI (Unifed Extensible Firmware Interface) для блокирования процессов загрузки и исполнения любых приложений запуска или драйвера без допустимой цифровой подписи чтобы защищать целостность ядра своей операционной системы, системных файлов и критически важных для запуска драйверов. Рисунок 6-2 отображает соответствующий процесс запуска с включённым Безопасным запуском.

 

Рисунок 6-2


Поток процесса запуска с Безопасным запуском

При включённом Безопасном запуске, имеющаяся BIOS проверяет наличие целостности всего UEFI и файлов запуска ОС, выполняемых при вводе в действие для обеспечения того что они поступают из законного источника и обладают допустимой цифровой подписью. Имеющиеся подписи всех критичных для запуска драйверов проверяются в winload.exe и через драйвер ELAM как часть проверки Безопасного запуска. Безопасный запуск аналогичен Политике подписи кода режима ядра, но применяется к модулям, которые исполняются до загрузки и инициализации самого ядра операционной системы. В результате, не обладающие доверием компоненты (то есть те, которые не обладают допустимыми подписями) не будут загружены и будет включено восстановление.

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

На протяжении последних нескольких лет сообщество по исследованиям безопасности уделяло значительное внимание уязвимостям BIOS, которые были способны предоставлять злоумышленникам обходить Безопасный запуск. Мы обсудим подробнее эти уязвимости в Главе 16 и глубже окунёмся в подробности Безопасного запуска в Главе 17.

Основанная на виртуализации безопасность в Windows 10

Вплоть до Windows 10 механизмы целостности кода являлись частью ядра системы самой по себе. Это по существу означает, что данный механизм целостности запускается с тем же самым уровнем полномочий, который он пытается защищать. Хотя это и может быть действенным во многих ситуациях, это также означает что злоумышленник обладает возможностью атаки этого механизма целостности самого по себе. Для увеличения действенности самого механизма целостности кода, Windows 10 ввёл две новых функциональные возможности: Режим виртуальной безопасности (Virtual Secure Mode) и Защитником устройств (Device Guard), причём оба они основываются на поддерживаемой аппаратными средствами изоляции памяти. Эта технология обычно именуется как Трансляция адресов второго уровня (Second Level Address Translation) и именно она включается в обоих ЦПУ, Intel (где она носит название EPT, Extended Page Tables) и в AMD (в котором она именуется RVI, Rapid Virtualization Indexing).

Трансляция адресов второго уровня

Windows имеет поддержку Second Level Address Translation (SLAT) начиная с Windows 8 с Hyper-V (гипервизором Microsoft). Hyper-V применяет SLAT для выполнения управлением памяти (например, защиты доступа) ввиртуальных машинах и для снижения общих накладных расходов трансляции гостевых физических адресов (память изолируется технологиями виртуализации) в реальные физические адреса.

SLAT предоставляет гипервизоры с неким промежуточным кэшем трансляции виртуальных адресов в физические, что радикально снижает то количество времени, которое требуется гипервизору на обслуживание запросов трансляции в значения физических адресов своего хоста. Он также используется в технологии Режима виртуальной безопасности в Windows 10.

Режим виртуальной безопасности и защитник устройств

Безопасность на основании виртуализации VSM (Virtual Secure Mode, Режима виртуальной безопасности) впервые появился в Windows 10 и основывается на Hyper-V Microsoft. Когда VSM установлен, сама операционная система и модули критически важных систем выполняются в изолированных контейнерах под защитой гипервизора. Это означает, что даже когда данное ядро скомпрометировано, критически важные компоненты, исполняемые в прочих виртуальных средах всё ещё безопасны, так как злоумышленник не способен провернуться из одного из скомпрометированных виртуальных компонентов в другой. VSM также изолирует компоненты целостности кода из ядра Windows самого по себе в неком защищённом гипервизором контейнере.

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

Технология Защитника устройств (Device Guard) применяет VSM для предотвращения исполнения в данной системе кода без доверия. Для осуществления такой страховки, Защитник устройства комбинирует защищённый VSM код целостности платформы и Безопасный запуск UEFI. Для выполнения этого Защитник устройства принуждает к политике целостности кода с самого начала своего процесса запуска на всём пути загрузки драйверов режима ядра ОС и приложений режима пользователя.

Рисунок 6-3 показывает как Защитник устройств оказывает воздействие на возможность Windows 10 выполнять защиту против буткитов и руткитов. Безопасный запуск осуществляет защиту от буткитов проверяя все компоненты встроенного ПО, исполняемые в своей среде предзапуска, включая сам загрузчик запуска ОС. Для предотвращения инъекций вредоносного кода в имеющееся адресное пространства режима ядра, установленный VSM изолирует все критически важные компоненты ОС, ответственные за принуждение к целостности кода (имеющего в данном контексте название HVCI, Hypervisor- Enforced Code Integrity, Принуждаемая гиервизором целостность кода) из установленного адресного пространства ядра ОС.

 

Рисунок 6-3


Процесс запуска с включёнными Режимом виртуальной безопасности и Защитником устройств

Ограничения защитника устройства при разработке драйвера

Защитник устройств высталяет особые требования и ограничения на весь процесс разработки драйвера, причём некоторые имеющиеся драйверы не будут корректно работать при его действии. Все драйверы обязаны следовать таким правилам:

  • Выделять всю не организованную в страницы память их имеющегося не исполняемого (NX, no- execute) пула без поддержки страниц. Модуль PE такого модуля не может обладать разделами, которые одновременно имеют доступ для записи и моут выполняться.

  • Не предпринимать попыток прямого изменения исполняемой системной памяти.

  • Не использовать динамического или самоизменяемого кода в режиме ядра.

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

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

Заключение

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

Windows 10 поднял безопасность процесса запуска на новый уровень, препятствуя обходу кода целостности путём изоляции компонентов HVCI от своего ядра ОС с помощью VSM. Тем не менее, без размещения некого механизма Безопасного запуска, буткиты способны обманывать такие защиты атакуя систему до их загрузки. В последующих главах мы обсудим более подробно Безопасный запуск и те атаки BIOS, которые разрабатываются для ускользания от него.