Модераторы: Snowy, bartram, MetalFan, bems, Poseidon, Riply

Поиск:

Ответ в темуСоздание новой темы Создание опроса
> Драйверы Windows режима ядра, как это делается в Delphi 
:(
    Опции темы
ama_kid
Дата 22.8.2008, 12:00 (ссылка) |    (голосов:3) Загрузка ... Загрузка ... Быстрая цитата Цитата


АСУТП-кодер
***


Профиль
Группа: Комодератор
Сообщений: 1460
Регистрация: 5.3.2007
Где: Москва

Репутация: 19
Всего: 95



1. Введение
Данная статья является, по своей сути, продолжением известной статьи Геннадия Порева  "Создание драйверов режима ядра в среде Borland Delphi". Все моё дальнейшее повествование будет предполагать, что читатель по крайней мере ознакомился с представленным в ней материалом, поэтому если это еще не сделано - то сейчас самое время. 
К статье прилагается архив со всеми исходными текстами, описанными и разобранными здесь, при этом хотелось бы упомянуть, что некоторые вещи, которые можно найти в исходниках - здесь не описаны, например логирование действий драйвера в файл на диске, которая мало чем отличается от работы с логами в user-mode, или найденная мной в интернете ручная приближенная реализация cdecl-формата вызова функции DbgPrint, которая на языке С++ имеет переменное число параметров с возможностью вставлять управляющие символы для вывода значений переменных (Delphi этого не умеет). Не описано это просто потому, что нет желания останавливаться на этом, при необходимости  - это можно самому посмотреть и понять. Также, при цитировании кода в тексте статьи я буду опускать те участки или параллельные вызовы функций (в основном, конечно, отладочный вывод), которые не нужны для понимания той части, о которой говорю в данный момент, поэтому не стоит удивляться, если обнаружите, что исходном коде наличествует еще какой-то "мусор" по сравнению со статьей. 
2. Инструментарий 
Прежде всего, хочу немного времени уделить описанию инструментария, который я использую у себя. В принципе, в изначальной статье он был достаточно подробно описан, так что я не буду повторяться, просто на всякий случай пошагово опишу его применение. Средой разработки является любой текстовый редактор (кстати, почитателям компоненто-на-форму-кидательства дальнейшее не рекомендуется к прочтению). Лично я пользуюсь встроенным редактором Far'а, но можно использовать любой удобный и привычный инструмент. В принципе, впоследствии я нашел на просторах интернета "Delphi Driver Development Kit" (где вроде как используется компилятор от 7-й версии дельфи), но к тому моменту уже привык к изначальной среде окружения, поэтому не стал его особо изучать (кроме того на него тявкает мой антивирус, а после замены инфицированных компилятора и линкера на рабочие варианты - мне так и не удалось заставить его работать). Кому надо - может погуглить:
Как правило, для разработки драйвера я копирую целиком  папку, содержащую нижеописанные файлы и работаю уже в ней:
а) Файл ntoskrnl.lib - самый главный файл, содержащий все импортируемые ядром Windows функции. Данный файл не только жизненно необходим для работы линковщика, но также может помочь узнать формат вызова конкретной необходимой функции, о чем я расскажу ниже.
б) dcc32.exe, rlink32.dll, system.dcu и sysinit.dcu - собственно компилятор Delphi3 с сопутствующими файлами.
в) link.exe, mspdb71.dll - линковщик от мелкомягких с необходимой для его работы библиотекой
г) DrvInst.exe - инсталлятор\деинсталлятор драйвера. Исходник данного приложения на языке Delphi можно найти в прилагающемся архиве и в нем мной реализованы два способа установки драйвера: моментальный (по умолчанию) и с перезагрузкой (во многих случаях может потребоваться и это). Второй способ в исходнике закомментирован, но рекомендуется его проверить для того, чтобы понимать, как это делается.
д) Пакетные файлы: _clear.bat (очистка папки), _copy.bat (копирование драйвера в системную папку), _Inst.bat (запуск драйвера), Make.bat и _make.bat (компилирование и сборка драйвера) и _Uninst.bat (очистка системы от следов драйвера) - собственно представляют из себя управление циклом жизни драйвера. Каждый из них вызывает соответствующие команды и исполняемые файлы для реализации своей задачи. Для корректной работы перед началом использования надо в них заменить все названия драйвера на своё собственное (заменив, естесственно, и соответствующие названия файлов). Затем, при наличии в этой же папке файла-исходника драйвера, запускать надо в следующем порядке: 
- _make.bat (если все прошло успешно - в папке появится sys-файл драйвера)
- _copy.bat (этот файл скопируется в системную папку)
- (в данном месте я обычно запускаю утилиту DbgView.exe)
- _Inst.bat (драйвер запустится и начнет свою работу)
- _UnInst.bat (драйвер выгрузится и удалится из системы и текущей папки)
е) Ну и не могу не упомянуть скрытых игроков нашей команды - это предустановленный DDK, MSDN и интернет. 99% информации - оттуда.
Естественно, этот способ организации процесса не является истиной в последней инстанции и можно его оптимизировать, но для того, чтобы дальнейшие описания были понятны и, самое главное, вопроизводимы - примем такую среду за отправную точку.

3. Первые шаги
В данной главе я постараюсь показать, как можно работать в kernel-mode и что делать с доставшимся правом "делать все, что захочется". Исходный код тестового драйвера, рассмотренный здесь, а также user-mode'ного приложения для его тестирования, доступен в прилагающемся архиве в папке под именем MyDrv. 

3.1 Перевод функций DDK
Итак, автор первоначальной статьи  оставил нас наедине с "голым" драйвером  tiny, который может только загружаться и выгружаться. Куда двигаться дальше? Надо сказать, что несмотря на использовании компилятора дельфи, все его RTTL-вкусности типа функций trunc, sqrt, random, равно как классов, объектов и других полезностей в ООП, остаются за бортом. Да что там функции - даже банальное сложение двух строковых переменных недопустимо, потому что для этой несложной с точки зрения программиста операции компилятору приходится генерировать вызовы нескольких функций, которых просто нет в режиме ядра! Ну и особую грусть вызывает невозможность использования стандартных обработчиков исключений try...except...finally... по той же самой причине. Поэтому необходимо посмотреть в сторону того, что разрешено. Здесь на помощь приходят файлы ntddk.h, ntdef.h (файлы описаний из WinDDK) и ntoskrnl.lib (описанный выше). Этих файлов конечно недостаточно для того, чтобы написать какой-то навороченный драйвер файловой системы низкого уровня, но для написания более-менее полезного драйвера в учебных целях их хватит (при этом  h-файлы используются только как справочный материал по объявлениям констант и необходимых нам функций). Для начала работы необходимо явно указать компилятору, какие функции режима ядра можно использовать - делается это путем статической линковки функций файла ntoskrnl.lib. Для тех, кто не очень четко представляет себе этот механизм - поясню сказанное: возьмем, к примеру, функцию IoCreateDevice, которая впоследствие нам еще пригодится. Объявление этой функции в файле ntddk.h выглядит следующим образом:
Код
NTKERNELAPI
NTSTATUS
IoCreateDevice(
    IN PDRIVER_OBJECT DriverObject,
    IN ULONG DeviceExtensionSize,
    IN PUNICODE_STRING DeviceName OPTIONAL,
    IN DEVICE_TYPE DeviceType,
    IN ULONG DeviceCharacteristics,
    IN BOOLEAN Exclusive,
    OUT PDEVICE_OBJECT *DeviceObject);
Все типы, используемые в объявлении этой и других функций можно посмотреть в заголовочных файлах WinDDK, в частности уже упомянутых мной ntdef.h и ntddk.h. Это только выглядят они так страшно, на самом деле большинство из них являются дефайнами для простейших типов byte, integer, word, pointer, а также типа record. NTKERNELAPI - это дефайн для формата вызова функции stdcall, а NTSTATUS - это тип возвращаемого значения (integer). Для того, чтобы перевести объявление данной функции - необходимо узнать "формат" её вызова. Я для этого открываю файл ntoskrnl.lib любым просмотрщиком бинарных файлов (в основном использую встроенный в far), и делаю поиск по наименованию требуемой мне функции. В итоге практически сразу нахожу такой фрагмент:
Цитата
IoCreateStreamFileObjectLite@8__IoCreateStreamFileObjectEx@12 ___imp__IoCreateStreamFileObjectEx@12__IoCreateStreamFileObject@8 ___imp__IoCreateStreamFileObject@8__IoCreateNotificationEvent@8 ___imp__IoCreateNotificationEvent@8__IoCreateFileSpecifyDeviceObjectHint@60 ___imp__IoCreateFileSpecifyDeviceObjectHint@60__IoCreateFile@56___imp__IoCreateFile@56 __IoCreateDriver@8___imp__IoCreateDriver@8__IoCreateDisk@8___imp__IoCreateDisk@8 __IoCreateDevice@28___imp__IoCreateDevice@28__IoCreateController@4 ___imp__IoCreateController@4__IoConnectInterrupt@44 ___imp__IoConnectInterrupt@44__IoCompleteRequest@8___imp__IoCompleteRequest@8 __IoCheckShareAccess@20___imp__IoCheckShareAccess@20 __IoCheckQuotaBufferValidity@12___imp__IoCheckQuotaBufferValidity@12 __IoCheckQuerySetVolumeInformation@12___imp__IoCheckQuerySetVolumeInformation@12 __IoCheckQuerySetFileInformation@12_
Выделенный фрагмент и есть описание имени, по которому она экспортируется из библиотеки ntoskrnl.lib (предшествующий ему символ подчеркивания учитывать следует только в единственном эксземпляре, второй - является непечатным символом и вставляется редактором при наборе статьи автоматически). Поэтому конечное описание этой функции на языке Delphi будет выглядеть следующим образом:
Код
function IoCreateDevice(
                        const DriverObject : PDRIVER_OBJECT;
                        const DeviceExtensionSize : ULong;
                        const DeviceName : PUNICODE_STRING;
                        const DeviceType : DEVICE_TYPE;
                        const DeviceCharacteristics : ULong;
                        const Exclusive : DDKBoolean;
                        var   DeviceObject : PDEVICE_OBJECT
                        ) : NTSTATUS; stdcall; external 'ntoskrnl.lib' name '_IoCreateDevice@28';
Здесь я использовал уже переведенные описания типов, которые можно найти в прилагающемся к статье архиве. Все остальные функции импортируются по образу и подобию...

3.2. Создание объекта "устройство"
Сам по себе драйвер, который умеет только загружаться и выгружаться - бесполезен. Как правило, все драйверы работают с какими-либо устройствами (но не обязательно, и впоследствии я покажу пример этого), поэтому самое первое, что необходимо сделать для того, чтобы труд не был напрасным - создать объект "устройство", к которому можно будет впоследствии обращаться из приложения. Создание устройства в принципе может происходить на любом этапе работы драйвера, но как правило это делают при начальной инициализации в функции DriverEnry:
Код
const
 DEVICE_NAME_STRING = 'TestDriver';

 DOSNameBuffer = '\DosDevices\'+DEVICE_NAME_STRING;
 NameBuffer    = '\Device\'+DEVICE_NAME_STRING;
...
function DriverEntry;
var
 Status        : NTSTATUS;
 DeviceObject  : PDEVICE_OBJECT;
 uniDosString  : UNICODE_STRING;
 uniNameString : UNICODE_STRING;
begin
...
 RtlInitUnicodeString(uniNameString,NameBuffer);
 RtlInitUnicodeString(uniDosString,DOSNameBuffer);
 Status:=IoCreateDevice(DriverObject,0,@uniNameString,
                        FILE_DEVICE_UNKNOWN,0,False,DeviceObject);
 if NT_SUCCESS(Status) Then
   Status:=IoCreateSymbolicLink(@uniDOSString,@uniNameString);
...
end;
В приведенным куске кода производится инициализация объектов-строк с помощью функции RtlInitUnicodeString, далее функцией IoCreateDevice создается устройство, к которому можно обращаться, а затем при помощи функции IoCreateSymbolicLink создается символьная связь между именем этого устройства и именем, находящимся в видимой области для user-mode, для того, чтобы приложение могло получить доступ к этому устройству. Естественно, при выгрузке драйвера необходимо выполнить аналогичные действия, но в обратной последовательности:
Код
procedure ADriverUnload(const DriverObject : PDRIVER_OBJECT); stdcall;
var
 uniDosString  : UNICODE_STRING;
begin
...
 RtlInitUnicodeString(uniDosString,DOSNameBuffer);
 IoDeleteSymbolicLink(@uniDosString);
 IoDeleteDevice(DriverObject.DeviceObject);
...
end;
Устройство создано и  теперь с ним можно работать. Присоединение\отсоединение к  устройству осуществляется обычными функциями CreateFile\CloseHandle. В функцию CreateFile передается параметром наш DEVICE_NAME_STRING, но в определенном формате:
Код
const
 DeviceName     = 'TestDriver';   
...
hDevice := CreateFile(pChar('\\.\'+DeviceName),
                       GENERIC_READ or GENERIC_WRITE,0,PSECURITY_DESCRIPTOR(nil),
                       OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,0);
...
Если необходимо выполнять в драйвере какие-либо действия по событиям присоединения\отсоединения от устройства - то для этого необходимо назначить свои функции-диспетчеры:
Код
function CreateDispatch(const DeviceObject : PDEVICE_OBJECT;
         const Irp : PIRP) : NTSTATUS; stdcall;
begin
 IRP.IoStatus.Status:= STATUS_SUCCESS;
 IRP.IoStatus.Information:=0;
 IoCompleteRequest(IRP,IO_NO_INCREMENT);
 Result:=STATUS_SUCCESS;
end;

function CloseDispatch(const DeviceObject : PDEVICE_OBJECT;
                const Irp : PIRP) : NTSTATUS; stdcall;
begin
 IRP.IoStatus.Status:=STATUS_SUCCESS;
 IRP.IoStatus.Information:=0;
 IoCompleteRequest(IRP,IO_NO_INCREMENT);
 Result:=STATUS_SUCCESS;
end;

function DriverEntry;
begin
...
 DriverObject^.DriverUnload:=ADriverUnload;
 DriverObject^.MajorFunction[IRP_MJ_CREATE]:=CreateDispatch;
 DriverObject^.MajorFunction[IRP_MJ_CLOSE]:=CloseDispatch;
...
end;
После этого, при вызове, допустим, CreateFile для нашего устройства, управление будет передано в нашу функцию-диспетчер CreateDispatch. В данном случае показан просто код, осуществляющий действие по умолчанию.

3.3. Общение с приложением user-mode

Но просто создать\удалить устройство - это еще полдела. Зачастую требуется получить некоторую информацию от процесса, исполняющегося в режиме ядра или передать туда порцию своей информации. Для начала рассмотрим самый простой способ - организацию функций-диспетчеров запросов IRP_MJ_READ ($03) и IRP_MJ_WRITE ($04), которые приходят драйверу при выполнении команд ReadFile и WriteFile соответственно. Для начала - необходимо назначить эти самые диспетчеры в функции DriverEntry:
Код
function DriverEntry;
begin
...
 DriverObject^.MajorFunction[IRP_MJ_READ]:=ReadWriteDispatch;
 DriverObject^.MajorFunction[IRP_MJ_WRITE]:=ReadWriteDispatch;
...
end;
...
function ReadWriteDispatch(
         const DeviceObject : PDEVICE_OBJECT;
         const Irp : PIRP) : NTSTATUS; stdcall;
var
 pIrp : PIO_STACK_LOCATION;
begin
...
 pIrp := Irp^.Tail.Overlay.IRPStruct2.IRPUnion2.CurrentStackLocation;
 case pIrp^.MajorFunction of 
   IRP_MJ_READ:  Result:=ReadDispatch(DeviceObject,Irp);
   IRP_MJ_WRITE: Result:=WriteDispatch(DeviceObject,Irp);
   else begin 
         IoCompleteRequest(IRP,IO_NO_INCREMENT);
         Result:=STATUS_SUCCESS;
        end;  
 end;
end;
...
function ReadDispatch(
         const DeviceObject : PDEVICE_OBJECT;
         const Irp : PIRP) : NTSTATUS; stdcall;
begin
...
 IRP.IoStatus.Status:=STATUS_SUCCESS;
 IRP.IoStatus.Information:=ulSize;
 IoCompleteRequest(IRP,IO_NO_INCREMENT);
 Result:=STATUS_SUCCESS;
end;

function WriteDispatch(
         const DeviceObject : PDEVICE_OBJECT;
         const Irp : PIRP) : NTSTATUS; stdcall;
begin
...
 IRP.IoStatus.Status:=STATUS_SUCCESS;
 IRP.IoStatus.Information:=ulSize;
 IoCompleteRequest(IRP,IO_NO_INCREMENT);
 Result:=STATUS_SUCCESS;
end;
Как вариант, можно назначить каждому типу запроса сразу свой диспетчер, в итоге будет равноценно. 
Теперь рассмотрим методы приема\передачи буфера данных. При создании устройства можно выбрать тип метода передачи этого буфера: буферизированный (DO_BUFFERED_IO = $00000004) или прямой (DO_DIRECT_IO = $00000010) путем установки поля Flags объекта-устройств:
Код
DeviceObject^.Flags := DeviceObject^.Flags or (DO_BUFFERED_IO);
Если  флаги не были установлены - то используется третий, "никакой" (neither) метод передачи буфера. Сразу скажу, что я лично практически во всех примерах использую именно такой (neither) метод несмотря на то, что он является относительно опасным по сравнению с остальными (из-за того, что пользовательское приложение может передать заведомо некорректный указатель, а т.к. диспетчер ввода\вывода не в этом случае проверяет валидность адресов, то эта проблема ложится на разработчика). Останавливаться на этом подробно не буду, но понимать различия между способами передачи буфера нужно обязательно. Основное отличие - это его расположение при обработке запросов чтения-записи (т.е. откуда брать полученную информацию и куда её складывать, а также её размеры). Подробно обо всём этом можно почитать в любой книжке по драйверам или в интернете (в частности, MSDN или WASM.RU). Приведу пример обработки запросов чтения\записи:
а)  Запрос чтения (ReadFile):
Сам запрос от приложения-инициатора выглядит следующим образом:
Код
...
ReadFile(hDevice,buf,STRSIZE,actualread,nil);
...
Где buf: array[1..STRSIZE]of byte; - буфер для принятых данных, STRSIZE - его максимальный размер, actualread - переменная для получения количества считанных данных.
Теперь рассмотрим обработку этого запроса в нашем тестовом драйвере:
Код
function ReadDispatch(
         const DeviceObject : PDEVICE_OBJECT;
         const Irp : PIRP) : NTSTATUS; stdcall;
