Assembler для Windows


Глава 7. Структура и написание драйверов .VXD

Последнюю главу нашей книги я посящаю написанию виртуальных драйверов. Сокращение VxD следует понимать как Virtual Device Driver, "x" относится к слову Device (имеется в виду любое устройство). Вопрос несколько устаревает, так как в Windows 98 принята на вооружение несколько иная концепция драйверов устройств, а Windows NT никогда не поддерживала VxD-драйверы, а использует модель, которая называется kernel mode (режим ядра). Все же программирование VxD остается актуальным по сей день, и знать основные положения должен каждый программирующий на языке ассемблера. В данной главе, отходя от нашей обычной практики, мы будем интенсивно использовать макроопределения, содержащиеся во включаемых (inc) файлах пакета DDK. Тем самым нам удастся поместить весь материал в одну главу. Кроме того, данная глава ориентирована на работу с MASM32.

I

Для программирования VxD-драйверов нам понадобятся файлы VMM.INC, SHELL.INC, VCOND.INC и др., которые можно найти в пакете DDK (Device Development Kit), свободно распространяемом фирмой Microsoft.

При загрузке Windows программа WIN.COM загружает драйвер VMM32.VXD, называемый Менеджером Виртуальной Машины (Virtual Machine Manager), который инициализирует другие VxD-драйверы. Затем VMM переключает процессор в защищенный режим и инициализирует системную виртуальную машину. Кроме уже сказанного, VMM обеспечивает сервис для других драйверов VXD. При запуске DOS-приложения для него выделяется виртуальная машина, так что приложению "кажется", что оно работает с настоящей машиной. При запуске обычных 32-битных приложений они работают в системной виртуальной машине. Приложения, работающие в разных виртуальных машинах, не подозревают о существовании других виртуальных машин. Одним из главных назначений виртуальных драйверов является обеспечение бесконфликтного коллективного доступа к физической аппаратуре для всех одновременно работающих виртуальных машин. Еще одна задача, которую приходится решать виртуальным драйверам, - это осуществление взаимодействия системной виртуальной машиной с другими виртуальными машинами.

Следует отметить, что в Windows существуют и так называемые обычные драйверы, имеющие расширение DRV и структуру DLL-библиотеки, которые экспортируют API-функции для работы с некоторыми внешними устройствами (например, видеоадаптером). Эти драйверы получают доступ к внешним устройствам не напрямую, а посредством VxD-драйверов. Поскольку драйверы VxD работают в нулевом кольце защиты, они имеют доступ к любому участку памяти и напрямую, посредством портов ввод-вывода, обращаются к внешним устройствам.

Все виртуальные драйверы делятся на два класса - статические и динамические. Статические драйверы загружаются во время загрузки системы и остаются в памяти до выхода из системы. Статические драйверы существовали еще в Windows 3.x. Динамические драйверы могут загружаться и выгружаться системой по мере необходимости. Они в основном используются для обслуживания устройств "Plug and Play" и загружаются менеджером конфигурации. Загрузить динамический виртуальный драйвер можно и из обычного приложения при помощи обычных функций работы с файлами.

Имеется три механизма взаимодействия виртуальных драйверов друг с другом.

  1. Управляющие сообщения. Эти сообщения посылает виртуальным драйверам менеджер виртуальной машины. Драйверы также могут обмениваться информацией посредством таких сообщений. Это очень напоминает сообщения Windows и взаимодействия приложений друг с другом и операционной системой через эти сообщения.
  2. Функции обратного вызова. Виртуальный драйвер может позволить вызов функции обратного вызова другому драйверу.
  3. Виртуальные драйверы и менеджер виртуальной машины могут экспортировать определенные функции для вызова из других виртуальных драйверов. Для вызова функции необходимо знать уникальный номер виртуального драйвера, который экспортирует данную функцию и номер этой функции.

Формат виртуальных драйверов называется LE-форматом, сокращенно от "linear executable". Данный формат поддерживает наличие как 16-битного, так и 32-битного кода. Это актуально для статических виртуальных драйверов, которые часть работы (инициализация) выполняют в реальном (незащищенном) режиме. В Windows NT драйверы грузятся в защищенном режиме, по этой причине данный формат в этой операционной системе не используется.

Код и данные в файле LE-формата располагаются в сегментах. Ниже мы опишем возможные классы сегментов.

LCODE - код или данные, заключенные в этом коде, не могут сбрасываться системой на диск (paging).

PCODE - код может временно помещаться на диск.

PDATE - аналогично предыдущему, но здесь хранятся данные.

ICODE - здесь располагается код инициализации, после инициализации сегмент удаляется из памяти.

DBOCODE - используется при запуске драйвера "под отладчиком".

SCODE - статические код и данные. Всегда остается в памяти, даже если драйвер выгружается.

RCODE — содержит 16-битный код для инициализации в реальном режиме.

16ICODE - 16-битный код инициализации в защищенном режиме.

MCODE - содержит строки сообщений.

Перечисленные классы сегментов не задаются непосредственно в тексте программы. Сегменты и классы объявляются в DEF-файле. Файл vmm.inc содержит огромное количество макросов, и нам не избежать пользоваться ими. Это позволит материал, который я хочу изложить, вместить в одну главу.

II

Начнем с содержимого DEF-файла. Содержимое показано на Рис. 4.7. Здесь перечислены сегменты на все случаи жизни. Вам нет необходимости использовать все определенные здесь сегменты. Т.о. образом данный файл можно использовать при создании любого виртуального драйвера. Сегменты, принадлежащие к одному классу, после компоновки объединяются в один сегмент. Менять следует только первую строку, где задается имя драйвера. Отметим, что имя драйвера следует задавать заглавными буквами. Кроме того, в первой строке можно задать тип драйвера. По умолчанию этот тип статический. Если бы мы записали строку VXD VXD1 DYNAMIC, то компоновщик создал бы динамический виртуальный драйвер.

VXD VXD1
SEGMENTS
_LPTEXT CLASS 'LCODE' PRELOAD NONDISCARDABLE
_LTEXT CLASS 'LCODE' PRELOAD NONDISCARDABLE
_LDATA CLASS 'LCODE' PRELOAD NONDISCARDABLE
_TEXT CLASS 'LCODE' PRELOAD NONDISCARDABLE
_DATA CLASS 'LCODE' PRELOAD NONDISCARDABLE
CONST CLASS 'LCODE' PRELOAD NONDISCARDABLE
_TLS CLASS 'LCODE' PRELOAD NONDISCARDABLE
_BSS CLASS 'LCODE' PRELOAD NONDISCARDABLE
_LMGTABLE CLASS 'MCODE' PRELOAD NONDISCARDABLE IOPL
_LMSGDATA CLASS 'MCODE' PRELOAD NONDISCARDABLE IOPL
_IMSGTABLE CLASS 'MCODE' PRELOAD DISCARDABLE IOPL
_IMSGDATA CLASS 'MCODE' PRELOAD DISCARDABLE IOPL
_ITEXT CLASS 'ICODE' DISCARDABLE
_IDATA CLASS 'ICODE' DISCARDABLE
_PTEXT CLASS 'PCODE' NONDISCARDABLE
_PMSGTABLE CLASS 'MCODE' NONDISCARDABLE IOPL
_PMSGDATA CLASS 'MCODE' NONDISCARDABLE IOPL
_PDATA CLASS 'PDATA' NONDISCARDABLE SHARED
_STEXT CLASS 'SCODE' RESIDENT
_SDATA CLASS 'SCODE' RESIDENT
_DBOSTART CLASS 'DBOCODE' PRELOAD NONDISCARDABLE CONFORMING
_DBOCODE CLASS 'DBOCODE' PRELOAD NONDISCARDABLE CONFORMING
_DBODATA CLASS 'DBOCODE' PRELOAD NONDISCARDABLE CONFORMING
_16ICODE CLASS '16ICODE' PRELOAD DISCARDABLE
_RCODE CLASS 'RCODE'
EXPORTS
VXD1_DDB @1

Рис. 4.7.1. Файл VXD.DEF используемый для компоновки виртуального драйвера.

В конце файла указывается единственная экспортируемая переменная — блок описания устройства. DDB - Device Descriptor Block. В этот блок, состоящий из 22 полей, который определен в файле vmm.inc, содержит информацию о виртуальном драйвере (см. ниже).

В файле vmm.inc определены макроимена для всех записанных выше сегментов. Например, для сегмента _LTEXT задано имя VxD_LOCKED_CODE SEG, а для _RCODE имя VxD_REAL_INIT_SEG. Мы будем использовать эти имена в своих программах.

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

