Прятки по хардкору как сделать свой драйвер режима ядра windows и скрывать процессы

Обновлено: 07.07.2024

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

Применяется к: Windows Server 2012 R2, Windows 10 — все выпуски
Исходный номер КБ: 816071

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

Сводка

При устранении неполадок может потребоваться отключить драйвер фильтра:

Проблемы копирования или резервного копирования файлов.

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

Отключение драйверов фильтрации

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

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

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

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

Дополнительные сведения о компьютерных вирусах см. в дополнительных сведениях о предотвращении и устранении вирусов и других вредоносных программ.

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

Выберите тип запуска Отключено. Для этого выполните следующие действия:

Установите клавишу реестра запуска соответствующих драйверов фильтра для 0x4. Значение 0x4 отключит драйвер фильтра. Для этого выполните указанные ниже действия.

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

  1. Откройте редактор реестра.
  2. Создание резервного копирования HKEY_LOCAL_MACHINE\System реестра.
  3. Найдите и нажмите кнопку подкайка HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services реестра.
  4. Щелкните запись для драйвера фильтра, который необходимо отключить.
  5. Дважды щелкните параметр Начните реестр, а затем установите его на значение 0x4.

Эта запись реестра обычно имеет значение 0x3.

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

Пример драйверов фильтрации

В этом разделе описываются некоторые типичные имена драйверов фильтра по продукту:

Защита от вирусов

  • Inoculan: INO_FLPY и INO_FLTR
  • Norton: SYMEVENT, NAVAP, NAVEN и NAVEX
  • McAfee (NAI): NaiFiltr и NaiFsRec
  • Trend Micro: Tmfilter.sys и Vsapint.sys

Агент резервного копирования

Агент резервного копирования для открытых файлов: Ofant.sys

Open Transaction Manager из Veritas BackupExec: Otman.sys (Otman4.sys или Otman5.sys)

Параметры реестра драйверов

В следующей таблице перечислены допустимые параметры и их описание для параметров реестра запуска и типа драйвера:

Заявление об отказе от ответственности за сведения о продуктах сторонних производителей

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

Процессорные архитектуры x86 и x64 имеют четыре кольца защиты, из которых в Windows по факту используются всего два — это ring 3 (режим пользователя) и ring 0 (режим ядра). Бытует мнение, что код режима ядра — самый привилегированный и «ниже» ничего нет. На самом деле архитектура x86/x64 позволяет опускаться еще ниже: это технология виртуализации (hypervisor mode), которая считается кольцом −1 (ring −1), и режим системного управления (System Management Mode, SMM), считающийся кольцом −2 (ring −2), которому доступна память режима ядра и гипервизора.

Итак, мы решили писать собственный драйвер. Начнем с выбора инструментария. Я советую использовать Microsoft Visual Studio, как наиболее user-friendly IDE. Также необходимо будет установить Windows SDK и Windows Driver Kit (WDK) для твоей версии ОС. Кроме того, я крайне рекомендую запастись такими утилитами, как DebugView (просмотр отладочного вывода), DriverView (позволяет получить список всех установленных драйверов) и KmdManager (удобный загрузчик драйверов).

Драйверы в Windows начиная с Vista могут быть как режима пользователя (User-Mode Driver Framework, UMDF), так и режима ядра (Kernel-Mode Driver Framework, KMDF). Более ранние драйверы Windows Driver Model (WDM) появились в Windows 98 и сейчас считаются устаревшими.

Драйверы UMDF имеют намного более ограниченные права, чем KMDF, однако они используются, например, для управления устройствами, подключенными по USB. Помимо ограничений, у них есть очевидные плюсы: их намного проще отлаживать, а ошибка в их написании не вызовет глобальный системный сбой и синий экран смерти. Такие драйверы имеют расширение dll.

Что до драйверов режима ядра (KMDF), то им дозволено куда больше, а расширение файлов, закрепленное за ними, — это sys. В этой статье мы научимся писать простые драйверы режима ядра, напишем драйвер для скрытия процессов методом DKOM (Direct Kernel Object Manipulation) и его загрузчик.

