48424 (608601), страница 4
Текст из файла (страница 4)
Таблица 1.8.3. Параметры функции ZwWriteFile
Тип параметра | Описание параметра |
IN HANDLE FileHandle | Дескриптор открытого или модифицированного файлового объекта |
IN HANDLE Event | Для драйверов устройств следует указывать NULL |
IN PIO_APC_ROUTINE | Для драйверов устройств следует указывать NULL |
IN PVOID ApcContext | Для драйверов устройств следует указывать NULL |
OUT PIO_STATUS_BLOCK pioStatusBlock | В поле pIoStatusBlock->Information по завершении вызова находится число реально записанных байт |
IN PVOID Buffer | Буфер с данными для записи |
IN ULONG Length | Размер записываемой порции данных |
IN PLARGE_INTEGER pByteOffset | Указатель на переменную где содержится смещение в файле от его начала, по которому следует производить запись |
IN PULONG Key | Для драйверов устройств следует указывать NULL |
Для закрытия дескриптора объекта следует применять функцию ZwCloseKey.
Следует отметить, что функции работы с файлами могут работать только на уровне IRQL, равном PASSIVE_LEVEL. Это приводит к необходимости применения специальной методики при протоколировании обмена данными с USB‑накопителем.
1.9 Работа с реестром в режиме ядра
Работа с реестром из драйвера уровня ядра необходима, так как именно в системном реестре хранится информация о настройках протоколирования. Информация о настройках хранится в ключе реестра, связанном с устройством, к которому подключается драйвер-фильтр. Имя этого устройства соответствует шаблону HKEY_LOCAL_MACHINE\System\CurrentControlSet\Enum\USB\XXX\XXX\DeviceParameters.
Доступ ключу устройства в реестре в драйвере предоставляется функцией IoOpenDeviceRegistryKey. Перечислим ее параметры:
Таблица 1.9.1. Параметры функции IoOpenDeviceRegistry
Тип параметра | Описание параметра |
IN PDEVICE_OBJECT DeviceObject | Указатель на объект физического устройства, ключ которого должен быть открыт |
IN ULONG DevInstKeyType | Параметр определяющий, связан ли открываемый ключ непосредственно с физ. устройством или его программным обеспечением |
IN ACCESS_MASK DesiredAccess | Этот параметр определяет права доступа к ключу |
OUT PHANDLE DevInstRegKey | Указатель на переменную, куда следует поместить дескриптор открытого ключа |
Открыв основной ключ, следует получить доступ к вложенному в него ключу с параметрами протоколирования. Для этого используется функция ZwOpenKey. Перечислим ее параметры:
Таблица 1.9.2. Параметры функции ZwOpenKey
Тип параметра | Описание параметра |
OUT PHANDLE KeyHandle | Указатель на переменную, куда следует поместить дескриптор открытого ключа |
IN ACCESS_MASK DesiredAccess | Этот параметр определяет права доступа к ключу |
IN POBJECT_ATTRIBUTES pObjectAttributes | Указатель на заполненную вызывающим кодом структуру данных, которая при использовании в данной функции должна содержать имя открываемого ключа |
Открыв ключ собственных параметров драйверу необходимо считать настройки протоколирования. Для чтения значения параметров ключа реестра используется функция ZwQueryValueKey. Перечислим ее параметры:
Таблица 1.9.3. Параметры функции ZwQueryValueKey
Тип параметра | Описание параметра |
IN HANDLE KeyHandle | Дескриптор ключа, которому принадлежит считываемый параметр |
IN PUNICODE_STRING ValueName | Строка юникод-символов, содержащая имя параметра ключа |
IN KEY_VALUE_INFORMATION_CLASS KeyValueInformationClass | Этот параметр принимает одно их трех значений в зависимости от полноты информации о параметре: KeyValueBasicInformation KeyValueFullInformation KeyValuePartialInformation |
OUT PVOID KeyInformation | Указатель на буфер, выделенный вызывающим кодом, в который должна быть помещена запрашиваемая информация |
IN ULONG Length | Длина предоставленного буфера |
OUT PULONG ResultLength | Указатель на переменную, содержащую число реально записанных в KeyInformation байт |
После того, как работа с ключом реестра закончена, его дескриптор следует освободить вызовом функции ZwClose.
1.10 MDL‑списки
MDL‑список – это структура, хранящая отображение блока виртуальной памяти на физическую память. MDL‑список используется в разрабатываемом драйвере для хранения информации из URB‑пакетов, связанных с вводом / выводом USB‑устройства. Кроме того, обмен информацией с USB‑устройством в режиме прямого доступа к памяти ведется именно посредством MDL‑списков.
Перед использовании MDL‑списка в драйвере необходимо провести ряд подготовительных действий:
-
выделить область в страничной памяти с помощью вызова функции
ExAllocatePool; -
вызвать функцию MmCreateMdl, создающую и инициализирующую MDL‑список;
-
выполнить фиксацию страниц, описанных в MDL‑списке, в физической памяти с помощью вызова функции MmProbeAndLockPages.
После завершения использования MDL‑списка его следует освободить:
-
отменить фиксацию страниц страничной памяти в оперативной памяти вызовом функции MmUnlockPages;
-
очистить MDL‑список, вызвав функцию IoFreeMdl;
-
освободить выделенную под список страничную память вызовом
ExFreePool.
2. Конструкторский раздел
2.1 Точки входа разрабатываемого драйвера
Разрабатываемый драйвер является драйвером нижнего уровня. В стеке драйверов USB‑накопителя он находится непосредственно под драйвером устройства, если после его загрузки не произойдет установки какого-либо другого драйвера-фильтра нижнего уровня.
Разрабатываемый драйвер включает в себя следующие точки входа:
-
DriverEntry;
-
AddDevice;
-
DriverUnload;
-
Функции обработки IRP‑пакетов:
-
обработка IRP‑пакетов с кодами IRP_MJ_INTERNAL_DEVICE_CONTROL – функция DispatchInternalDeviceControl;
-
обработка IRP пакетов с прочими кодами – функция DispatchRoutine.
-
Рассмотрим каждую из них более подробно.
2.1.1 Функция DriverEntry
В этой функции происходит регистрация всех стандартных точек входа драйвера и обработчиков IRP‑пакетов. В разрабатываемом драйвере пакеты IRP c кодами, не равными IRP_MJ_INTERNAL_DEVICE_CONTROL обрабатываются функцией DispatchRoutine.
2.1.2 Функция AddDevice
Управление этой функции передается диспетчером ввода / вывода после того, как завершает свою работу DriverEntry. AddDevice создает функциональный объект устройства с помощью вызова IoCreateDevice и подключает его к стеку драйверов выбранного устройства (вызовом IoAttachDeviceToDeviceStack). Кроме того, в этой функции производятся действия по подготовке к протоколированию: считываются настройки из системного реестра, выделяется буфер для сбора протоколируемой информации, создается лог-файл.
2.1.3 Функция DriverUnload
Функция DriverUnload необходима для того, чтобы сделать драйвер выгружаемым. В унаследованных драйверах на эту функцию возложен весь процесс выгрузки драйвера: удаление символьных ссылок, объектов устройств драйвера, отключение прерываний от объектов, освобождение выделенной памяти. В WDM‑драйверах все эти действия возложены на функцию-обработчик пакетов с кодом IRP_MJ_PNP.
2.1.4 Функция DispatchRoutine
На эту функцию возложены обязанности по обработке IRP‑пакетов с различными кодами, хотя в разрабатываемом драйвере существует необходимость в обработке только двух типов запросов. Все запросы с кодом, отличным от IRP_MJ_PNP передаются по стеку драйверов без изменений. Запросы же IRP_MJ_PNP диспетчеризуются по суб-кодам в функции PnP_Dispatch. Необходимость диспетчеризации по суб-кодам запросов IRP_MJ_PNP вызвана тем, что драйвер не должен нарушать порядка работы операционной системы и обязан подчиняться PnP‑менеджеру, то есть в драйвере должны корректно обрабатываться события старта и удаления устройства.
2.1.5 Функция DispatchInternalDeviceControl
Запросы ввода / вывода к USB‑накопителю передаются в составе IRP‑пакетов с кодом IRP_MN_INTERNAL_DEVICE_CONTROL. Этот пакет содержит полную информацию о направлении и характере передаваемых данных. То есть для протоколирования обмена информацией с USB‑носителем следует перехватывать пакеты именно этого типа.
Для того чтобы перехватывать информацию, передаваемую в обоих направлениях, следует установить функцию обратного вызова диспетчера ввода / вывода. Методика установки этой функции была описана в разделе 1.7. При наличии этой функции разрабатываемый драйвер-фильтр получит возможность перехвата данных, передаваемых от устройства к хосту.
Для сохранения протоколируемой информации используется, как уже было сказано в разделе 1.10, MDL‑список. Этот MDL‑список создается в функции AddDevice. Объем памяти, выделяемой под список, совпадает с максимальным размером лог-файла, задаваемым в пользовательском приложении. После создания список фиксируется в страничной памяти, что предотвращает его выгрузку на жесткий диск во время работы драйвера. После этих подготовительных действий список используется в функции DispatchInternalDeviceControl – он заполняется перехватываемой информацией.
Запись накопленного буфера в лог-файл происходит при удалении устройства.
Такая методика выбрана из-за того, что функция DispatchInternalDeviceControl работает на уровне запроса прерываний, равном DISPATCH_LEVEL, что сильно затрудняет использование механизмов синхронизации, которые могли бы позволить перейти на уровень запроса прерываний, равный PASSIVE_LEVEL, где становятся доступными функции работы с файлами. Если бы это было достигнуто в разрабатываемом драйвере, то отпала бы необходимость выделения больших объемов нестраничной памяти для хранения протокола.
Запись файла на диск в момент удаления устройства возможна, так как это событие инициализируется PnP‑менеджером, запросы которого всегда происходят на уровне IRQL, равном PASSIVE_LEVEL.
2.2 Размещение кода драйвера в памяти
Некоторые функции драйвера, например те, которые выполняют инициализацию, выгодно выполнить и освободить память, занимаемую ими. В языке C есть специальная директива #pragma_alloc_text (, ), позволяющая управлять размещением кода. В качестве типа секции могут указываться значения «INIT» или «PAGE».
Функции с размещением в секции «INIT» выгружаются, и память, занимаемая ими, освобождается сразу по завершении их работы. В разрабатываемом драйвере в секции «INIT» размещена точка входа DriverEntry, поскольку она выполняется единожды при загрузке драйвера.
Точки входа AddDevice и DriverUnload располагаются в секции «PAGE», то есть в страничной памяти, поскольку они гарантированно вызываются на уровне привилегий, равном PASSIVE_LEVEL и, даже оказавшись выгруженными на диск, будут немедленно загружены в физическую память менеджером страничной памяти (который способен работать только на уровне PASSIVE_LEVEL).
Остальные же точки входа (DispatchRoutine и DispatchInternalDeviceControl) располагаются по умолчанию, в нестраничной памяти, поскольку их работа зависит от клиентского драйвера USB‑устройства, под которым в стеке драйверов располагается разрабатываемый драйвер-фильтр. Уровень привилегий его запросов слабо предсказуем и может быть равен DISPATCH_LEVEL. На этом уровне подкачка страниц невозможна, что при обращении к выгруженной функции приведет к краху системы.
2.3 Установка драйвера в системе
Для установки драйвера следует создать его ключ в системном реестре.
Имя ключа должно иметь следующий вид: