Статья Создание драйвера под Windows. Часть 1: Введение

MLNK

Mod. Ethical Hacking
Red Team
23.01.2018
560
706
BIT
7

Предыстория

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

После 20 минут поисков по сети я наткнулся на Github Павла Иосифовича (zodiacon - Overview). Личность легендарная в своих кругах, достаточно посмотреть на его репозиторий, публикации и выступления на именитых конференциях. Помимо этого, Павел является автором/соавтором нескольких книг: «Windows Internals» (книга, имеющаяся у меня на полке, которая принесла немало пользы), и «Windows Kernel Programming» 2019 года выпуска (бегло пролистав 11 Глав или 390 страниц, я понял – это то, что нужно!).
Кстати, книгу вы можете купить прямо на сайте Павла

Книгу я приобрёл в бумажной версии, чтобы хоть и немного, но поддержать автора. Безупречное качество, несмотря на то, что она издается в мягком переплете. Хорошие плотные листы формата А4 и качественная краска. (книга без проблем пережила вылитую на нее кружку горячего кофе).
Пока я сидел на балконе и читал четвёртую главу книги, в голову пришла мысль: а почему бы не сделать ряд статей на тему «Программирования драйвера под Windows», так сказать, совместить полезное, с еще более полезным.

И вот я здесь, пишу предысторию.

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

Базовые понятия о внутреннем устройстве Windows (Windows Internals)

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

Следовательно, нам стоит ознакомиться с такими базовыми понятиями как:


Процесс – это объект, который управляет запущенной инстанцией программы.​


Технология, позволяющая создавать закрытые пространства памяти для процессов. В своем роде - это песочница.​


Это сущность, которая содержится внутри процесса и использует для работы ресурсы, выделенные процессом - такие, как виртуальная память. По сути, как раз таки потоки и запускают код.​


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


Это сложно описать словами коротко, проще один раз увидеть картинку.​
В упрощённом виде это выглядит так:​
image1.png

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

Для более глубокого понимания Операционных систем могу посоветовать следующие материалы:
Книги:
  • Таненбаум, Бос: Современные операционные системы
  • Windows Internals 7th edition (Part 1)
Видео:

Настройка рабочего пространства

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

Что нам понадобится:

1. Visual Studio 2017 и старше.​

(Community Version хватает с головой) Также во вкладке „Individual components” необходимо установить
Код:
MSVC v142 - VS 2019 C++ ARM build tools (Latest)
MSVC v142 - VS 2019 C++ ARM Spectre-mitigated libs (Latest)
MSVC v142 - VS 2019 C++ ARM64 build tools (Latest)
MSVC v142 - VS 2019 C++ ARM64 Spectre-mitigated libs (Latest)
MSVC v142 - VS 2019 C++ ARM64EC build tools (Latest - experimental)
MSVC v142 - VS 2019 C++ ARM64EC Spectre-mitigated libs (Latest - experimental)
MSVC v142 - VS 2019 C++ x64/x86 build tools (Latest)
MSVC v142 - VS 2019 C++ x64/x86 Spectre-mitigated libs (Latest)

image10.png


и далее по списку.

2. Windows 10/11 SDK (последней версии)​



image3.png


Тут все просто. Качаем iso файл, монтируем и запускаем установщик.

3. Windows 10/11 Driver Kit (WDK)​



image4.png


В конце установки вам будет предложено установить расширение для Visual Studio. Обязательно установите его!

image11.png


После закрытия окна установки WDK появится установщик Расширения VisualStudio

image2.png


4. Sysinternals Suite

Скачайте и распакуйте в удобное для вас место. Это набор полезных утилит, которые пригодятся для исследования Windows, дебага драйвера и прочего.

image8.png


5. Виртуальная Машина с Windows для тестов.​

Выбор ПО для виртуализации на ваше усмотрение. Я буду использовать «VMware Workstation 16 pro».
Написанные драйверы лучше тестировать именно в виртуальной машине, так как Ядро - ошибок не прощает, и вы будете часто улетать в синий экран смерти.

После того, как все было установлено, пора запускать Visual Studio и начинать писать драйвер.

Создание проекта

Запускаем Visual Studio и создаем новый проект. Создадим пустой проект „Empty WDM Driver“

image21.png


Называем его как душе угодно.

image24.png


