Решил разрабатывать MiniFilter драйвер, конфигурируемый при помощи текстового файла.
Рассмотрим, что из себя в общем виде представляет MiniFilter:
Фильтрация осуществляется через так называемый Filter Manager, который поставляется с операционной системой Windows, активируется только при загрузке мини фильтров. Filter Manager подключается напрямую к стеку файловой системы. Мини фильтры регистрируются на обработку данных по операциям ввода/вывода при помощи функционала Filter Manager, получая, таким образом, косвенный доступ к файловой системе. После регистрации и запуска мини фильтр получает набор данных по операциям ввода/вывода, которые были указаны при конфигурировании, при необходимости может вносить изменения в эти данные, таким образом влияя на работу файловой системы.
На следующей схеме в упрощенном виде показано как функционирует Filter Manager.
Более подробную теоретическую информацию Вы можете получить на сайте MSDN, воспользовавшись ссылкой в конце статьи. Достаточно не плохо все разобрано.
Мы же двинемся в сторону разработки и рассмотрим некоторые базовые структуры, которые необходимо заполнить.
Общие глобальные данные.
typedef struct _MINIFILTER { PDRIVER_OBJECT pDriverObject; PFLT_FILTER pFilter; } MINIFILTER, *PMINIFILTER; MINIFILTER fileManager;
В этой структуре будем хранить ссылку на объект нашего драйвера и ссылку на экземпляр фильтра. Хочу заметить, что PFLT_FILTER уникально идентифицирует мини фильтр и остается константой на все время работы драйвера. Используется при активации или остановке процесса фильтрации.
Регистрируем фильтр
CONST FLT_REGISTRATION FilterRegistration = { sizeof( FLT_REGISTRATION ), // Size FLT_REGISTRATION_VERSION, // Version 0, // Flags NULL, // Context Callbacks, // Operation callbacks FilterUnload, // FilterUnload FilterLoad, // InstanceSetup NULL, // InstanceQueryTeardown NULL, // InstanceTeardownStart NULL, // InstanceTeardownComplete NULL, // GenerateFileName NULL // NormalizeNameComponent };
Тут стоит остановиться на нескольких поляx:
- Callbacks – ссылка на структуру, определяющую, что и при помощи каких функций мы собираемся обрабатывать.
- FilterUnload – функция, которая будет вызвана при отключении фильтра.
- FilterLoad – функция, которая будет вызвана при инициализации фильтра.
Далее рассмотрим структуру Callbacks:
const FLT_OPERATION_REGISTRATION Callbacks[] = { { IRP_MJ_CREATE, 0, PreFileOperationCallback, PostFileOperationCallback }, { IRP_MJ_OPERATION_END } };
Здесь мы указываем, что будем перехватывать операцию CreateFile, также указываем функции, которые будут вызываться, соответственно, до и после выполнения операции над файлом.
Далее привожу код функций, которые вызываются при инициализации и отключении фильтра.
NTSTATUS FilterLoad (IN PCFLT_RELATED_OBJECTS FltObjects, IN FLT_INSTANCE_SETUP_FLAGS Flags, IN DEVICE_TYPE VolumeDeviceType, IN FLT_FILESYSTEM_TYPE VolumeFilesystemType) { if (VolumeDeviceType == FILE_DEVICE_NETWORK_FILE_SYSTEM) { return STATUS_FLT_DO_NOT_ATTACH; } return STATUS_SUCCESS; } NTSTATUS FilterUnload ( IN FLT_FILTER_UNLOAD_FLAGS Flags ) { return STATUS_SUCCESS; }
Думаю, код не нуждается в дополнительных комментариях, так как все достаточно стандартно. Замечу только, что наш драйвер не будет работать для сети.
Теперь давайте рассмотрим функцию инициализации драйвера:
NTSTATUS DriverEntry( IN PDRIVER_OBJECT theDriverObject, IN PUNICODE_STRING theRegistryPath ) { int i; NTSTATUS status; PCHAR ConfigInfo; UNICODE_STRING test; DbgPrint("MiniFilter: Started."); // Register a dispatch function for (i = 0; i < IRP_MJ_MAXIMUM_FUNCTION; i++) { theDriverObject->MajorFunction[i] = OnStubDispatch; } theDriverObject->DriverUnload = OnUnload; fileManager.pDriverObject = theDriverObject; status = FltRegisterFilter(theDriverObject, &FilterRegistration, &fileManager.pFilter); if (!NT_SUCCESS(status)) { DbgPrint("MiniFilter: Driver not started. ERROR FltRegisterFilter - %08x\n", status); return status; } ConfigInfo = ReadConfigurationFile(); if(ConfigInfo != NULL && NT_SUCCESS(ParseConfigurationFile(ConfigInfo))) { ExFreePool(ConfigInfo); DbgPrint("MiniFilter: Configuration finished."); }else { if(ConfigInfo != NULL)ExFreePool(ConfigInfo); FltUnregisterFilter( fileManager.pFilter ); DbgPrint("MiniFilter: Driver configuration was failed. Driver not started."); return STATUS_DEVICE_CONFIGURATION_ERROR; } status = FltStartFiltering( fileManager.pFilter ); if (!NT_SUCCESS( status )) { FltUnregisterFilter( fileManager.pFilter ); FreeConfigInfo(); DbgPrint("MiniFilter: Driver not started. ERROR FltStartFiltering - %08x\n", status); return status; } DbgPrint("MiniFilter: Filter was started and configured."); return STATUS_SUCCESS; }
Регистрация мини фильтра осуществляется посредством вызова функции FltRegisterFilter, в которую мы передаем полученный на входе theDriverObject, структуру FilterRegistration, описанную ранее и ссылку на переменную, куда будет помещен созданный экземпляр фильтра fileManager.pFilter. Для запуска процесса фильтрации нужно вызвать функцию FltStartFiltering( fileManager.pFilter ).
Так же обращу внимание, что загрузка файла конфигурации и его обработка выполняется посредством следующих вызовов ConfigInfo = ReadConfigurationFile(); и ParseConfigurationFile(ConfigInfo) соответственно.
Данные из конфигурационного файла преобразуются в следующий набор структур.
typedef struct FILE_REDIRECT_RULE { UNICODE_STRING From; UNICODE_STRING To; struct FILE_REDIRECT_RULE *NextRule; }FileRedirectRule, *PFileRedirectRule; struct PROCESS_CONFIGURATION_RULE { UNICODE_STRING ProcessName; struct FILE_REDIRECT_RULE *Rule; }; typedef struct CONFIGURATION_MAP { struct PROCESS_CONFIGURATION_RULE ProcessRule; struct REDIRECT_MAP *NextItem; }ConfigurationMap ,*PConfigurationMap;
Головной структурой выступает CONFIGURATION_MAP, которая хранит в себе ссылку на описание процесса ProcessRule, а так же указатель на следующий элемент. В свою очередь PROCESS_CONFIGURATION_RULE хранит ссылку на имя процесса и непосредственно на структуру правил перенаправления ввода/вывода, которая так же, как и REDIRECT_MAP является связным списком.
Рассмотрим функцию выгрузки драйвера, она достаточно проста:
VOID OnUnload( IN PDRIVER_OBJECT DriverObject ) { FltUnregisterFilter(fileManager.pFilter); FreeConfigInfo(); DbgPrint("MiniFilter: Unloaded"); }
Здесь мы лишь удаляем регистрацию фильтра и высвобождаем все наши конфигурационные структуры.
Теперь давайте обратимся к самой интересной части, а именно к функции, которая занимается перенаправлением операций ввода/вывода. Так как у нас достаточно простой драйвер, делать это мы будем прямо в PreFileOperationCallback.
FLT_PREOP_CALLBACK_STATUS PreFileOperationCallback ( __inout PFLT_CALLBACK_DATA Data, __in PCFLT_RELATED_OBJECTS FltObjects, __deref_out_opt PVOID *CompletionContext ) { NTSTATUS status; PFILE_OBJECT FileObject; PFileRedirectRule redirectRuleItem; PFLT_FILE_NAME_INFORMATION pFileNameInformation; PConfigurationMap rule; UNICODE_STRING fullPath; UNICODE_STRING processName; PWCHAR Volume; FLT_PREOP_CALLBACK_STATUS returnStatus = FLT_PREOP_SUCCESS_NO_CALLBACK; if(FLT_IS_FS_FILTER_OPERATION(Data)) { return FLT_PREOP_SUCCESS_NO_CALLBACK; }
Определяем основные переменные, а также проверим, не пришло ли нам уже что-то отфильтрованное, и если так, то эту операцию нужно пропустить, в противном случае мы можем получить рекурсию вызовов, что может повлечь BSOD.
if (FltObjects->FileObject != NULL && Data != NULL) { FileObject = Data->Iopb->TargetFileObject; if(FileObject != NULL && Data->Iopb->MajorFunction == IRP_MJ_CREATE) {
Здесь обращаемся к данным структур полученных от FilterManager. Структура PFLT_CALLBACK_DATA – хранит данные по текущей операции ввода/вывода, FilterManager руководствуется полями этой структуры при обращении к файловой системе. Соответственно, если мы хотим изменить поведение Windows при обращении к файлам или каталогам, мы должны отразить это в PFLT_CALLBACK_DATA. Более конкретно, нас интересует поле Data->Iopb->TargetFileObject, используя его мы сможем получить путь до файла в текущем разделе и позже изменить его при необходимости, изменив таким образом поведение ОС. PCFLT_RELATED_OBJECTS — содержит объекты связанные с данной операцией ввода/вывода, такие как ссылку на файл, раздел и прочее. Проверим, что нужные нам элементы структуры заполнены. Также проверим, что функция в контексте которой мы выполняемся действительно MJ_CREATE.
processName.Length = 0; processName.MaximumLength = NTSTRSAFE_UNICODE_STRING_MAX_CCH * sizeof(WCHAR); processName.Buffer = ExAllocatePoolWithTag(NonPagedPool, processName.MaximumLength,CURRENT_PROCESS_TAG); RtlZeroMemory(processName.Buffer, processName.MaximumLength); status = GetProcessImageName(&processName);
В этом участке кода мы выделяем память для пути и имени процесса. Не представляю какого размера будет строка, так что выделяем максимально возможную строку WCHAR. Исходный код GetProcessImageName рассматривать не буду, скажу только, что она возвращает полный путь до файла в следующем виде: \Device\HarddiskVolume4\Windows\notepad.exe. т.е раздел, ну и собственно, путь до файла.
if(NT_SUCCESS(status)) { if(LoggingEnabled()== 1) { DbgPrint("MiniFilter: Process: %ws", processName.Buffer); } } else { return FLT_PREOP_SUCCESS_NO_CALLBACK; } rule = FindRuleByProcessName(&processName,GetRedirectionMap());
Функция FindRuleByProcessName в случае успеха возвращает первый элемент связанного списка содержащего правила перенаправления по текущему процессу, в противном случае NULL.
ExFreePool(processName.Buffer); if(rule != NULL){ if(LoggingEnabled() == 1) { DbgPrint("MiniFilter: File name %ws", FileObject->FileName.Buffer); } redirectRuleItem = rule->ProcessRule.Rule;
Высвобождаем ненужную память и проверяем то, что мы получили какой-то объект, а не NULL. redirectRuleItem = rule->ProcessRule.Rule — обращение к первому правилу для данного процесса.
while(redirectRuleItem) { if(RtlCompareUnicodeString(&FileObject->FileName ,&redirectRuleItem->From, FALSE) == 0) { status = FltGetFileNameInformation( Data, FLT_FILE_NAME_NORMALIZED | FLT_FILE_NAME_QUERY_ALWAYS_ALLOW_CACHE_LOOKUP, &pFileNameInformation );
Начинаем проход по всем правилам для данного процесса, сравниваем ссылку на текущий файл с тем, что у нас есть в конфигурации. Если совпало, пытаемся получить дополнительную информацию о файле, например, к какому разделу он принадлежит. Для этого используем функцию FltGetFileNameInformation.
if(NT_SUCCESS(status)) { fullPath.Length = 0; fullPath.MaximumLength = NTSTRSAFE_UNICODE_STRING_MAX_CCH * sizeof(WCHAR); fullPath.Buffer = ExAllocatePoolWithTag(NonPagedPool, fullPath.MaximumLength, FULL_PATH_TAG); RtlZeroMemory(fullPath.Buffer, fullPath.MaximumLength); Volume = wcssplt(pFileNameInformation->Volume.Buffer, redirectRuleItem->From.Buffer ); RtlAppendUnicodeToString(&fullPath, Volume); RtlAppendUnicodeToString(&fullPath, redirectRuleItem->To.Buffer); ExFreePool(Volume); ExFreePool(FileObject->FileName.Buffer);
Если все ок, пытаемся выделить раздел, после чего формируем итоговую строку. Итоговый путь = Текущий раздел + Куда направить запрос ввода/вывода.
FileObject->FileName.Length = fullPath.Length; FileObject->FileName.MaximumLength = fullPath.MaximumLength; FileObject->FileName.Buffer = fullPath.Buffer; Data->Iopb->TargetFileObject->RelatedFileObject = NULL; Data->IoStatus.Information = IO_REPARSE; Data->IoStatus.Status = STATUS_REPARSE; DbgPrint("MiniFilter: Redirect done %ws", fullPath.Buffer); return FLT_PREOP_COMPLETE;
Далее, конфигурируем системные структуры, так чтобы File Manager еще раз обработал этот запрос, но только теперь уже по другому пути. Для этого важно проставить следующие значения полей Data->IoStatus.Information = IO_REPARSE и Data->IoStatus.Status = STATUS_REPARSE;, а так же указать новый путь до файла FileObject->FileName.Buffer = fullPath.Buffer;. В качестве результата функции возвращаем FLT_PROP_COMPLETE.
} } redirectRuleItem = redirectRuleItem->NextRule; } } } } return FLT_PREOP_SUCCESS_NO_CALLBACK; }
Не забываем перейти к следующему элементу списка перенаправлений. FLT_PREOP_SUCCESS_NO_CALLBACK возвращаем если делать с текущей операцией Filter Manger ничего не должен.
На данный момент переопределение ввода/вывода работает только в рамках одного раздела, как только отлажу вариант с поддержкой нескольких разделов, выложу.
Устанавливать мини фильтр необходимо при помощи специально оформленного inf файла, пример, которого Вы найдете в исходниках к данной статье.
Конфигурационный файл имеет следующий вид:
#minifilter config start { #logging : off #process : \Device\HarddiskVolume4\Windows\notepad.exe { #rule : redirect { #from : \test.txt #to : \data\test.txt } #rule : redirect { #from : \ioman.log #to : \IRCCL.ini } } }
Файл должен располагаться в корне диска C, имя должно быть: minifilter.conf.
Итак мы имеем возможность перенаправления запросов файлового ввода/вывода, однако реализовать в дополнение, скажем, механизм запрета доступа к файлу достаточно просто. Необходимо выделить файл, доступ к которому нужно запретить и указать следующее значение для поля системной структуры Data->IoStatus.Status = STATUS_ACCESS_DENIED;. Не забыть вернуть FLT_PROP_COMPLETE в качестве результата функции.
Чтобы стартовать или остановить сервис я использую KMD Manager. Для анализа утечек памяти PoolTag. Что касается отладки, то можно использовать DbgView, однако для Windows Vista и выше отладочные сообщения необходимо активировать, для этого нужно создать DWORD ключ в реестре по следующему пути HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Debug Print Filter с именем DEFAULT и значением 8.
Для запуска драйвера в 64 битной версии Windows 7 нужно будет отключить проверку подписи драйверов, для этого нужно перезагрузить компьютер, при старте системы нажать F8 и выбрать пункт Disable Driver Signature Enforcement, либо воспользоваться утилитой Driver Signature Enforcement Overrider(DSEO). Данная утилита позволит активировать тестовый режим отладки драйверов и подписать нужный драйвер фейковым сертификатом, что в конечном итоге позволит без проблем его использовать.
В не зависимости от того, включено логирование или нет, после запуска сервиса в DbgView Вы должны наблюдать нечто подобное.
А так наш драйвер будет выглядеть в DeviceTree
Могу добавить, что код пока еще достаточно сырой и требует доработок, однако в целом функционирует нормально. Собственно, если у Вас будет BSOD, я не виноват). Тестировал только на Windows 7 X86 и Windows 7 IA64.
Ссылка на исходники и утилиты: publish.rar
Что почитать:
PS. Хочу заметить, что не являюсь профессионалом в системном программировании, так что данная статья не претендует на полноту. По роду своей деятельности занимаюсь разработкой под Microsoft Dynamics CRM (.net, asp.net и прочее).
Буду рад Вашим комментариям.
ссылка на оригинал статьи http://habrahabr.ru/post/176739/
Добавить комментарий