Зачем специалисту по ИБ может понадобиться написать kernel-mode драйвер?

  • Для защиты своей утилиты от действий вредоносов и поиска зловредов, маскирующихся в режиме ядра
  • Для противодействия blue pill и другим руткитам, использующим режим аппаратной виртуализации
  • Для ускорения антивирусной проверки

Создание драйвера KMDF

После того как ты создашь проект драйвера, Visual Studio автоматически настроит некоторые параметры. Проект будет компилироваться в бинарный файл в соответствии с тем, какая выбрана подсистема. Наш вариант — это NATIVE, подсистема низкого уровня, как раз для того, чтобы писать драйверы.

Точка входа в драйвер

Строго говоря, точка входа в драйвер может быть любой — мы можем сами ее определить, добавив к параметрам компоновки проекта -entry:[DriverEntry] , где [DriverEntry] — название функции, которую мы хотим сделать стартовой. Если в обычных приложениях основная функция обычно называется main, то в драйверах точку входа принято называть DriverEntry.

Выглядеть это будет так:

NTSTATUS DriverEntry (PDRIVER_OBJECT pDriverObject, PUNICODE_STRING pRegistryPath); Давай пройдемся по параметрам, которые передаются DriverEntry . pDriverObject имеет тип PDRIVER_OBJECT , это значит, что это указатель на структуру DRIVER_OBJECT , которая содержит информацию о нашем драйвере. Мы можем менять некоторые поля этой структуры, тем самым меняя свойства драйвера. Второй параметр имеет тип PUNICODE_STRING , который означает указатель на строку типа UNICODE . Она, в свою очередь, указывает, где в системном реестре хранится информация о нашем драйвере.


affa855507620b971a0577d2df63e802.jpg

WARNING

Любая ошибка в драйвере может вызвать общесистемный сбой и BSOD. Вероятна потеря данных и повреждение системы. Все эксперименты я рекомендую проводить в виртуальной машине.

Может ли зловред скрыть свой процесс от KMDF-драйвера?

  • Нет, даже если он сам работает в режиме ядра
  • Да, если он использует апаратную виртуализацию или SMM
  • Да, если зловред был загружен в память до него

Interrupt Request Level (IRQL)

IRQL — это своеобразный «приоритет» для драйверов. Чем выше IRQL, тем меньшее число других драйверов будут прерывать выполнение нашего кода. Существует несколько уровней IRQL: Passive, APC, Dispatch и DIRQL. Если открыть документацию MSDN по функциям WinAPI, то можно увидеть примечания, которые регламентируют уровень IRQL, который требуется для обращения к каждой функции. Чем выше этот уровень, тем меньше WinAPI нам доступно для использования. Первые три уровня IRQL используются для синхронизации программных частей ОС, уровень DIRQL считается аппаратным и самым высоким по сравнению с программными уровнями.

Пакеты запроса ввода-вывода (Input/Output Request Packet)

IRP — это запросы, которые поступают к драйверу. Именно при помощи IRP один драйвер может «попросить» сделать что-то другой драйвер либо получить запрос от программы, которая им управляет. IRP используются диспетчером ввода-вывода ОС. Чтобы научить программу воспринимать наши IRP, мы должны зарегистрировать функцию обратного вызова и настроить на нее массив указателей на функции. Код весьма прост:

for(x = 0; x < IRP_MJ_MAXIMUM_FUNCTION; ++x) pDriverObject->MajorFunction[x] = MyCallbackFunc; А вот код функции-заглушки, которая всегда возвращает статусный код STATUS_SUCCESS . В этой функции мы обрабатываем запрос IRP.

NTSTATUS MyCallbackFunk(PDEVICE_OBJECT pDeviceObject, PIRP pIrp) < pIrp->IoStatus.Status = STATUS_SUCCESS; IoCompleteRequest(pIrp, IO_NO_INCREMENT); return pIrp->IoStatus.Status; >Теперь любой запрос к нашему драйверу вызовет функцию-заглушку, которая всегда возвращает STATUS_SUCCESS . Но что, если нам нужно попросить драйвер сделать что-то конкретное, например вызвать определенную функцию? Для этого регистрируем управляющую процедуру:

// Заполним все коды IRP ссылкой на функцию-заглушку for(x = 0; x < IRP_MJ_MAXIMUM_FUNCTION; ++x) pDriverObject->MajorFunction[x] = MyCallbackFunc; // Настроим вызов функции MyCallbackControl на запрос IRP_MJ_DEVICE_CONTROL pDriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = MyCallbackControl; После этого нам нужно получить указатель на стек IRP, который мы будем обрабатывать. Это делается при помощи функции IoGetCurrentIrpStackLocation , на вход которой подается указатель на пакет. Кроме этого, необходимо будет получить от диспетчера ввода-вывода размеры буферов ввода-вывода, чтобы иметь возможность передавать и получать данные от пользовательского приложения. Шаблонный код каркаса обработчика управляющей процедуры:

// Получаем указатель на стек IRP пакета PIO_STACK_LOCATION pIrpSt = IoGetCurrentIrpStackLocation(pIrp); // Получаем размер буфера ввода ULONG InBufLen = IrpStack->Parameters.DeviceIoControl.InputBufferLength; // Получаем размер буфера вывода ULONG OutBufLen = IrpStack->Parameters.DeviceIoControl.OutputBufferLength; // Получаем код управляющей процедуры ULONG CtrlCode = IrpStack->Parameters.DeviceIoControl.IoControlCode; NTSTATUS status = STATUS_SUCCESS; swich(CtrlCode) < case IRP_MY_FUNC: // Здесь код, который будет вызываться управляющей процедурой IRP_MY_FUNC break; default: status = STATUS_INVALID_DEVICE_REQUEST; break; >return status;

Создание устройства драйвера

Скрытие процессов методом DKOM (Direct Kernel Object Manipulation)

Настало время применить полученные знания о драйверах режима ядра на практике для закрепления результата. Сейчас мы напишем драйвер KMDF для скрытия процессов методом прямой манипуляции объектами ядра (DKOM). Как именно мы будем скрывать наши процессы? Информация о процессах хранится в структуре ядра под названием EPROCESS, так что обратимся к ней.

Структура EPROCESS, блок процесса. В ней содержится много информации о процессе, указатели на несколько структур данных, например PEB, структуру KPROCESS, структуры KTHREAD и ETHREAD. Эта структура заполняется исполнительной системой ОС, находится в системном адресном пространстве (kernelmode), как и все связанные структуры, кроме PEB. Все процессы имеют эту структуру.

Чтобы увидеть EPROCESS самостоятельно, достаточно подключиться ядерным отладчиком WinDbg к ядру ОС и ввести команду dt _EPROCESS . После этого ты увидишь что-то вроде этого (смещения отличаются в разных версиях ядер Windows):

typedef struct _LIST_ENTRY < struct _LIST_ENTRY *FLink; struct _LIST_ENTRY *BLink; >LIST_ENTRY, *PLIST_ENTRY; Размеры, типы данных и смещения от начала списка:

Загрузчик драйверов

Загрузить драйвер в ядро можно несколькими способами, самые популярные из них — это загрузка при помощи SCM (Service Control Manager) и при помощи NTAPI-функции NtLoadDriver . Мы выберем первый вариант, как рекомендованный Microsoft и избавляющий нас от многих излишних манипуляций: основную работу за нас сделает именно Service Control Manager. Но для начала зададим нужные привилегии:

setPrivileges("SeLoadDriverPrivilege"); После этого регистрируем драйвер в системе (проверки на успешность вызовов умышленно опускаю для лучшей читаемости кода):

Итак, драйвер установлен и запущен. Теперь самое важное: мы должны послать драйверу управляющий код, который заставит его скрыть нужный нам процесс (он задается переменной pid):