И вот он, наш свеженький чистенький проект для нашего первого драйвера.

image27.png


Теперь необходимо создать cpp файл, в котором мы будем писать сам драйвер.

image7.png


image14.png


Вот и все. Настройку системы и среды мы закончили.

image22.png


Первый драйвер

Сначала импортируем ntddk.h эта одна из базовых библиотек для работы с ядром. Больше информации . Как и у любой программы, у драйвера должна быть точка входа DriverEntry, как функция Main в обычной программе. Готовый прототип этой функции выглядит так
C++:
#include <ntddk.h>
NTSTATUS DriverEntry(_In_ PDRIVER_OBJECT DriverObject, _In_ PUNICODE_STRING RegistryPath) {
/*
    In_ это часть SAL(Source Code Ananotation Language) Аннотации не видимы для компилятора,
    но содержат метаданные которые, улучшают анализ и чтение кода.
*/
    return STATUS_SUCCESS;
}
Если мы попробуем собрать наш проект, то получим следующие ошибки и предупреждения.

image15.png


В данном случае пункт 1 является следствием пунктов 2 и 3. Дело в том, что по дефолту в Visual Studio некоторые “предупреждения” расцениваются как ошибки.
Чтобы решить эту проблему есть 2 пути.
  1. Отключить эту фичу в Visual Studio, что делать не рекомендуется. Так как сообщения об ошибках могут быть полезны и сэкономят вам время и нервы в дальнейшем.
  2. Более правильный и классический метод это использовать макросы в c++. Как видно из сообщения с кодом C4100 объекты RegistryPath и DriverObject не упомянуты в теле функции. Подробнее .
Для того, чтобы избавиться от предупреждений, и заставить наш код работать, стоит поместить объекты в макрос UNREFERENCED_PARAMETER(ObjectName)
C++:
include <ntddk.h>

NTSTATUS DriverEntry(_In_ PDRIVER_OBJECT DriverObject, _In_ PUNICODE_STRING RegistryPath) {
    UNREFERENCED_PARAMETER(DriverObject);
    UNREFERENCED_PARAMETER(RegistryPath);
    return STATUS_SUCCESS;
}
Теперь, если пересобрать проект, то мы увидим, что ошибка С220 и предупреждение C4100 пропали, но к ним на смену пришли LNK2019 и LNK1120. Однако это уже не ошибки компиляции - это ошибки линкера. А кто говорил что будет легко?
О том, что такое линкер можно почитать .

Дело в том, что наша функция не представлена в стандартном линкере С++ и вообще она девушка капризная и хочет Си-линкер. Удовлетворим желание дамы и дадим ей то, чего она хочет.

Делается это просто. Перед функцией надо добавить extern "C" так наш линкер будет понимать, что эта функция должна линковаться С-линкером.

Собираем проект заново и вуаля - Драйвер собрался.

image17.png


Что на данный момент умеет наш драйвер? Сейчас это по сути пустышка, которая после загрузки, в случае успеха, вернет нам сообщения об удачном запуске. Давайте заставим его нас поприветствовать и проверим его работоспособность. Выводить сообщения мы будем при помощи функции KdPrint(()); да именно в двойных кавычках.

Итоговый код драйвера будет выглядеть так:
C++:
#include <ntddk.h>

//Указываем линкеру, что DriverEntry должна линковаться С-линкером
extern "C"
NTSTATUS DriverEntry(_In_ PDRIVER_OBJECT DriverObject, _In_ PUNICODE_STRING RegistryPath)
{
    //Убираем варнинг C4100 и связанную с ним ошибку C220
    UNREFERENCED_PARAMETER(DriverObject);
    UNREFERENCED_PARAMETER(RegistryPath);
    //Выводим сообщение
    KdPrint(("Hi Codeby, this is our first driver! Yuhu!\n"));
   
    return STATUS_SUCCESS;
}
Собираем или пересобираем драйвер.

image6.png

Важно! Сборка драйвера должна происходить в режиме Debug!!!
image12.png


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

image5.png


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

Дальнейшие действия нужно проделать в Windows на виртуальной машине.
Чтобы отключить эту проверку подписи, а точенее перевести Windows в тестовый режим, запустите cmd.exe от имени администратора и введите следующую команду bcdedit /set testsigning on.

image13.png


