Часть 1. Цель работы. Постановка задачи.
Признаюсь честно: мне нравится Ассемблер. Вернее, даже не сам Ассемблер, а стиль общения с компьютером через него.
В сети есть несколько примеров создания драйверов виртуальных устройств VxD на Ассемблере.
Но нет ни одного аналогичного примера для драйвера WDM.
Так исправим же эту досадную оплошность!
Создание несложного драйвера с использованием только лишь Ассемблера – довольно трудоёмкое занятие.
По двум причинам:
1) Отсутствие ассемблерных заголовочных файлов для использования драйверного API.
2) Методически трудная отладка драйверов в системе Windows.
Первая причина может быть некритичной. Были бы руки да голова. Ведь известно, что значительная часть заголовков Win32 API была переведена энтузиастами на Ассемблер. И работа эта немалая.
Вторая причина более серьёзна и именно она является сдерживающей. Практически, самым доступным способом отладки является отладочный вывод из самого драйвера. При этом код осторожно дописывается небольшими кусочками.
Однако, теперь у вас будет хороший кусок работающего кода. Мы его сейчас напишем! Изменяя и дополняя его, вы сможете создать свой собственный драйвер, довольно быстро и легко.
Сразу оговорюсь: я не собираюсь подробно объяснять принципы функционирования драйверной системы Windows и растолковывать специальные понятия. Для этого существует специальная литература.
Что нам потребуется? Вот что:
1) Текстовый редактор.
Notepad. Но лучше что-нибудь поудобнее, например, Патриот XP.
2) MS Windows DDK.
DDK содержит почти всё, что требуется для создания драйверов. Но нам важны: справка DDK, Ассемблер masm 6.1, компоновщик Link, также оттуда мы возьмём библиотеки и заголовочные файлы для C (что с ними делать – см. далее).
3) Утилита для визуализации отладочного вывода. Я использую DbgView , который можно взять с сайта www.sysinternals.com
4) Delphi для компиляции тестовой программы.
Но вам необязательно набирать текст с нуля. К счастью, я сделал это до вас :)
Скачайте файл с исходниками проекта AsmDrv и распакуйте его в подкаталог \NTDDK\src\AsmDrv.
(http://progrex.narod.ru/sources/AsmDrv.zip)
Вот, кажется, всё. Можно начинать!
Часть 2. API для WDM драйвера.
Большинство функций драйверного API, которые нас интересуют, предоставляются модулем ntoskrnl.exe.
Для их использования надо сделать следующее:
1) Объявить типы данных и определить константы.
Большинство определений для C находятся в файлах ntdef.h и wdm.h.
2) Объявить прототипы функций, которые мы намерены использовать.
Эти определения для C также находятся в wdm.h
3) Выполнить сборку драйвера с подключением библиотеки wdm.lib
Все три файла (wdm.h, ntdef.h и wdm.lib) входят в состав Windows DDK.
Я перевёл часть заголовков на Ассемблер и поместил их в файл usewdm.inc, который находится в базовом каталоге проекта.
Вы можете проследить за последовательностью и содержанием действий, открыв файл main.asm для просмотра.
Начнём, пожалуй, так:
>.586p ; Процессор Intel Pentium, разрешены инструкции защищённого режима
>.model flat, stdcall ; Здесь всё ясно. Плоская модель адресации и тип вызовов stdcall.
>option casemap:none ; "case-sensitive"
Дальше нужно задействовать файл включений usewdm.inc и библиотеку wdm.lib, чтобы мы смогли использовать драйверный API:
>.include usewdm.inc
>.includelib wdm.lib
Затем размещаем два сегмента – данных и кода:
>.data
>; […]
>.code
>; […]
3.2. Процедура инициализации
Каждый драйвер имеет процедуру инициализации . Эта процедура вызывается системой сразу после загрузки драйвера в память.
У нас такая процедура называется DriverEntry. Объявим её как
>Driver Entry proc near public, DriverObject:PDRIVER_OBJECT, RegistryPath:PUNICODE_STRING
DriverObject – это указатель на служебную структуру, сопоставленную драйверу. Она используется системой для вызова процедур драйвера. Её-то и следует инициализировать – записать в эту структуру адреса соответствующих процедур нашего драйвера.
Наш драйвер довольно прост. Он будет отрабатывать только 4 стандартных запроса:
IRP_MJ_CREATE – Вызов CreateFile() в приложении пользователя для установления связи с драйвером;
IRP_MJ_CLOSE – Вызов CloseHandle() в приложении пользователя для разрыва связи с драйвером;
IRP_MJ_DEVICE_CONTROL – Вызов DeviceIoControl() в приложении пользователя для запроса выполнения какой-либо функции в драйвере.
Все эти три запроса мы адресуем некоей диспетчерской функции OnDispatch. Мы узнаем о ней позже.
Четвёртый запрос – на выгрузку. Об этом пойдёт речь ниже.
А пока необходимо сделать ещё 2 важные вещи – создать логический объект устройства при помощи функции IoCreateDevice() и символическую связь, имя которой пользовательские приложения будут использовать для связи с драйвером при помощи функции CreateFile(). Символическая связь создаётся при помощи вызова IoCreateSymbolicLink():
>; Инициализируем юникодовые строки с именами устройства и линка
>invoke RtlInitUnicodeString, offset NtDeviceName, offset wsNtDeviceName
>invoke RtlInitUnicodeString, offset Win32DeviceName, offset wsWin32DeviceName
>; […]
>; Создаём логический объект устройства