hDvc = CreateFile( TEXT(DEVICE), GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL ); BOOLEAN total = DeviceIoControl( hDevice, IRP_HIDE_PROC, // Наш управляющий код pid, strlen(pid) + 1, retbuf, 200, &bytes_returned, (LPOVERLAPPED) NULL );

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

Итоги


Процессорные архитектуры x86 и x64 имеют четыре кольца защиты, из которых в Windows по факту используются всего два — это ring 3 (режим пользователя) и ring 0 (режим ядра). Бытует мнение, что код режима ядра — самый привилегированный и «ниже» ничего нет. На самом деле архитектура x86/x64 позволяет опускаться еще ниже: это технология виртуализации (hypervisor mode), которая считается кольцом −1 (ring −1), и режим системного управления (System Management Mode, SMM), считающийся кольцом −2 (ring −2), которому доступна память режима ядра и гипервизора.

Итак, мы решили писать собственный драйвер. Начнем с выбора инструментария. Я советую использовать Microsoft Visual Studio, как наиболее user-friendly IDE. Также необходимо будет установить Windows SDK и Windows Driver Kit (WDK) для твоей версии ОС. Кроме того, я крайне рекомендую запастись такими утилитами, как DebugView (просмотр отладочного вывода), DriverView (позволяет получить список всех установленных драйверов) и KmdManager (удобный загрузчик драйверов).

Драйверы в Windows начиная с Vista могут быть как режима пользователя (User-Mode Driver Framework, UMDF), так и режима ядра (Kernel-Mode Driver Framework, KMDF). Более ранние драйверы Windows Driver Model (WDM) появились в Windows 98 и сейчас считаются устаревшими.

Драйверы UMDF имеют намного более ограниченные права, чем KMDF, однако они используются, например, для управления устройствами, подключенными по USB. Помимо ограничений, у них есть очевидные плюсы: их намного проще отлаживать, а ошибка в их написании не вызовет глобальный системный сбой и синий экран смерти. Такие драйверы имеют расширение dll.

Что до драйверов режима ядра (KMDF), то им дозволено куда больше, а расширение файлов, закрепленное за ними, — это sys. В этой статье мы научимся писать простые драйверы режима ядра, напишем драйвер для скрытия процессов методом DKOM (Direct Kernel Object Manipulation) и его загрузчик.

Создание драйвера KMDF

После того как ты создашь проект драйвера, Visual Studio автоматически настроит некоторые параметры. Проект будет компилироваться в бинарный файл в соответствии с тем, какая выбрана подсистема. Наш вариант — это NATIVE, подсистема низкого уровня, как раз для того, чтобы писать драйверы.

Точка входа в драйвер

Строго говоря, точка входа в драйвер может быть любой — мы можем сами ее определить, добавив к параметрам компоновки проекта -entry:[DriverEntry] , где [DriverEntry] — название функции, которую мы хотим сделать стартовой. Если в обычных приложениях основная функция обычно называется main, то в драйверах точку входа принято называть DriverEntry.

Выглядеть это будет так:

Давай пройдемся по параметрам, которые передаются DriverEntry . pDriverObject имеет тип PDRIVER_OBJECT , это значит, что это указатель на структуру DRIVER_OBJECT , которая содержит информацию о нашем драйвере. Мы можем менять некоторые поля этой структуры, тем самым меняя свойства драйвера. Второй параметр имеет тип PUNICODE_STRING , который означает указатель на строку типа UNICODE . Она, в свою очередь, указывает, где в системном реестре хранится информация о нашем драйвере.

WARNING

Любая ошибка в драйвере может вызвать общесистемный сбой и BSOD. Вероятна потеря данных и повреждение системы. Все эксперименты я рекомендую проводить в виртуальной машине.

Interrupt Request Level (IRQL)

IRQL — это своеобразный «приоритет» для драйверов. Чем выше IRQL, тем меньшее число других драйверов будут прерывать выполнение нашего кода. Существует несколько уровней IRQL: Passive, APC, Dispatch и DIRQL. Если открыть документацию MSDN по функциям WinAPI, то можно увидеть примечания, которые регламентируют уровень IRQL, который требуется для обращения к каждой функции. Чем выше этот уровень, тем меньше WinAPI нам доступно для использования. Первые три уровня IRQL используются для синхронизации программных частей ОС, уровень DIRQL считается аппаратным и самым высоким по сравнению с программными уровнями.

Пакеты запроса ввода-вывода (Input/Output Request Packet)

IRP — это запросы, которые поступают к драйверу. Именно при помощи IRP один драйвер может «попросить» сделать что-то другой драйвер либо получить запрос от программы, которая им управляет. IRP используются диспетчером ввода-вывода ОС. Чтобы научить программу воспринимать наши IRP, мы должны зарегистрировать функцию обратного вызова и настроить на нее массив указателей на функции. Код весьма прост:

А вот код функции-заглушки, которая всегда возвращает статусный код STATUS_SUCCESS . В этой функции мы обрабатываем запрос IRP.

Теперь любой запрос к нашему драйверу вызовет функцию-заглушку, которая всегда возвращает STATUS_SUCCESS . Но что, если нам нужно попросить драйвер сделать что-то конкретное, например вызвать определенную функцию? Для этого регистрируем управляющую процедуру:

Здесь мы объявили процедуру с именем IRP_MY_FUNC и ее кодом — 0x801 . Чтобы драйвер ее обработал, мы должны настроить на нее ссылку, создав таким образом дополнительную точку входа в драйвер:

После этого нам нужно получить указатель на стек IRP, который мы будем обрабатывать. Это делается при помощи функции IoGetCurrentIrpStackLocation , на вход которой подается указатель на пакет. Кроме этого, необходимо будет получить от диспетчера ввода-вывода размеры буферов ввода-вывода, чтобы иметь возможность передавать и получать данные от пользовательского приложения. Шаблонный код каркаса обработчика управляющей процедуры:

Продолжение доступно только участникам

Членство в сообществе в течение указанного срока откроет тебе доступ ко ВСЕМ материалам «Хакера», позволит скачивать выпуски в PDF, отключит рекламу на сайте и увеличит личную накопительную скидку! Подробнее


Все мало-мальски серьезные защитные приложения, будь то файрволы или антивирусы, используют собственные модули режима ядра (ring 0), через которые работает большинство их функций: защита процессов от завершения, фильтры различных событий, получение актуальной информации о состоянии сетевого трафика и количестве процессов в системе. Если у программы есть такой драйвер, то пробовать скрываться от нее из режима пользователя (ring 3) бессмысленно. Так же бесполезно пытаться на нее как-то воздействовать. Решение — написать собственный драйвер. В этой статье я покажу, как это делается.

Процессорные архитектуры x86 и x64 имеют четыре кольца защиты, из которых в Windows по факту используются всего два — это ring 3 (режим пользователя) и ring 0 (режим ядра). Бытует мнение, что код режима ядра — самый привилегированный и «ниже» ничего нет. На самом деле архитектура x86/x64 позволяет опускаться еще ниже: это технология виртуализации (hypervisor mode), которая считается кольцом -1 (ring -1), и режим системного управления (System Management Mode, SMM), считающийся кольцом -2 (ring -2), которому доступна память режима ядра и гипервизора.

Итак, мы решили писать собственный драйвер. Начнем с выбора инструментария. Я советую использовать Microsoft Visual Studio, как наиболее user-friendly IDE. Также необходимо будет установить Windows SDK и Windows Driver Kit (WDK) для твоей версии ОС. Кроме того, я крайне рекомендую запастись такими утилитами, как DebugView (просмотр отладочного вывода), DriverView (позволяет получить список всех установленных драйверов) и KmdManager (удобный загрузчик драйверов).

Драйверы в Windows начиная с Vista могут быть как режима пользователя (User-Mode Driver Framework, UMDF), так и режима ядра (Kernel-Mode Driver Framework, KMDF). Более ранние драйверы Windows Driver Model (WDM) появились в Windows 98 и сейчас считаются устаревшими.

Драйверы UMDF имеют намного более ограниченные права, чем KMDF, однако они используются, например, для управления устройствами, подключенными по USB. Помимо ограничений, у них есть очевидные плюсы: их намного проще отлаживать, а ошибка в их написании не вызовет глобальный системный сбой и синий экран смерти. Такие драйверы имеют расширение dll.

Что до драйверов режима ядра (KMDF), то им дозволено куда больше, а расширение файлов, закрепленное за ними, — это sys. В этой статье мы научимся писать простые драйверы режима ядра, напишем драйвер для скрытия процессов методом DKOM (Direct Kernel Object Manipulation) и его загрузчик.

Зачем специалисту по ИБ может понадобиться написать kernel-mode драйвер?

  • Для защиты своей утилиты от действий вредоносов и поиска зловредов, маскирующихся в режиме ядра
  • Для противодействия blue pill и другим руткитам, использующим режим аппаратной виртуализации
  • Для ускорения антивирусной проверки

Создание драйвера KMDF

После того как ты создашь проект драйвера, Visual Studio автоматически настроит некоторые параметры. Проект будет компилироваться в бинарный файл в соответствии с тем, какая выбрана подсистема. Наш вариант — это NATIVE, подсистема низкого уровня, как раз для того, чтобы писать драйверы.

Точка входа в драйвер

Строго говоря, точка входа в драйвер может быть любой — мы можем сами ее определить, добавив к параметрам компоновки проекта -entry:[DriverEntry] , где [DriverEntry] — название функции, которую мы хотим сделать стартовой. Если в обычных приложениях основная функция обычно называется main, то в драйверах точку входа принято называть DriverEntry.

Выглядеть это будет так:

NTSTATUS DriverEntry (PDRIVER_OBJECT pDriverObject, PUNICODE_STRING pRegistryPath);

Давай пройдемся по параметрам, которые передаются DriverEntry . pDriverObject имеет тип PDRIVER_OBJECT , это значит, что это указатель на структуру DRIVER_OBJECT , которая содержит информацию о нашем драйвере. Мы можем менять некоторые поля этой структуры, тем самым меняя свойства драйвера. Второй параметр имеет тип PUNICODE_STRING , который означает указатель на строку типа UNICODE . Она, в свою очередь, указывает, где в системном реестре хранится информация о нашем драйвере.

WARNING

Любая ошибка в драйвере может вызвать общесистемный сбой и BSOD. Вероятна потеря данных и повреждение системы. Все эксперименты я рекомендую проводить в виртуальной машине.

Может ли зловред скрыть свой процесс от KMDF-драйвера?

  • Нет, даже если он сам работает в режиме ядра
  • Да, если он использует апаратную виртуализацию или SMM
  • Да, если зловред был загружен в память до него

Interrupt Request Level (IRQL)

IRQL — это своеобразный «приоритет» для драйверов. Чем выше IRQL, тем меньшее число других драйверов будут прерывать выполнение нашего кода. Существует несколько уровней IRQL: Passive, APC, Dispatch и DIRQL. Если открыть документацию MSDN по функциям WinAPI, то можно увидеть примечания, которые регламентируют уровень IRQL, который требуется для обращения к каждой функции. Чем выше этот уровень, тем меньше WinAPI нам доступно для использования. Первые три уровня IRQL используются для синхронизации программных частей ОС, уровень DIRQL считается аппаратным и самым высоким по сравнению с программными уровнями.

Пакеты запроса ввода-вывода (Input/Output Request Packet)

IRP — это запросы, которые поступают к драйверу. Именно при помощи IRP один драйвер может «попросить» сделать что-то другой драйвер либо получить запрос от программы, которая им управляет. IRP используются диспетчером ввода-вывода ОС. Чтобы научить программу воспринимать наши IRP, мы должны зарегистрировать функцию обратного вызова и настроить на нее массив указателей на функции. Код весьма прост:

А вот код функции-заглушки, которая всегда возвращает статусный код STATUS_SUCCESS . В этой функции мы обрабатываем запрос IRP.

Теперь любой запрос к нашему драйверу вызовет функцию-заглушку, которая всегда возвращает STATUS_SUCCESS . Но что, если нам нужно попросить драйвер сделать что-то конкретное, например вызвать определенную функцию? Для этого регистрируем управляющую процедуру:

Здесь мы объявили процедуру с именем IRP_MY_FUNC и ее кодом — 0x801 . Чтобы драйвер ее обработал, мы должны настроить на нее ссылку, создав таким образом дополнительную точку входа в драйвер:

После этого нам нужно получить указатель на стек IRP, который мы будем обрабатывать. Это делается при помощи функции IoGetCurrentIrpStackLocation , на вход которой подается указатель на пакет. Кроме этого, необходимо будет получить от диспетчера ввода-вывода размеры буферов ввода-вывода, чтобы иметь возможность передавать и получать данные от пользовательского приложения. Шаблонный код каркаса обработчика управляющей процедуры:

Продолжение доступно только подписчикам

Материалы из последних выпусков можно покупать отдельно только через два месяца после публикации. Чтобы продолжить чтение, необходимо купить подписку.

Подпишись на «Хакер» по выгодной цене!

Читайте также: