Разработчик
- Статус
- Онлайн
- Регистрация
- 21 Июн 2025
- Сообщения
- 248
- Реакции
- 103
Всем привет. На югейме я практически не встречал подробных статей по технологии ELAM и тому, как антивирусы могут детектировать уязвимые драйверы ещё на этапе загрузки системы через ELAM-драйвер. Возможно кому-то будет полезно, просто выложить некуда)
Статью разделять на несколько частей я не буду, попытаюсь все уместить в одну часть.
Сам драйвер для практики в 3 главе был разработан мною на ассемблере, я понимаю, что многим людям было бы удобнее ознакомиться с кодом написанным на C++, но, у меня была проблема с WDK, а решать ее довольно долго, так что решил пусть будет так, если будет необходимо, буду предоставлять псевдокод непосредственно уже на языке С++, чтобы людям было проще читать статью. Приступаем.
Содержание статьи:
1. ELAM в теории;
2. Демонстрация работы реального ELAM-драйвера на примере антивируса BitDefender;
3. Разрабатываем собственный ELAM-драйвер;
4. Заключение;
ELAM
ELAM - это технология которая появилась в Windows 8, она предназначена для раннего запуска определенного загрузочного драйвера еще до полной инициализации большинства драйверов системы, благодаря этой технологии антивирусное ПО получает возможность анализировать первые загружаемые драйверы практически в самом начале загрузки ОС, ELAM-драйвер может проверять: подпись драйвера, проверять совпадение имени драйвера с черным списком драйверов, а также принимать решение о разрешении или блокировке дальнейшей загрузки драйвера. Все это будет реализовано нами в статье.
После загрузки ELAM-драйвера ОС начинает уведомлять его о драйверах, которые инициализируются во время старта системы, для этого используется специальный каллбек-механизм.
Для получения информации о загружаемых драйверов используется функция регистрации каллбека IoRegisterBootDriverCallback, если еще проще когда ядро вызывает каллбек-функцию ELAM-драйвера, она передает информацию о загружаемом драйвере:
1. Имя драйвера;
2. Путь к образу;
3. Хэш сертификата;
4. ImageHandle;
5. Тип проверки;
Непосредственно сам ELAM-драйвер получает эту информацию в виде структуры, указатель на которую передается в каллбек-функцию драйвера, то есть в IoRegisterBootDriverCallback, этой структуре передаются базовые метаданные загружаемого образа о загружаемом драйвере, то есть все, что было описано выше.
Также есть структура под названием
Однако каллбек-функция ELAM вызывается не только при инициализации драйвера, ядро передает каллбек дополнительный параметр, который указывает на тип текущего события, в ELAM существует несколько типов уведомлений, но в рамках статьи нас будет интересовать именно BdCbInitializeImage.
Событие BdCbInitializeImage вызывается во время инициализации загрузочного драйвера, на этом моменте ELAM-драйвер получает возможность проанализировать драйвер и принять решение о разрешении либо блокировки его загрузки.
Событие BdCbStatusUpdate используется ядром для обновления статуса ELAM-драйвера, в рамках статьи этот параметр будет игнорироваться.
В общем виде процесс работы ELAM можно описать так - во время загрузки системы ядро последовательно инициализирует загрузочные драйверы и при каждом таком событии вызывает зарегистрированный ELAM-ом каллбек. В каллбеке передается информация о конкретном драйвере и тип события, после чего ELAM-драйвер может принять решение о дальнейшей загрузке этого драйвера, путем анализа хэша драйвера или сравнения его имени с черным списком драйверов.
Демонстрация работы реального ELAM-драйвера на примере антивируса BitDefender
Я долго думал, где я могу найти подобный драйвер и сразу вспомнил про антивирусы, я скачал первый попавшийся в голову антивирус, а именно BitDefender, который кстати, довольно хорошо оценивается в кругу безопасников и разработчиков малвари. Разбирать будем конкретно ELAM-драйвер этого антивируса, попытаюсь разъяснить все без воды.
BitDefender разделяется на несколько драйверов, на моей версии, которую я скачал бесплатно(кстати был удивлен, что в РФ доступно) -
bdelam.sys - драйвер имеет сертификацию, подгружается он системой самым первым, еще до загрузки остальных программ и основных служб, задача драйвера не дать руткитам перехватить управление ПК.
Ну, что, приступим
Первое, что бросается в глазах при анализе данного драйвера, это конечно же вызовы функций регистрации каллбека и дерегистрации.
Каллбек регистрируется через IoRegisterBootDriverCallback, а освобождается через IoUnregisterBootDriverCallback, конкретно этот механизм позволяет ELAM получать уведомления от ядра о каждом загрузочном драйвере в системе
Если сильно упростить и продемонстрировать, то выглядит это так:
1. BootDriverCallbackRountine это функция которая будет вызываться ядром при каждом событии загрузки драйвера;
2. Второй параметр это контекст который не используется;
3. g_CallbackHandle это дескриптор регистрации, необходимый для последующей отмены каллбека, дескриптор будет передаваться в функцию IoUnregisterBootDriverCallback.
Дерегистрация, кстати выглядит выполняется аналогичным образом:
После регистрации каллбека ядро начинает вызывать указанную функцию каждый раз, когда происходит событие.
После того как управление успешно перехвачено, антивирусный драйвер в параметрах регистрации каллбека указывает функцию
происходит первичная обработка событий от ОС, где аргумент a2 является типом события
То есть если значение a2 равно единице, что соответствует системному флагу BdCbInitializeImage, сигнализирующему о попытке загрузки нового загрузочного драйвера в систему, управление немедленно перенаправляется в функцию sub_140001EC0 которая обрабатывает уже сформированный системой контекст проверки образа, получая через BDCB_IMAGE_INFORMATION метаданные загружаемого драйвера, включая сведения о сертификате и его издателе.
После чего антивирус передает очищенный бинарный контейнер главному диспетчеру безопасности sub_1400017FC который проверяет проприетарные маркеры BMAL и SG, в случае равенства a2 нулю что означает системное уведомление BdCbStatusUpdate выполнение уходит в ветку else где через глобальный указатель P по смещению плюс 12 в памяти ядра сохраняется статус текущей сессии ELAM
В самом начале функция sub_140001EC0 вызывает подфункцию sub_140001D8C, результат которой сразу падает в *a1, одновременно с этим антивирус накручивает счетчик общего числа проверенных драйверов в системе, увеличивая значение по смещению *((_WORD *)P + 45) на единицу, если проверка спотыкается на ошибке со статусом 2 или 3, код понимает, что дело пахнет керосином, в этом случае срабатывает триггер безопасности и выставляется флаг жесткой изоляции по адресу *((_DWORD *)P + 23) = 1
Если же на первом этапе все чисто, управление улетает в ветку else, там инкрементируется счетчик успешно одобренных чистых драйверов *((_WORD *)P + 44). Сразу после этого вызывается функция sub_140001C98, которая берет из глобального контекста P данные по смещению +664 и пушит очищенный бинарный контейнер прямо в руки главному диспетчеру sub_1400017FC, который уже ищет внутри маркеры "BMAL" и "SG". В статье функция не разбирается
Что же там по поводу проверок хэша? Драйвер проверяет хэш не через какую-то кастомную функцию, а вызывая функции из cng.
В первую очередь драйвер вызывает функцию BCryptCreateHash, в этой функции создаётся объект хэширования, который будет использоваться для вычисления контрольной суммы проверяемого драйвера. Предварительно был уже выбран драйвером алгоритм SHA1 и выделена память, под внутреннюю структуру cng, в результате выполнения функция BCryptCreateHash возвращает дескриптор, который дальше используется во всех последующих криптографических операциях
После создания хэш-объекта драйвер переходит к следующему этапу, а именно к последовательной подаче данных через BCryptHashData, конкретно этот момент ядроначинает обрабатывать содержимое проверяемого драйвера частями, обновляя внутреннее состояние SHA1 после каждого вызова
Когда все данные переданы, вызывается функция BCryptFinishHash, данная функция завершает вычисления и формирует итоговый хэш фиксированной длины, непосредственно этот результат используется далее для проверки цифровой подписи через BCryptVerifySignature
Я признаюсь, я не являюсь каким-то супер невъебическим криптографом, я объяснил все то, что находится в границах моих знаний конкретно в данной части, чтобы не вести читателя в заблуждение я решил не углубляться в проверку через хэш.
Если же собрать данную часть, и часть выше про ELAM в теории в один камок, то механизм проверки можно описать достаточно просто. ELAM-драйвер получает уведомление от ядра о попытке загрузки нового драйвера, извлекает метаданные через структуру BDCB_IMAGE_INFORMATION и на основе этих данных принимает решение о дальнейшем допуске(Хэш, название драйвера или путь к нему).
После данного процесса, драйвер дополнительно вычисляет криптографическую проверку целостности образа, сначала формируя хэш содержимого драйвера через CNG(BCryptCreateHash -> BCryptHashData -> BCryptFinishHash -> BCryptVerifySignature), после чего этот хэш сравнивая с цифровой подписью, используя открытый ключ из сертификата.
На этом криптографическую часть думаю можно закончить, надеюсь читателю все понятно.
Разрабатываем свой ELAM-драйвер
Сама интересная часть статьи это непосредственно разработка самого ELAM-драйвера, вкратце пройдемся по тому, что должен содержать драйвер, после чего перейдем к основной логике с каллбеками.
Внутри главной функции DriverEntry регистрируем выгрузку драйвера, после чего регистрируем основные IRP-обработчики, в объект драйвера записываются адреса функций, которые будут вызываться ядром при обработке запросов:
1. CreateCloseHandler - обработка создания и закрытия дескриптора на устройство;
2. IoControlHandler - обработка IOCTL;
3. DriverUnload - функция выгрузки драйвера
После чего внутри той же функции DriverEntry создаем устройство через IoCreateDevice, то есть создаем объект, этот объект является точкой взаимодействия между UM и KM и необходим для дальнейшей работы драйвера, если создание устройства прошло успешно, следующим шагом выполняется создание символьной ссылки через IoCreateSymbolicLink, функция связывает внутренний путь устройства (\Device\YouGameDevice) с пользовательским пространством (\DosDevices\YouGame), позволяя обращаться к драйверу из приложений UM через функции типа DeviceIoControl, либо через *NT функцию из ntdll.dll.
Получается код вида:
Переходим к основной логике драйвера. После успешной инициализации драйвер дополнительно отключает флаг скрытого устройства в объекте DEVICE_OBJECT, а затем регистрирует ELAM-каллбек через
После чего логика переносится в BootDriverCallbackRountine, внутри этой функции происходит самое важное - обработка уведомлений ядра о загружаемых драйверах, вообщем начинается основная логика ELAM обработки, сначала сохраняются входные регистры RCX RDX R8 на стек для сохранения контекста вызова, далее происходит проверка типа события через EDX, сравнение выполняется с
если имя не совпало ни с одним элементом списка, выполнение переходит к следующему этапу, на этом этапе начинается проверка цифровой подписи, из структуры
после успешного получения хэша выполняется второй уровень фильтрации, вызывается
если все четыре блока совпали полностью, функция возвращает EAX = 1, это означает что сертификат найден в черном списке и драйвер должен быть заблокирован, если ни один элемент списка не совпал, функция возвращает 0 и драйвер считается доверенным, в финальной стадии BootDriverCallbackRountine выполняет проверку результата CompareCertHashInList через TEST AL, если установлен флаг совпадения происходит переход в .DRIVER_BLOCK и возврат STATUS_ACCESS_DENIED, если же обе проверки пройдены, и имя и сертификат не попали в черные списки, выполнение идет в .DRIVER_SUCCESS и возвращается нулевой статус, что означает разрешение загрузки.
Заключение
Надеюсь статья была для кого-то полезной и кто-то открыл для себя что-то новое.
Касаемо драйвера, доделать я не успел конкретно blacklist.inc, но драйвер полностью рабочий, можно пополнить массив разными драйверами, либо CERT_BLACK_LIST хэшами драйверов.
Также подмечу, что ELAM-драйвер не запускается как обычный драйвер по запросу системы или пользователя, он попадает в очень раннюю фазу загрузки ОСи подхватывается только механизмом доверенной загрузки, если соответствует требованиям к подписи и классификации.
Главное, чтобы читатель все понял. Всем пока.
Полезные ссылки:
Каллбеки:
Статью разделять на несколько частей я не буду, попытаюсь все уместить в одну часть.
Сам драйвер для практики в 3 главе был разработан мною на ассемблере, я понимаю, что многим людям было бы удобнее ознакомиться с кодом написанным на C++, но, у меня была проблема с WDK, а решать ее довольно долго, так что решил пусть будет так, если будет необходимо, буду предоставлять псевдокод непосредственно уже на языке С++, чтобы людям было проще читать статью. Приступаем.
Содержание статьи:
1. ELAM в теории;
2. Демонстрация работы реального ELAM-драйвера на примере антивируса BitDefender;
3. Разрабатываем собственный ELAM-драйвер;
4. Заключение;
ELAM
ELAM - это технология которая появилась в Windows 8, она предназначена для раннего запуска определенного загрузочного драйвера еще до полной инициализации большинства драйверов системы, благодаря этой технологии антивирусное ПО получает возможность анализировать первые загружаемые драйверы практически в самом начале загрузки ОС, ELAM-драйвер может проверять: подпись драйвера, проверять совпадение имени драйвера с черным списком драйверов, а также принимать решение о разрешении или блокировке дальнейшей загрузки драйвера. Все это будет реализовано нами в статье.
После загрузки ELAM-драйвера ОС начинает уведомлять его о драйверах, которые инициализируются во время старта системы, для этого используется специальный каллбек-механизм.
Для получения информации о загружаемых драйверов используется функция регистрации каллбека IoRegisterBootDriverCallback, если еще проще когда ядро вызывает каллбек-функцию ELAM-драйвера, она передает информацию о загружаемом драйвере:
1. Имя драйвера;
2. Путь к образу;
3. Хэш сертификата;
4. ImageHandle;
5. Тип проверки;
Непосредственно сам ELAM-драйвер получает эту информацию в виде структуры, указатель на которую передается в каллбек-функцию драйвера, то есть в IoRegisterBootDriverCallback, этой структуре передаются базовые метаданные загружаемого образа о загружаемом драйвере, то есть все, что было описано выше.
Также есть структура под названием
BDCB_IMAGE_INFORMATION, отражающая метаданные загружаемого образа, имеет она такое определение:
struct:
typedef struct _BDCB_IMAGE_INFORMATION
{
LIST_ENTRY ListEntry;
UNICODE_STRING ImageName; // <<< имя драйвера
HANDLE ImageHandle; // <<< хэндл на образ драйвера
ULONG ImageFlags;
ULONG Classification;
ULONG ImageHashAlgorithm;
ULONG ImageHashLength;
PVOID ImageHash; // <<< указатель на хэш сертификата драйвера
} BDCB_IMAGE_INFORMATION, *PBDCB_IMAGE_INFORMATION;
struct:
typedef enum _BDCB_CALLBACK_TYPE
{
BdCbInitializeImage = 0,
BdCbStatusUpdate = 1
} BDCB_CALLBACK_TYPE;
Событие BdCbInitializeImage вызывается во время инициализации загрузочного драйвера, на этом моменте ELAM-драйвер получает возможность проанализировать драйвер и принять решение о разрешении либо блокировки его загрузки.
Событие BdCbStatusUpdate используется ядром для обновления статуса ELAM-драйвера, в рамках статьи этот параметр будет игнорироваться.
В общем виде процесс работы ELAM можно описать так - во время загрузки системы ядро последовательно инициализирует загрузочные драйверы и при каждом таком событии вызывает зарегистрированный ELAM-ом каллбек. В каллбеке передается информация о конкретном драйвере и тип события, после чего ELAM-драйвер может принять решение о дальнейшей загрузке этого драйвера, путем анализа хэша драйвера или сравнения его имени с черным списком драйверов.
Демонстрация работы реального ELAM-драйвера на примере антивируса BitDefender
Конкретно данная часть статьи, с разбором драйвера антивируса является перезаливом моей статьи которую я писал для своего ТГ-канала, максимум я немного попытался подправить ее под эту статью. А если возникнет вопрос зачем ее вставлять, то только чтобы люди лучше поняли механизм работы этой технологии на практике
Я долго думал, где я могу найти подобный драйвер и сразу вспомнил про антивирусы, я скачал первый попавшийся в голову антивирус, а именно BitDefender, который кстати, довольно хорошо оценивается в кругу безопасников и разработчиков малвари. Разбирать будем конкретно ELAM-драйвер этого антивируса, попытаюсь разъяснить все без воды.
BitDefender разделяется на несколько драйверов, на моей версии, которую я скачал бесплатно(кстати был удивлен, что в РФ доступно) -
Пожалуйста, авторизуйтесь для просмотра ссылки.
используется ровно в 4 драйвера, антивирус подгружает их в System\drivers. Наша цель это конкретно ELAM-драйверbdelam.sys - драйвер имеет сертификацию, подгружается он системой самым первым, еще до загрузки остальных программ и основных служб, задача драйвера не дать руткитам перехватить управление ПК.
Ну, что, приступим
Первое, что бросается в глазах при анализе данного драйвера, это конечно же вызовы функций регистрации каллбека и дерегистрации.
Каллбек регистрируется через IoRegisterBootDriverCallback, а освобождается через IoUnregisterBootDriverCallback, конкретно этот механизм позволяет ELAM получать уведомления от ядра о каждом загрузочном драйвере в системе
C++:
PVOID IoRegisterBootDriverCallback(
[in] PBOOT_DRIVER_CALLBACK_FUNCTION CallbackFunction,
[in, optional] PVOID CallbackContext
);
C++:
VOID IoUnregisterBootDriverCallback(
[in] PVOID CallbackHandle
);
Если сильно упростить и продемонстрировать, то выглядит это так:
C++:
NTSTATUS RegisterCallback()
{
return IoRegisterBootDriverCallback(
BootDriverCallbackRoutine,
NULL,
&g_CallbackHandle
);
}
1. BootDriverCallbackRountine это функция которая будет вызываться ядром при каждом событии загрузки драйвера;
2. Второй параметр это контекст который не используется;
3. g_CallbackHandle это дескриптор регистрации, необходимый для последующей отмены каллбека, дескриптор будет передаваться в функцию IoUnregisterBootDriverCallback.
Дерегистрация, кстати выглядит выполняется аналогичным образом:
C++:
VOID UnregisterCallback()
{
if (g_CallbackHandle)
{
IoUnregisterBootDriverCallback(g_CallbackHandle);
g_CallbackHandle = NULL;
}
}
После того как управление успешно перехвачено, антивирусный драйвер в параметрах регистрации каллбека указывает функцию
sub_140001DBC.Я не мог понять, что это за функция, но изучив структуру выше подробнее, я понял, что вот в этом псевдо-коде:
C++:
PVOID __fastcall sub_140001DBC(__int64 a1, int a2, _DWORD *a3)
{
PVOID result; // rax
if ( a2 )
{
if ( a2 == 1 )
return (PVOID)sub_140001EC0(a3);
}
else
{
result = P;
*((_DWORD *)P + 12) = *a3;
}
return result;
}
BDCB_CALLBACK_TYPE, а аргумент a3 представляет собой системную структуру PBDCB_IMAGE_INFORMATION с метаданными проверяемого файла.То есть если значение a2 равно единице, что соответствует системному флагу BdCbInitializeImage, сигнализирующему о попытке загрузки нового загрузочного драйвера в систему, управление немедленно перенаправляется в функцию sub_140001EC0 которая обрабатывает уже сформированный системой контекст проверки образа, получая через BDCB_IMAGE_INFORMATION метаданные загружаемого драйвера, включая сведения о сертификате и его издателе.
После чего антивирус передает очищенный бинарный контейнер главному диспетчеру безопасности sub_1400017FC который проверяет проприетарные маркеры BMAL и SG, в случае равенства a2 нулю что означает системное уведомление BdCbStatusUpdate выполнение уходит в ветку else где через глобальный указатель P по смещению плюс 12 в памяти ядра сохраняется статус текущей сессии ELAM
В самом начале функция sub_140001EC0 вызывает подфункцию sub_140001D8C, результат которой сразу падает в *a1, одновременно с этим антивирус накручивает счетчик общего числа проверенных драйверов в системе, увеличивая значение по смещению *((_WORD *)P + 45) на единицу, если проверка спотыкается на ошибке со статусом 2 или 3, код понимает, что дело пахнет керосином, в этом случае срабатывает триггер безопасности и выставляется флаг жесткой изоляции по адресу *((_DWORD *)P + 23) = 1
Если же на первом этапе все чисто, управление улетает в ветку else, там инкрементируется счетчик успешно одобренных чистых драйверов *((_WORD *)P + 44). Сразу после этого вызывается функция sub_140001C98, которая берет из глобального контекста P данные по смещению +664 и пушит очищенный бинарный контейнер прямо в руки главному диспетчеру sub_1400017FC, который уже ищет внутри маркеры "BMAL" и "SG". В статье функция не разбирается
Что же там по поводу проверок хэша? Драйвер проверяет хэш не через какую-то кастомную функцию, а вызывая функции из cng.
В первую очередь драйвер вызывает функцию BCryptCreateHash, в этой функции создаётся объект хэширования, который будет использоваться для вычисления контрольной суммы проверяемого драйвера. Предварительно был уже выбран драйвером алгоритм SHA1 и выделена память, под внутреннюю структуру cng, в результате выполнения функция BCryptCreateHash возвращает дескриптор, который дальше используется во всех последующих криптографических операциях
Если же собрать данную часть, и часть выше про ELAM в теории в один камок, то механизм проверки можно описать достаточно просто. ELAM-драйвер получает уведомление от ядра о попытке загрузки нового драйвера, извлекает метаданные через структуру BDCB_IMAGE_INFORMATION и на основе этих данных принимает решение о дальнейшем допуске(Хэш, название драйвера или путь к нему).
После данного процесса, драйвер дополнительно вычисляет криптографическую проверку целостности образа, сначала формируя хэш содержимого драйвера через CNG(BCryptCreateHash -> BCryptHashData -> BCryptFinishHash -> BCryptVerifySignature), после чего этот хэш сравнивая с цифровой подписью, используя открытый ключ из сертификата.
На этом криптографическую часть думаю можно закончить, надеюсь читателю все понятно.
Разрабатываем свой ELAM-драйвер
Сама интересная часть статьи это непосредственно разработка самого ELAM-драйвера, вкратце пройдемся по тому, что должен содержать драйвер, после чего перейдем к основной логике с каллбеками.
C++:
#include <ntddk.h>
#define IOCTL_AV_COMMAND CTL_CODE(FILE_DEVICE_UNKNOWN, 0x800, METHOD_BUFFERED, FILE_ANY_ACCESS)
PDEVICE_OBJECT pDeviceObject = nullptr;
UNICODE_STRING DevName = RTL_CONSTANT_STRING(L"\\Device\\YouGameDevice");
UNICODE_STRING SymName = RTL_CONSTANT_STRING(L"\\DosDevices\\Yougame");
NTSTATUS CreateCloseHandler(PDEVICE_OBJECT DeviceObject, PIRP Irp);
NTSTATUS IoControlHandler(PDEVICE_OBJECT DeviceObject, PIRP Irp);
VOID DriverUnload(PDRIVER_OBJECT DriverObject);
NTSTATUS MAIN_REGISRTER_ELAM_CALLBACK(VOID);
NTSTATUS DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath)
{
UNREFERENCED_PARAMETER(RegistryPath);
DriverObject->MajorFunction[IRP_MJ_CREATE] = CreateCloseHandler;
DriverObject->MajorFunction[IRP_MJ_CLOSE] = CreateCloseHandler;
DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = IoControlHandler;
DriverObject->DriverUnload = DriverUnload;
NTSTATUS status = IoCreateDevice(
DriverObject,
0,
&DevName,
0x00000022,
0,
FALSE,
&pDeviceObject
);
if (!NT_SUCCESS(status))
{
return 0xC0000001;
}
status = IoCreateSymbolicLink(&SymName, &DevName);
if (!NT_SUCCESS(status))
{
IoDeleteDevice(pDeviceObject);
return 0xC0000001;
}
pDeviceObject->Flags &= ~DO_DEVICE_INITIALIZING;
MAIN_REGISRTER_ELAM_CALLBACK();
return STATUS_SUCCESS;
}
NTSTATUS CreateCloseHandler(PDEVICE_OBJECT DeviceObject, PIRP Irp)
{
UNREFERENCED_PARAMETER(DeviceObject);
Irp->IoStatus.Status = STATUS_SUCCESS;
Irp->IoStatus.Information = 0;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
return STATUS_SUCCESS;
}
NTSTATUS IoControlHandler(PDEVICE_OBJECT DeviceObject, PIRP Irp)
{
UNREFERENCED_PARAMETER(DeviceObject);
PIO_STACK_LOCATION stack = IoGetCurrentIrpStackLocation(Irp);
NTSTATUS status = STATUS_SUCCESS;
ULONG_PTR information = 0;
if (stack->Parameters.DeviceIoControl.IoControlCode == IOCTL_AV_COMMAND)
{
PVOID buffer = Irp->AssociatedIrp.SystemBuffer;
status = STATUS_SUCCESS;
information = 4;
}
else
{
status = 0xC00000BB;
information = 0;
}
Irp->IoStatus.Status = status;
Irp->IoStatus.Information = information;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
return STATUS_SUCCESS;
}
1. CreateCloseHandler - обработка создания и закрытия дескриптора на устройство;
2. IoControlHandler - обработка IOCTL;
3. DriverUnload - функция выгрузки драйвера
После чего внутри той же функции DriverEntry создаем устройство через IoCreateDevice, то есть создаем объект, этот объект является точкой взаимодействия между UM и KM и необходим для дальнейшей работы драйвера, если создание устройства прошло успешно, следующим шагом выполняется создание символьной ссылки через IoCreateSymbolicLink, функция связывает внутренний путь устройства (\Device\YouGameDevice) с пользовательским пространством (\DosDevices\YouGame), позволяя обращаться к драйверу из приложений UM через функции типа DeviceIoControl, либо через *NT функцию из ntdll.dll.
Получается код вида:
IoRegisterBootDriverCallback, чтобы получать уведомления о загрузке других драйверов в системе
C++:
#include <ntddk.h>
#define SHA256_HASH_SIZE 32
extern "C" {
NTSTATUS IoRegisterBootDriverCallback(PBD_CB_ROUTINE CallbackRoutine, PVOID Context, PVOID* RegistrationHandle);
VOID IoUnregisterBootDriverCallback(PVOID RegistrationHandle);
NTSTATUS IoGetImageCertificateInfo(PVOID ImageHandle, PVOID CertificateBuffer, PULONG CertificateLength, PULONG CertificateType);
}
PVOID ElamCallbackHandle = nullptr;
UCHAR CertHash[SHA256_HASH_SIZE] = { 0 };
extern UNICODE_STRING DRIVER_BLACK_LIST[];
extern ULONG DRIVER_BLACK_LIST_COUNT;
extern UCHAR CERT_BLACK_LIST[][SHA256_HASH_SIZE];
extern ULONG CERT_BLACK_LIST_COUNT;
BOOLEAN CompareCertHashInList(PUCHAR CurrentHash, PUCHAR BlackList, ULONG Count);
VOID BootDriverCallbackRoutine(PVOID CallbackContext, BD_CB_TYPE Classification, PBD_CB_IMAGE_INFORMATION ImageInformation);
NTSTATUS MAIN_REGISRTER_ELAM_CALLBACK(VOID)
{
PVOID handle = nullptr;
NTSTATUS status = IoRegisterBootDriverCallback(
(PBD_CB_ROUTINE)BootDriverCallbackRoutine,
nullptr,
&handle
);
ElamCallbackHandle = handle;
return status;
}
VOID UNREGITSER_ELAM_CALLBACK(VOID)
{
if (ElamCallbackHandle != nullptr)
{
IoUnregisterBootDriverCallback(ElamCallbackHandle);
}
}
VOID BootDriverCallbackRoutine(PVOID CallbackContext, BD_CB_TYPE Classification, PBD_CB_IMAGE_INFORMATION ImageInformation)
{
UNREFERENCED_PARAMETER(CallbackContext);
if (Classification != BdCbInitializeImage)
{
return;
}
for (ULONG i = 0; i < DRIVER_BLACK_LIST_COUNT; i++)
{
if (RtlEqualUnicodeString(&ImageInformation->ImageName, &DRIVER_BLACK_LIST[i], TRUE))
{
ImageInformation->ClassificationFlags = STATUS_ACCESS_DENIED;
return;
}
}
ULONG hashSize = SHA256_HASH_SIZE;
ULONG certType = 0;
NTSTATUS status = IoGetImageCertificateInfo(
ImageInformation->ImageHandle,
CertHash,
&hashSize,
&certType
);
if (!NT_SUCCESS(status))
{
ImageInformation->ClassificationFlags = STATUS_ACCESS_DENIED;
return;
}
if (CompareCertHashInList(CertHash, (PUCHAR)CERT_BLACK_LIST, CERT_BLACK_LIST_COUNT))
{
ImageInformation->ClassificationFlags = STATUS_ACCESS_DENIED;
return;
}
}
BOOLEAN CompareCertHashInList(PUCHAR CurrentHash, PUCHAR BlackList, ULONG Count)
{
if (Count == 0)
{
return FALSE;
}
for (ULONG i = 0; i < Count; i++)
{
PUCHAR targetHash = BlackList + (i * SHA256_HASH_SIZE);
BOOLEAN match = TRUE;
for (ULONG j = 0; j < 4; j++)
{
if (*(PULONG64)(CurrentHash + j * 8) != *(PULONG64)(targetHash + j * 8))
{
match = FALSE;
break;
}
}
if (match)
{
return TRUE;
}
}
return FALSE;
}
cpp.h:
#ifndef BLACKLIST_H
#define BLACKLIST_H
#include <ntddk.h>
#define SHA256_HASH_SIZE 32
const ULONG DRIVER_BLACK_LIST_COUNT = 6;
const UNICODE_STRING DRIVER_BLACK_LIST[DRIVER_BLACK_LIST_COUNT] = {
RTL_CONSTANT_STRING(L"driver1.sys"),
RTL_CONSTANT_STRING(L"driver2.sys"),
RTL_CONSTANT_STRING(L"driver3.sys"),
RTL_CONSTANT_STRING(L"driver4.sys"),
RTL_CONSTANT_STRING(L"driver5.sys"),
RTL_CONSTANT_STRING(L"driver6.sys")
};
const UCHAR CERT_BLACK_LIST[][SHA256_HASH_SIZE] = {
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 },
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }
};
const ULONG CERT_BLACK_LIST_COUNT = sizeof(CERT_BLACK_LIST) / SHA256_HASH_SIZE;
#endif
BdCbInitializeImage, напомню, что это фильтрация событий ядра, чтобы обрабатывать только момент инициализации образа драйвера, все остальные уведомления просто игнорируются и выполнение уходит в .exit. После чего загружается указатель на массив DRIVER_BLACK_LIST, запускается цикл перебора каждого элемента списка, каждый элемент берется через RSI и помещается в RDX как текущая строка для сравнения, затем вызывается RtlEqualUnicodeString, которая выполняет сравнение имени загружаемого драйвера с текущим элементом черного списка, при этом в RCX передается строка имени из структуры BDCB_IMAGE_INFORMATION по смещению R8 плюс 08h, это фактически ImageName, если совпадение найдено AL становится равен 1 и происходит немедленный переход в .DRIVER_BLOCK без дальнейших проверок, данный этап это лишь первая часть защитыесли имя не совпало ни с одним элементом списка, выполнение переходит к следующему этапу, на этом этапе начинается проверка цифровой подписи, из структуры
PBOOT_DRIVER_INFO извлекается ImageHandle по смещению 38h, далее вызывается IoGetImageCertificateInfo, эта функция заполняет буфер CertHash SHA256_HASH_SIZE, то есть вытаскивает хэш сертификата драйвера напрямую из системного хранилища подписи, если функция возвращает ошибку через JS переход сразу идет в .DRIVER_BLOCK, это защита от отсутствующих или битых сертификатовпосле успешного получения хэша выполняется второй уровень фильтрации, вызывается
CompareCertHashInList, туда передается CertHash, список CERT_BLACK_LIST и количество элементов CERT_BLACK_LIST_COUNT, дальше внутри происходит постраничное сравнение хэша по 32 байта, логика разбита на блоки по 8 байт, используется R9D как счетчик четырех итераций, то есть полный SHA256 разбивается на четыре куска по 8 байт и сравнивается последовательно, если хотя бы один блок не совпал происходит переход в .NEXT_HASH, где указатель RDX сдвигается на следующий элемент черного списка и цикл продолжается
C++:
CompareCertHashInList:
SUB RSP, 48H
MOV [RSP + 30H], RCX
MOV [RSP + 38H], RDX
MOV [RSP + 40H], R8D
XOR EAX, EAX
TEST R8D, R8D
JZ @F
.LOOP_LIST:
MOV RCX, [RSP + 30H]
MOV R9D, 4
.LOOP_HASH:
MOV R10, QWORD [RCX] ; // чтение 8 байт хэша
CMP R10, QWORD [RDX]
JNE .NEXT_HASH
ADD RCX, 8
ADD RDX, 8
DEC R9D
JNZ .LOOP_HASH
MOV EAX, 1
JMP @F
.NEXT_HASH:
MOV RDX, [RSP + 38H]
ADD RDX, 32
MOV [RSP + 38H], RDX
DEC DWORD [RSP + 40H]
JNZ .LOOP_LIST
@@:
ADD RSP, 48h
RET
C:
// yougame.biz
FORMAT PE64 NATIVE
ENTRY DriverEntry
include 'blacklist.inc'
BdCbInitializeImage EQU 0
BdCbStatusUpdate EQU 1
STATUS_ACESS_DENIED EQU 0C0000022H
IOCTL_AV_COMMAND EQU 00222000H
SECTION '.text' CODE READABLE EXECUTABLE
DriverEntry:
SUB RSP, 50H
MOV [RSP + 40H], RCX
MOV [RSP + 48H], RDX
MOV RAX, CreateCloseHandler
MOV [RCX + 70H], RAX
MOV [RCX + 80H], RAX
MOV RAX, IoControlHandler
MOV [RCX + 0E0H], RAX
MOV RAX, DriverUnload
MOV [RCX + 68H], RAX
MOV RCX, [RSP + 40H]
XOR EDX, EDX
LEA R8, [DevName]
MOV R9D, 22H
MOV QWORD [RSP + 20H], 0
MOV BYTE [RSP + 28H], 0
LEA RAX, [pDeviceObject]
MOV QWORD [RSP + 30H], RAX
CALL QWORD [IoCreateDevice]
TEST EAX, EAX
JS @F
LEA RCX, [SymName]
LEA RDX, [DevName]
CALL QWORD [IoCreateSymbolicLink]
TEST EAX, EAX
JS .err_dev
MOV RAX, [pDeviceObject]
AND DWORD [RAX + 1CH], NOT 80H
CALL MAIN_REGISRTER_ELAM_CALLBACK
XOR EAX, EAX
ADD RSP, 50H
RET
.err_dev:
MOV RCX, [pDeviceObject]
CALL QWORD [IoDeleteDevice]
@@:
MOV EAX, 0C0000001H
ADD RSP, 50H
RET
CreateCloseHandler:
SUB RSP, 28H
XOR EAX, EAX
MOV [RDX + 30H], EAX
MOV QWORD [RDX + 38H], 0
MOV RCX, RDX
XOR EDX, EDX
CALL QWORD [IoCompleteRequest]
XOR EAX, EAX
ADD RSP, 28H
RET
IoControlHandler:
SUB RSP, 28H
MOV RCX, RDX
MOV [RSP + 30H], RDX
CALL QWORD [IoGetCurrentIrpStackLocation]
MOV EAX, [RAX + 08H]
CMP EAX, IOCTL_AV_COMMAND
JNE .err_ioctl
MOV RCX, [RSP + 30H]
MOV RAX, [RCX + 18H]
MOV RCX, [RSP + 30H]
XOR EAX, EAX
MOV [RCX + 30H], EAX
MOV QWORD [RCX + 38H], 4
JMP .complete
.err_ioctl:
MOV RCX, [RSP + 30H]
MOV EAX, 0C00000BBH
MOV [RCX + 30H], EAX
MOV QWORD [RCX + 38H], 0
.complete:
XOR EDX, EDX
CALL QWORD [IoCompleteRequest]
XOR EAX, EAX
ADD RSP, 28H
RET
MAIN_REGISRTER_ELAM_CALLBACK:
SUB RSP, 28H
LEA RCX, [BootDriverCallbackRountine]
XOR EDX, EDX
CALL QWORD [IoRegisterBootDriverCallback]
MOV [ElamCallbackHandle], RAX
ADD RSP, 28H
RET
UNREGITSER_ELAM_CALLBACK:
SUB RSP, 28H
MOV RCX, [ElamCallbackHandle]
TEST RCX, RCX
JZ @F
CALL QWORD [IoUnregisterBootDriverCallback]
@@:
ADD RSP, 28H
RET
BootDriverCallbackRountine:
SUB RSP, 40H
MOV [RSP + 20h], RCX
MOV [RSP + 28h], RDX
MOV [RSP + 30h], R8
CMP EDX, BdCbInitializeImage
JNE .exit
MOV [RSP + 30H], R8
MOV RSI, DRIVER_BLACK_LIST
MOV ECX, DRIVER_BLACK_LIST_COUNT
.@@:
MOV RDX, [RSI]
PUSH RCX
PUSH RSI
LEA RCX, [R8 + 08h]
MOV R8D, 1
CALL [RtlEqualUnicodeString]
POP RSI
POP RCX
TEST AL, AL
JNZ .DRIVER_BLOCK
ADD RSI, 8
DEC ECX
JNZ @b
; ========================================
MOV R8, [RSP + 20H]
MOV RCX, [R8 + 38H] ; <<< IMAGEHANDLE
LEA RDX, [CertHash] ; << БУФЕР ДЛЯ ХЭША R
MOV R8D, SHA256_HASH_SIZE
XOR R9D, R9D
CALL QWORD [IoGetImageCertificateInfo]
TEST EAX, EAX
JS .DRIVER_BLOCK
LEA RCX, [CertHash]
LEA RDX, [CERT_BLACK_LIST]
MOV R8D, CERT_BLACK_LIST_COUNT
CALL CompareCertHashInList
TEST AL, AL
JNZ .DRIVER_BLOCK
.DRIVER_SUCESS:
XOR EAX, EAX
ADD RSP, 40H
RET
.DRIVER_BLOCK:
MOV EAX, STATUS_ACESS_DENIED
ADD RSP, 40H
RET
.exit:
ADD RSP, 40H
RET
CompareCertHashInList:
SUB RSP, 48H
MOV [RSP + 30H], RCX
MOV [RSP + 38H], RDX
MOV [RSP + 40H], R8D
XOR EAX, EAX
TEST R8D, R8D
JZ @F
.LOOP_LIST:
MOV RCX, [RSP + 30H]
MOV R9D, 4
.LOOP_HASH:
MOV R10, QWORD [RCX] ; чтение 8 байт хэша
CMP R10, QWORD [RDX]
JNE .NEXT_HASH
ADD RCX, 8
ADD RDX, 8
DEC R9D
JNZ .LOOP_HASH
MOV EAX, 1
JMP @F
.NEXT_HASH:
MOV RDX, [RSP + 38H]
ADD RDX, 32
MOV [RSP + 38H], RDX
DEC DWORD [RSP + 40H]
JNZ .LOOP_LIST
@@:
ADD RSP, 48h
RET
DriverUnload:
SUB RSP, 40H
CALL UNREGITSER_ELAM_CALLBACK
LEA RCX, [SymName]
CALL QWORD [IoDeleteSymbolicLink]
MOV RAX, [pDeviceObject]
TEST RAX, RAX
JZ @F
MOV RCX, RAX
CALL QWORD [IoDeleteDevice]
@@:
ADD RSP, 40H
RET
SECTION '.data' DATA READABLE WRITEABLE
; ////////
pDeviceObject DQ 0
ElamCallbackHandle DQ 0
align 16
CertHash db 32 dup(0)
DevName:
DW .str_end - .str
DW .str_end - .str + 2
DD 0
DQ .str
.str:
DU '\Device\YouGameDevice'
DW 0
.str_end:
SymName:
DW .str_end - .str
DW .str_end - .str + 2
DD 0
DQ .str
.str:
DU '\DosDevices\YouGame'
DW 0
.str_end:
section '.idata' import data readable writeable
dd 0, 0, 0, RVA ntoskrnl_name, RVA ntoskrnl_table
dd 0, 0, 0, 0, 0
ntoskrnl_table:
IoCreateDevice DQ RVA _IoCreateDevice
IoDeleteDevice DQ RVA _IoDeleteDevice
IoCreateSymbolicLink DQ RVA _IoCreateSymbolicLink
IoDeleteSymbolicLink DQ RVA _IoDeleteSymbolicLink
IoCompleteRequest DQ RVA _IoCompleteRequest
IoGetCurrentIrpStackLocation DQ RVA _IoGetCurrentIrpStackLocation
IoRegisterBootDriverCallback DQ RVA _IoRegisterBootDriverCallback
IoUnregisterBootDriverCallback DQ RVA _IoUnregisterBootDriverCallback
RtlEqualUnicodeString DQ RVA _RtlEqualUnicodeString
IoGetImageCertificateInfo DQ RVA _IoGetImageCertificateInfo
DQ 0
ntoskrnl_name:
DB 'ntoskrnl.exe', 0
ALIGN 2
_IoCreateDevice:
DW 0
DB 'IoCreateDevice', 0
ALIGN 2
_IoDeleteDevice:
DW 0
DB 'IoDeleteDevice', 0
ALIGN 2
_IoCreateSymbolicLink:
DW 0
DB 'IoCreateSymbolicLink', 0
ALIGN 2
_IoDeleteSymbolicLink:
DW 0
DB 'IoDeleteSymbolicLink', 0
ALIGN 2
_IoCompleteRequest:
DW 0
DB 'IoCompleteRequest', 0
ALIGN 2
_IoGetCurrentIrpStackLocation:
DW 0
DB 'IoGetCurrentIrpStackLocation', 0
ALIGN 2
_IoRegisterBootDriverCallback:
DW 0
DB 'IoRegisterBootDriverCallback', 0
ALIGN 2
_IoUnregisterBootDriverCallback:
DW 0
DB 'IoUnregisterBootDriverCallback', 0
ALIGN 2
_RtlEqualUnicodeString:
DW 0
DB 'RtlEqualUnicodeString', 0
ALIGN 2
_IoGetImageCertificateInfo:
DW 0
DB 'IoGetImageCertificateInfo', 0
C:
SHA256_HASH_SIZE equ 32
CERT_BLACK_LIST_COUNT dd (CERT_BLACK_LIST_END - CERT_BLACK_LIST) / 32
DRIVER_BLACK_LIST_COUNT = 6
DRIVER_BLACK_LIST:
DQ .black1
DQ .black2
DQ .black3
DQ .black4
DQ .black5
DQ .black6
.black1 DU 'driver1.sys', 0
.black2 DU 'driver2.sys', 0
.black3 DU 'driver3.sys', 0
.black4 DU 'driver4.sys', 0
.black5 DU 'driver5.sys', 0
.black6 DU 'driver6.sys', 0
align 16
CERT_BLACK_LIST:
dq 0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000
dq 0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000
dq 0x0000000000000000, 0x0000000000000000, 0x0000000000000000, 0x0000000000000000
CERT_BLACK_LIST_END:
Заключение
Надеюсь статья была для кого-то полезной и кто-то открыл для себя что-то новое.
Касаемо драйвера, доделать я не успел конкретно blacklist.inc, но драйвер полностью рабочий, можно пополнить массив разными драйверами, либо CERT_BLACK_LIST хэшами драйверов.
Также подмечу, что ELAM-драйвер не запускается как обычный драйвер по запросу системы или пользователя, он попадает в очень раннюю фазу загрузки ОСи подхватывается только механизмом доверенной загрузки, если соответствует требованиям к подписи и классификации.
Главное, чтобы читатель все понял. Всем пока.
Полезные ссылки:
Каллбеки:
Пожалуйста, авторизуйтесь для просмотра ссылки.
|
Пожалуйста, авторизуйтесь для просмотра ссылки.
Последнее редактирование: