Подписывайтесь на наш Telegram и не пропускайте важные новости! Перейти

Гайд ELAM. как детектируются уязвимые драйверы

Разработчик
Разработчик
Статус
Онлайн
Регистрация
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, этой структуре передаются базовые метаданные загружаемого образа о загружаемом драйвере, то есть все, что было описано выше.
Также есть структура под названием BDCB_IMAGE_INFORMATION, отражающая метаданные загружаемого образа, имеет она такое определение:
struct:
Expand Collapse Copy
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;
Однако каллбек-функция ELAM вызывается не только при инициализации драйвера, ядро передает каллбек дополнительный параметр, который указывает на тип текущего события, в ELAM существует несколько типов уведомлений, но в рамках статьи нас будет интересовать именно BdCbInitializeImage.
struct:
Expand Collapse Copy
typedef enum _BDCB_CALLBACK_TYPE
{
    BdCbInitializeImage = 0,
    BdCbStatusUpdate    = 1
} BDCB_CALLBACK_TYPE;

Событие BdCbInitializeImage вызывается во время инициализации загрузочного драйвера, на этом моменте ELAM-драйвер получает возможность проанализировать драйвер и принять решение о разрешении либо блокировки его загрузки.
Событие BdCbStatusUpdate используется ядром для обновления статуса ELAM-драйвера, в рамках статьи этот параметр будет игнорироваться.

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

Демонстрация работы реального ELAM-драйвера на примере антивируса BitDefender
Конкретно данная часть статьи, с разбором драйвера антивируса является перезаливом моей статьи которую я писал для своего ТГ-канала, максимум я немного попытался подправить ее под эту статью. А если возникнет вопрос зачем ее вставлять, то только чтобы люди лучше поняли механизм работы этой технологии на практике

Я долго думал, где я могу найти подобный драйвер и сразу вспомнил про антивирусы, я скачал первый попавшийся в голову антивирус, а именно BitDefender, который кстати, довольно хорошо оценивается в кругу безопасников и разработчиков малвари. Разбирать будем конкретно ELAM-драйвер этого антивируса, попытаюсь разъяснить все без воды.
1779591993838.png

BitDefender разделяется на несколько драйверов, на моей версии, которую я скачал бесплатно(кстати был удивлен, что в РФ доступно) -
Пожалуйста, авторизуйтесь для просмотра ссылки.
используется ровно в 4 драйвера, антивирус подгружает их в System\drivers. Наша цель это конкретно ELAM-драйвер

bdelam.sys - драйвер имеет сертификацию, подгружается он системой самым первым, еще до загрузки остальных программ и основных служб, задача драйвера не дать руткитам перехватить управление ПК.
Ну, что, приступим
1779592640689.png

Первое, что бросается в глазах при анализе данного драйвера, это конечно же вызовы функций регистрации каллбека и дерегистрации.
Каллбек регистрируется через IoRegisterBootDriverCallback, а освобождается через IoUnregisterBootDriverCallback, конкретно этот механизм позволяет ELAM получать уведомления от ядра о каждом загрузочном драйвере в системе
1779593105767.png


C++:
Expand Collapse Copy
PVOID IoRegisterBootDriverCallback(
  [in]           PBOOT_DRIVER_CALLBACK_FUNCTION CallbackFunction,
  [in, optional] PVOID                          CallbackContext
);
C++:
Expand Collapse Copy
VOID IoUnregisterBootDriverCallback(
  [in] PVOID CallbackHandle
);

Если сильно упростить и продемонстрировать, то выглядит это так:
C++:
Expand Collapse Copy
NTSTATUS RegisterCallback()
{
    return IoRegisterBootDriverCallback(
        BootDriverCallbackRoutine,
        NULL,
        &g_CallbackHandle
    );
}

1. BootDriverCallbackRountine это функция которая будет вызываться ядром при каждом событии загрузки драйвера;
2. Второй параметр это контекст который не используется;
3. g_CallbackHandle это дескриптор регистрации, необходимый для последующей отмены каллбека, дескриптор будет передаваться в функцию IoUnregisterBootDriverCallback.

Дерегистрация, кстати выглядит выполняется аналогичным образом:
C++:
Expand Collapse Copy
VOID UnregisterCallback()
{
    if (g_CallbackHandle)
    {
        IoUnregisterBootDriverCallback(g_CallbackHandle);
        g_CallbackHandle = NULL;
    }
}
После регистрации каллбека ядро начинает вызывать указанную функцию каждый раз, когда происходит событие.
После того как управление успешно перехвачено, антивирусный драйвер в параметрах регистрации каллбека указывает функцию sub_140001DBC.Я не мог понять, что это за функция, но изучив структуру выше подробнее, я понял, что вот в этом псевдо-коде:
C++:
Expand Collapse Copy
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;
}
происходит первичная обработка событий от ОС, где аргумент a2 является типом события BDCB_CALLBACK_TYPE, а аргумент a3 представляет собой системную структуру PBDCB_IMAGE_INFORMATION с метаданными проверяемого файла.
То есть если значение a2 равно единице, что соответствует системному флагу BdCbInitializeImage, сигнализирующему о попытке загрузки нового загрузочного драйвера в систему, управление немедленно перенаправляется в функцию sub_140001EC0 которая обрабатывает уже сформированный системой контекст проверки образа, получая через BDCB_IMAGE_INFORMATION метаданные загружаемого драйвера, включая сведения о сертификате и его издателе.

После чего антивирус передает очищенный бинарный контейнер главному диспетчеру безопасности sub_1400017FC который проверяет проприетарные маркеры BMAL и SG, в случае равенства a2 нулю что означает системное уведомление BdCbStatusUpdate выполнение уходит в ветку else где через глобальный указатель P по смещению плюс 12 в памяти ядра сохраняется статус текущей сессии ELAM
1779593462034.png

В самом начале функция 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". В статье функция не разбирается
1779593537158.png

Что же там по поводу проверок хэша? Драйвер проверяет хэш не через какую-то кастомную функцию, а вызывая функции из cng.
В первую очередь драйвер вызывает функцию BCryptCreateHash, в этой функции создаётся объект хэширования, который будет использоваться для вычисления контрольной суммы проверяемого драйвера. Предварительно был уже выбран драйвером алгоритм SHA1 и выделена память, под внутреннюю структуру cng, в результате выполнения функция BCryptCreateHash возвращает дескриптор, который дальше используется во всех последующих криптографических операциях
1779594023689.png
После создания хэш-объекта драйвер переходит к следующему этапу, а именно к последовательной подаче данных через BCryptHashData, конкретно этот момент ядроначинает обрабатывать содержимое проверяемого драйвера частями, обновляя внутреннее состояние SHA1 после каждого вызова
1779594094513.png
Когда все данные переданы, вызывается функция BCryptFinishHash, данная функция завершает вычисления и формирует итоговый хэш фиксированной длины, непосредственно этот результат используется далее для проверки цифровой подписи через BCryptVerifySignature
1779594152041.png
1779594189746.png
Я признаюсь, я не являюсь каким-то супер невъебическим криптографом, я объяснил все то, что находится в границах моих знаний конкретно в данной части, чтобы не вести читателя в заблуждение я решил не углубляться в проверку через хэш.

Если же собрать данную часть, и часть выше про ELAM в теории в один камок, то механизм проверки можно описать достаточно просто. ELAM-драйвер получает уведомление от ядра о попытке загрузки нового драйвера, извлекает метаданные через структуру BDCB_IMAGE_INFORMATION и на основе этих данных принимает решение о дальнейшем допуске(Хэш, название драйвера или путь к нему).
После данного процесса, драйвер дополнительно вычисляет криптографическую проверку целостности образа, сначала формируя хэш содержимого драйвера через CNG(BCryptCreateHash -> BCryptHashData -> BCryptFinishHash -> BCryptVerifySignature), после чего этот хэш сравнивая с цифровой подписью, используя открытый ключ из сертификата.

На этом криптографическую часть думаю можно закончить, надеюсь читателю все понятно.

Разрабатываем свой ELAM-драйвер
Сама интересная часть статьи это непосредственно разработка самого ELAM-драйвера, вкратце пройдемся по тому, что должен содержать драйвер, после чего перейдем к основной логике с каллбеками.
1779596875471.png
C++:
Expand Collapse Copy
#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;
}
Внутри главной функции DriverEntry регистрируем выгрузку драйвера, после чего регистрируем основные IRP-обработчики, в объект драйвера записываются адреса функций, которые будут вызываться ядром при обработке запросов:

1. CreateCloseHandler - обработка создания и закрытия дескриптора на устройство;
2. IoControlHandler - обработка IOCTL;
3. DriverUnload - функция выгрузки драйвера

После чего внутри той же функции DriverEntry создаем устройство через IoCreateDevice, то есть создаем объект, этот объект является точкой взаимодействия между UM и KM и необходим для дальнейшей работы драйвера, если создание устройства прошло успешно, следующим шагом выполняется создание символьной ссылки через IoCreateSymbolicLink, функция связывает внутренний путь устройства (\Device\YouGameDevice) с пользовательским пространством (\DosDevices\YouGame), позволяя обращаться к драйверу из приложений UM через функции типа DeviceIoControl, либо через *NT функцию из ntdll.dll.

1779597236114.png

Получается код вида:
1779597291260.png
Переходим к основной логике драйвера. После успешной инициализации драйвер дополнительно отключает флаг скрытого устройства в объекте DEVICE_OBJECT, а затем регистрирует ELAM-каллбек через IoRegisterBootDriverCallback, чтобы получать уведомления о загрузке других драйверов в системе
1779597410145.png

C++:
Expand Collapse Copy
#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:
Expand Collapse Copy
#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
После чего логика переносится в BootDriverCallbackRountine, внутри этой функции происходит самое важное - обработка уведомлений ядра о загружаемых драйверах, вообщем начинается основная логика ELAM обработки, сначала сохраняются входные регистры RCX RDX R8 на стек для сохранения контекста вызова, далее происходит проверка типа события через EDX, сравнение выполняется с BdCbInitializeImage, напомню, что это фильтрация событий ядра, чтобы обрабатывать только момент инициализации образа драйвера, все остальные уведомления просто игнорируются и выполнение уходит в .exit. После чего загружается указатель на массив DRIVER_BLACK_LIST, запускается цикл перебора каждого элемента списка, каждый элемент берется через RSI и помещается в RDX как текущая строка для сравнения, затем вызывается RtlEqualUnicodeString, которая выполняет сравнение имени загружаемого драйвера с текущим элементом черного списка, при этом в RCX передается строка имени из структуры BDCB_IMAGE_INFORMATION по смещению R8 плюс 08h, это фактически ImageName, если совпадение найдено AL становится равен 1 и происходит немедленный переход в .DRIVER_BLOCK без дальнейших проверок, данный этап это лишь первая часть защиты
1779597793925.png

если имя не совпало ни с одним элементом списка, выполнение переходит к следующему этапу, на этом этапе начинается проверка цифровой подписи, из структуры PBOOT_DRIVER_INFO извлекается ImageHandle по смещению 38h, далее вызывается IoGetImageCertificateInfo, эта функция заполняет буфер CertHash SHA256_HASH_SIZE, то есть вытаскивает хэш сертификата драйвера напрямую из системного хранилища подписи, если функция возвращает ошибку через JS переход сразу идет в .DRIVER_BLOCK, это защита от отсутствующих или битых сертификатов

1779597902914.png