const ReadBuffer='Buffer For ReadFile routine';
var
 ulSize:ULong;
 pBuffer:Pointer;
 pIrp : PIO_STACK_LOCATION;
 ans:ANSI_STRING;
begin
...
 pIrp := Irp^.Tail.Overlay.IRPStruct2.IRPUnion2.CurrentStackLocation;
 ulSize := pIrp^.Parameters.Read.Length;
 if ulSize>=sizeof(ReadBuffer) then ulSize:=sizeof(ReadBuffer)-1;
 ans:=BCAnsiStringImply(ReadBuffer);
 pBuffer := Irp^.UserBuffer;
 if pBuffer<>nil then RtlMoveMemory(pBuffer,ans.Buffer,ulSize);

 IRP.IoStatus.Status:=STATUS_SUCCESS;
 IRP.IoStatus.Information:=ulSize;
 IoCompleteRequest(IRP,IO_NO_INCREMENT);
 Result:=STATUS_SUCCESS;
end;
Здесь стоит отметить следующие моменты: максимальная длина буфера, переданная нам в запросе (STR_SIZE) считывается из pIrp^.Parameters.Read.Length, Irp^.UserBuffer  указывает напрямую на область памяти виртуального адресного пространства процесса-инициатора запроса (нашего приложения), в которую мы копируем данные, а количество действительно обработанных данных указывается в IRP.IoStatus.Information. Ну а при получении STATUS_SUCCESS (const STATUS_SUCCESS = $00000000) менеджер запросов автоматически завершит за нас остальную работу (напоминаю, что при использовании буферизированного или прямого способа ввода-вывода будет изменяться расположение указателя на буфер данных, к примеру при использовании буферизированного метода - он будет располагаться не в Irp^.UserBuffer, а в Irp^.AssociatedIrp.SystemBuffer, который указывает уже не на область памяти процесса-инициатора, а на промежуточный системный буфер, организованный менежером запросов внутри ядра системы и который по завершению запроса будет скопирован в буфер вызывающего процесса).
б)  Запрос записи (WriteFile):
Запрос от приложения-инициатора:
Код
...
WriteFile(hDevice,buf,Len,NumWritten,nil);
...
Где buf: array[1..STRSIZE]of byte; - буфер для принятых данных, Len - его размер, NumWritten - переменная для получения количества реально записанных данных.
В драйвере:
Код
function WriteDispatch(
         const DeviceObject : PDEVICE_OBJECT;
         const Irp : PIRP) : NTSTATUS; stdcall;
var
 ulSize:ULong;
 pIrp : PIO_STACK_LOCATION;
 pBuffer:Pointer;
 ans:ANSI_STRING;
begin
...
 pIrp := Irp^.Tail.Overlay.IRPStruct2.IRPUnion2.CurrentStackLocation;
 ulSize := pIrp^.Parameters.Write.Length;
 pBuffer := Irp^.UserBuffer;
 if pBuffer<>nil then 
  begin 
   ans:=BCAnsiStringAllocate(ulSize);
   RtlMoveMemory(ans.Buffer,pBuffer,ulSize);
   ...
   BCAnsiStringFree(ans);
  end;
 IRP.IoStatus.Status:=STATUS_SUCCESS;
 IRP.IoStatus.Information:=ulSize;
 IoCompleteRequest(IRP,IO_NO_INCREMENT);
 Result:=STATUS_SUCCESS;
end;
Здесь все аналогично предыдущему случаю, за исключением направления записи данных, думаю, особого пояснения ничего не требует. Функции BCAnsiStringImply и BCAnsiStringAllocate - это просто вспомогательные функции-обертки над аналогичными функциями ядра, которые можно найти в исходниках драйвера.

Это сообщение отредактировал(а) ama_kid - 8.9.2008, 22:24

Присоединённый файл ( Кол-во скачиваний: 452 )
Присоединённый файл  _Release.part01.rar 781,25 Kb


--------------------
самурай без меча подобен самураю с мечом, но только без меча 
PM MAIL   Вверх
ama_kid
Дата 22.8.2008, 12:13 (ссылка) |    (голосов:3) Загрузка ... Загрузка ... Быстрая цитата Цитата


АСУТП-кодер
***


Профиль
Группа: Комодератор
Сообщений: 1460
Регистрация: 5.3.2007
Где: Москва

Репутация: 19
Всего: 95



3.4 Продвинутое общение
Рассмотренный нами в предыдущей главе метод общения драйвера с внешним миром, конечно, прост и понятен, но что делать, если задача требует не просто считать\записать данные, а произвести несколько более сложные действия, например, передать управляющие команды, обеспечить диалог драйвер-приложение или выполнить что-либо ещё специфическое для данного драйвера? Можно, конечно, при желании все организовать через ReadFile\WriteFile, но есть более универсальное решение -  функция DeviceIoControl. Для примера, рассмотрим следующие функции:
Код
Procedure CapsLockOn;assembler;
asm
 mov ax,0edh
 out 60h,ax
 mov ax,4
 out 60h,ax
end;

function InPort(port:word):byte;assembler;stdcall;
asm
 mov dx,port
 in al,dx
 mov Result,al
end;
Эти функции еще со времен DOS позволяли зажечь индикатор CapsLock на клавиатуре и считать последние значения клавиатурного порта 60h соответственно. Но в современной реальности, если попробовать вызвать их напрямую из приложения Delphi - мы получим исключение "Privileged Instruction" благодаря тому, что в системе NT давным-давно обычным приложениям запрещен доступ к портам ввода-вывода. С помощью тестового драйвера я покажу, как можно обойти ограничение системы на такие функции (написанные, напоминаю, в приложении режима user-mode и располагающиеся в его виртуальном адресном пространстве). Усилий для этого требуется совсем минимум - надо всего лишь передать в драйвер адреса этих функций и выполнить их непосредственно в нём. Сделаем это с помощью управляющих кодов и упомянутой нами функции DeviceIoControl:
Код
const
 IOCTRL_CODE_MESSAGE_RUN_ROUTINE_1 = (1 shl 3) or METHOD_NEITHER;
 IOCTRL_CODE_MESSAGE_RUN_ROUTINE_2 = (2 shl 3) or METHOD_NEITHER;
...
procedure IoControlDriver_CapsOn;
var
 bytesret:cardinal;
 p:pointer;
 i:integer;
begin
 p:=@i;
 Integer(p^):=Integer(@CapsLockOn);
 DeviceIoControl(hDevice,IOCTRL_CODE_MESSAGE_RUN_ROUTINE_1,
                 p,4,
                 nil,0,bytesret,nil);
...
end;

procedure IoControlDriver_KbPortIn;
var
 bytesret:cardinal;
 p:pointer;
 i:integer;
 w:integer;
begin
 p:=@i;
 Integer(p^):=Integer(@InPort);
 w:=0;
 DeviceIoControl(hDevice,IOCTRL_CODE_MESSAGE_RUN_ROUTINE_2,
                 p,4,
                 @w,4,bytesret,nil);
 // После этого вызова bytesret содержит количество возвращенных байт, 
 // w - возвращенное значение:
...
end;
Здесь приложение передает в драйвер адреса требуемых функций для того, чтобы он их исполнил, и во втором случае - получает некоторый ответ. Особых пояснений, пожалуй, требует только формат объявлений констант IOCTRL_CODE_MESSAGE_RUN_ROUTINE_1 и IOCTRL_CODE_MESSAGE_RUN_ROUTINE_2. Дело в том, что, как я уже упоминал, для обработки функций ReadFile и WriteFile способ передачи буфера (BUFFERED_IO|DIRECT_IO|NEITHER) - устанавливается путем формирования поля Flags объекта DeviceObject. Для обработки же ввода-вывода с помощью функции DeviceIoControl способ передачи буфера указывается индивидуально для каждого вызова путем специального формирования управляющего двойного  слова, передаваемого вторым параметром. Его формат представлен на картинке
user posted image
Расшифровка полей управляющего слова следующая:
1.    Поле DeviceType определяет тип объекта-устройства, которому предназначен запрос. Это тот самый тип устройства, который передается функции IoCreateDevice()
при создании устройства.
2.    Поле Function идентифицирует конкретные действия, которые должно предпринять устройство при получении запроса. Значения поля Function должны быть уникальны внутри устройства.
3.    Поле Method указывает метод передачи буферов данных. Для задания этого поля можно использовать константы:
      METHOD_BUFFERED         = 0;
      METHOD_IN_DIRECT         = 1;
      METHOD_OUT_DIRECT     = 2;
      METHOD_NEITHER            = 3;
4.    Поле Access указывает тип доступа, который должен был быть запрошен (и предоставлен) при открытии объекта-файла, для которого передается данный код Управления вводом/выводом. Возможные значения: FILE_ANY_ACCESS = $0000, FILE_READ_ACCESS = $0001 и  FILE_WRITE_ACCESS = $0002.
Для нас самое важное значение имеют биты 0-2 (в которых указывается метод передачи буфера) и 3-12 (в которых указывается передаваемый код управления драйвером). Остальные байты можно формировать по желанию и необходимости, в нашем случае такой необходимости нет. 
Теперь рассмотрим обработку этого запроса со стороны драйвера:
Для начала необходимо назначить функцию-обработчик данного запроса. Это выполняется, как обычно, в DriverEntry:
Код
...
DriverObject^.MajorFunction[IRP_MJ_DEVICE_CONTROL]:=DeviceIOControlDispatch;
...
Ну а сам обработчик выглядит следующим образом:
Код
const
 IOCTRL_RUN_ROUTINE_1 = (1 shl 3) or 3;
 IOCTRL_RUN_ROUTINE_2 = (2 shl 3) or 3;
type
 TUserRoutine1 = procedure;
 TUserRoutine2 = function (port:word):byte;stdcall;
...
function DeviceIOControlDispatch(
         const DeviceObject : PDEVICE_OBJECT;
         const Irp : PIRP) : NTSTATUS; stdcall;
var
 pIrp : PIO_STACK_LOCATION;
 iBuffer:pointer;
 inputBufLength:ULONG;
 outputBufLength:ULONG;
 FunctionCode:ULONG; 
 RetStr:ANSI_STRING;
 RetCode:integer;
begin
 pIrp := Irp^.Tail.Overlay.IRPStruct2.IRPUnion2.CurrentStackLocation;
 inputBufLength  := pIrp^.Parameters.DeviceIoControl.InputBufferLength;
 outputBufLength := pIrp^.Parameters.DeviceIoControl.OutputBufferLength;

 iBuffer:=pIrp^.Parameters.DeviceIoControl.Type3InputBuffer;

 FunctionCode := pIrp^.Parameters.DeviceIoControl.IoControlCode;
     
 case FunctionCode of 
   IOCTRL_RUN_ROUTINE_1:  
            begin
             ...
             TUserRoutine1(iBuffer^);
            end;
   IOCTRL_RUN_ROUTINE_2: 
            begin
             ...
             RetCode:=TUserRoutine2(iBuffer^)($60);
             RtlMoveMemory(Irp^.UserBuffer,@RetCode,4);
             ...
            end;
 end;
 IRP.IoStatus.Status:=STATUS_SUCCESS;
 IRP.IoStatus.Information:=4;
 IoCompleteRequest(IRP,IO_NO_INCREMENT);
 Result:=STATUS_SUCCESS;
end;
Как видно, здесь в зависимости от передаваемого управляющего кода - выполняется приведение переданного указателя к соответствующему типу-функции и последующее его исполнение. В итоге запрещенный код уровня пользователя благополучно выполнится. Но на такие функции, естесственно, накладывается ограничение - нельзя использовать функции, которых нет в режиме ядра, да и вообще следует быть осторожным. Я не стал здесь делать проверку типа передачи буфера (хотя, конечно, надо, потому что если драйвер начнет читать или писать в буфер, предполагая один тип передачи данных, а на самом деле окажется другой - то естесственным продолжением окажется BSOD). При желании - это можно сделать самостоятельно. Следует отметить, что расположение указателей буферов ввода-вывода для DeviceIoControl отличается от расположения буферов для обработки ReadFile\WriteFile - на этот раз они находятся в структуре Parameters.DeviceIoControl текущего IRP-пакета, но самого принципа это не меняет - знать расположение буфера в зависимости от типа передачи нужно обязательно.

4. Из жизни
Предыдущая глава была посвящена решению проблем, выдуманных нами фактически на пустом месте. Это не сильно интересно, поэтому в данной главе я хотел бы разобрать пару примеров, основа которых была мной написана для реальных проектов. Надеюсь, они помогут в более быстром освоении темы. Оба примера будут рассмотрены, естественно, на языке Delphi, но если кому интересно - то в соответствующих папках в архиве можно найти и C++ - варианты этих драйверов, которые собираются из-под DDKXP.

4.1 Пример №1: Перехват клавиатуры
Рассмотрим частный пример распространенной проблемы глобального перехвата клавиатуры - отлов сочетания CAD. В операционных системах семейства NT это сочетание является единственным, которое не отлавливается из user-mode (исключение составляет способ отлова через MSGINA.dll, но он обладает своими недостатками), поэтому я и взял его для примера, но в принципе данный подход применим для отлова любого требуемого сочетания клавиш. Основной идеей является установка драйвера-фильтра клавиатуры верхнего уровня. Что такое драйвер-фильтр и чем верхний уровень отличается от нижнего я объяснять не буду, об этом можно почитать в соответствующих источниках, но суть сводится к тому, что мы "посадим" наш драйвер поверх устройства основного драйвера клавиатуры, оставляя всю рутинную работу по работе с портами ввода-вывода и первичной обработке результатов ему, а сами будем только фильтровать запросы, проходящие между режимом пользователя и этим драйвером. Данный пример был написан мною по мотивам и на основе широко известного примера Ctrl2Cap, но с некоторыми изменениями, на которых я остановлюсь подробнее. Исходный код драйвера находится в прилагающемся архиве в папке под названием KbdIntcpt.
Итак, рассмотрим функцию DriverEntry: первым делом мы создаем объект "устройство", как было показано ранее, с помощью функций IoCreateDevice и IoCreateSymbolicLink. 
Код
...
DriverObject^.DriverUnload := DriverUnload;
 
RtlInitUnicodeString(deviceNameUnicodeString, NT_DEVICE_NAME);
Status := IoCreateDevice(DriverObject,sizeof (DEVICE_EXTENSION), 
    @deviceNameUnicodeString,FILE_DEVICE_UNKNOWN,
                     0,false,deviceObject);
RtlInitUnicodeString(deviceLinkUnicodeString, DOS_DEVICE_NAME);
Status:=IoCreateSymbolicLink(@deviceLinkUnicodeString,@deviceNameUnicodeString);
...
Создание символьной связи, в данном случае достаточно бесполезно, потому что я не осуществляю взаимодействия с этим драйвером, но если кому-нибудь понадобится - в предыдущей главе я описал как это можно сделать. Далее идет важный код - осуществляется присоединения фильтра в стек клавиатуры:
Код
extension := PDEVICE_EXTENSION(deviceObject^.DeviceExtension);
 extension^.DriverObject := DriverObject;

 RtlInitUnicodeString(kbdname,'\Device\KeyboardClass0');
 Status := IoAttachDevice(deviceObject, @kbdname,extension^.DeviceObject);
 if (extension^.DeviceObject<>nil)and(NT_SUCCESS(Status)) then
   begin
    deviceObject^.Flags := deviceObject^.Flags or DO_BUFFERED_IO;
    deviceObject^.Flags := deviceObject^.Flags and (not DO_DEVICE_INITIALIZING);
   end;
Поясню немного: сначала мы получаем указатель на область "расширений" нашего устройства, а затем с помощью функции IoAttachDevice помещаем наше устройство в стек устройств, которые по цепочке будут получать IRP-пакеты от драйвера клавиатуры. '\Device\KeyboardClass0' - это соответственно имя основного устройства, которое и является целью для нашего драйвера. Обратите внимание, что тип передачи данных указывается DO_BUFFERED_IO.
Затем идёт кусок кода по созданию объекта-евента:
Код
const
 EVENT_NAME        = '\BaseNamedObjects\'+DRIVER_NAME+'Event'; 
var
 UnloadEvent        : PKEVENT  = nil;    
 UnloadEventHandle    : THandle   = 0;    
 EventName        : UNICODE_STRING;    
...
RtlInitUnicodeString(EventName, EVENT_NAME);
UnloadEvent := IoCreateNotificationEvent(@EventName, @UnloadEventHandle);
Назначение этого кода и объекта я объясню чуть ниже, сам процесс создания и работы с евентами в режиме ядра мало чем отличается от аналогичных действий в режиме user-mode. Ну и в конце необходимо назначить обработчики-диспетчеры для запросов:
Код
for i:=0 to IRP_MJ_MAXIMUM_FUNCTION do 
   DriverObject^.MajorFunction[i]         := DriverDispatchGeneral;
 DriverObject^.MajorFunction[IRP_MJ_READ]   := DriverRead;
 DriverObject^.MajorFunction[IRP_MJ_CREATE] := DriverCreate;
 DriverObject^.MajorFunction[IRP_MJ_CLOSE]  := DriverClose;
В конце работы драйвера необходимо выполнить обратные действия:
Код
procedure DriverUnload(const DriverObject : PDRIVER_OBJECT); stdcall;
var
 deviceLinkUnicodeString    : UNICODE_STRING;
 extension            : PDEVICE_EXTENSION;
begin
 UnloadProcess:=true;
 extension := DriverObject.DeviceObject^.DeviceExtension;
 ...
 KeWaitForSingleObject(UnloadEvent,Executive,KernelMode,false,nil);
 ZwClose(UnloadEventHandle);
 ...
 RtlInitUnicodeString(deviceLinkUnicodeString, DOS_DEVICE_NAME);
 IoDeleteSymbolicLink(@deviceLinkUnicodeString);
 IoDetachDevice(extension^.DeviceObject);
 IoDeleteDevice(DriverObject.DeviceObject);
 ...
end;
Основным, притягивающим наше внимание, обработчиком будет  DriverRead, потому что к нему будут поступать все IRP-пакеты от драйвера клавиатуры. Остальные обработчики - просто заглушки, их описание я опускаю. DriverRead будет вызываться при любом событии, возникающем с клавиатурой, в частности, при нажатии и отпускании клавиши. Разберем его исходный код:
Код
Var
    UnloadProcess        : boolean = false;
...

function ReadCompleteCallback(const DeviceObject : PDEVICE_OBJECT;
                              const Irp : PIRP;
                              const Context : PVoid) : NTSTATUS; stdcall;
var
...
begin
 ...
 // Помечаем IRP как ожидающий завершения, если требуется
 if (Irp^.PendingReturned) then IoMarkIrpPending(Irp);
 KeSetEvent(UnloadEvent, 0, false);
 Result:=Irp^.IoStatus.Status;
end; 

function DriverRead(const DeviceObject : PDEVICE_OBJECT;
              const Irp : PIRP) : NTSTATUS; stdcall;
var
 current_irp_stack      : PIO_STACK_LOCATION;
 next_irp_stack        : PIO_STACK_LOCATION;
begin
 ...
 current_irp_stack := IoGetCurrentIrpStackLocation(Irp);
 next_irp_stack := IoGetNextIrpStackLocation(Irp);    
 next_irp_stack^ := current_irp_stack^;
 KeClearEvent(UnloadEvent);
 if not UnloadProcess then IoSetCompletionRoutine(Irp, ReadCompleteCallback, DeviceObject, true, true, true);
 Result:=IoCallDriver(PDEVICE_EXTENSION(DeviceObject^.DeviceExtension)^.DeviceObject, 
 Irp);
end;
Самым важным моментом здесь является DDK-шный макрос (я перевел его в функцию) IoSetCompletionRoutine. Спускаясь по цепочке фильтров к драйверу, IRP попадает в наш обработчик, где ему устанавливается т.н. "функция завершения запроса" ReadCompleteCallback. Таким образом, когда драйвер клавиатуры обработает запрос - он вызовет нашу функцию и мы сможем обработать результат по нашему усмотрению. Здесь возникает несколько тонких моментов, требующих разъяснения:
а) Дело в том, что в обработке драйвера клавиатуры всегда находится как минимум один IRP-пакет, поэтому когда мы присоединяемся в цепочку фильтров - то неизбежно пропустим тот пакет, который уже сидит в обработке, потому что на него еще не была установлена наша функция возврата. Фактически это означает что первое после загрузки драйвера нажатие клавиши не будет отловлено.
б) Опять же, благодаря тому, что в обработке всегда наличествует как минимум один пакет, в момент выгрузки драйвера в этом пакете сидит привязанная к нему наша callback-функция ReadCompleteCallback (если, конечно, между загрузкой и выгрузкой драйвера происходили нажатия клавиш, что должно было привести к вызову IoSetCompletionRoutine) и если мы выгрузимся до того, как драйвер клавиатуры вызовет эту функцию, мы неизбежно получим BSOD. Для того, чтобы этого избежать - я и использовал евент, сигнализирующий о получении очередного пакета и не дающий выгрузиться драйверу, если он не сработал. Этой же цели служит булевская переменная UnloadProcess, которая не дает установить процедуру завершения, если пошел процесс выгрузки драйвера. Но все это приводит к небольшому неудобству - при выгрузке драйвера необходимо нажать клавишу. Упомянутый мной пример Ctrl2Cap этот  момент обходит другим способом - он для своей установки\выгрузки требует перезагрузки компьютера, кому интересно - может погуглить и посмотреть, как это реализовано.
в) Ну и еще один достаточно тонкий момент, который не относится именно к данному драйверу, но который я впервые испытал на своей шкуре именно в этом месте, поэтому упомяну здесь. Дело в том, что callback-функция ReadCompleteCallback вызывается на уровне DISPATCH_LEVEL, в то время как остальной код драйвера по умолчанию работает на самом низкоприоритетном уровне PASSIVE_LEVEL. Это различие аукнулось тем, что попытка записи в лог-файл внутри этой функции приводила к BSOD, потому что работа с файлами производится тоже на уровне PASSIVE_LEVEL. Поэтому при разработке механизмов взаимодействия рекомендую не забывать о таких вещах, как приоритет выполнения.
Ну а что же собственно выполняет функция ReadCompleteCallback? Её код выглядит следующим образом:
Код
Const
// Коды отлавливаемых клавиш
 VK_CONTROL        = $1D;
 VK_ALT            = $38;
 VK_DELETE        = $53;
 // Константа для работы с KEYBOARD_INPUT_DATA
 KEY_BREAK        = 1;
var
 CtrlAlt        : array [0..1] of boolean = (false,false);
...

function IsKeyDown(pKeyData:Pointer;const Key:byte):boolean;
begin
 Result:=(KEYBOARD_INPUT_DATA(pKeyData^).MakeCode = Key) and 
     ((KEYBOARD_INPUT_DATA(pKeyData^).Flags and KEY_BREAK) = 0);
end;                                                           

function IsKeyUp(pKeyData:Pointer;const Key:byte):boolean;
begin
 Result:=(KEYBOARD_INPUT_DATA(pKeyData^).MakeCode = Key) and 
     ((KEYBOARD_INPUT_DATA(pKeyData^).Flags and KEY_BREAK) > 0);
end;

function ReadCompleteCallback(const DeviceObject : PDEVICE_OBJECT;
                              const Irp : PIRP;
                              const Context : PVoid) : NTSTATUS; stdcall;
var
 KeyData        : PKEYBOARD_INPUT_DATA;
 num_keys, i        : integer; 
begin
 ...
 if NT_SUCCESS(Irp^.IoStatus.Status) then
  begin
   KeyData:=Irp^.AssociatedIrp.SystemBuffer;
   num_keys := integer((Irp^.IoStatus.Information div sizeof(KEYBOARD_INPUT_DATA)));
   for i:=0 to num_keys-1 do
    begin
     if IsKeyDown   (Pointer(Cardinal(KeyData)+i*SizeOf(KEYBOARD_INPUT_DATA)),VK_CONTROL) then CtrlAlt[0]:=true
     else if IsKeyUp(Pointer(Cardinal(KeyData)+i*SizeOf(KEYBOARD_INPUT_DATA)),VK_CONTROL) then CtrlAlt[0]:=false; 
     if IsKeyDown   (Pointer(Cardinal(KeyData)+i*SizeOf(KEYBOARD_INPUT_DATA)),VK_ALT)     then CtrlAlt[1]:=true
     else if IsKeyUp(Pointer(Cardinal(KeyData)+i*SizeOf(KEYBOARD_INPUT_DATA)),VK_ALT)     then CtrlAlt[1]:=false; 

     if (IsKeyDown(Pointer(Cardinal(KeyData)+i*SizeOf(KEYBOARD_INPUT_DATA)),VK_DELETE) or
         IsKeyUp  (Pointer(Cardinal(KeyData)+i*SizeOf(KEYBOARD_INPUT_DATA)),VK_DELETE)) and (CtrlAlt[0] and CtrlAlt[1]) then
    begin
     RtlZeroMemory(Pointer(Cardinal(KeyData)+i*SizeOf(KEYBOARD_INPUT_DATA)),sizeof(KEYBOARD_INPUT_DATA));
{$IFDEF __DEBUG__}
         DbgPrint(DRIVER_NAME+' : ' + 'Ctrl+Alt+Delete Key Sequence Is Blocked!!!');
{$ENDIF}
    end;    
    end;
  end;

 // Помечаем IRP как ожидающий завершения, если требуется
 if (Irp^.PendingReturned) then IoMarkIrpPending(Irp);
 KeSetEvent(UnloadEvent, 0, false);
 Result:=Irp^.IoStatus.Status;
end;
Передаваемый с IRP буфер (ввиду того, что мы указали буферизированный метод передачи - то буфер содержится в Irp^.AssociatedIrp.SystemBuffer) содержит массив из элементов KEYBOARD_INPUT_DATA, каждый из которых содержит информацию об очередной нажатой клавише. Больше одного элемента в этом массиве, я конечно не видел, но обрабатывать как массив все равно надо, поэтому и организован цикл по количеству элементов. В этом цикле производится проверка нажатия сооветствующих клавиш. Нажатие и отжатие клавиш CTRL и ALT - приводит соответственно к установке\сбросу соответствующих внутренних флагов, а нажатие DELETE с установленными флагами CTRL и ALT - к блокировке нажатия путем очистки буфера, о чем сигнализирует вывод в отладочную консоль. В конце функции - сбрасываем евент для возможности выгрузки драйвера. 

4.2 Пример №2: Перехват системных функций ядра
Ну и еще один пример, который я хотел бы рассмотреть - это пример осуществления перехвата функций ядра на примере функции ZwQueryDirectoryFile для обеспечения "невидимости" требуемого файла\папки. Делается это путем модификации системной таблицы служебных дескрипторов (SSDT). Данный метод зачастую называют "грязным", но иногда приходится прибегать и к таким способам, благо из режима ядра это разрешено. Этот пример тоже был написан под большим влиянием найденного в сети драйвера, осуществляющего какую-то работу с таблицей дескрипторов, но, к сожалению, я уже не помню что это был за драйвер, поэтому авторство идеи оставляю за "неизвестным героем". Исходный код находится в прилагающемся архиве в папке под названием FileHider и полный код я приводить здесь не буду, просто поясню основные моменты.
Итак, хук устанавливается в функции DriverEntry с помощью экспортируемой недокументированной глобальной переменной KeServiceDescriptorTable (со скачиваемыми примерами рекомендую проявлять осторожность - антивирус ругается на скомпилированные файлы, принимая rootkit-технологии за зверьё). Тонким моментом здесь является использование бита WP системного регистра cr0 при установке\снятии хука. Этот бит управляет защитой от записи системных страниц памяти. Если у вас на машине стоит физической памяти больше 256 Мб (WinXP) или 128 Мб (Win2k), тогда данный код никак не влияет на функциональность (т.е. если его убрать - никаких отличий не найти). Но! Если на машине стоит памяти меньше чем указано - тогда система осуществляет защиту системных страниц памяти от записи и при попытке что-то записать в область системного кода будет генерироваться исключение и это вызовет BSOD (подробнее об этом можно прочитать в журнале Хакер, глава "Типы атак на ядро"). Именно для того, чтобы это учесть, и сделан сброс бита WP перед записью в область системного кода - тогда системные страницы с любом случае будут доступны для модификации. 
После установки хука - остальное дело техники. Все запросы, благодаря наличию в SSDT адреса нашей функции, перенаправляются ей, затем она тут же вызывает оригинальную системную процедуру по сохраненному указателю, а возвращаемый результат анализирует на предмет соответствия требуемым условиям. В примере я сделал проверку по соответствии имени файла\папки определенному шаблону, а также закомментировал проверку по атрибутам файла. Анализ осуществляется путем перебора всего списка элементов типа FILE_BOTH_DIRECTORY_INFORMATION, который возвращается оригнальной функцией ZwQueryDirectoryFile. В случае выполнения условий проверки  - проверяемый элемент удаляется из списка путем перенаправления указателя предыдущего элемента списка на следующий. При завершении работы драйвера - в функции DriverUnload адрес оригинальной процедуры возвращается на место : 
Конечно, для того, чтобы полностью "спрятать" требуемый файл - просто перехвата ZwQueryDirectoryFile недостаточно, даже если спрятать папку таким образом - в неё все равно можно будет зайти путем ввода команды "cd". Но это уже вопрос наращивания функциональности.

5. Заключение
Данная статья не охватывает многие и многие аспекты темы разработки драйверов. Например, не были рассмотрены вопросы обмена с реальными устройствами, не приведено примеров работы с файловой системой на низком уровне, даже не рассмотрен вопрос построения inf-файлов, необходимых для полноценной установки устройства в систему, или других узкоспециализированных моментов. Но и не в этом её цель. Целью является дать дополнительный импульс для тех, кто хочет осилить эту необъятную тему, с тем, чтобы дальше можно было двигаться самостоятельно, дать начальные познания при помощи привычной "среды", а также дать своеобразные "направляющие" для правильного составления конкретных вопросов на соответствующих форумах. Если не получилось  - прошу строго не судить

P.S. Выражаю огромную благодарность Snowy за помощь в подготовке и оформлении статьи.

Это сообщение отредактировал(а) ama_kid - 8.9.2008, 22:11

Присоединённый файл ( Кол-во скачиваний: 482 )
Присоединённый файл  _Release.part02.rar 436,36 Kb


--------------------
самурай без меча подобен самураю с мечом, но только без меча 
PM MAIL   Вверх
THandle
Дата 22.8.2008, 12:23 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Хранитель Клуба
Group Icon
Награды: 1



Профиль
Группа: Админ
Сообщений: 3639
Регистрация: 31.7.2007
Где: Moscow, Dubai

Репутация: 7
Всего: 372



ama_kid,  smile 


Цитата(ama_kid @  22.8.2008,  13:18 Найти цитируемый пост)
фу, блин... склейка сообщений все мозги вынесла...



Ага. Бываетsmile

+ тебе за статью. Когда нибудь точно пригодится smile 
PM   Вверх
MetalFan
  Дата 22.8.2008, 12:25 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Аццкий Сотона
****


Профиль
Группа: Комодератор
Сообщений: 3815
Регистрация: 2.10.2006
Где: Moscow

Репутация: 16
Всего: 128



ama_kid, сам написал?! ну ты smile! очень познавательно! и с меня +


--------------------
There are always someone smarter than you...
PM MAIL   Вверх
ama_kid
Дата 22.8.2008, 12:39 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


АСУТП-кодер
***


Профиль
Группа: Комодератор
Сообщений: 1460
Регистрация: 5.3.2007
Где: Москва

Репутация: 19
Всего: 95



THandleMetalFan, спасибо smile

Это сообщение отредактировал(а) ama_kid - 5.12.2008, 23:26


--------------------
самурай без меча подобен самураю с мечом, но только без меча 
PM MAIL   Вверх
Riply
Дата 22.8.2008, 21:36 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Комодератор
Сообщений: 572
Регистрация: 27.3.2007
Где: St. Petersburg

Репутация: 21
Всего: 32



ama_kid
Спасибо.
Когда при решении задач мне стало довольно сильно мешать
мое неумение писать драйвера, я решила обратиться к С,
считая что в родной и любимой Delphi это будет "не естественно"  smile 
Еще раз спасибо, за избавление от столь пагубного заблуждения  smile 
PM MAIL   Вверх
ne0n
  Дата 22.8.2008, 21:56 (ссылка)    | (голосов:1) Загрузка ... Загрузка ... Быстрая цитата Цитата


PlayBoy
**


Профиль
Группа: Участник
Сообщений: 733
Регистрация: 5.8.2005
Где: Н.Новгород

Репутация: 4
Всего: 11



Riply,  ну на самом деле - это не такое пагубное заблуждение...переводить ddk на паскаль, никто не будет...а самому эту задачу не осилить, хотя может и найдуться интузиасты smile 

ничего нового не узнал, но за стотью в любом случаи респек и + 


PM MAIL ICQ   Вверх
Riply
Дата 8.9.2008, 19:06 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Комодератор
Сообщений: 572
Регистрация: 27.3.2007
Где: St. Petersburg

Репутация: 21
Всего: 32



ama_kid
Наконец-то выдалось свободное время и можно заняться всякими вкусностями smile
Начала смотреть код (Delphi версия) и у меня сразу появилась куча вопросов:
1. STRSIZE        = 115;
   Что это за число (115) и откуда оно взялось ?
2. NTSTATUS спецально объявлен как ULong ?
   Если да, то почему ?
3. Вытекает из второго
   Реализация ф-ии NT_Success ( Result := (NTS=STATUS_SUCCESS) )
   обусловлена именно таким объявлением NTSTATUS ?
4. Почему, после вызовов BCAnsiStringAllocate не всегда
   вызывается BCAnsiStringFree (например в WriteDispatch) ?
5. Rtl-кие функции для работы со строками валидны в нашем случае ?
   (Имеется ввиду совместимость способов выделения и освобождения памяти 
    Rtl-кими функциями и BC-функциями)  ?

P.S.
 При ответах, просьба учесть нулевой уровень вопрошающей в данной области  smile  

PM MAIL   Вверх
ama_kid
Дата 8.9.2008, 22:14 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


АСУТП-кодер
***


Профиль
Группа: Комодератор
Сообщений: 1460
Регистрация: 5.3.2007
Где: Москва

Репутация: 19
Всего: 95



Цитата(Riply @  8.9.2008,  20:06 Найти цитируемый пост)
1. STRSIZE        = 115;
   Что это за число (115) и откуда оно взялось ?
115 - это "магическое число" и взялось оно мной практически с потолка, дабы ограничить размер статического массива  smile 
Цитата(Riply @  8.9.2008,  20:06 Найти цитируемый пост)
2. NTSTATUS спецально объявлен как ULong ?
   Если да, то почему ?
3. Вытекает из второго
   Реализация ф-ии NT_Success ( Result := (NTS=STATUS_SUCCESS) )
   обусловлена именно таким объявлением NTSTATUS ?
Я не совсем понял вопрос. А как он должен быть объявлен? В принципе, в DDK он объявлен знаковым (long), но в изначальной статье (на которую я ссылаюсь вначале) он был объявлен как cardinal (и в принципе, я нигде не видел использования знаковых особенностей этого дефайна), поэтому перенес все один в один.  smile 

Цитата(Riply @  8.9.2008,  20:06 Найти цитируемый пост)
4. Почему, после вызовов BCAnsiStringAllocate не всегда
   вызывается BCAnsiStringFree (например в WriteDispatch) ?
Мда... Это, надо сказать, ты хорошо заметила, молодец smile Конечно, обязано вызываться, моя ошибка... Поправил, попутно найдя ещё аналогичную ошибку в DeviceIOControlDispatch smile

Цитата(Riply @  8.9.2008,  20:06 Найти цитируемый пост)
5. Rtl-кие функции для работы со строками валидны в нашем случае ?
   (Имеется ввиду совместимость способов выделения и освобождения памяти 
    Rtl-кими функциями и BC-функциями)  ?
Не понял, что ты имеешь ввиду под выделением\освобождением памяти Rtl-кими функциями? Память выделяется\освобождается функциями MmAllocateNonCachedMemory\MmFreeNonCachedMemory, а Rtl-кие функции как раз и предназначены для работы со строками (ну и для многого другого). BC-функции, как я говорил, это просто обертки над ними (посмотри их код в модуле BCore.inc - там они как раз и вызывают Rtl-функции)


--------------------
самурай без меча подобен самураю с мечом, но только без меча 
PM MAIL   Вверх
Riply
Дата 8.9.2008, 23:48 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Комодератор
Сообщений: 572
Регистрация: 27.3.2007
Где: St. Petersburg

Репутация: 21
Всего: 32



Цитата(ama_kid @  8.9.2008,  22:14 Найти цитируемый пост)
115 - это "магическое число" и взялось оно мной практически с потолка, дабы ограничить размер статического массива  


Я только начала разбираться, но мне кажется, что можно работать с блоками
размером до MAXWORD shr 1. Я пыталась исходить из размерности полей.



Цитата(ama_kid @  8.9.2008,  22:14 Найти цитируемый пост)
Я не совсем понял вопрос. А как он должен быть объявлен? В принципе, в DDK он объявлен знаковым (long), но в изначальной статье (на которую я ссылаюсь вначале) он был объявлен как cardinal (и в принципе, я нигде не видел использования знаковых особенностей этого дефайна), поэтому перенес все один в один.


К сожалению не знаю как обстоят дела в мире драйверов, но в обычном приложении,
работающем с Nt-функциями знаковость NTSTATUS очень важна. 
А NT_SUCCESS должна иметь такой вид:
Код

function NT_SUCCESS(const Status: NTSTATUS): Boolean; inline;
begin
 Result := Status >= 0;
end;

Дело в том, что некоторые (если не многие) функции могут в случае успеха возвращать не STATUS_SUCCESS
а "некий код", содержащий STATUS_SEVERITY_INFORMATIONAL.
В этом случае фунция выполнена успешно, а в ее результате дается дополнительная информация.
Как правило это используется в Query функциях (например STATUS_MORE_ENTRIES говорит, что все удачно записано,
но меня можно вызвать еще раз для дозаписи).
Но встречается и в другин. Например я сталкивалась с STATUS_PREDEFINED_HANDLE, STATUS_EVENT_PENDING
или при работе с секциями (мутантами) STATUS_OBJECT_NAME_EXISTS а вот здесь это становится довольно важно.

Цитата(ama_kid @  8.9.2008,  22:14 Найти цитируемый пост)
Не понял, что ты имеешь ввиду под выделением\освобождением памяти Rtl-кими функциями?

Ой. Прошу маленький тай-аут. Еще чуть поразбираюсь и сформулирую вопрос более точно.
Но пока мне не понятно, чем обусловлен выбор MmAllocateNonCachedMemory вместо Rtl - Heap - функций ?
(Они кстати "совместимы" с такими как RtlFreeUnicodeString)


PM MAIL   Вверх
ama_kid
Дата 9.9.2008, 09:26 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


АСУТП-кодер
***


Профиль
Группа: Комодератор
Сообщений: 1460
Регистрация: 5.3.2007
Где: Москва

Репутация: 19
Всего: 95



Цитата(Riply @  9.9.2008,  00:48 Найти цитируемый пост)
Я только начала разбираться, но мне кажется, что можно работать с блоками размером до MAXWORD shr 1. Я пыталась исходить из размерности полей.
Это - ради бога, как удобно так и работай, я просто не хотел гонять большой блок данных smile

