Рефераты. Драйвер-фильтр сокрытия файлов в файловой системе NTFS

1.5 Механизмы взаимодействия драйвера режима ядра и

пользовательского приложения

Необходимо выбрать механизм для передачи команд от приложения пользовательского уровня к драйверу режима ядра и для передачи информации, собранной фильтр-драйвером режима ядра на пользовательский уровень в приложение, которое будет отображать данные в удобной для пользователя форме. В операционных системах Windows были обнаружены следующие средства решения данной задачи:

1. WMI - расширение WDM предоставляющие интерфейс через который драйверы могут взаимодействовать с приложениями. С точки зрения приложения WMI реализован как COM-интерфейс. Драйвер же реализует функции WMI через специальные точки входа, стандартизированные в расширениях WDM. С помощью WMI можно осуществлять мониторинг и управление устройствами компьютера с другой машины по сети.

2. Системные вызов API Win32 - кроме WMI точек входа, драйверы WDM поддерживают точку входа c кодом функции IRP_MJ_DEVICE_CONTROL, которая отвечает за программное управление устройством с локального компьютера. С точки зрения приложения обращение к этой функции выглядит как вызов API-функции DeviceIOControl.

Поскольку использование API вызова является более простым и быстрым в реализации подходом, чем доступ к информации через интерфейсы WMI, таким образом, значительно упрощая разработку приложения и драйвера, был выбран этот подход.

2. Технологическая часть

2.1 Структура разрабатываемого программного продукта

Необходимые для работы компоненты

1. Пользовательское приложение, обеспечивающее выбор файла для сокрытия,

Отображение уже скрытых файлов, загрузка и выгрузка драйвера

2. Драйвер-фильтр, обеспечивающий сокрытие файлов по разработанному алгоритму

Рис.6. Схема взаимодействия драйвера и приложения

Две компоненты системы взаимодействуют друг с другом через интерфейсы Win32 API. Приложение создает IRP с кодом IRP_MJ_DEVICE_CONTROL вызовом API-функции DeviceIoContol. Получив этот пакет, драйвер возвращает информацию о запросах, записывая ее в буфер, указанный приложением. Буфер представляет собой неструктурированный массив байтов, поэтому отдельным вопросом является упаковка и распаковка данных в соответствующие структуры.

2.2 Анализ и выбор алгоритма сокрытия данных

Для скрытия файлов на диске нам нужно перехватить функцию ZwQueryDirectoryFile из ntdll.dll. Она является базовой для всех API перечисления файлов.

API функции представляют и себя ничто иное, как функции в системных DLL. Любой процесс в системе обязательно имеет в своем адресном пространстве Ntdll.dll, где располагаются функции Native API - базовые функции низкоуровневой работы с системой, функции Kernel32.dll являются переходниками к более мощным функциям Ntdll, следовательно, целесообразно будет перехватывать именно функции Native API.

Проблема в том, что Native API функции не документированы в SDK, но узнать модель их вызова можно дизассемблируя Kernel32.dll. Нельзя утверждать, что адреса функций в системных библиотеках не изменяются в зависимости от версии ОС, ее сборки либо даже конкретной ситуации. Это происходит из-за того, что предпочитаемая база образа библиотеки (dll preferred imagebase) является константой, которую можно изменять при компиляции. Более того, совсем не обязательно, что dll будет загружена именно по предпочитаемому адресу, - этого может не произойти в результате коллизии с другими модулями, динамически выделенной памятью и т.п. Поэтому статический импорт функций происходит по имени модуля и имени функции (либо ее номера - ординала), предоставляемой этим модулем. Загрузчик PE файла анализирует его таблицу импорта и определяет адреса функций, им импортируемых. В случае, если в таблице импорта указана библиотека, не присутствующая в контексте, происходит ее отображение в требуемый контекст, настройка ее образа и ситуация рекурсивно повторяется. В результате в требуемом месте определенной секции PE файла (имеющей атрибут "readable") заполняется массив адресов импортируемых функций. В процессе работы каждый модуль обращается к своему массиву для определения точки входа в какую-либо функцию.

Следовательно, существуют два способа перехвата API вызовов: изменение точки входа в таблице импорта и изменение начальных байт самой функции (сплайсинг функции).

1. Изменение таблиц импорта. Этот метод выглядит так - определяется точка входа перехватываемой функции. Составляется список модулей, в настоящий момент загруженных в контекст требуемого процесса. Затем перебираются дескрипторы импорта этих модулей в поиске адресов перехватываемой функции. В случае совпадения этот адрес изменяется на адрес нашего обработчика.

К достоинствам данного метода можно отнести то, что код перехватываемой функции не изменяется, что обеспечивает корректную работу в многопоточном приложении. Недостаток этого метода в том, что приложения могут сохранить адрес функции до перехвата, и затем вызывать её минуя обработчик. Также можно получить адрес функции, используя GetProcAddress из Kernel32.dll.

