• 3.1. Итак, приступим.
  • 3.2. Процедура инициализации
  • 3.3. Процедура выгрузки.
  • 3.4. Главная диспетчерская процедура.
  • Часть 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, где и будет выполнена обработка.








    Главная | В избранное | Наш E-MAIL | Прислать материал | Нашёл ошибку | Наверх