html текст
All interests
  • All interests
  • Design
  • Food
  • Gadgets
  • Humor
  • News
  • Photo
  • Travel
  • Video
Click to see the next recommended page
Like it
Don't like
Add to Favorites

Разработка MiniFilter драйвера из песочницы

Довелось мне как-то на работе столкнуться с задачей управления доступа и перенаправления запросов к файловой системе в рамках определенных процессов. Реализовать необходимо было простое, легко конфигурируемое решение.

Решил разрабатывать 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:
  1. Callbacks – ссылка на структуру, определяющую, что и при помощи каких функций мы собираемся обрабатывать.
  2. FilterUnload – функция, которая будет вызвана при отключении фильтра.
  3. 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

Что почитать:
  1. Документация MSDN
  2. Блог о файловых системах и фильтрах


PS. Хочу заметить, что не являюсь профессионалом в системном программировании, так что данная статья не претендует на полноту. По роду своей деятельности занимаюсь разработкой под Microsoft Dynamics CRM (.net, asp.net и прочее).

Буду рад Вашим комментариям.

Читать дальше
Twitter
Одноклассники
Мой Мир

материал с habrahabr.ru

3

      Add

      You can create thematic collections and keep, for instance, all recipes in one place so you will never lose them.

      No images found
      Previous Next 0 / 0
      500
      • Advertisement
      • Animals
      • Architecture
      • Art
      • Auto
      • Aviation
      • Books
      • Cartoons
      • Celebrities
      • Children
      • Culture
      • Design
      • Economics
      • Education
      • Entertainment
      • Fashion
      • Fitness
      • Food
      • Gadgets
      • Games
      • Health
      • History
      • Hobby
      • Humor
      • Interior
      • Moto
      • Movies
      • Music
      • Nature
      • News
      • Photo
      • Pictures
      • Politics
      • Psychology
      • Science
      • Society
      • Sport
      • Technology
      • Travel
      • Video
      • Weapons
      • Web
      • Work
        Submit
        Valid formats are JPG, PNG, GIF.
        Not more than 5 Мb, please.
        30
        surfingbird.ru/site/
        RSS format guidelines
        500
        • Advertisement
        • Animals
        • Architecture
        • Art
        • Auto
        • Aviation
        • Books
        • Cartoons
        • Celebrities
        • Children
        • Culture
        • Design
        • Economics
        • Education
        • Entertainment
        • Fashion
        • Fitness
        • Food
        • Gadgets
        • Games
        • Health
        • History
        • Hobby
        • Humor
        • Interior
        • Moto
        • Movies
        • Music
        • Nature
        • News
        • Photo
        • Pictures
        • Politics
        • Psychology
        • Science
        • Society
        • Sport
        • Technology
        • Travel
        • Video
        • Weapons
        • Web
        • Work

          Submit

          Thank you! Wait for moderation.

          Тебе это не нравится?

          You can block the domain, tag, user or channel, and we'll stop recommend it to you. You can always unblock them in your settings.

          • habrahabr.ru
          • домен habrahabr.ru

          Get a link

          Спасибо, твоя жалоба принята.

          Log on to Surfingbird

          Recover
          Sign up

          or

          Welcome to Surfingbird.com!

          You'll find thousands of interesting pages, photos, and videos inside.
          Join!

          • Personal
            recommendations

          • Stash
            interesting and useful stuff

          • Anywhere,
            anytime

          Do we already know you? Login or restore the password.

          Close

          Add to collection

             

            Facebook

            Ваш профиль на рассмотрении, обновите страницу через несколько секунд

            Facebook

            К сожалению, вы не попадаете под условия акции