Из-за этого существенного недостатка чаще пользуются вторым способом, который был выбран для реализации и в данном курсовом проекте.

2. Сплайсинг функции. Этот метод состоит в следующем - определяется адрес перехватываемой функции, и первые 5 байт её начала заменяются на длинный jmp переход по адресу обработчика перехвата.

Если необходимо вызывать перехватываемую функцию, то перед заменой необходимо сохранить её начальные байты и перед вызовом восстанавливать их.

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

Схематически перехват API функции можно изобразить так:

Рис.4. Перехват API функции

Теперь рассмотрим прототип функции ZwQueryDirectoryFile:

NTSTATUS ZwQueryDirectoryFile(IN HANDLE FileHandle,

IN HANDLE Event OPTIONAL,

IN PIO_APC_ROUTINE ApcRoutine OPTIONAL,

IN PVOID ApcContext OPTIONAL,

OUT PIO_STATUS_BLOCK IoStatusBlock,

OUT PVOID FileInformation,

IN ULONG Length,

IN FILE_INFORMATION_CLASS FileInformationClass,

IN BOOLEAN ReturnSingleEntry,

IN PUNICODE_STRING FileName OPTIONAL,

IN BOOLEAN RestartScan);

Для нас важны параметры FileHandle, FileInformation и FileInformationClass.

FileHandle - хэндл объекта директории, который может быть получен с использованием функции ZwOpenFile

FileInformation - указатель на выделенную память, куда функция запишет необходимые данные

FileInformationClass определяет тип записей в FileInformation.

FileInformationClass перечислимого типа, но нам необходимы только четыре его значения, используемые для просмотра содержимого директории.

FileDirectoryInformation:FileFullDirectoryInformation:

FileBothDirectoryInformation:FileNamesInformation:

Функция ZwQueryDirectoryFile записывает набор этих структур в буфер FileInformation. Во всех этих типах структур для нас важны только три переменных:

NextEntryOffset - размер данного элемента списка

Первый элемент расположен по адресу FileInformation + 0, а второй элемент по адресу FileInformation + NextEntryOffset первого элемента. У последнего элемента поле NextEntryOffset содержит нуль.

FileName - это полное имя файла.

FileNameLength - это длина имени файла

Для сокрытия файла, необходимо сравнить имя каждой возвращаемой записи и имя файла, который мы хотим скрыть. Если мы хотим скрыть первую запись, нужно сдвинуть следующие за ней структуры на размер первой записи. Это приведет к тому, что первая запись будет затерта. Если мы хотим скрыть другую запись, мы можем просто изменить значение NextEntryOffset предыдущей записи. Новое значение NextEntryOffset будет нуль, если мы хотим скрыть последнюю запись, иначе значение будет суммой полей NextEntryOffset записи, которую мы хотим скрыть и предыдущей записи. Затем необходимо изменить значение поля Unknown предыдущей записи, которое предоставляет индекс для последующего поиска. Значение поля Unknown предыдущей записи должно равняться значению поля Unknown записи, которую мы хотим скрыть. Если нет ни одной записи, которую можно видеть, мы должны вернуть ошибку STATUS_NO_SUCH_FILE.

1

Рис.5. Блок-схема алгоритма сокрытия файлов

2.3 Описание функций драйвера

Undocnt.h - заголовочный файл недокументированных функций и структур

Windwos NT, использованных в программе

Drvcomm.h - заголовочный файл определения структур

Структура буфера запроса о сокрытии файла

Собственный код IOCTL, с которым можно будет обращаться к драйверу при помощи вызова DeviceIOControl

IOCTL_PROTECT_FILE CTL_CODE(FILE_DEVICE_UNKNOWN, 0x01, METHOD_BUFFERED, FILE_READ_DATA | FILE_WRITE_DATA), где, макрос CTL_CODE собирает информацию о типе запроса и на его основе генерирует код запроса

Driver.c - главный файл драйвера, содержит все основные функции и точки входа, описанные в конструкторском разделе, а так же вспомогательные функции, реализующие сокрытие файлов.

DriverEntry - основная главная точка входа в драйвер, внутри этой функции драйвер выполняет инициализацию для себя и для используемого устройства (в данном случае - «\FileSystem\NTFS». В этой же функции в случае удачи создания устройства создается символическая ссылка на него, устанавливаются обработчики основных функций драйвера (IRP_MJ_CREATE, IRP_MJ_CLOSE, IRP_MJ_DEVICE_CONTROL), но т.к. нет смысла полностью переопределять обработчик открытия файла (IRP_MJ_CREATE), следует взять адрес старого обработчика и в новом, после выполнения некоторых действий, вызывать его.

Страницы: 1, 2, 3



2012 © Все права защищены
При использовании материалов активная ссылка на источник обязательна.