Цитата(Riply @  9.9.2008,  00:48 Найти цитируемый пост)
знаковость NTSTATUS очень важна
Вот как это дело расписано в DDK:
Цитата
//
//  Status values are 32 bit values layed out as follows:
//
//   3 3 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1
//   1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0
//  +---+-+-------------------------+-------------------------------+
//  |Sev|C|       Facility          |               Code            |
//  +---+-+-------------------------+-------------------------------+
//
//  where
//
//      Sev - is the severity code
//
//          00 - Success
//          01 - Informational
//          10 - Warning
//          11 - Error
//
//      C - is the Customer code flag
//
//      Facility - is the facility code
//
//      Code - is the facility's status code
//
Знаковый бит обозначает либо Warning, либо Error. Т.е. тут важна не знаковость сама по себе, а наличие последнего бита в "severity code" (фактически это одно и то же, но под другим соусом smile ).
Цитата(Riply @  9.9.2008,  00:48 Найти цитируемый пост)
NT_SUCCESS должна иметь такой вид:
Я наконец понял, что ты имеешь ввиду. smile Формально ты, конечно, права и я с тобой тут спорить не буду. Да и DDK говорит о том же:
Код
//
// Generic test for success on any status value (non-negative numbers
// indicate success).
//
#define NT_SUCCESS(Status) ((NTSTATUS)(Status) >= 0)
//
// Generic test for information on any status value.
//
#define NT_INFORMATION(Status) ((ULONG)(Status) >> 30 == 1)
//
// Generic test for warning on any status value.
//
#define NT_WARNING(Status) ((ULONG)(Status) >> 30 == 2)
//
// Generic test for error on any status value.
//
#define NT_ERROR(Status) ((ULONG)(Status) >> 30 == 3)
проверку именно на неравенство
Код
const STATUS_SUCCESS                    = NTStatus( $00000000 );
я использую просто для удобства, чтобы  получить гарантированный STATUS_SUCCESS.  Если хочешь - можешь переделать под свой вкус в соответствие с канонами DDK smile
Цитата(Riply @  9.9.2008,  00:48 Найти цитируемый пост)
Но пока мне не понятно, чем обусловлен выбор MmAllocateNonCachedMemory вместо Rtl - Heap - функций ?
Да практически ничем за исключением того с ней меньше геморроя (чтобы использовать RtlAllocateHeap, надо предварительно вызвать RtlCreateHeap с кучей параметров), ну и благодаря тому, что блок памяти выделяется в некэшируемой области - его можно использовать на любом уровне приоритета выполнения (хотя в данном примере это и не сильно надо, все равно весь код исполняется на PASSIVE_LEVEL).

Цитата(Riply @  9.9.2008,  00:48 Найти цитируемый пост)
Они кстати "совместимы" с такими как RtlFreeUnicodeString
Цитата(MSDN)
The RtlFreeUnicodeString routine releases storage that was allocated by RtlAnsiStringToUnicodeString or RtlUpcaseUnicodeString.



--------------------
самурай без меча подобен самураю с мечом, но только без меча 
PM MAIL   Вверх
Riply
Дата 9.9.2008, 17:24 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Комодератор
Сообщений: 572
Регистрация: 27.3.2007
Где: St. Petersburg

Репутация: 21
Всего: 32



Цитата(ama_kid @  9.9.2008,  09:26 Найти цитируемый пост)
чтобы  получить гарантированный STATUS_SUCCESS


IMHO, довольно опасный способ проверки. Например при создании секции такая проверка может показать неудачу,
в то время как все успешно и мы получили на руки открытый Handle. Который еще и закрыть надо smile


Цитата(ama_kid @  9.9.2008,  09:26 Найти цитируемый пост)
Да практически ничем за исключением того с ней меньше геморроя 


Я почему спрашиваю. Мы ведь в наших Dispatch функциях предполагаем многократное выделение/освобождение.
Соответственно меня и интересует какой из способов работы с памятью лучше всего подходит
для этого случая. С MmAllocateNonCachedMemory я еще не окончательно разобралась, но
меня сильно смущает следующая фраза из MSDN:
 "A device driver that must use noncached memory should allocate only what it needs during 
driver initialization because nonpaged pool is likely to become fragmented as the system runs."


Насчет же функций типа RtlAnsiStringToUnicodeString (а они предназначены для частого использования) , 
мое копательство в них показало, что они работают именно через Rtl-Heap. 
Поэтому я и говорила о "совместимости". Если бы BC функции тоже работали через Heap,
то их можно было бы использовать "вперемежку" с Rtl-скими (проверяла в юзер-моде).
P.S.
Я ни в коем случае не спорю, а просто пытаюсь понять что к чему и с чем едят.
Соответственно многие мои утверждения приведены не как контр-аргументы, 
а для того, чтобы мне показали ошибку в моих рассуждениях и пояснили "как надо на самом деле" smile
Где еще найдешь бесплатное образование, кроме как не на таких форумах smile




Это сообщение отредактировал(а) Riply - 9.9.2008, 17:26
PM MAIL   Вверх
ama_kid
Дата 9.9.2008, 18:54 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


АСУТП-кодер
***


Профиль
Группа: Комодератор
Сообщений: 1460
Регистрация: 5.3.2007
Где: Москва

Репутация: 19
Всего: 95



Цитата(Riply @  9.9.2008,  18:24 Найти цитируемый пост)
IMHO, довольно опасный способ проверки. Например при создании секции такая проверка может показать неудачу, в то время как все успешно и мы получили на руки открытый Handle. Который еще и закрыть надо
Ну вот сначала закрываем, а потом отлаживаем и разбираемся, что же мы получили smile
Цитата(Riply @  9.9.2008,  18:24 Найти цитируемый пост)
какой из способов работы с памятью лучше всего подходит
для этого случая
Конкретно для этого случая мне было удобнее использовать то, что я использовал smile
Но вообще, режим ядра предоставляет гораздо большие возможности для работы с памятью, чем user-mode и на эту тему есть интересные статьи на Wasm.ru (главы "Работа с памятью"). Можешь почитать, если интересно...



--------------------
самурай без меча подобен самураю с мечом, но только без меча 
PM MAIL   Вверх
Riply
Дата 9.9.2008, 19:47 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Комодератор
Сообщений: 572
Регистрация: 27.3.2007
Где: St. Petersburg

Репутация: 21
Всего: 32



Цитата(ama_kid @  9.9.2008,  18:54 Найти цитируемый пост)
на эту тему есть интересные статьи на Wasm.ru (главы "Работа с памятью"). 


Спасибо.


Цитата(ama_kid @  9.9.2008,  18:54 Найти цитируемый пост)
если интересно...


Издеваетесь ?   smile 
PM MAIL   Вверх
ama_kid
Дата 10.9.2008, 15:35 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


АСУТП-кодер
***


Профиль
Группа: Комодератор
Сообщений: 1460
Регистрация: 5.3.2007
Где: Москва

Репутация: 19
Всего: 95



Цитата(Riply @  9.9.2008,  20:47 Найти цитируемый пост)
Издеваетесь ?   smile  
ни в коем разе smile



--------------------
самурай без меча подобен самураю с мечом, но только без меча 
PM MAIL   Вверх
Ответ в темуСоздание новой темы Создание опроса
Правила форума "Delphi: WinAPI и системное программирование"
Snowybartram
MetalFanbems
PoseidonRrader
Riply

Запрещено:

1. Публиковать ссылки на вскрытые компоненты

2. Обсуждать взлом компонентов и делиться вскрытыми компонентами

  • Литературу по Delphi обсуждаем здесь
  • Действия модераторов можно обсудить здесь
  • С просьбами о написании курсовой, реферата и т.п. обращаться сюда
  • Вопросы по реализации алгоритмов рассматриваются здесь
  • 90% ответов на свои вопросы можно найти в DRKB (Delphi Russian Knowledge Base) - крупнейшем в рунете сборнике материалов по Дельфи
  • 99% ответов по WinAPI можно найти в MSDN Library, оставшиеся 1% здесь

Если Вам понравилась атмосфера форума, заходите к нам чаще! С уважением, Snowy, bartram, MetalFan, bems, Poseidon, Rrader, Riply.

 
0 Пользователей читают эту тему (0 Гостей и 0 Скрытых Пользователей)
0 Пользователей:
« Предыдущая тема | Delphi: WinAPI и системное программирование | Следующая тема »


 




[ Время генерации скрипта: 0.2217 ]   [ Использовано запросов: 23 ]   [ GZIP включён ]


Реклама на сайте     Информационное спонсорство

 
По вопросам размещения рекламы пишите на vladimir(sobaka)vingrad.ru
Отказ от ответственности     Powered by Invision Power Board(R) 1.3 © 2003  IPS, Inc.