ml /coff /c /Сх /DMASM6 /DBLD_COFF /DIS_32 vxd1.asm
link /vxd /def:vxd.def vxd1.obj

Константы MASM6, BLD_COFF, IS_32 используются операторами условной трансляции, которые заданы в файлах vmm.inc и vcond.inc. Отметим, что если Вы используете DEF-файл в том виде, как мы его определили на Рис. 4.7.1, то при компоновке будут появляться сообщения об отсутствии той или иной секции, что смело можно проигнорировать.

Для того чтобы выполнить какие-либо действия, нам часто придется использовать макроопределения из файла vmm.inc. Познакомимся с некоторыми из них.

Во-первых, конечно, это Declare_Virtual_Device. Этот макрос заполняет структуру DDB, несколько облегчая нашу задачу. Вот его полный формат (саму структуру макроса, довольно сложную, можно изучить в файле vmm.inc):

Declare_Virtual_Device Name, MajorVer, MinorVer, CtrlProc,
DeviceID, InitOrder, V86Proc, PMProc, RefData

Разберем параметры.
Name - имя виртуального драйвера. Должно совпадать с именем, определенным в DEF-файле.
MajorVer, MinorVer - младшая и старшая части версии драйвера.
CtrlProc - имя управляющей процедуры драйвера. Эта процедура принимает и обрабатывает сообщения, приходящие на драйвер. Принято формировать имя процедуры из двух частей: имя драйвера и добавка Control. Например, если имя нашего драйвера будет VXD1, то имя процедуры следует взять VXD1_Control.
DeviceID - 16-битный уникальный идентификатор виртуального драйвера. Необходимо указывать в том случае, когда виртуальный драйвер будет предоставлять свой сервис другим драйверам. Кроме того, идентификатор может понадобиться, если ваш драйвер собирается работать в реальном режиме.
InitOrder - порядок загрузки драйвера. Это всего лишь номер. Драйверы, имеющие меньший номер, загружаются в первую очередь. Имеет смысл для статических драйверов.
V86Proc, PMProc - специфицируют адреса функций, которые будет экспортировать драйвер для DOS- и обычных приложений. Если экспортирование не предполагается, параметры следует опустить.
Ref_Data - ссылка на данные, используемые супервизором ввода-вывода. Обычно опускается.

Макросы Begin_control_dispatch и End_control_dispatch используются для определения управляющей процедуры. Выглядит это следующим образом.

Begin control_dispatch VXD1
Control_Dispatch message, function
End_control_dispatch VXD1

Макрос Control_Dispatch определяет, какие сообщения какими функциями будут обрабатываться. Например:

Begin_control_dispatch VXD1
Control_Dispatch INIT_COMPLETE, INIT
End_control_dispatch VXD1

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

.386P
include vmm.inc
include vcond.inc
DECLARE_VIRTUAL_DEVICE VXD1,1,0, VXD1_Control, \
UNDEFINED_DEVICE_ID, UNDEFINED_INIT_ORDER
Begin_control_dispatch VXD1
Control_Dispatch INIT_COMPLETE, INIT
End_control_dispatch VXD1
VxD_LOCKED_CODE_SEG
BeginProc INIT
EndProc INIT
VxD_LOCKED_CODE_ENDS
end

Рис. 4.7.2. "Скелет" виртуального драйвера.

Оттранслируем драйвер на Рис. 4.7.2 согласно ранее указанному алгоритму. Только при компоновке добавьте ключ /MAP, в результате, кроме драйвера VXD1.VXD, в каталоге появится файл VXD1.MAP. Содержимое этого файла мы поместили на Рис. 4.7.3.

VXD1

Timestamp is 3bb5ad7a (Sat Sep 29 17:16:10 2001) Preferred load address is 00400000 Start Length Name Class 0001:00000000 00000050H _LDATA CODE 0001:00000050 00000007H _LTEXT CODE Address Publics by Value Rva+Base Lib:Object 0001:00000000 VXD1_DDB 00401000 vxd1.obj 0001:00000050 VXD1_Control 00401050 f vxd1.obj 0001:00000057 INIT 00401057 f vxd1.obj entry point at 0000:00000000 Static symbols