после успешного получения хэша выполняется второй уровень фильтрации, вызывается CompareCertHashInList, туда передается CertHash, список CERT_BLACK_LIST и количество элементов CERT_BLACK_LIST_COUNT, дальше внутри происходит постраничное сравнение хэша по 32 байта, логика разбита на блоки по 8 байт, используется R9D как счетчик четырех итераций, то есть полный SHA256 разбивается на четыре куска по 8 байт и сравнивается последовательно, если хотя бы один блок не совпал происходит переход в .NEXT_HASH, где указатель RDX сдвигается на следующий элемент черного списка и цикл продолжается
C++:
Expand Collapse Copy
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
если все четыре блока совпали полностью, функция возвращает EAX = 1, это означает что сертификат найден в черном списке и драйвер должен быть заблокирован, если ни один элемент списка не совпал, функция возвращает 0 и драйвер считается доверенным, в финальной стадии BootDriverCallbackRountine выполняет проверку результата CompareCertHashInList через TEST AL, если установлен флаг совпадения происходит переход в .DRIVER_BLOCK и возврат STATUS_ACCESS_DENIED, если же обе проверки пройдены, и имя и сертификат не попали в черные списки, выполнение идет в .DRIVER_SUCCESS и возвращается нулевой статус, что означает разрешение загрузки.

C:
Expand Collapse Copy
// 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:
Expand Collapse Copy
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-драйвер не запускается как обычный драйвер по запросу системы или пользователя, он попадает в очень раннюю фазу загрузки ОСи подхватывается только механизмом доверенной загрузки, если соответствует требованиям к подписи и классификации.
Главное, чтобы читатель все понял. Всем пока.

Полезные ссылки:
Каллбеки:
Пожалуйста, авторизуйтесь для просмотра ссылки.
|
Пожалуйста, авторизуйтесь для просмотра ссылки.
 
Последнее редактирование:
Всем привет. На югейме я практически не встречал подробных статей по технологии 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, этой структуре передаются базовые метаданные загружаемого образа о загружаемом драйвере, то есть все, что было описано выше.
Также есть структура под названием BDCB_IMAGE_INFORMATION, отражающая метаданные загружаемого образа, имеет она такое определение:
struct:
Expand Collapse Copy
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;
Однако каллбек-функция ELAM вызывается не только при инициализации драйвера, ядро передает каллбек дополнительный параметр, который указывает на тип текущего события, в ELAM существует несколько типов уведомлений, но в рамках статьи нас будет интересовать именно BdCbInitializeImage.
struct:
Expand Collapse Copy
typedef enum _BDCB_CALLBACK_TYPE
{
    BdCbInitializeImage = 0,
    BdCbStatusUpdate    = 1
} BDCB_CALLBACK_TYPE;

Событие BdCbInitializeImage вызывается во время инициализации загрузочного драйвера, на этом моменте ELAM-драйвер получает возможность проанализировать драйвер и принять решение о разрешении либо блокировки его загрузки.
Событие BdCbStatusUpdate используется ядром для обновления статуса ELAM-драйвера, в рамках статьи этот параметр будет игнорироваться.

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

Демонстрация работы реального ELAM-драйвера на примере антивируса BitDefender
Конкретно данная часть статьи, с разбором драйвера антивируса является перезаливом моей статьи которую я писал для своего ТГ-канала, максимум я немного попытался подправить ее под эту статью. А если возникнет вопрос зачем ее вставлять, то только чтобы люди лучше поняли механизм работы этой технологии на практике

Я долго думал, где я могу найти подобный драйвер и сразу вспомнил про антивирусы, я скачал первый попавшийся в голову антивирус, а именно BitDefender, который кстати, довольно хорошо оценивается в кругу безопасников и разработчиков малвари. Разбирать будем конкретно ELAM-драйвер этого антивируса, попытаюсь разъяснить все без воды.
Посмотреть вложение 336947
BitDefender разделяется на несколько драйверов, на моей версии, которую я скачал бесплатно(кстати был удивлен, что в РФ доступно) -
Пожалуйста, авторизуйтесь для просмотра ссылки.
используется ровно в 4 драйвера, антивирус подгружает их в System\drivers. Наша цель это конкретно ELAM-драйвер

