Пишем драйвер Windows на ассемблере (fb2) читать онлайн
[Настройки текста] [Cбросить фильтры]
[Оглавление]
Пишем драйвер WDM на Ассемблере. (Комментарии к исходникам AsmDrv)
Часть 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, который находится в базовом каталоге проекта.Часть 3. Пишем рыбу.
3.1. Итак, приступим.
Вы можете проследить за последовательностью и содержанием действий, открыв файл 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
; […]
; Создаём логический объект устройства
invoke IoCreateDevice, DriverObject, 0, offset NtDeviceName, FILE_DEVICE_UNKNOWN,0,FALSE,offset DeviceObject;
cmp eax,STATUS_SUCCESS ; Проверим, не было ли ошибки.
jnz @F
; Создаём symbolic link
invoke IoCreateSymbolicLink, offset Win32DeviceName, offset NtDeviceName ; в eax останется код результата
@@:
ret
Итак, только что мы завершили разбор процедуры инициализации.
3.3. Процедура выгрузки.
У нас она реализуется функцией OnUnload. Эта функция производит действия, обратные процедуре инициализации по отношению к связанным объектам: она удаляет символическую связь (вызов IoDeleteSymbolicLink()), и затем логическое устройство, сопоставленное драйверу (IoDeleteDevice()):; Удаляем символическую связь
invoke IoDeleteSymbolicLink, offset Win32DeviceName
; Удаляем логическое устройство
invoke IoDeleteDevice, DeviceObject
3.4. Главная диспетчерская процедура.
Она называется OnDispatch и объявлена какOnDispatch proc near, pDeviceObject:PDEVICE_OBJECT, pIrp:PIRP
Здесь нам важен указатель на структуру с данными запроса pIrp. Данная структура довольно сложна. Вы можете найти её объявление в файле usewdm.inc.
Но нам понадобятся лишь некоторые данные.
Сначала мы должны определить код запроса – он будет один из трёх: IRP_MJ_CREATE, IRP_MJ_CLOSE или IRP_MJ_DEVICE_CONTROL.
Мы получаем этот код из структуры IO_STACK_LOCATION, указатель на которую мы получаем из структуры IRP(в свою очередь, указатель на irp был передан нам в пераметре pIrp):
mov ebx,pIrp
mov eax,(_IRP ptr [ebx]).Tail.Overlay.CurrentStackLocation ; Восстанавливаем указатель на структуру IO_STACK_LOCATION
mov pIrpStack,eax
mov ebx,pIrpStack
mov al,(IO_STACK_LOCATION ptr [ebx]).MajorFunction ; al – Код сообщения
Дальше отрабатываем запросы по-разному.
Для IRP_MJ_CREATE и IRP_MJ_CLOSEобработка фиктивная. Мы просто возвращаем код успеха STATUS_SUCCESS в регистреeax.
Для IRP_MJ_DEVICE_CONTROL мы должны получить данные о длине входного и выходного буферов приложения пользователя, восстановить указатель на промежуточный системный буфер и адрес переменной, в которую будет записана длина информационного пакета, передаваемого из драйвера приложению пользователя.
Мы размещаем эти данные в локальных переменных, чтобы потом вызвать вторичную функцию DeviceIoControlHandler, где и будет выполнена обработка.
Часть 4. Детализация.
Детализация заключается в размещении пользовательского кода во вторичной процедуре обработчика IRP_MJ_DEVICE_CONTROL. Мы будем отрабатывать 2 запроса: IOCTL_USER_REQUEST_1 – отправка переданной строки в отладочный вывод, и IOCTL_USER_REQUEST_2 – перевод литер переданной строки в нижний регистр. Коды запросов объявлены в файле-включении ioctlcodes.inc В принципе, реализация этих вещей довольно проста и не требует комментариев.Часть 5. Компиляция и сборка.
Для компиляции программы следует выполнить командный файл assemble.cmd. Его содержимое:..\..\bin\ml.exe –coff –Fl –c –Foasmdrv.obj main.asm
В результате мы получим листинг main.lst и объектный модульasmdrv.obj.
Дальше мы должны собрать бинарник драйвера из объектного модуля. Для этой цели существует команда link.cmd:
..\..\bin\link.exe @linkcmd.rsp
в файле linkcmd.rsp размещены настройки линкера. Полный список выглядит так:
-MACHINE:IX86
-STACK:32768,4096
-OPT:REF
-OPT:ICF
-INCREMENTAL:NO
-FORCE:MULTIPLE
-RELEASE
-DEFAULTLIB:wdm.lib
-DRIVER
-ALIGN:0x20
-SUBSYSTEM:NATIVE
-BASE:0x10000
-ENTRY:DriverEntry@8
-OUT:disk1\asmdrv.sys
asmdrv.obj
В результате сборки мы получаем файлAsmDrv.sys в подкаталоге Disk1.
Последние комментарии
11 часов 48 минут назад
20 часов 40 минут назад
20 часов 43 минут назад
3 дней 3 часов назад
3 дней 7 часов назад
3 дней 9 часов назад