Рис. 4.7.3. Содержимое файла VXD1.MAP.

Просматривая MAP-файл, обратите внимание, что в объектном файле определень два сегмента: _LDATA и _LTEXT, относящиеся к одному классу.

В заключение отмечу, что вместо стандартного name proc/name endp мы пользуемся макросами BeginProc и EndProc, определение которых также можно обнаружить в VMM.INC.

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

int 20h
DD 00110002H

При этом 11Н - идентификатор виртуального драйвера, 02Н - номер сервиса (индекс в таблице сервисов). Мы не будем непосредственно писать вызов в таком виде, а будем пользоваться макросами VMMCall и VxDCall. Первый - для вызова сервисов VMM, а второй - для сервисов других виртуальных драйверов.

III

Приступим теперь к написанию простого, но работающего статического виртуального драйвера. Такой драйвер представлен на Рис. 4.7.3. Ниже мы обсудем этот драйвер, а также построение статических виртуальных драйверов.

.386P
include vmm.inc
include shell.inc
include vcond.inc
; заполнить структуру DDB
DECLARE_VIRTUAL_DEVICE VXD2,1,0, VXD2_Control, \
UNDEFINED_DEVICE_ID, UNDEFINED_INIT_ORDER
; объявить принимаемые сообщения и процедуры,
; которые их обрабатывают
Begin_control_dispatch VXD2
Control_Dispatch Create_VM, OnVMCreate
Control_Dispatch VM_Terminate2, OnVMClose
End_control_dispatch VXD2
; сегмент для хранения сообщений
VxD_PAGEABLE_DATA_SEG
MsgTitle db "Сообщение драйвера VXD",0
VMCreated db "Создается виртуальная машина",0
VMDestroyed db "Уничтожается виртуальная машина",0
VMFocus db "Смена фокуса виртуальной машины"
VxD_PAGEABLE_DATA_ENDS
; сегмент, содержащий код
VxD_PAGEABLE_CODE_SEG
; процедура - реакция на создание виртуальной машины
BeginProc OnVMCreate
; здесь можно вставить код - реакцию на
; открытие виртуальной машины
MOV ECX,OFFSET VMCreated
CALL MES
RET
EndProc OnVMCreate
; процедура - реакция на закрытие виртуальной машины
BeginProc OnVMClose
; здесь можно вставить код - реакцию на
; закрытие виртуальной машины
MOV ECX,OFFSET VMDestroyed
CALL MES
RET
EndProc OnVMClose
; процедура, выводящая сообщение
MES PROC
; получить дескриптор системной виртуальной машины
VMMCall Get_sys_vm_handle
; дескриптор возвращается в EBX
; в EAX - флаг сообщения
MOV EAX,MB_OK ; адрес заголовка сообщения
MOV EDI, OFFSET MsgTitle
; адрес CallBack функции, в данном случае NULL
XOR ESI,ESI
; ссылка на данные для CallBack-функции
XOR EDX,EDX
; сервисная функция VXD - окно сообщения
VxDCall SHELL_Message
MES ENDP
VxD_PAGEABLE_CODE_ENDS
end

Рис. 4.7.3. Пример простого статического виртуального драйвера.

Как мы уже указывали, статический драйвер загружается при загрузке системы и остается в памяти до завершения работы операционной системы. Удобнее всего загрузить драйвер, указав строку device=имя_драйвера в секции [386enh] файла SYSTEM.INI. Можно также использовать системный реестр: HKEY_LOCAL_MACHINE\System\CurrentControlSet\Services\VxD\key\StaticVxD=pathname.

Первое, однако, более удобно, т.к. в случае ошибки в VXD-драйвере можно исключить его запуск, отредактировав файл SYSTEM.INI в MS DOS.

При установке виртуальных драйверов VMM посылает драйверам следующие три сообщения в порядке списка.

SysCriticalInit - посылается при переключении в защищенный режим, но до разрешения прерываний.

Device_Init - посылается после разрешения прерываний. Именно это сообщение чаще всего используется виртуальными драйверами для проведения начальной инициализации.

Init_Complete - последнее сообщение, посылаемое виртуальным драйверам при загрузке системы.

Получив сообщение и выполнив необходимую работу, драйвер должен сбросить флаг переноса и возвратить управление системе.

