Таблица 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_CLASSKeyValueInformationClass | Этот параметр принимает одно их трех значений в зависимости от полноты информации о параметре:KeyValueBasicInformationKeyValueFullInformationKeyValuePartialInformation |
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.1 Точки входа разрабатываемого драйвера
Разрабатываемый драйвер является драйвером нижнего уровня. В стеке драйверов USB‑накопителя он находится непосредственно под драйвером устройства, если после его загрузки не произойдет установки какого-либо другого драйвера-фильтра нижнего уровня.
Разрабатываемый драйвер включает в себя следующие точки входа:
· DriverEntry;
· AddDevice;
· DriverUnload;
· Функции обработки IRP‑пакетов:
· обработка IRP‑пакетовскодами IRP_MJ_INTERNAL_DEVICE_CONTROL – функцияDispatchInternalDeviceControl;
· обработка IRP пакетов с прочими кодами – функция DispatchRoutine.
Рассмотрим каждую из них более подробно.
2.1.1 Функция DriverEntry
В этой функции происходит регистрация всех стандартных точек входа драйвера и обработчиков IRP‑пакетов. В разрабатываемом драйвере пакеты IRPc кодами, не равными 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.