bdelam.sys - драйвер имеет сертификацию, подгружается он системой самым первым, еще до загрузки остальных программ и основных служб, задача драйвера не дать руткитам перехватить управление ПК.
Ну, что, приступим
Посмотреть вложение 336948
Первое, что бросается в глазах при анализе данного драйвера, это конечно же вызовы функций регистрации каллбека и дерегистрации.
Каллбек регистрируется через IoRegisterBootDriverCallback, а освобождается через IoUnregisterBootDriverCallback, конкретно этот механизм позволяет ELAM получать уведомления от ядра о каждом загрузочном драйвере в системе
Посмотреть вложение 336949

C++:
Expand Collapse Copy
PVOID IoRegisterBootDriverCallback(
  [in]           PBOOT_DRIVER_CALLBACK_FUNCTION CallbackFunction,
  [in, optional] PVOID                          CallbackContext
);
C++:
Expand Collapse Copy
VOID IoUnregisterBootDriverCallback(
  [in] PVOID CallbackHandle
);

Если сильно упростить и продемонстрировать, то выглядит это так:
C++:
Expand Collapse Copy
NTSTATUS RegisterCallback()
{
    return IoRegisterBootDriverCallback(
        BootDriverCallbackRoutine,
        NULL,
        &g_CallbackHandle
    );
}

1. BootDriverCallbackRountine это функция которая будет вызываться ядром при каждом событии загрузки драйвера;
2. Второй параметр это контекст который не используется;
3. g_CallbackHandle это дескриптор регистрации, необходимый для последующей отмены каллбека, дескриптор будет передаваться в функцию IoUnregisterBootDriverCallback.

Дерегистрация, кстати выглядит выполняется аналогичным образом:
C++:
Expand Collapse Copy
VOID UnregisterCallback()
{
    if (g_CallbackHandle)
    {
        IoUnregisterBootDriverCallback(g_CallbackHandle);
        g_CallbackHandle = NULL;
    }
}
После регистрации каллбека ядро начинает вызывать указанную функцию каждый раз, когда происходит событие.
После того как управление успешно перехвачено, антивирусный драйвер в параметрах регистрации каллбека указывает функцию sub_140001DBC.Я не мог понять, что это за функция, но изучив структуру выше подробнее, я понял, что вот в этом псевдо-коде:
C++:
Expand Collapse Copy
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;
}
происходит первичная обработка событий от ОС, где аргумент a2 является типом события BDCB_CALLBACK_TYPE, а аргумент a3 представляет собой системную структуру PBDCB_IMAGE_INFORMATION с метаданными проверяемого файла.
То есть если значение a2 равно единице, что соответствует системному флагу BdCbInitializeImage, сигнализирующему о попытке загрузки нового загрузочного драйвера в систему, управление немедленно перенаправляется в функцию sub_140001EC0 которая обрабатывает уже сформированный системой контекст проверки образа, получая через BDCB_IMAGE_INFORMATION метаданные загружаемого драйвера, включая сведения о сертификате и его издателе.