Перед тем как произвести выгрузку, статические виртуальные драйверы также получают сообщения.

System_Exit2 - сообщение посылается перед выгрузкой системы. Микропроцессор находится еще в защищенном режиме.

Sys_Critical_Exit2 - следующее сообщение перед выгрузкой системы.

Device_Reboot_Notify2 - сообщают виртуальным драйверам, что система "собирается" выгружаться. Прерывания еще доступны.

Crit_Reboot_Notify2 - аналогичное предыдущему сообщению, но прерывания недоступны.

Обратимся теперь к программе на Рис. 4.7.3. Этот драйвер выводит сообщение при активизации виртуальной машины (например, создание консоли или просто запуск DOS-приложения) и при ее деактивизации. В драйвере мы использовали две сервисные функции: получить дескриптор системной виртуальной машины и вывести сообщение. Рассмотрим эти функции:

Get_sys_vm_handle - получить дескриптор системной виртуальной машины. Причем дескриптор возвращается в регистре EBX.

SHELL_Message — вывести сообщение. Параметры хранятся в регистрах:

  • EBX — дескриптор виртуальной машины.
  • EAX - флаг сообщения, например MB_OK.
  • ECX - 32-битный адрес строки-сообщения.
  • EDI - 32-битный адрес строки-заголовка.
  • ESI - адрес функции - реакции на действие пользователя. Если функции нет, тогда 0.
  • EDX - адрес данных, которые будут посылаться функции.

И еще, при выходе драйвер должен очищать флаг переноса. В нашем случае очистка флага переноса обусловлена правильным выполнением функции SHELL_Message.

IV

В последнем разделе главы мы рассмотрим динамические виртуальные драйверы. Существует три способа загрузки виртуальных динамических драйверов.

  1. Поместить драйвер в каталог \SYSTEM\IOSUBSYS. Драйверы из этого директория загружаются супервизором ввода- вывода.
  2. Использовать сервис VxDLDR. Эту сервисную функцию можно вызывать только из виртуальных драйверов.
  3. Использовать функцию CreateFile.

Именно последним способом загрузки динамических драйверов мы сейчас и займемся. Схема использования динамических виртуальных драйверов следующая:

  1. Открываем драйвер с помощью функции CreateFile. При удачном открытии функция возвращает идентификатор, который затем используется при вызове функций экспортируемых данным драйвером.
  2. Используем функции динамического драйвера посредством вызова функции API DeviceIoControl.
  3. Закрываем драйвер с помощью CloseHandle, при этом он автоматически выгружается из памяти.

Перейдем теперь к программе загрузки динамического драйвера. Программа показана на Рис. 4.7.4. Она загружает драйвер msg.vxd и вызывает сервис 3 драйвера.

; файл FILES1.ASM
.386P
; плоская модель
.MODEL FLAT, stdcall
; константы
STD_INPUT_HANDLE equ -10
FILE_FLAG_DELETE_ON_CLOSE equ 4000000h
; прототипы внешних процедур
EXTERN GetStdHandle@4:NEAR
EXTERN ExitProcess@4:NEAR
EXTERN GetCommandLineA@0:NEAR
EXTERN CreateFileA@28:NEAR
EXTERN CloseHandle@4:NEAR
EXTERN MessageBoxA@16:NEAR
EXTERN ReadConsoleA@20:NEAR
EXTERN DeviceIoControl@32:NEAR
;
;------------------------------------------------
;директивы компоновщику для подключения библиотек
includelib c:\masm32\lib\user32.lib
includelib c:\masm32\lib\kernel32.lib
;------------------------------------------------
;
; сегмент данных
_DATA SEGMENT DWORD PUBLIC USE32 'DATA'
HANDL DWORD ?
HFILE DWORD ?
BUF DB "\\.\msg.vxd",0
CAP DB "Окно сообщения",0
MES DB "Ошибка загрузки драйвера",0
BUFER DB 20 DUP (0)
LENS DWORD ? ; количество выведенных символов
MES1 DB "Вызов сервиса OK!",0
_DATA ENDS
; сегмент кода
_TEXT SEGMENT DWORD PUBLIC USE32 'CODE'
START:
; получить HANDLE ввода
PUSH STD_INPUT_HANDLE
CALL GetStdHandle@4
MOV HANDL,EAX
; открыть файл
PUSH 0
PUSH FILE_FLAG_DELETE_ON_CLOSE
PUSH 0
PUSH 0
PUSH 0
PUSH 0
PUSH OFFSET BUF
CALL CreateFileA@28
CMP EAX,-1
JE _ERR
MOV HFILE,EAX
; вызов сервиса VXD
PUSH 0
PUSH 0
PUSH 0
PUSH 0
PUSH 18
PUSH OFFSET MES1
PUSH 3 ; номер сервиса
PUSH HFILE
CALL DeviceIoControl@32
; ждать клавиши ENTER
PUSH 0
PUSH OFFSET LENS
PUSH 200
PUSH OFFSET BUFER
PUSH HANDL
CALL ReadConsoleA@20
; закрыть и выгрузить драйвер
PUSH HFILE
CALL CloseHandle@4
_EXIT:
; конец работы программы
PUSH 0
CALL ExitProcess@4
_ERR:
PUSH 0 ; MB_OK
PUSH OFFSET CAP
PUSH OFFSET MES
PUSH 0 ; дескриптор окна
CALL MessageBoxA@16
JMP _EXIT
_TEXT ENDS
END START

Рис. 4.7.4. Программа, загружающая, использующая и выгружающая динамический драйвер.

Прокомментируем программу на Рис. 4.7.4. Главное здесь - разобрать работу функции DeviceIoControl. Вот параметры этой функции:

  • 1-й параметр, дескриптор драйвера, полученный через функцию CreateFile.
  • 2-й параметр, номер необходимой Вам операции.
  • 3-й параметр, адрес данных для драйвера.
  • 4-й параметр, длина данных.
  • 5-й параметр, буфер, куда драйвер поместит свои данные.
  • 6-й параметр, длина буфера.
  • 7-й параметр, адрес переменной, куда будет занесено количество байтов, помещенных в буфер драйвером.
  • 8-й параметр, адрес OVERLAPPED-структуры.

Как видите, при вызове мы передаем указатель на строку MES1.

Теперь, я думаю. Вы легко сможете разобраться в программе загрузки. Переходим к самому драйверу. Драйвер выполняет весьма простую функцию. При вызове его сервиса он выводит на экран сообщение. Причем текст сообщения передается вызывающей программой. При вызове функции DeviceIoControl с дескриптором драйвера, на драйвер приходит сообщение w32_deviceIoControl. При этом регистр EBX содержит дескриптор виртуальной машины, ESI указывает на структуру, содержимое которой мы сейчас разберем. При этом надо иметь в виду, что при загрузке драйвера на него тоже приходит это же сообщение, и мы его должны также обработать. Разберем теперь структуру, на которую указывает регистр ESI.

DIOCParams STRUC
Internal1 DD ?
VMHandle DD ?
Internal2 DD ?
dwIoControlCode DD ?
lpvInBuffer DD ?
cbInBuffer DD ?
lpvOutBuffer DD ?
cbOutBuffer DD ?
lpcbBytesReturned DD ?
lpoOverlapped DD ?
hDevice DD ?
tagProcess DD ?
DIOCParams ENDS

Опишем поле структуры:
Internal1 - указатель на структуру Client_Reg_Struc, определяющую регистры вызывающего приложения (см. Рис. 4.7.6. и пояснения к нему).
VMHandle - дескриптор виртуальной машины.
Internal2 - указатель на блок DDB.
dwIoControlCode - номер необходимой операции.
lpvInBuffer - указатель на буфер, содержащий информацию вызывающей программы.
cbInBuffer - количество байт, пересылаемых в буфере.
lpvOutBuffer - указатель на буфер, в который драйвер может поместить информацию для вызывающей программы.
cbOutBuffer - количество байт в буфере.
lpcbBytesReturned - количество возвращаемых байт.
lpoOverlapped - указатель на OverLapped-структуру.
hDevice - дескриптор драйвера, возвращаемый функцией CreateFile.
tagProcess - признак процесса.

.386P
include vmm.inc
include vcond.inc
include vwin32.inc
include shell.inc
DECLARE_VIRTUAL_DEVICE MSG,1,0, MSG_Control,\
UNDEFINED_DEVICE_ID, UNDEFINED_INIT_ORDER
Begin_control_dispatch MSG
; будем обрабатывать сообщение w32_Device!oControl
; процедурой PROC1
Control_Dispatch w32_DeviceIoControl, PROC1
End_control_dispatch MSG
; сегмент данных
VxD_PAGEABLE_DATA_SEG
CAP1 DB "Окно сообщения",0
MES1 DB 50 DUP (0)
VxD_PAGEABLE_DATA_ENDS
; сегмент кода
VxD_PAGEABLE_CODE_SEG
BeginProc PROC1
CMP DWORD PTR [ESI]+12,DIOC_Open
JNE L1
XOR EAX,EAX
JMP _EXIT
L1:
CMP DWORD PTR [ESI]+12,3
JNZ _EXIT
; длина строки
MOV EDI,DWORD PTR [ESI]+16
VMMCall _lstrlen, <EDI>
; копировать в буфер
INC EAX ; длина
VMMCall _lstrcpyn,<OFFSET MES1,EDI,EAX>
; вызвать функцию SHELL_Message
MOV ECX,OFFSET MES1 ; DWORD PTR [ESI]+14
MOV EDI,OFFSET CAP1
MOV EAX,MB_OK + MB_ICONEXCLAMATION
VMMCall Get_Sys_VM_Handle
; адрес CaliBack функции, в данном случае NULL
XOR ESI,ESI
; ссылка на данные для CallBack-функции
XOR EDX,EDX
VxDCall SHELL_Message
XOR EAX,EAX
_EXIT:
RET
EndProc PROC1
VxD_PAGEABLE_CODE_ENDS
end

Рис. 4.7.5. Пример динамического драйвера.

Прокомментируем программу на Рис. 4.7.5.

  1. Как я уже говорил, при загрузке драйвера на него приходит сообщение w32_DeviceIoControl, при этом на структуру указывает регистр ESI. При этом поле dwIoControlCode будет содержать число DIOC_Open, в действительности равное просто 0. Поле dwIoControlCode находится по смещению ESI+12. Убедившись, что там содержится 0, мы возвращаем управление, обнулив предварительно EAX (это необходимо).
  2. При вызове драйвера из программы мы используем номер 3. Убедившись, что в поле dwIoControlCode содержится 3, мы таким образом должны сделать то, что ждет от нас программа.
  3. Собственно задача драйвера - вывести сообщение со строкой, полученной от программы. Известен адрес строки и ее длина. Чтобы продемонстрировать некоторые функции VXD-сервиса, мы еще раз определяем длину строки и копируем ее в буфер, подготовленный в теле драйвера.
  4. Наконец вывод сообщения и возвращение управления с обнулением содержимого EAX.

Сделаем теперь некоторые пояснения к структуре Client_Reg_Struc.

Client_Reg_Struc STRUC
Client_EDI DD ?
Client_ESI DD ?
Client_EBP DD ?
Client_res0 DD ?
Client_EBX DD ?
Client_EDX DD ?
Client_ECX DD ?
Client_EAX DD ?
Client_Error DD ?
Client_EIP DD ?
Client_CS DW ?
Client_res1 DW ?
Client_EFlags DD ?
Client_ESP DD ?
Client_SS DW ?
Client_res2 DW ?
Client_ES DW ?
Client_res3 DW ?
Client_DS DW ?
Client_res4 DW ?
Client_FS DW ?
Client_res5 DW ?
Client_GS DW ?
Client_res6 DW ?
Client_Alt_EIP DD ?
Client_Alt_CS DW ?
Client_res7 DW ?
Client_Alt_EFlags DD ?
Client_Alt_ESP DD ?
Client_Alt_SS DW ?
Client_res8 DW ?
Client_Alt_ES DW ?
Client_res9 DW ?
Client_Alt_DS DW ?
Client_res10 DW ?
Client_Alt_FS DW ?
Client_res11 DW ?
Client_Alt_GS DW ?
Client_res12 DW ?
Client_Reg_Struc ENDS

Рис. 4.7.6. Структура, содержащая значения регистров вызывающего приложения.

Пояснение к Рис. 4.7.6. Структура содержит три типа полей:
Client_resX - зарезервированные поля.
Client_XXX - регистры программы, запускаемой в виртуальной машине.
Client_Alt_XXX - регистры 32-битной программы, запускаемой в системной виртуальной машине.

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

58 При написании главы были использованы материалы с сайта http://win32asm.cjb.net