Перезагрузите виртуальную машину.
Если все прошло удачно, в правом нижнем углу вы увидите следующую надпись (2 нижнее строчки могут отличиться в зависимости от версии Windows)

image9.png


Возвращаемся в папку с драйвером и копируем его в виртуальную машину. Теперь нам надо создать службу для запуска драйвер. Открываем консоль от имени администратора и вводим следующую команду:
sc create Name type= kernel binPaht= PATH_TO_DRIVER

в моем случае это выглядит так:

image19.png


Также проверить успешность создания можно через реестр.

image16.png


В той же консоли мы можем попробовать запустить нашу службу.
sc start CodebyDriver

image18.png


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

Создадим новый ключ в реестре и назовем его Debug Print Filter.

image23.png


В качестве значения задаем DWORD с именем DEFAULT и определяем данные для значения как 8.

image25.png


Перезагружаем виртуальную машину.

После перезапуска запускаем DebugView данный инструмент находится в архиве Sysinternals, который мы ранее скачали. Ее можно смело скопировать в виртуальную машину.

Запускаем DebugView от имени Администратора и ставим галочку “Capture Kerner”

image20.png


Capture Win32 и Capture Global Win32 можно снять, если летит много сообщений.

Затем запускаем консоль от имени администратора и запускаем службу загрузки драйвера.

image26.png


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

Спасибо за чтение!

P.S: Я сам только начал изучать тему работы с драйверами. Так что если у вас есть предложения или правки по технической части статьи, прошу отписать в комментарии, чтобы я мог внести изменения в статью.
P.P.S: Как вы могли заметить, писать мы будем преимущественно на С++, посему могу посоветовать отличный канал с уроками по С++ - The Cherno.
 
ну круто конечно. особенно про Павла, кто не слышал про утилиты sysinternals
 
Ну да вообще вещи которые могу выручать очень сильно. На самом деле я думаю много кто о них не знаешь, или знает про базовые типо Process explorer
 
Есть ещё хорошая книжка на русском windows driver foundation.
 
  • Нравится
Реакции: MLNK
Посмотреть вложение 51166
Я так понимаю - ты об этой книге. Просмотрел содержание, выглядит неплохо. Спасибо потом почитаю.
Она самая, к ней в идеале fx2lp брать(но с ней придется много разбираться, это всё-таки не совсем системное программирование и нюансов в ней много, с учётом того что там даже ядро слегка изменено от классической 51 серии), стоимость которой с Алика 450 рублей. Но и так есть что почитать. Кстати, перевода упоминаемой книги нет случайно?
 
  • Нравится
Реакции: MLNK
Она самая, к ней в идеале fx2lp брать(но с ней придется много разбираться, это всё-таки не совсем системное программирование и нюансов в ней много, с учётом того что там даже ядро слегка изменено от классической 51 серии), стоимость которой с Алика 450 рублей. Но и так есть что почитать. Кстати, перевода упоминаемой книги нет случайно?
На сколько я знаю его нет в природе. Книга 2019 года пока не переводилась. Но написано просто, хороший язык без "умничества" так что, владея английским хотя бы на Б1 можно читать редко подсматривая в словарь.
 
  • Нравится
Реакции: rusrst
На какую конкретно книгу?

1625834284967.png

А у меня вот такая ошибка вылезла. А тех ошибок, что вы описали не было вообще.
 
  • Нравится
Реакции: Deniss Matjusevs
На сколько я знаю его нет в природе. Книга 2019 года пока не переводилась. Но написано просто, хороший язык без "умничества" так что, владея английским хотя бы на Б1 можно читать редко подсматривая в словарь.
Не так давно была выпущена на русском . Это не она?
 
У меня почему-то в Win 7 при установке wdk не устанавливается расширение для Visual Studio 2019 с такой ошибкой- VSIXInstaller.NoApplicableSKUsException: Это расширение не может быть установлено ни для одного из установленных продуктов.
В проектах шаблонов у меня нет проекта Empty DVM Driver это из-за семерки?
 
Последнее редактирование:
Оказывается проблема была не в Win 7, версия wdk должна соответствовать версии sdk как написано в статье, после установки расширения шаблон проекта Empty DVM Driver, стал доступен!
 
Мы в соцсетях:

Обучение наступательной кибербезопасности в игровой форме. Начать игру!