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
Итак, только что мы завершили разбор процедуры инициализации.
У нас она реализуется функцией 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, где и будет выполнена обработка.
Детализация заключается в размещении пользовательского кода во вторичной процедуре обработчика 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