После чего антивирус передает очищенный бинарный контейнер главному диспетчеру безопасности sub_1400017FC который проверяет проприетарные маркеры BMAL и SG, в случае равенства a2 нулю что означает системное уведомление BdCbStatusUpdate выполнение уходит в ветку else где через глобальный указатель P по смещению плюс 12 в памяти ядра сохраняется статус текущей сессии ELAM
Посмотреть вложение 336951
В самом начале функция 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-драйвера, вкратце пройдемся по тому, что должен содержать драйвер, после чего перейдем к основной логике с каллбеками.
C++:
Expand Collapse Copy
#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;
}
Внутри главной функции 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-каллбек через IoRegisterBootDriverCallback, чтобы получать уведомления о загрузке других драйверов в системе
Посмотреть вложение 336960
C++:
Expand Collapse Copy
#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:
Expand Collapse Copy
#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
После чего логика переносится в BootDriverCallbackRountine, внутри этой функции происходит самое важное - обработка уведомлений ядра о загружаемых драйверах, вообщем начинается основная логика ELAM обработки, сначала сохраняются входные регистры RCX RDX R8 на стек для сохранения контекста вызова, далее происходит проверка типа события через EDX, сравнение выполняется с BdCbInitializeImage, напомню, что это фильтрация событий ядра, чтобы обрабатывать только момент инициализации образа драйвера, все остальные уведомления просто игнорируются и выполнение уходит в .exit. После чего загружается указатель на массив DRIVER_BLACK_LIST, запускается цикл перебора каждого элемента списка, каждый элемент берется через RSI и помещается в RDX как текущая строка для сравнения, затем вызывается RtlEqualUnicodeString, которая выполняет сравнение имени загружаемого драйвера с текущим элементом черного списка, при этом в RCX передается строка имени из структуры BDCB_IMAGE_INFORMATION по смещению R8 плюс 08h, это фактически ImageName, если совпадение найдено AL становится равен 1 и происходит немедленный переход в .DRIVER_BLOCK без дальнейших проверок, данный этап это лишь первая часть защиты
Посмотреть вложение 336963
если имя не совпало ни с одним элементом списка, выполнение переходит к следующему этапу, на этом этапе начинается проверка цифровой подписи, из структуры PBOOT_DRIVER_INFO извлекается ImageHandle по смещению 38h, далее вызывается IoGetImageCertificateInfo, эта функция заполняет буфер CertHash SHA256_HASH_SIZE, то есть вытаскивает хэш сертификата драйвера напрямую из системного хранилища подписи, если функция возвращает ошибку через JS переход сразу идет в .DRIVER_BLOCK, это защита от отсутствующих или битых сертификатов

Посмотреть вложение 336964
после успешного получения хэша выполняется второй уровень фильтрации, вызывается CompareCertHashInList, туда передается CertHash, список CERT_BLACK_LIST и количество элементов CERT_BLACK_LIST_COUNT, дальше внутри происходит постраничное сравнение хэша по 32 байта, логика разбита на блоки по 8 байт, используется R9D как счетчик четырех итераций, то есть полный SHA256 разбивается на четыре куска по 8 байт и сравнивается последовательно, если хотя бы один блок не совпал происходит переход в .NEXT_HASH, где указатель RDX сдвигается на следующий элемент черного списка и цикл продолжается
C++:
Expand Collapse Copy
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
если все четыре блока совпали полностью, функция возвращает EAX = 1, это означает что сертификат найден в черном списке и драйвер должен быть заблокирован, если ни один элемент списка не совпал, функция возвращает 0 и драйвер считается доверенным, в финальной стадии BootDriverCallbackRountine выполняет проверку результата CompareCertHashInList через TEST AL, если установлен флаг совпадения происходит переход в .DRIVER_BLOCK и возврат STATUS_ACCESS_DENIED, если же обе проверки пройдены, и имя и сертификат не попали в черные списки, выполнение идет в .DRIVER_SUCCESS и возвращается нулевой статус, что означает разрешение загрузки.

C:
Expand Collapse Copy
// 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:
Expand Collapse Copy
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-драйвер не запускается как обычный драйвер по запросу системы или пользователя, он попадает в очень раннюю фазу загрузки ОСи подхватывается только механизмом доверенной загрузки, если соответствует требованиям к подписи и классификации.
Главное, чтобы читатель все понял. Всем пока.

Полезные ссылки:
Каллбеки:
Пожалуйста, авторизуйтесь для просмотра ссылки.
|
Пожалуйста, авторизуйтесь для просмотра ссылки.

Мне нравится
 
Назад
Сверху Снизу