Основы разработки прикладных виртуальных драйверов
Часть 7. Взаимодействие драйвера с 32-разрядным приложением Windows
В 16-разрядных приложениях Windows для получения адреса API-процедуры прикладного виртуального драйвера используется прерывание 2Fh (функция 1684h). Попытка воспользоваться той же процедурой в 32-разрядном приложении Windows приведет к аварийному завершению приложения: в 32-разрядных приложениях недопустим вызов широко используемых в DOS и в 16-разрядных приложениях Windows программных прерываний общего назначения 2Fh, 21h и ряда других. Запрет этот связан с тем, что обработчики этих прерываний, входящие в состав Windows, являются 16-разрядными программами, которые по ходу своего выполнения используют контекст прерванного приложения, в частности работают с его стеком. При этом обращение к стеку часто осуществляется через регистр BP, в который сначала переносится текущее содержимое указателя стека SP. Однако содержимое SP в данной ситуации лишено смысла, поскольку отражает лишь младшую половину истинного смещения в стеке, которое в 32-разрядном приложении находится в расширенном регистре ESP. Первое же обращение к стеку через регистр BP адресует программу в случайное место стека, и дальнейший ход программы неминуемо нарушается.
Другое, более тонкое обстоятельство, не позволяющее использовать прерывания 2Fh и 21h в 32-разрядных приложениях, связано с содержимым дескрипторов этих прерываний, входящих в состав таблицы дескрипторов IDT прерываний защищенного режима. Дескрипторы прерываний 2Fh и 21h описаны как шлюзы 286-го процессора (в отличие от дескрипторов многих других программных и всех аппаратных прерываний, являющихся шлюзами 386-го процессора). Тип дескриптора прерывания влияет на процедуру выполнения команды int. Переход на обработчик прерывания через шлюз 386-го процессора сопровождается сохранением в стеке трех двойных слов (EFLAGS, CS и EIP) и может использоваться как в 16-разрядных, так и в 32-разрядных приложениях. Для шлюзов 286-го процессора команда int действует так же, как и в реальном режиме, сохраняя в стеке 16-разрядные регистры: флаги, CS и IP. Поскольку при этом старшая часть расширенного указателя команд EIP не сохраняется, теряется возможность возврата из обработчика прерывания в вызвавшее это прерывание 32-разрядное приложение.
Для связи 32-разрядных приложений Windows с виртуальными драйверами предусмотрен так называемый IOCTL-интерфейс (от Device In-Out Control). Приложение, вызывая специально предусмотренную функцию Windows DeviceIoControl(), заставляет Windows послать в заданный драйвер системное сообщение W32_DeviceIOControl, которое включает в себя условный код требуемого действия, а также адреса входного и выходного буферов для передачи данных. Драйвер, получив это сообщение, определяет, какое именно действие (если их несколько) ему следует выполнить. При этом драйвер имеет возможность через входной буфер, оговоренный в IOCTL-интерфейсе, получить из приложения необходимую информацию, а через выходной передать в приложение результаты своей работы. Структура клиента, с помощью которой мы передавали данные из приложения в драйвер и обратно, в 32-разрядных приложениях не используется.
Реализация IOCTL-интерфейса в вызывающем приложении Windows включает в себя три этапа: открытие драйвера с получением его дескриптора, посылку, с помощью вызова функции DeviceIОControl(), сообщения W32_DeviceIOControl с указанием кода требуемого действия и закрытие драйвера с освобождением выделенного для связи с ним дескриптора.
Открытие драйвера осуществляется 32-разрядной файловой функцией CreateFile(), которая для этого случая имеет вид:
HANDLE hVxd=CreateFile("\\\\.\\VMYD",0,0,NULL,0,0,NULL);
где VMYD – имя виртуального драйвера (без расширения); hVxd – переменная типа HANDLE, в которую функция возвращает дескриптор открытого драйвера. Если по тем или иным причинам драйвер открыть не удалось, функция CreateFile() вместо дескриптора драйвера (небольшого числа, например 2) возвращает в качестве кода ошибки значение INVALID_HANDLE_VALUE (равное 0xFFFFFFFF).
Вызов функции DeviceIОControl может выглядеть следующим образом:
DeviceIОControl (hVxd,DIOC_FUNC,&inBuf,inSize,&outBuf,outSize,&cb,0);
где:
hVxd – дескриптор виртуального драйвера;
DIOC_FUNC – код требуемой функции (его значение будет анализировать API-процедура драйвера);
inBuf – имя программного буфера, в котором приложение подготавливает данные, передаваемые в драйвер. В документации по IOCTL-интерфейсу буферам обмена данными даются названия «с точки зрения» драйвера. Так, inBuf – это входной для драйвера (и, естественно, выходной для приложения) буфер, а outBuf – буфер, выходной для драйвера и входной для приложения. В вызове DeviceIОControl() указывается адрес буфера, о чем свидетельствует значок амперсанта (&inBuf);
inSize – размер буфера inBuf, то есть число передаваемых в драйвер байтов;
outBuf – имя программного буфера, в который драйвер поместит передаваемые им в приложение данные;
outSize – размер буфера outBuf, то есть число байтов, передаваемых в приложение;
cb – имя переменной длиной 32 бит, в которую после выполнения функции будет возвращена фактическая длина передаваемых в приложение данных.
Закрытие драйвера осуществляется стандартной файловой функцией CloseHandle(hVxd) с указанием закрываемого дескриптора.
Рассмотрим теперь особенности виртуального драйвера, использующего IOCTL-интерфейс. Как уже отмечалось ранее, в блоке описания драйвера указываются адреса трех процедур: процедуры для обработки сообщений Windows, API-процедуры, вызываемой из приложений DOS, и API-процедуры, вызываемой из 16-разрядных приложений защищенного режима. Поскольку IOCTL-интерфейс 32-разрядных приложений реализуется с помощью посылки драйверу сообщения Windows (конкретно, W32_DeviceIOControl), прием этого сообщения необходимо выполнить в процедуре VMyD_Control. API-процедуры для связи с 32-разрядными приложениями не используются; если, однако, драйвер носит универсальный характер и предназначен для взаимодействия со всеми тремя видами приложений (DOS, 16-разрядные Windows и 32-разрядные Windows), то в нем должны быть и соответствующие API-процедуры. В наших примерах эти процедуры будут отсутствовать.
Для диспетчеризации поступившего в драйвер сообщения удобно воспользоваться макросом ControlDispatch:
BeginProc VMyD_Control Control_Dispatch W32_DeviceIOControl,IOControl clc ret EndProc VMyDControl
Здесь IOControl – произвольное имя прикладной процедуры, предназначенной для обработки запросов драйверу. В приведенном фрагменте поступление сообщения W32_DeviceIOControl приведет к передаче управления процедуре IOControl; все остальные сообщения Windows не обрабатываются. В течение своей жизни Windows посылает каждому драйверу, установленному в системе, целый ряд сообщений: Device_Init об инициализации данного драйвера, Sys_VM_Init об инициализации системной виртуальной машины, Sys_VM_Terminate о ее завершении, Begin_PM_App о запуске приложения защищенного режима и др. Если предполагается обрабатывать какие-либо из этих сообщений Windows, то для каждого сообщения в процедуру VMyD_Control следует включить свой макрос Control_Dispatch с символическим кодом сообщения и именем прикладной процедуры для его обработки.
Как уже отмечалось, сообщение W32_DeviceIOControl содержит в себе номер требуемого действия. В процессе открытия драйвера вызовом CreateFile() Windows посылает в открываемый драйвер сообщение W32_DeviceIOControl с кодом действия DIOC_GETVERSION (значение этого кода равно 0), чтобы определить, поддерживает ли драйвер IOCTL-интерфейс. Драйвер в ответ на получение кода действия DIOC_GETVERSION обязан вернуть нулевое значение в регистре EAX, иначе система будет считать, что драйвер не поддерживает IOCTL-интерфейс, и драйвер открыт не будет, а функция CreateFile() вернет код ошибки.
Сообщение W32_DeviceIOControl сопровождается передачей в драйвер через регистр ESI адреса структуры DIOCParams, формируемой системой. Эта структура описана в файле VWIN32.INC (который необходимо подключить к исходному тексту драйвера директивой include); она содержит 12 членов, из которых нас будут интересовать следующие:
VMHandle – дескриптор виртуальной машины;
dwIOControlCode – условный код требуемой функции;
lpvInBuffer – адрес буфера приложения, в котором находятся данные, передаваемые в драйвер (входные для драйвера);
cbInBuffer – размер буфера с входными данными;
lpvOutBuffer – адрес буфера приложения, в который драйвер может переслать свои выходные данные;
cbOutBuffer – размер буфера с выходными данными;
lpoOverlapped – адрес структуры типа OVERLAPPED, используемой в асинхронных операциях.
Драйвер, получив через регистр ESI адрес структуры DIOCParams, может обращаться к ее членам с помощью перечисленных выше обозначений. Мы видим, что в процессе обработки сообщения W32_DeviceIOControl драйверу доступны оба буфера приложения — и с входными, и с выходными данными, и обмен данными с приложением может осуществляться путем прямых пересылок. Разумеется, и драйвер и приложение должны при работе с пересылаемыми данными использовать одинаковые форматы данных.
Рассмотрим программный комплекс, в котором связь драйвера с 32-разрядным приложением Windows организуется посредством IOCTL-интерфейса. Предусмотрим в драйвере обработку трех кодов действия: системного кода DIOC_GETVERSION, поступающего в драйвер в процессе его открытия, и прикладных кодов DIOC_FUNC1 со значением 1 и DIOC_FUNC2 со значением 2 (значения выбраны произвольно).
Получив код действия DIOC_GETVERSION, драйвер сообщает системе о поддержке им IOCTL-интерфейса возвратом EAX=0.
В ответ на получение кода действия DIOC_FUNC1 драйвер переходит на процедуру с именем Func1, которая пересылает в приложение линейные адреса всех трех процедур драйвера. Это чрезвычайно полезная информация, которая позволяет, запустив приложение в отладчике SoftICE, установить точку останова на требуемой процедуре драйвера и исследовать ее выполнение.
В ответ на код DIOC_FUNC2 драйвер получает от системы и пересылает в приложение содержимое дескрипторов сегментов команд и данных приложения: их линейные адреса, пределы и атрибуты. В отличие от рассмотренного ранее примера, где для получения дескриптора мы с помощью относительно трудоемкой процедуры определяли его линейный адрес, в данном примере для этого использована предусмотренная в VMM функция GetDescriptor.
При разработке виртуального драйвера необходимо заранее оговорить формат данных, поступающих в него из приложения и возвращаемых им в приложение. Поскольку все входные данные должны размещаться в едином буфере ввода, а все выходные данные – в едином буфере вывода, их следует упаковать таким образом, чтобы каждый пакет данных имел определенное имя. В рассматриваемом примере эта проблема была решена так, как показано на рис. 1. Выходной буфер для функции DIOC_FUNC1 должен вмещать три линейных адреса; он объявлен как массив из трех длинных слов типа int (напомним, что в 32-разрядных приложениях тип int обозначает 32-битовое данное). Входной буфер для функции DIOC_FUNC2 должен содержать два селектора размером в слово каждый; под них выделено одно длинное слово типа int. Наконец, через выходной буфер для функции DIOC_FUNC2 будут переданы два 8-байтовых дескриптора; каждый дескриптор описывается в приложении с помощью структуры DESCR, а для размещения двух дескрипторов предусмотрен двухэлементный массив таких структур. Приведенные на рис. 1 обозначения OutBuffer[3], InBuffer и sr[2] будут использованы в приложении Windows, написанном на языке Си; с точки зрения драйвера эти буферы представляют собой просто безымянные последовательности байтов, обращение к которым становится возможным по адресам, передаваемым драйверу в структуре DIOCParams.
Рассмотрим сначала текст драйвера.
Виртуальный драйвер, обеспечивающий интерфейс с 32-разрядным приложением Windows
.386p .XLIST include vmm.inc include vwin32.inc ;Поддержка IOCTL-интерфейса .LIST DIOC_FUNC1=1 ;Символические обозначения DIOC_FUNC2=2 ;номеров кодов действия Declare_Virtual_Device VMyD,1,0,VMyD_Control,8000h,Undefined_Init_Order ;======================= VxD_REAL_INIT_SEG ... ;Стандартная процедура инициализации реального режима EndProc VMyD_Real_Init VxD_REAL_INIT_ENDS ;====================== VxD_DATA_SEG CSReg dd 0 ;Ячейки для временного хранения DSReg dd 0 ;селекторов, полученных из приложения VxD_DATA_ENDS ;======================= VxD_CODE_SEG BeginProc VMyD_Control Control_Dispatch W32_DeviceIOControl,IOControl clc ret EndProc VMyD_Control BeginProc IOControl ;Анализ получаемых из приложения (в структуре DIOCParams) кодов действия cmp dword ptr [ESI.dwIOControlCode],DIOC_GETVERSION je GetVer cmp dword ptr [ESI.dwIOControlCode],DIOC_FUNC1 je Func1 cmp dword ptr [ESI.dwIOControlCode],DIOC_FUNC2 je Func2 clc ret GetVer: xor EAX,EAX clc ret Func1: mov EDI,dword ptr [ESI.lpvOutBuffer] mov [EDI],offset32 GetVer mov [EDI+4],offset32 Func1 mov [EDI+8],offset32 Func2 clc ret Func2: mov EDI,dword ptr [ESI.lpvInBuffer] ;Примем параметры из приложения (CS, DS) movzx EAX,word ptr [EDI] mov CSReg,EAX movzx EAX,word ptr [EDI+2] mov DSReg,EAX ;Получим дескриптор CS VMMCall Get_Cur_VM_Handle push 0 ;Флаги push EBX ;VM-дескриптор push CSReg ;Селектор CS VMMCall _GetDescriptor;Получим дескриптор add ESP,3*4 ;Подправим стек ;Передадим весь дескриптор в приложение mov EDI,dword ptr [ESI.lpvOutBuffer] mov [EDI],EAX mov [EDI+4],EDX ;Получим дескриптор DS push 0 ;Флаги push EBX ;VM-дескриптор push DSReg ;Селектор DS VMMCall _GetDescriptor;Получим дескриптор add ESP,3*4 ;Подправим стек ;Передадим весь дескриптор в приложение mov EDI,dword ptr [ESI.lpvOutBuffer] mov [EDI+8],EAX mov [EDI+12],EDX clc ret EndProc IOControl VxD_CODE_ENDS end VMyD_Real_Init
В начале текста драйвера определены числовые значения символических обозначений кодов действия DIOC_FUNC1 и DIOC_FUNC2.
В сегменте данных драйвера предусмотрены две ячейки CSReg и DSReg для временного хранения селекторов CS и DS, полученных из приложения. Размер этих ячеек (двойные слова) обусловлен тем, что, хотя селекторы имеют длину 16 бит, функция VMM GetDescriptor требует передачи ей значения селектора в двойном слове (в котором старшие 16 бит игнорируются).
В управляющей процедуре VMyD_Control с помощью макроса Control_Dispatch указано имя IOControl процедуры драйвера, которая предназначена для обработки сообщения W32_DeviceIOControl. В самой процедуре IOControl выполняется анализ поступившего в драйвер кода действия и переходы, в зависимости от значения этого кода, на соответствующие процедуры драйвера.
Процедура GetVer возвращает 0 в регистре EAX, сообщая тем самым Windows, что данный драйвер поддерживает IOCTL-интерфейс.
В процедуре Func1 сначала в регистр EDI помещается адрес выходного буфера, а затем с помощью косвенной адресации через EDI со смещением в этот выходной буфер, то есть, в сущности, непосредственно в вызывающее приложение, передаются три смещения процедур GetVer, Func1 и Func2. Драйвер работает в плоском 4-гигабайтовом адресном пространстве, и указанные смещения представляют собой линейные адреса этих процедур.
В процедуре Func2 таким же способом (через регистр EDI со смещением) из входного буфера последовательно извлекаются два слова, представляющие собой селекторы сегментов приложения CS и DS. Поскольку, как уже отмечалось, для функции VMM _GetDescriptor входные параметры должны быть двойными словами, получаемые параметры тут же командой movzx преобразуются в двойные слова и сохраняются в предусмотренных для них ячейках сегмента данных драйвера.
В предыдущих примерах мы использовали то обстоятельство, что при переходе в драйвер средствами 16-разрядного интерфейса (функция 1684h прерывания 2Fh) VMM загружает в регистр EBX дескриптор виртуальной машины. При вызове драйвера из 32-разрядного приложения с помощью IOCTL-интерфейса этого не происходит, и поэтому для получения дескриптора VM (который необходим для передачи его в качестве параметра в функцию _GetDescriptor) следует вызвать функцию VMM Get_Cur_VM_Handle, которая возвращает дескриптор в регистре EBX. Другой способ получения дескриптора VM – извлечение его из поля VMHandle структуры DIOCParams.
Для получения дескрипторов сегментов в данном примере использована функция VMM _GetDescriptor. Согласно справочнику DDK формат ее вызова
VMMCall _GetDescriptor, <Selector, VM, flags>
предполагает указание параметров функции в виде конкретных чисел. У нас значения селекторов находятся в ячейках памяти CSReg и DSReg, а дескриптор VM – в регистре EBX. Поэтому нам придется воспользоваться расширенной формой вызова этой функции, в которой ее параметры (от правого к левому) проталкиваются в стек явным образом. После возврата из функции (она возвращает младшую половину дескриптора в регистре EAX, а старшую – в EDX) так же, «вручную», следует восстановить стек, прибавив к содержимому ESP число байтов, равное суммарному размеру параметров функции.
Полученные дескрипторы передаются в приложение точно так же, как в процедуре Func1 передавались линейные адреса функций. Следует подчеркнуть, что, хотя предложения извлечения из структуры DIOCParams адреса выходного буфера в обеих функция выглядят одинаково
mov EDI,dword ptr [ESI.lpvOutBuffer],
в действительности в каждом случае через структуру DIOCParams передаются адреса различных буферов приложения, именно тех, которые указаны в соответствующем вызове драйвера DeviceIОControl.
Теперь перейдем к рассмотрению приложения Windows, предназначенного для работы в паре с описанным выше драйвером. 32-разрядное приложение Windows можно подготовить в среде разработки Borland C++ 4.5, однако удобнее воспользоваться версией 5.0 (или более поздней).
Структура программы проста. В приложении не создается главное окно и, соответственно, отсутствует цикл обработки сообщений. Из изобразительных средств Windows используется только функция MessageBox(), выводящая на экран все полученные из виртуального драйвера данные.
Текст приложения, вызывающего виртуальный драйвер
#define STRICT #include <windows.h> #define DIOC_FUNC1 1 #define DIOC_FUNC2 2 struct DESCR{ WORD lim; //Граница (биты 0...15) WORD base_l; //База, биты 0...15 BYTE base_m; //База, биты 16...23 BYTE attr_1; //Байт атрибутов 1 BYTE attr_2; //Граница (биты 16...19) и атрибуты 2 BYTE base_h; //База, биты 24...31 }; //Главная функция WinMain int WINAPI WinMain(HINSTANCE,HINSTANCE,LPSTR,int){ DESCR sr[2]; //Массив из двух дескрипторов сегментов char txt[300]; //Строка для вывода в окно сообщения int InBuffer=0; //Буфер для упаковки двух сегментов int OutBuffer[3]; //Буфер для вывода из драйвера 3х адресов DWORD cb; //Счетчик char unsigned HLimCS; //Для старших 4х бит границы сегмента //Откроем драйвер HANDLE hVMyD=CreateFile("\\\\.\\VMYD",0,0,NULL,0, FILE_FLAG_DELETE_ON_CLOSE,NULL); //Вызовем функцию 1 (она во входных параметрах не нуждается) DeviceIoControl(hVMyD,DIOC_FUNC1,NULL,0,&OutBuffer,12,&cb,0); //Получили в буфере OutBuffer все три линейных адреса //Подготовим параметры для вызова функции 2 InBuffer=(DWORD)_DS; //DS в младшей половине InBuffer InBuffer=InBuffer<<16; //Сдвинем DS в старшую половину InBuffer|=_CS; //Наложим CS на младшую половину //Вызовем функцию 2 DeviceIoControl(hVMyD,DIOC_FUNC2,&InBuffer,4,sr,16,&cb,0); /*Получили в буфере sr оба дескриптора. Выделим для наглядности старшие 4 бит границ сегментов, находящиеся в байте атрибута 2*/ HLimCS=sr[0].attr_2; HLimCS=HLimCS&0xF; //Оставим только старшие 4 бит границы //Преобразуем все результаты в символьную форму wsprintf(txt,"Адреса функций:\nGetVer=%lXh\nFunc1= %lXh\nFunc2= %lXh\n\n" "Сегмент команд:\nЛинейный адрес=%X%X%Xh" "\nАтрибут 1 = %Xh Атрибут 2 = %Xh\nГраница сегмента = %X%Xh" "\n\nСегмент данных:\nЛинейный адрес = %X%X%Xh" "\nАтрибут 1 = %Xh Атрибут 2 = %Xh\nГраница сегмента = %Xh", OutBuffer[0],OutBuffer[1],OutBuffer[2], sr[0].base_h,sr[0].base_m,sr[0].base_l,sr[0].attr_1, sr[0].attr_2,HLimCS,sr[0].lim, sr[1].base_h,sr[1].base_m,sr[1].base_l,sr[1].attr_1, sr[1].attr_2,sr[1].lim); //Выведем окно сообщения с результатами MessageBox(NULL,txt,"Info",MB_ICONINFORMATION); return 0; }
В начале программы определяются константы DIOC_FUNC1 и DIOC_FUNC2, а также структура DESCR. В функции WinMain() вводятся необходимые переменные; их состав описан выше. Содержательная часть программы включает вызов функции CreateFile(), открывающей драйвер, и двух вызовов DeviceIОControl() с указанием сначала кода действия DIOC_FUNC1, а затем DIOC_FUNC2. Функция DIOC_FUNC1 не требует входных параметров, поэтому на месте адреса входного буфера в числе параметров DeviceIOControl() указан нулевой указатель NULL. Ноль указан также в качестве числа передаваемых байтов (4-й параметр функции).
Перед вызовом Func2 драйвера необходимо подготовить исходные параметры – объединить два селектора, находящиеся в регистрах DS и CS, в одно двойное слово. Для этого содержимое DS помещается в младшую часть буфера InBuffer, сдвигается в нем на 16 разрядов влево с помощью оператора Си << и объединяется с содержимым CS с помощью операции побитового ИЛИ (оператор |).
32-разрядные приложения Windows работают в плоской модели памяти, и граница сегмента команд составляет FFFFFFFFh. В этом случае величина границы указывается в селекторе не в байтах, а в блоках по 4 Кбайт, о чем свидетельствует установленный бит дробности G в байте атрибутов 2. Таким образом, при формировании значения границы следует учитывать не только байты 0 и 1 дескриптора, где содержатся младшие 16 бит границы, но и байт 6, где биты границы 19...16 занимают младшую половину байта.
Для выделения из байта атрибутов младшей половины в программе предусмотрена вспомогательная переменная HLimCS, в которую сначала переносится байт атрибутов 2 дескриптора сегмента команд, а затем с помощью операции побитового умножения И обнуляется ее старшая половина . Эта переменная используются в дальнейшем при выводе на экран значения границы. Для сегмента данных описанная операция не нужна, так как его граница указывается не в блоках по 4 Кбайт, а в байтах.
Оставшаяся часть программы предназначена для преобразования полученных из драйвера числовых данных в символьную форму (функция Windows wsprintf()) и вывода их на экран в виде окна сообщения (функция MessageBox()). На рис. 2 приведен вывод рассмотренной программы.
В качестве примера практического приложения изложенных выше принципов рассмотрим виртуальный драйвер, позволяющий 32-разрядной прикладной программе обращаться к физическому адресному пространству. Предусмотрим в драйвере единственную прикладную функцию, которая возвращает в приложение линейный базовый адрес ПЗУ BIOS (F0000h). Поскольку 32-разрядное приложение работает в плоской модели памяти, этого адреса достаточно, чтобы обратиться в любому байту заданного диапазона физических адресов.
Драйвер, передающий в 32-разрядное приложение линейный адрес ПЗУ BIOS
.386p .XLIST include vmm.inc include vwin32.inc .LIST DIOC_FUNC1=1 Declare_Virtual_Device VMyD,1,0,VMyD_Control,8000h,Undefined_Init_Order ;======================= VxD_REAL_INIT_SEG ... ;Стандартная процедура инициализации реального режима VxD_REAL_INIT_ENDS ;======================= VxD_CODE_SEG BeginProc VMyD_Control Control_Dispatch W32_DeviceIOControl,IOControl clc ret EndProc VMyD_Control BeginProc IOControl cmp dword ptr [ESI.dwIOControlCode],DIOC_GETVERSION je GetVer cmp dword ptr [ESI.dwIOControlCode],DIOC_FUNC1 je Func1 clc ret GetVer: xor EAX,EAX clc ret Func1: mov EDI,dword ptr [ESI.lpvOutBuffer] VMMCall _MapPhysToLinear,<0F0000h,10000h,0> mov [EDI],EAX clc ret EndProc IOControl VxD_CODE_ENDS end VMyD_Real_Init
Для получения линейного адреса, соответствующего заданному физическому, в драйвере вызывается функция VMM MapPhysToLinear. Как уже отмечалось ранее, это фактически макрос, который, при указании его с набором параметров, отправляет их в стек и вызывает соответствующую функцию VMM. В данном варианте параметры задаются в виде числовых значений, что дает возможность использовать макрос в его стандартном виде. Полученный (в регистре EAX) линейный базовый адрес заданного диапазона передается в буфер приложения.
Приложение, работающее с описанным выше драйвером, не имеет каких-либо особенностей. В нем обычным образом открывается драйвер, после чего вызовом функции DeviceIОControl в драйвер посылается код действия 1. Возвращаемый из драйвера линейный адрес поступает непосредственно в переменную OutBuffer, объявленную как указатель на символы. С помощью этого указателя приложение имеет доступ ко всему диапазону адресов BIOS. В качестве демонстрации 8 байтов ПЗУ, начиная с адреса FFFF5h, копируются в символьный массив revision, в конец этого массива записывается ноль, служащий идентификатором конца символьной строки, и вся строка revision выводится в окно сообщений.
32-разрядное приложение Windows, работающее с физической памятью
#define STRICT #include <windows.h> #define DIOC_FUNC1 1 int WINAPI WinMain(HINSTANCE,HINSTANCE,LPSTR,int){ char* OutBuffer; char revision[9]; DWORD cb; //Откроем драйвер HANDLE hVMyD=CreateFile("\\\\.\\VMYD",0,0,NULL,0, FILE_FLAG_DELETE_ON_CLOSE,NULL); //Вызовем функцию 1 DeviceIoControl(hVMyD,DIOC_FUNC1,NULL,0,&OutBuffer,4,&cb,0); //Извлечем из полученых данных дату выпуска BIOS for(int i=0;i<8;i++) revision[i]=OutBuffer[i+0xfff5]; revision[8]=0; //Выведем окно сообщения с результатами MessageBox(NULL,revision,"Info",MB_ICONINFORMATION); return 0; }
КомпьютерПресс 9'2001