Самоучитель по программированию систем защиты

         

Анализ сетевой архитектуры ОС



Анализ сетевой архитектуры ОС Windows NT с точки зрения возможностей реализации средств защиты и анализа сетевого трафика

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

Не на всех уровнях сетевой архитектуры операционная система Windows NT предоставляет возможность встраивания дополнительных программных модулей, которые могли бы контролировать вызовы сетевых программных компонент этого уровня и реализовывать функции защиты. Для реализации защиты на этих уровнях приходится использовать недокументированные методы, к которым относятся реализации защиты на уровне системных сетевых DLL, сетевых сервисов и «родного» API. Эти методы представлены в общем виде в этой главе, для их разработки использовался программный продукт фирмы NuMega - Softlce - средство отладки, работающее ниже уровня ядра операционной системы и позволяющее отлаживать такие компоненты ядра, как драйверы. С помощью Softlce удалось исследовать некоторые важные структуры данных.

Вначале будут рассмотрены основные факторы, влияющие на выбор конкретного способа реализации системы защиты.

Драйверы - фильтры



Драйверы - фильтры

Система ввода/вывода Windows NT является расширяемой. Один из методов расширения возможностей системы ввода/вывода - разработка и применение драйверов-фильтров.

Драйвер-фильтр - это промежуточный драйвер, перехватывающий запросы, предназначенные некоторым программным модулям (например, драйверу файловой системы или драйверу диска). Перехватив запрос, драйвер-фильтр имеет возможность либо расширения, либо замещения функциональности, обеспечиваемой первоначальным получателем запроса. Драйвер-фильтр может, либо использовать сервисы драйвера, которому изначально предназначался запрос, либо использовать сервисы других программных модулей уровня ядра для обеспечения дополнительной функциональности. Драйверы-фильтры могут встраиваться в любое место в стеке драйверов, их может быть несколько, в том числе расположенных подряд.

Примером использования драйвера-фильтра может послужить реализация прозрачного зашифрования/расшифрования данных, хранимых на диске или в сети. Ни одна из поставляемых с ОС Windows NT файловых систем (FASTFAT, NTFS, CDFS, LAN Manager Redirector) не обеспечивает поддержки прозрачного зашифрования данных перед записью их на диск (или отправкой в сеть) и расшифрования данных перед предоставлением их авторизированному пользователю. Но для того, чтобы выполнять шифрование, необязательно реализовать свой собственный драйвер файловой системы или специальный драйвер диска. Достаточно разработать драйвер-фильтр, располагающийся либо выше драйвера файловой системы (в этом случае драйвер-фильтр сможет обработать запрос перед тем, как драйвер файловой системы получит возможность его увидеть), либо ниже драйвера файловой системы (этот случай позволяет драйверу-фильтру выполнить любые требуемые операции после того, как драйвер файловой системы завершит свою задачу, и перед тем, как запрос будет получен драйвером диска (или сетевым драйвером)).

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

Присоединение к нужному объекту-устройству. Диспетчер ввода/вывода включает возможность присоединения к объекту-устройству (1), созданному некоторым драйвером (1), объекта-устройства (2), созданного другим драйвером(2). В результате такого присоединения пакеты IRP, направленные драйверу (1), ассоциированному с объектом-устройством (1), будут перенаправляться драйверу (2), ассоциированному с присоединенным объектом-устройством (2). Этот «присоединенный» драйвер (2) и является драйвером-фильтром.

Каждый объект-устройство имеет поле, называемое AttachedDevice и содержащее указатель на объект-устройство, созданное драйвером-фильтром, который первым присоединил свое устройство. Если это поле содержит NULL, значит нет присоединенных устройств. В процессе присоединения нового объекта-устройства, процедура присоединения пройдет по связанному списку присоединенных устройств до конца и выполнит присоединение нового объекта-устройства к последнему объекту-устройству в этом списке. В результате, любой запрос на создание/открытие, предназначающийся некоторому объекту-устройству, будет перенаправляться последнему (в его списке присоединенных устройств) объекту-устройству. То есть последнему драйверу-фильтру, ассоциированному с последним объектом-устройством. Аналогично, при вызове функции, которая по имени объекта-устройства возвращает его указатель, будет возвращен указатель на объект-устройство, являющийся последним в списке присоединенных устройств.

Но важно заметить, что диспетчер ввода/вывода проходит до конца списка присоединенных устройств не во всех случаях. Например, никакого перенаправления не будет, если будет вызвана функция IoCallDriver(). Следовательно, чтобы обращение к драйверу назначения не миновало драйвер-фильтр, этот драйвер-фильтр должен присоединить свой объект-устройство к объекту-устройству драйвера-назначения, прежде, чем вышележащий драйвер вызовет процедуру определения указателя нижележащего объекта-устройства по его имени (это делается во время запроса на открытие), а затем будет использовать этот указатель (в частности при вызове функции IoCallDriver()).

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

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

Отсоединение от объекта-устройства назначения.

Ниже рассмотрены основные принципы, которых необходимо придерживаться при разработке драйвера-фильтра, а также недостатки, из-за которых применение драйверов-фильтров становится нежелательным:



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

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

Например, в случае фильтрации всех запросов, предназначенных некоторому логическому тому, управляемому драйвером файловой системы (NTFS), необходимо понимать всевозможные пути вовлечения драйвера NTFS в обработку запроса, так как во всех этих случаях будут вовлекаться процедуры распределения драйвера-фильтра. Процедуры обработки запроса на чтения/запись драйвера файловой системы могут вовлекаться несколькими путями, например: из потока режима ядра, вызвавшего системные сервисы ZwReadFile(), ZwWriteFile(); из потока пользовательского режима, вызвавшего системные сервисы NtReadFile(), NtWriteFile(); из диспетчера памяти из-за обращения к отсутствующей странице спроецированного файла; из диспетчера кэша в результате асинхронного сброса буферов диспетчера кэша на диск; и так далее.

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

В случае, когда драйвер-фильтр привязывается к объекту-устройству нижнего уровня (например, к устройству, представляющему физический диск), то возникают другие проблемы, с которыми сталкивается подобный драйвер-фильтр. Например, пакеты IRP, посланные драйверу диска, могут быть чаще всего ассоциированными пакетами, созданными драйвером файловой системы, и поэтому драйвер-фильтр не должен пытаться в свою очередь создавать ассоциированные пакеты. Обычно от драйверов дисков ожидают, что они будут выполнять операции асинхронно, поэтому драйвер-фильтр должен удовлетворять этим ожиданиям.

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

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

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

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

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

Анализ сетевой архитектуры



Глава 4. Анализ сетевой архитектуры ОС Windows NT с точки зрения возможностей реализации средств защиты и анализа сетевого трафика



Используемые средства построения



Используемые средства построения объединенных сетей и их влияние на уровень расположения средства защиты (согласно модели OSI)

В качестве средства наращивания и интеграции сетей может использоваться:

1. Повторитель или концентратор. Устройство, которое передает электрические сигналы из одного кабеля в другой без маршрутизации или фильтрации пакетов и служит для наращивания однотипных сетей. В терминах OSI повторитель представляет собой промежуточное устройство физического уровня.

2. Мост. Устройство, соединяющее две или несколько физических сетей. Мосты могут фильтровать пакеты, то есть передавать в другие сегменты или сети только часть трафика на основе информации канального уровня (MAC - адреса), В терминологии OSI мост является промежуточной системой на уровне канала передачи данных.

3. Маршрутизатор. Функционирует на сетевом уровне эталонной модели OSI и служит для организации связи между сетями с одинаковыми сетевыми протоколами. Маршрутизатор принимает решение о передаче пакетов на основе различных критериев, основанных на информации сетевого уровня. Для передачи пакетов их адресатам оптимальным образом маршрутизатор использует протоколы маршрутизации, например RIP.

4. Шлюз. Обеспечивает взаимодействие систем и сетей, которые используют несовместимые протоколы. Предназначен для сопряжения локальных сетей с сетями других архитектур типа SNA, DNA, X.400. Работает на самом верхнем уровне стека протоколов.

Проблем с использованием средства построения объединенных сетей не возникнет, если это средство реализует межсетевое взаимодействие, используя уровни модели OSI, расположенные ниже уровня, на котором реализован модуль защиты. Если же средство построения объединенных сетей реализует межсетевое взаимодействие, используя уровни модели OSI, расположенные как ниже, так и выше уровня реализации модуля защиты, то необходимо рассмотреть следующее условие — можно ли в это средство встроить модуль защиты:

Если можно, то после встраивания модуля защиты, средство построения объединенных сетей не повлияет на работу сети, в которой используется данный модуль защиты.

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

Для иллюстрации этого случая рассмотрим пример реализации модуля, осуществляющего шифрование сетевых данных. Если модуль защиты располагается ниже сетевого уровня, то надо предусмотреть случай фрагментации пакета. То есть, если средство построения объединенных сетей разобьет зашифрованный пакет, принятый от системы-отправителя, использующего этот модуль защиты, то модуль защиты в системе-получателе должен уметь собрать фрагментированный пакет (реализовать функции сетевого уровня) и выполнить его расшифрование.

Объем информации, проходящей через средство защиты



Объем информации, проходящей через средство защиты

От расположения средства защиты относительно иерархии сетевых компонентов, зависит количество проходящих через это средство данных, и, следовательно, возмож-

ность контроля и защиты этих данных. Чем выше уровень реализации средства защиты, тем меньше объем данных, проходящих через него (смотри Рисунок 25 и 26, где представлена обобщенная сетевая архитектура ОС Windows NT).

Для иллюстрации этого принципа рассмотрим следующий пример. Если разработан драйвер транспорта с функциями шифрования, то надо учитывать, что процессам зашифрования и расшифрования будут подвергаться только те данные, для которых драйверы файловых систем выбирают именно этот транспорт. В данном случае можно повлиять на объем проходящих данных через собственный драйвер транспорта, удалив из системы все другие транспорты законными методами, используя, например, NCPA (Network Control Panel Application).

Но, к сожалению, не во всех случаях возможно такое простое изменение конфигурации ОС. Например, если защита осуществляется на уровне API Windows Sockets, то удалить API NetBIOS законными методами не удастся, а простое удаление библиотеки динамической загрузки, реализующей интерфейс NetBIOS, приведет к невозможности работы приложений, использующих это API.

Поэтому наиболее естественным, и не требующим дополнительного конфигурирования способом защиты относительно этого фактора является защита на уровне драйверов сетевых карт (или на уровне промежуточного драйвера, привязанного снизу к драйверам сетевых карт). Но тогда появятся проблемы относительно реализации в этих драйверах функциональности, не свойственной уровню модели OSI, на котором они располагаются.

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



Особенности реализации NDIS-драйвeров



Особенности реализации NDIS-драйвeров

Среда NDIS и NDIS-драйверы ранее уже описывались в разделе «Среда NDIS и NDIS драйверы». Хотя по требованиям переносимости NDIS-драйвер должен пользоваться только функциями, экспортируемыми средой NDIS, в реальных приложениях ничто не запрещает использовать функции ядра той ОС, под которую реализуется этот драйвер.

Переносимость и поддержка многопроцессорности. Драйвер должен быть написан на C/C++, причем необходимый формат результирующего файла может быть сгенерирован только компиляторами фирмы Microsoft. Драйвер должен избегать любой зависимости от типов данных, чей размер различается между платформами. Драйвер не должен вызывать никаких функций стандартных библиотек С, кроме функций обеспечиваемых NDIS. В режиме ядра не разрешены никакие операции с плавающей точкой.

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

Уровни IRQL исполнения функций драйвера. Любая функция драйвера, вызываемая NDIS, исполняется с уровнем приоритета, определяемым операционной системой, и варьирующимся между PASSIVEJLEVEL < DISPATCH_ LEVEL < DIRQL. Например, функция инициализации минипорта, функции останова, сброса и закрытия обычно исполняются с приоритетом PASSIVE_ LEVEL. Все другие функции NDIS-драйвера исполняются на уровне IRQL меньшем или равном DISPATCHJLEVEL. Промежуточный NDIS-драйвер или драйвер протокола никогда не исполняются на уровне DIRQL.

Уровень приоритета потока драйвера влияет на то, какие функции NDIS библиотеки он может вызывать. Некоторые функции могут вызываться только, если уровень приоритета PASSIVE_LEVEL, другие - если DISPATCH_LEVEL или ниже.

Выгружаемый и уничтожаемый код драйвера. Функции драйвера, которые всегда выполняются с уровнем приоритета PASSIVE_LEVEL, могут быть помечены как pageable (выгружаемые), используя макрос NDIS_PAGEABLE_ FUNCTION. При разработке драйверов поощряется помечать код как выгружаемый где это возможно, освобождая тем самым системную область памяти для кода, который должен быть резидентным. Функция драйвера, которая исполняется с приоритетом PASSIVEJLEVEL, может быть помечена как выгружаемая, если она сама не вызывает или ее не вызывает любая функция, которая исполняется с приоритетом большим или равным DISPATCH_LEVEL. (Например, если она не вызывает функцию, которая захватывает спин-блокировку, так как ее захват вызывает повышение уровня приоритета захватывающего потока до уровня DISPATCH_LEVEL).

Функция DriverEntry промежуточного драйвера должна быть определена как функция инициализации, используя макрос NDIS_INIT_FUNCTION. Предполагается, что код, помеченный этим макросом, исполняется только один раз во время инициализации системы, в результате чего он присутствует в оперативной памяти только в это время. После того как функция DriverEntry вернет управление, код, помеченный как NDIS_INIT_FUNCTION, уничтожается из оперативной памяти.

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



реализации драйвера шифрования



Пример реализации драйвера шифрования сетевых пакетов

В качестве основы драйвера, шифрующего сетевые пакеты, может быть использован пример промежуточного драйвера ImSamp с сервера Microsoft (для NT 4.0) или промежуточный драйвер Passthru из DDK (NtDDK\src\network\ ndis\passthru, для Win2000). Этот NDIS-драйвер промежуточного уровня разработан так, чтобы располагаться между транспортным драйвером и драйвером сетевой карты (Рисунок 30). Единственное что он делает - это получает пакеты от драйвера сетевой карты и передает их драйверу транспорта, и наоборот.

Используя Network Control Panel Application (NCPA), можно привязать протокол TCP/IP к виртуальному адаптеру, создаваемому драйвером ImSamp, а все остальные привязки заблокировать. Для драйвера ТСР/IP промежуточный драйвер ImSamp выглядит как драйвер виртуальной сетевой карты. А для реальной сетевой карты Ethernet драйвер ImSamp выглядит как драйвер протокола.

Если разрабатываемый драйвер шифрования не реализует функции сетевого уровня модели OSI, то его нельзя будет использоваться в сети, где в качестве средств построения объединенных сетей используются маршрутизаторы, работающие на сетевом уровне. Так как такой драйвер не рассчитан на то, что выпущенный зашифрованный пакет может быть фрагментирован, поэтому его можно использовать только в сегменте сети до маршрутизаторов.



Реализация защиты на уровне драйвера MUP



Реализация защиты на уровне драйвера MUP

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

После запроса на создание/открытие файла по имени UNC драйвер mup.sys уже не вовлекается в обработку дальнейших запросов к этому файлу. То есть через драйвер mup.sys данные пользователя не проходят, но этот драйвер играет важную роль при открытии файлов и устройств по имени UNC. В связи с этим стоит рассмотреть возможность регистрации и аудита открытия файлов и устройств с помощью драйвера-фильтра, присоединенного к драйверу mup.sys. Так как драйвер mup.sys, является довольно простым драйвером, то реализация драйвера-фильтра в данном случае допустима.



Реализация защиты на уровне драйверов файловых систем



Реализация защиты на уровне драйверов файловых систем

Драйвер файловой системы обеспечивает пользователям механизм хранения и получения информации, реализуя возможности по созданию, модификации, удалению, разделению файлов и идентификации файлов по их символическим/логическим именам. В дополнение к этим функциям сетевые файловые системы обеспечивают в той или иной степени прозрачность местоположения файлов в сети и мобильность файлов. Файловые системы бывают разных типов, например: локальные файловые системы (FASTFAT, NTFS); сетевые и распределенные файловые системы (редиректор + сервер); псевдофайловые системы, предоставляющие пользователю интерфейс, подобный интерфейсу файловых систем, но реализующие особую функциональность, отличную от традиционных задач хранения и предоставления данных с диска, к ним относятся файловые системы, реализующие механизм межпроцессной коммуникации (NPFS-файловая система именованных каналов и MSFS-файловая система почтовых ящиков).

В принципе, ОС Windows NT предоставляет стандартный механизм для перехвата всех запросов ввода/вывода к драйверу файловой системы - это описанный выше механизм использования драйверов - фильтров. Для реализации этого механизма можно воспользоваться книгой автора Rajeev Negar «Windows NT File System Internals: A Developer's Guide», которая посвящена разработке драйверов файловых систем, и затрагивает вопрос реализации и драйверов-фильтров.

Основным недостатком, мешающим реализации защиты на уровне драйверов файловых систем, в том числе и реализации драйверов-фильтров, является закрытость и недокументированность интерфейса с этими драйверами. Microsoft неохотно поддерживает разработку драйверов файловых систем. В MSDN разработка драйверов файловых систем не документирована. И все же в 1997 году Microsoft выпустила документацию WinNT IFS Kit, необходимую для разработки драйверов файловых систем, и содержащую требуемые заголовочные файлы для успешной компиляции драйверов файловых систем с помощью компилятора Microsoft Visual C++, а также примеры простейших файловых систем. Но это средство разработки дорого стоит (1000$). Из-за этого многие запросы к драйверам файловых систем остаются загадкой, и поэтому применение драйверов-фильтров, в данном случае, скорее всего, приведет к некорректной работе стека драйверов.

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

Согласно реестру Windows NT почти все части сетевых служб, исполняющихся в режиме ядра, являются драйверами файловых систем, а именно: встроенный сетевой редиректор frdr.sys) и сервер (srv.sys), файловая система именованных каналов (npfs.sys), файловая система почтовых ящиков (msfs.sys), эмулятор интерфейса NetBIOS

(netbios.sys). Только эмулятор интерфейса WinSock (afd.sys) имеет тип стандартного драйвера. (Все эти вышеперечисленные драйверы являются TDI-клиентами и предоставляют в своей нижней части интерфейс TDI.)

Но далеко не все из этих драйверов действительно реализуют в своей верхней части интерфейс драйвера файловой системы. Например, драйвер srv.sys, который отвечает на запросы, посылаемые редиректором с удаленной машины, и взаимодействует с локальной файловой системой, не нуждается в предоставлении интерфейса файловой системы. Аналогично, драйвер netbios.sys не предоставляет интерфейс файловой системы, так как он является частью эмулятора сетевого интерфейса NetBIOS и взаимодействует с соответствующей DLL для преобразования «родных» функций этого интерфейса, вызываемых приложениями, в TDI-функции для вызова драйверов транспортов. Драйвер netbios.sys имеет только одну процедуру распределения для пакетов всего четырех типов IRP_MJ_CREATE, IRP_MJ_CLOSE, IRP_MJ_DEVICE_ CONTROL, IRP_MJ_CLEANUP, в то время как драйвер файловой системы должен обрабатывать гораздо большее число специфичных для него типов пакетов. Возможно, что это обстоятельство облегчит разработку драйвера-фильтра к нему. Чего нельзя сказать, о драйверах srv.sys и afd.sys, которые регистрируют одну процедуру распределения для всевозможных типов пакетов IRP, поэтому потребуется более детальное исследование того, какие из этих пакетов они действительно обрабатывают, а для каких просто имеют заглушки.

Архитектура Windows NT позволяет инсталлировать собственные драйверы файловых систем, это сделано специально для того, чтобы ОС Windows NT обладала способностью подключения к разнообразным сетям. Поэтому возможна разработка собственного редиректора, который сможет заменить встроенный и реализовывать функции защиты (при этом надо выгрузить встроенный редиректор и сервер).

Если разрабатывается собственный редиректор, который будет единственным в системе, то нужно также разработать и DLL сетевого провайдера (DLL компонента сетевого доступа), позволив тем самым приложениям использовать сетевой интерфейс API Win32 (WNet) для запроса сервисов от этого редиректора. Если редиректор выполняется одновременно со встроенным, то соответствующую ему библиотеку DLL провайдера можно не реализовывать, тогда запросы по интерфейсу API WNet через собственный редиректор не пойдут, а пойдут только запросы по стандартному интерфейсу API ввода/вывода через драйвер mup.sys.

Чтобы зарегистрировать DLL сетевого провайдера для MPR (предоставляющего API WNet), нужно изменить реестр. Порядок, в котором вовлекаются DLL провайдеров, зависит от порядка, в котором перечислены эти провайдеры в реестре. MPR изучает содержимое следующего ключа реестра, чтобы определить какие DLL провайдеров существуют в системе и порядок, в котором они будут вовлекаться:

HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\NetworkProvi-der\order.

В этом ключе содержится список имен провайдеров. Каждое имя соответствует некоторому сервису в ключе HKEY_LOCAL_MACHINE\System\CurrentControl-

Set\Services\«HMfl провайдера», который должен создаваться при инсталляции сетевого провайдера.

В книге автора Rajeev Negar «Windows NT File System Internals: A Developer's Guide» описаны функции, которые могут быть реализованы в DLL провайдера. Из них только одна функция, позволяющая пользователям узнать возможности сети, является обязательной.

DLL сетевого провайдера вовлекает сетевой редиректор в обработку запроса, используя управляющие запросы к файловой системе (FSCTL) через функцию DeviceloControl(). Эти запросы передаются менеджером ввода/вывода редиректору в пакете IRP со значением кода основной функции IRP_MJ_FILE_SYSTEM_CONTROL.

Ниже рассматриваются основные отличительные особенности драйверов файловых систем:

Первой особенностью драйверов файловых систем является то, что они находятся в вершине стека драйверов, и поэтому гарантированно вызываются в контексте потока, инициировавшего запрос. Эта особенность позволяет драйверам файловых систем манипулировать данными из адресного пространства инициатора запроса, а также осуществлять «быстрый» ввод/вывод (fast I/O), при котором диспетчер ввода/вывода вызывает драйвер файловой системы без использования пакетов запроса IRP, а с помощью определенной функции с параметрами, необходимыми для выполнения запроса драйвером файловой системы.

Учитывать эту особенность (заключающуюся в том, что запрос ввода/вывода должен передаваться драйверу файловой системы в контексте потока - инициатора запроса) необходимо также при разработке драйвера-фильтра, присоединяемого к драйверу файловой системы.

Следующая особенность драйвера файловой системы заключается в тесном взаимодействии с подсистемой виртуальной памяти, то есть с менеджером памяти и менеджером кеша. Для иллюстрации этого взаимодействия рассмотрим, упрощенно, «быстрый» запрос на чтение данных от диспетчера ввода/вывода к драйверу файловой системы. По этому запросу драйвер файловой системы вызывает диспетчер кэша, который в свою очередь копирует требуемые данные из системного кэша (если все необходимые данные там присутствуют) в пользовательский буфер. Если же не все требуемые данные присутствуют в кеше, то возникает ошибка страницы, в результате чего вовлекается диспетчер памяти для загрузки в память данных из файла на диске. Для этого он строит пакет запроса ввода/вывода IRP для получения данных с диска и передает этот IRP драйверу файловой системы. В результате данные будут прочитаны с диска в системный кэш, после чего диспетчер кеша скопирует данные из системного кэша в пользовательский буфер.

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

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

Кроме описанных выше взаимосвязей с подсистемой виртуальной памяти драйвер файловой системы очень тесно (в отличие, например, от драйвера физического устройства) взаимодействует с менеджером ввода/вывода и менеджером объектов, так как каждая файловая система в Windows NT отвечает за реализацию своей части системного пространства имен. При открытии файла диспетчер ввода/вывода вызывает диспетчер объектов, который просматривает свое пространство имен, до тех пор, пока не попадает в объект-устройство. После чего разрешением оставшейся части имени файла занимается соответствующий драйвер файловой системы в тесной взаимосвязи с менеджером ввода/вывода.

Несмотря на желание разработчиков ОС Windows NT поддерживать четкое разграничение между файловой системой и остальной частью ОС, постепенно эта граница размывалась, и все более и более скрытая функциональность проникала в систему, есное взаимодействие драйвера файловой системы с различными компонентами исполнительной системы: менеджеров ввода/вывода, менеджером объектов, менеджером памяти и менеджером кэша, естественно, отражается на сложности разработки обственного драйвера файловой системы.

Примером реализации фильтра к драйверам файловой системы может служить рограмма FileMon, разработанная Mark Russinovich и Bruce Cogswell.

Реализация защиты на уровне драйверов сетевых устройств



Реализация защиты на уровне драйверов сетевых устройств

Сюда входят драйверы сетевых карт, драйверы устройств, которым могут направляться сетевые запросы ввода/вывода (например, драйвер последовательного порта). Реализация защиты на этом уровне зависит от того, разработан ли драйвер устройства в соответствии со стандартом NDIS (то есть это NDIS драйвер сетевой карты локальной или глобальной сети), или же это драйвер устройства, управляемый пакетами IRP. В первом случае лучше всего перейти к реализации промежуточного драйвера, а во втором к реализации драйвера-фильтра. Также на этом уровне возможна реализация

собственного драйвера устройства со встроенными функциями защиты, так как разработка подобных драйверов хорошо документирована в Windows NT DDK.

Разработка собственных драйверов сетевых карт глобальных и локальных сетей, поддерживаемых интерфейсом NDIS, хорошо документирована в DDK в главе Network drivers, в части Miniport NIC drivers и имеется ряд примеров в DDK\src\network. Если же сетевое устройство не является сетевой картой, то можно воспользоваться общими рекомендациями по разработке драйверов устройств в главе Kernel-mode drivers.

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



Реализация защиты на уровне приложений и собственных DLL



Реализация защиты на уровне приложений и собственных DLL

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

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

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

Реализация защиты на уровне собственной DLL также не обеспечивает прозрачной защиты. Для того чтобы защиту данных приложений выполняла некоторая библиотека DLL, необходимо, чтобы приложения были разработаны так, чтобы вызывать функции из этой DLL. После того, как исполняемый код DLL спроецируется на адресное пространство вызывающего процесса (при помощи неявной компоновки при загрузке приложения, или явной - в период выполнения), код и данные DLL просто становятся частью процесса приложения.

Уровень приложений и собственных DLL не имеет всех возможностей по реализа-циичфункций защиты сетевых ресурсов. Интерфейсы API, предоставляемые операционной системой приложениям и DLLs, не позволяют им выполнять пассивный или активный мониторинг сетевых пакетов или контролировать доступ к сетевым системным DLL и сетевым сервисам. Единственное, что они могут - это зашифровывать собственные данные перед отправкой их в сеть и расшифровывать после получения их из сети для предоставления пользователю. Только лишь во взаимодействии с собственным драйвером приложение может стать мощным средством защиты, причем в некоторых случаях приложению приходится использовать недокументированные методы.



Реализация защиты на уровне «родного» API для ОС Windows NT



Реализация защиты на уровне «родного» API для ОС Windows NT

Один из способов обеспечения прозрачной защиты связан с заменой механизма предоставления функций «родного» API. Как уже рассматривалось в главе «Общая архитектура Windows NT», системные сервисы могут быть предоставлены коду пользовательского режима посредством использования библиотеки ntdll.dll. В ntdll.dll вызов системных сервисов происходит с помощью программного прерывания int 2E. После чего обработчик прерывания для вызова соответствующего системного сервиса использует таблицу распределения системных сервисов (KeServiceDescriptorTable), экспортируемую ntoskrnl.exe (ядро и исполнительная система). Структура этой таблицы следующая:

1. указатель на массив, содержащий 4-х байтовые адреса точек входа системных сервисов в ntoskrnl.exe (4 байта);

2. 00000000 (4 байта);

3. количество системных сервисов (4 байта);

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

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

Так как эта таблица находится в системной области памяти, то такая замена может выполняться только кодом режима ядра (например, драйвером).

Примером подобного средства защиты может служить программный продукт RegMon for Windows NT, выполняющий перехват системных сервисов, реализующих обращение к реестру Windows NT.



Реализация защиты на уровне сетевого драйвера промежуточного уровня, поддерживающего интерфейс NDIS



Реализация защиты на уровне сетевого драйвера промежуточного уровня, поддерживающего интерфейс NDIS

Разработка NDIS драйверов промежуточного уровня - это один из хорошо документированных механизмов расширения возможностей системы ввода/вывода ОС Windows NT. Библиотека NDIS является очень мощным средством, используемым для разработки драйверов сетевых карт глобальных и локальных сетей и промежуточных драйверов. С использованием библиотеки NDIS можно реализовать промежуточные драйверы, служащие самым разным целям, включая фильтрацию и шифрование пакетов. Промежуточные драйверы хорошо документированы в Windows NT DDK в главе Network drivers, в части Intermediate NDIS drivers and TDI drivers. Имеется пример промежуточного драйвера packet в DDK\src\ network\packet\driver. Типичные варианты реализации промежуточного драйвера рассмотрены на Рисунок 27.



Реализация защиты на уровне сетевых сервисов



Реализация защиты на уровне сетевых сервисов

Почти все сетевые сервисы, такие как сервис рабочей станции (LanMan Workstation), сервис сервера (LanMan Server), сервис оповещений (alerter), сервис сообщений (messenger), обозреватель сети (Computer Browser), DHCP client содержатся в одном ЕХЕ-файле с именем services. Остальные исполняются в собственных процессах, например, wins.exe (WINS SERVER), dns.exe (Domain Name System (DNS) Server), rpcss.exe (RFC and Distribute COM Services), locator.exe (RFC locator), nddeagnt.exe (Network DDE Agent), tapisrv.exe (Microsoft Windows Telephony Server), rassrv.exe (Remote Access Server Supervisor).

Сетевой сервис - это серверный процесс. Некоторые сетевые сервисы выполняются в фоновом режиме, в то время как другие предоставляют API через соответствующие DLLs клиентской стороны, например: 1) tapi32.dll (Microsoft Windows Telephony API Client DLL) предоставляет приложениям взаимодействие с tapisrv.exe; 2) библиотека rassapi.dll (Remote Access Admin APIs dll) может использоваться приложениями для конфигурирования RAS сервера (rassrv.exe); 3) библиотека netapi32.dll (Net Win32 API DLL) используется приложениями для взаимодействия с browser, workstation, server, alerter, messenger, replicator; и т.д.

Сервисы, имеющие собственные API, для обмена данными со своими клиентами обычно используют механизм RPC. При этом для реализации RPC-запросов между процессами, исполняющимися на одной машине, в действительности используется механизм LPC.

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

Чтобы контролировать передачу данных между клиентским процессом и процессом сетевого сервиса, исполняющимися на одной машине, нужно вмешаться в механизм LPC. В общих чертах он выглядит следующим образом: после, того как клиентский запрос к сервису перехватится заглушкой в DLL клиентского процесса, эта заглушка упакует параметры вызова, необходимые процедуре сервиса. Затем LPC обеспечит механизм передачи данных клиентского процесса процессу сетевого сервиса с помощью разделяемой памяти или с помощью посылки сообщения в очередь сообщений. Затем после того как процедура сервиса выполнит необходимую обработку, LPC возвратит результаты обработки обратно клиентскому процессу с помощью тех же механизмов.

LPC при своей работе создает и использует объекты-порты (port object), которые в качестве атрибутов имеют описатель секции (описатель совместно используемой памяти) и указатель на очередь сообщений. Объект-порт ассоциируется с серверным процессом и обеспечивает выполнение сервисов для клиентского процесса. Объект-порт как бы представляет канал для передачи сообщений между клиентским и серверным процессами. Серверный процесс определяет для связи с собой именованный объект-порт (порт соединения), а когда клиентский процесс свяжется с этим именованным портом, серверный процесс создаст два неименованных объекта-порта (порты связи) для взаимодействия.

API, необходимое для получения доступа к сервисам LPC, не документировано. Скорее всего, заглушка в клиентской DLL получает обслуживание LPC через прерывание int 2E с помощью вызова функций NtlmpersonateClientOfPort, NtAccept ConnectPort, NtCompleteConnectPort, NtCreatePort, NtReplyWaitReceive Port, NtReplyWaitReplyPort, NtRequestPort, NtConnectPort, NtReplyPort, NtRequest WaitReplyPort и т.п., предоставляемых библиотекой ntdll.dll. В результате чего обработчик прерывания 2Е (диспетчер системных сервисов) использует таблицу распределения системных сервисов (KeServiceDescriptorTable) для вызова соответствующих сервисов LPC из ntoskrnl.exe. Следовательно, для перехвата обращений к сервису LPC, можно воспользоваться методом перехвата обращений к системным сервисам, который будет рассмотрен ниже. Тем самым будет получен доступ к данным, передаваемым от клиентских процессов серверным процессам, в том числе и процессам сетевых сервисов.



Реализация защиты на уровне системных DLL, предоставляющих приложениям различные сетевые интерфейсы



Реализация защиты на уровне системных DLL, предоставляющих приложениям различные сетевые интерфейсы

К этим DLLs относятся, в первую очередь, следующие библиотеки DLLs сетевых интерфейсов:

1. Netapi32.dll (Net Win32 API DLL), предоставляющая интерфейс NetBIOS и Net-функции.

2. Ws2_32.dll (Windows Socket 2.0 32-Bit DLL), msafd.dll (Microsoft Windows Sockets 2.0 Service Provider), wsock32.dll (Windows Socket 32-Bit DLL), являющиеся DLLs интерфейса Windows Sockets.

3. Mpr.dll (Multiple Provider Router DLL), предоставляющая сетевой интерфейс Win32 (WNet), и реализующая функции маршрутизатора многосетевого доступа.

4. Kernel32.dll (Windows NT BASE API Client DLL) - DLL API ввода/вывода Win32, эта библиотека предоставляет стандартные функции, такие как функции открытия, закрытия, чтения, записи и т.п. Если файл, именованный канал, почтовый ящик, устройство, к которым обращены запросы в этих функциях, находятся на удаленной машине, то эти запросы пройдут по сети.

5. Wininet.dll (Internet Extensions for Win32), реализующая функции передачи HTTP запросов, навигации файлов по протоколу FTP, функции удаленного доступа в Internet, URL (Uniform Resource Еоса1ог)-функции и т.п.

6. Rpcrt4.dll (Remote Procedure Call Runtime), реализующая вызовы удаленных процедур.

7. Rasapi32.dll (Remote Access API), обеспечивающая приложениям удаленный доступ.

8. Nddeapi.dll (Network DDE Share Management APIs) и ddeml.dll (DDE management library), реализующие динамический обмен данными по сети.

9. Tapi32.dll (Microsoft Windows Telephony API Client DLL), предоставляющая приложениям телефонные сервисы.

10. Dlcapi.dll (DLC APIs), обеспечивающая взаимодействие по протоколу DLC.

11. Wsnmp32.dll (Microsoft WinSNMP Manager API DLL), mgmtapi.dll (Microsoft SNMP Management API DLL), snmpapi.dll (SNMP utilities DLL), используемые SNMP-приложениями.

Прежде чем перейти к рассмотрению возможностей реализации защиты на уровне этих DLL, ниже будет представлен процесс загрузки ЕХЕ-файла. При запуске ЕХЕ-файла загрузчик операционной системы выполняет следующие действия:

1. Отыскивает ЕХЕ-файл, создает новый объект-процесс, создает адресное пространство нового процесса размером 4 Гб. Резервирует регион адресного пространства - такой, чтобы в него поместился заданный ЕХЕ-файл. Желаемое расположение этого региона указывается внутри самого ЕХЕ-файла. По умолчанию базовый адрес ЕХЕ-файла - 0x00400000 (но при компоновке это значение может быть изменено). И отмечает, что физическая память, увязанная с зарезервированным регионом, - ЕХЕ-файл на диске.

2. Затем загрузчик просматривает таблицу импорта, содержащуюся в этом файле, и пытается найти и спроецировать на адресное пространство нового процесса все необходимые DLLs. Желаемое расположение региона адресного пространства, куда будет спроецирована конкретная DLL, указывается внутри самого DLL-файла, по умолчанию базовый адрес DLL-файла - 0x10000000. Но у всех стандартных системных DLL модулей (в том числе и вышеперечисленных), входящих в комплект поставки Windows NT, разные базовые адреса. Затем загрузчик отмечает, что физическая память, увязанная с зарезервированным регионом, - DLL-файл на диске.

3. Затем загрузчик сохраняет адреса импортируемых идентификаторов (функций и переменных), используемых ЕХЕ-файлом, в особой таблице адресов импорта (Import Address Table). Всякий раз, когда приложение ссылается на один из таких идентификаторов, сгенерированный компилятором код выбирает его адрес из таблицы и предоставляет необходимую связь.

Приведенная выше схема загрузки и исполнения функций DLL-файла позволяет реализовать защиту на уровне системных сетевых DLLs следующими способами:

Первый способ связан с использованием таблиц адресов импорта (Import Address Table). Для того чтобы найти эту таблицу, нужно сначала найти по сигнатуре «.idata» в таблице секций, располагающейся после заголовка файла, описание секции .idata. Секция .idata содержит информацию о функциях и данных, импортируемых приложением из DLLs. В описании этой секции есть поле, содержащее виртуальный адрес, по которому загрузчик отобразил секцию .idata. Содержимое этой секции начинается с массива структур, по одной структуре, содержащей пять элементов, на каждую DLL. Первый элемент каждой структуры указывает на таблицу имен функций, четвертый является указателем на имя DLL, а пятый указывает на таблицу адресов функций. Найдя по таблице имен функций имя нужной функции, можно узнать ее адрес в соответствующем элементе таблицы адресов функций, и затем заменить этот адрес на адрес собственного обработчика. Собственный обработчик может располагаться в собственной DLL. После завершения необходимой обработки нужно перейти по сохраненному первоначальному адресу импортируемой функции. Для того чтобы собственная DLL загружалась в адресное пространство любого процесса GUI-приложения, нужно в реестре в ключе HKEY_ LOCAL_MACHINE\ SYSTEM\ Software\ Microsoft WindowsNT\ CurrentVersion \ Windows\APPINIT_DLLS включить имя этой DLL.

В этом способе нужно менять таблицы адресов импорта во всех процессах приложений, использующих системные DLLs. Но обычно процессов в системе не так уж много (несколько десятков). Можно разработать приложение, которое будет получать описатели объектов-процессов, соответствующих защищаемым приложениям, а затем осуществлять чтение и запись в их виртуальные адресные пространства, с помощью функций ReadProssMem() и WriteProssMem().

Секция .idata имеет атрибут «для чтения и записи», поэтому запись в нее возможна.

Второй способ - создание собственной DLL, в которой необходимо заменить только часть функций, непосредственно через которые проходят данные пользователя. Имена этих новых функций должны быть сохранены. При этом новая DLL должна иметь то же имя. Или же в ключе реестра HKEY_LOCAL_ MACHINE \ SYSTEM \ CurrentControlSet \ Control \ Session Manager \ KnownDLLs должна быть строка: «имя заменяемой DLL без расширения» «имя собственной DLL с расширением dll», тогда система загружает вместо первоначальной DLL собственную библиотеку DLL из каталога, указанного в параметре реестра DllDirectory.

Так как все системные DLL имеют определенные базовые адреса, по которым они проецируются в адресное пространство процесса приложения, то преобразованная DLL не должна перекрывать базовый адрес другой системной DLL.

Третий способ основан на изменении части кода функций (необязательно начальной) вышеперечисленных DLL так, чтобы передать управление (с помощью команд call или jmp) собственному коду, реализующему необходимые функции по защите. Этот собственный код может содержаться в собственной DLL. Это изменение может происходить до загрузки DLL (исправляя DLL-файл во время загрузки ОС с помощью приложения-сервиса), или же можно модифицировать функции DLL после того, как DLL загружена. Этот способ основан на том, что все эыполняемые приложения, импортирующие функции некоторой DLL, совместно используют один и тот же код этой DLL, загруженный в оперативную память. И если страницы, содержащие код DLL, изменить, то это изменение коснется всех приложений.

Для реализации этого способа нужно разработать собственное приложение (или сервис), которое будет подгружать системные DLLs в свое адресное пространство. В этом приложении нужно получить адрес начала кода требуемой функции (например, с помощью функции GetProcAddress(«HMfl функции»)). Затем в том случае, если команда перехода будет находить не в самом начале, вычислить адрес кода, который будет перезаписан командой перехода, и по этому адресу осуществить запись инструкции JMP или CALL на адрес собственного обработчика, при этом надо сохранить перезаписываемые инструкции.

Но запись в секцию кода не так-то просто осуществить, так как эта секция является исполняемой и предназначена для чтения. Один из способов решения этой проблемы состоит в отображении физического адреса, соответствующего виртуальному адресу региона с кодом на другой регион, в который запись разрешена, и затем осуществить в него запись нужных инструкций. (Получится, что двум разным регионам виртуального пространства соответствует одна и та же физическая память). Но подобные махинации можно проводить только в драйвере, следовательно, для реализации защиты потребуется разработка драйвера. Возможно, что можно обойтись и без драйвера, воспользовавшись тем, что физическая память в ОС Windows NT представляется объектом-секцией, содержащимся в пространстве имен диспетчера объектов под именем \Device\PhysicalMemory. Приложение может спроецировать физическую память в свое адресное пространство и затем манипулировать ее содержимым.

Несмотря на то, что вышеперечисленные способы реализации защиты на уровне системных DLLs, кажутся довольно надуманными и неосуществимыми, подобные программные продукты все же существуют, например PRUDENS SPY product series, реализует технологию перехвата вызовов любых DLLs, в том числе и системных.

Реализация защиты на уровне транспортного драйвера



Реализация защиты на уровне транспортного драйвера

Транспортные драйверы являются, фактически, стандартными драйверами про-[ежуточного уровня и реализуют в своей верхней части стандартный интерфейс, со-тветствующий TDI спецификации. Этот интерфейс в основном базируется на полу-ении и обработке пакетов IRP_MJ_INTERNAL_DEVICE_CONTROL, содержащих различные значения контрольных кодов TDI_XXX, определяемых TDI спецификаци-й. В своей нижней части TDI драйвер взаимодействует с NDIS библиотекой.

Разработка драйверов транспорта хорошо документирована в Windows NT DDK в паве Network drivers, в части Intermediate NDIS drivers and TDI drivers. Имеется при-юр транспортного драйвера в DDK\src\network\tdi.

Можно также реализовать драйвер-фильтр, который будет присоединяться к объек-ам-устройствам, создаваемым драйвером транспорта. Например, к объектам-устрой-твам \Device\Tcp и \Device\Udp, создаваемым драйвером транспорта TCP/IP.

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

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

TDI предоставляет гибкую схему адресации, позволяющую определять и использовать множество форматов адресов.

TDI определяет механизм, с помощью которого драйвер транспорта может сигнализировать клиенту об интересующих клиента событиях, произошедших в сети (без предварительного запроса от клиента). Это происходит с помощью вызова драйвером транспорта функций, указатели на которые передаются TDI-клиентом драйверу транспорта в самом начале сетевых операций в пакете IRP_MJ_INTERNAL_DEVICE_ CONTROL с контрольным кодом TDI_SET_ EVENTJHANDLER, и регистрируются драйвером транспорта. TDI-клиент даже может использовать подобный механизм функций обратного вызова в качестве альтернативы обычным пакетам запросов к транспорту с контрольными кодами TDI_XXX. Когда драйвер транспорта вызывает подобную функцию, он может передать TDI-клиенту в качестве параметров некоторое ограниченное количество данных. При этом, если существует драйвер-фильтр, присоединенный к драйверу транспорта, то он не получит возможности проконтролировать эти данные.

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

В то же время сохраняется возможность разработки собственного драйвера транспорта с использованием документации DDK, который будет реализовать требуемые функции защиты.

Реализация защиты с помощью перехвата функций NDIS - библиотеки



Реализация защиты с помощью перехвата функций NDIS - библиотеки

Этот метод является трудоемким, совершенно не документированным, но в то же время чрезвычайно эффективным.

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

Рассмотрим упрощенно, какие NDIS-функции вызываются во время загрузки сетевых драйверов. Сначала инициализируется драйвер сетевой карты, при этом для него вызывается функция NdisMInitializeWrapper, предупреждающая NDIS о том, что инициализируется новый минипорт (драйвер сетевой карты). Затем вызывается функция NdisMRegisterMiniport, которая регистрирует точки входа MiniportAjcx минипорта для NDIS-библиотеки. Далее вызывается точка входа в драйвере сетевой карты Miniportlnitialize, которая готовит реальную или виртуальную сетевую карту для выполнения сетевых операций ввода/вывода, запрашивает все аппаратные ресурсы, необходимые сетевой карте, и размещает ресурсы, необходимые драйверу для выполнения сетевых операций ввода/вывода.

Затем начинают инициализироваться драйверы протоколов TCP/IP, NETBEUI, IPX/ SPX и т.д. Для каждого из них вызывается функция NdisRegisterProtocol, которая регистрирует точки входа ProtocoLAjcc драйвера и имя протокола для библиотеки NDIS.

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

Из приведенного выше процесса инициализации сетевых драйверов видно, что, перехватив вызовы таких функций как NdisMInitializeWrapper, NdisMRegisterMiniport, NdisRegisterProtocol, NdisOpenAdapter, можно отследить загрузку сетевого драйвера, и установить адреса своих обработчиков для точек входа MiniportXxx-процедур (например, Miniportlnitialize, MiniportQuery Information, MiniportSetlnformation, MiniportSend, MiniportSendPackets, MiniportTransferData и т.д.) и для точек входа ProtocolXxx-процедур (например, ProtocolBindAdapter, ProtocolReceive, Protocol ReceiveComplete, ProtocolSend Complete, ProtocolTransferDataComplete, ProtocolStatus, ProtocolPnPEvent, ProtocolReceiyePacket, ProtocolUnbindAdapter, ProtocolUnload и т.д.), а затем контролировать все сетевые операции ввода/вывода, проходящие через него.

Важно, чтобы ваш драйвер, который должен заменить адреса NDIS-функций в таблице экспорта библиотеки NDIS на адреса собственных обработчиков, загружался сразу после загрузки драйвера Ndis.sys. Иначе, к моменту загрузки вашего драйвера может уже произойти инициализация некоторых сетевых драйверов.



В первых двух случаях



Рисунок .27



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

Во втором случае NDIS драйвер промежуточного уровня будет взаимодействовать посредством использования пакетов IRP с драйвером устройства, не являющимся сетевой картой (следовательно, этот драйвер устройства не поддерживается интерфейсом NDIS, им может быть, например, драйвер последовательного устройства). Подобные NDIS драйверы промежуточного уровня поставляются с Windows NT, например asyncmac.sys, взаимодействующий с драйвером последовательного порта. Поэтому необходимость в их реализации возникает только в случае разработки собственного сетевого устройства (но тогда придется разрабатывать и драйвер этого устройства, куда можно встроить функции защиты), или в случае, когда необходимо полностью заменить встроенный драйвер (но в этом случае лучше реализовать драйвер-фильтр, присоединенный к драйверу устройства).

Третий случай соответствует случаю реализации драйвера транспорта, рассмотренного выше.

Во всех этих случаях промежуточный драйвер может реализовать в свой верхней части закрытый интерфейс, посредством использования пакетов IRP с вышележащим драйвером или приложением. (Для подобного взаимодействия вышележащий драйвер или приложение должны открыть устройство, созданное промежуточным драйвером.) Этот механизм используется в сетевых анализаторах, являющихся программными комплексами, состоящими из приложения и драйвера.

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

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

Надо отметить, что драйвер ndiswan.sys сам по себе является промежуточным драйвером, поставляемым Microsoft, и участвующим в поддержке взаимодействия по глобальным сетям типа ISDN, FR, Switched 56. Драйвер ndiswan.sys во взаимодействии с ndistapi.sys может служить провайдером сервисов TAPI. Для того чтобы контролировать трафик на уровне этих драйверов, необязательно заменять их собственными аналогами со встроенными функциями защиты, можно создать промежуточный драйвер, реализующий функции защиты, и расположить его выше или ниже этих драйверов.

Промежуточные драйверы являются неотъемлемой частью многих систем защиты сетевых ресурсов, реализующих пассивный мониторинг сетевых пакетов. Примером такой системы безопасности может служить программный анализатор сетевого трафика Microsoft Network Monitor. Также подобные драйверы являются частью многих систем безопасности, выполняющих фильтрацию и шифрование сетевого трафика, например, Guardian Windows NT Firewall.

Структура NDIS-пакета и NDIS-буфера



Рисунок . 28. Структура NDIS-пакета и NDIS-буфера



Расположение промежуточных NDIS-драйверов



Рисунок . 29. Расположение промежуточных NDIS-драйверов




Расположение драйвера шифрования



Рисунок . 30. Расположение драйвера шифрования


Как уже отмечалось выше, спецификация NDIS позволяет добавлять заголовки (или хвост) к NDIS-пакету без необходимости его перекопирования, во время передачи пакета по стеку сетевых драйверов. Таким образом, промежуточный драйвер шифрования может зашифровать пакеты двумя способами:

1. Путем модификации исходного NDIS-пакета, переданного драйвером транспорта, с добавлением к нему (при необходимости) новых NDIS- буферов.

2. Путем создания собственного NDIS-пакета копированием в него исходного NDIS-пакета, переданного драйвером транспорта, а затем уже модификации собственного пакета.

Первый способ использовать не рекомендуется, так как он чреват ошибками из-за использования библиотекой NDIS зарезервированных областей в пакете (при этом они будут перезаписываться). Но в простейших случаях этот способ работает.

При реализации первого способа важно помнить, что после завершения операции отправки зашифрованного пакета в сеть, в функции ProtocolSendComplete перед возвращением описателя пакета драйверу транспорта необходимо преобразовать пакет в исходное состояние. То есть расшифровать его и отделить все добавленные NDIS-бу-фера.

Драйвер может осуществлять зашифрование всех данных пакета, кроме Ethernet и IP-заголовков, IP-заголовок, при этом, можно создать свой, а первоначальный зашифровать.

Ключ шифрования обычно передается в драйвер шифрования из кода пользовательского режима (библиотеки DLL или приложения) с помощью вызова функции DeviceloControl. Обмен ключами между компьютерами можно, например, осуществить по схеме ISAKMP/Oakley, реализовав приложение (или библиотеку DLL), использующее интерфейс API WinSocket.

Если зашифрованный пакет имеет длину большую, чем первоначальный пакет, то для того, чтобы драйвер протокола не мог послать пакет такой длиной, что после добавления к нему необходимого числа байт промежуточным драйвером, драйвер сетевой карты (или сама сетевая карта) обрезал бы его (например, если длина преобразованного пакета вместе с Ethernet заголовком - 1514 байт), то при обработке запроса от драйвера протокола на максимально возможную длину пакета, значение, которое в действительности получено от драйвера сетевой карты, уменьшается на необходимое число байт.



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

Отправление:

1. MiniportSendPackets;

2. MiniportSend;

3. ProtocolSendComplete.

Получение:

1. ProtocolReceive;

2. ProtocolReceivePacket;

3. ProtocolTransferDataComplete;

4. MiniportReturnPacket;

5. MiniportTransferData.

Рассмотрим эти функции подробнее:

1. MiniportSendPackets - функция интерфейса верхнего уровня. Она получает несколько указателей на описатели NDIS-пакетов от драйвера транспорта. Во время инициализации, перед тем, как отправлять пакеты, драйвер транспорта опрашивает параметры конфигурации и характеристики нижележащего драйвера, в том числе и то, сколько пакетов одновременно он может передавать. Эта функция должна, по меньшей мере, заменить первоначальные описатели NDIS-пакетов, полученные от драйвера транспорта, на свои собственные, сохранив первоначальные описатели, а затем отослать обновленные описатели NDIS-пакетов ниже - драйверу сетевой карты с помощью функции Ndis SendPackets.

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

3. ProtocolSendComplete - функция интерфейса нижнего уровня. Она является завершающей функцией для функций NdisSendPackets и NdisSend. Она получает в качестве параметра статус завершения операции отправления пакета и описатель этого отправленного пакета (обновленный описатель). Эта функция, по меньшей мере, должна просигнализировать драйверу транспорта о завершении операции отправления, передав ему первоначальный описатель отправленного пакета.

4. ProtocolReceive - функция интерфейса нижнего уровня. Вызывается NDIS, после того как драйвер сетевой карты передал выше полученный пакет (или его часть).


По меньшей мере, эта функция должна скопировать предоставленные ей данные во вновь созданный NDIS-пакет и передать его выше драйверу транспорта (обычно описатель этого пакета и его буфера берутся, соответственно, из очереди NDIS-пакетов и очереди буферов, созданных во время инициализации драйвера). Если драйвер сетевой карты предоставил не весь пакет, то необходимо вызвать функцию NdisTransferData для получения оставшейся части пакета.

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

6. MiniportTransferData - это функция интерфейса верхнего уровня. Она вызывается для того, чтобы передать вышележащему драйверу транспорта оставшуюся часть полученного от драйвера сетевой карты пакета, ранее не переданную в драйвер транспорта с помощью функции NdisMXxxIndicateReceive. Эта функция требуется только в том случае, если промежуточный драйвер сигнализирует о полученных пакетах вышележащим драйверам путем вызова зависящих от типа сетевой карты функций NdisMXxxIndicateReceive (вместо Ххх могут быть Ethernet, TR, Fddi). Если же промежуточный драйвер всегда передает полученные пакеты наверх с по-

мощью вызова функции NdisMIndicateReceivePacket, то ему не надо обеспечивать функцию MiniportTransferData.

7. ProtocolTransferDataComplete - функция интерфейса нижнего уровня и является завершающей функцией для NdisTransferData. Она вызывается библиотекой NDIS, когда драйвер сетевой карты завершит операцию передачи оставшейся части пакета, ранее не переданной в ProtocolReceive. Эта функция, по меньшей мере, должна передать драйверу транспорта начало пакета, полученного ранее в функцию ProtocolReceive, вместе с остатком, полученным только что.

8. MiniportReturnPacket - функция интерфейса верхнего уровня и вызывается библиотекой NDIS, чтобы вернуть NDIS-пакет, который был ранее создан и предоставлен драйверу транспорта путем вызова функции NdisMIndicate ReceivePacket в функции ProtocolReceive или ProtocolReceivePacket или ProtocolTransferDataComplete.


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

При изучении этих функций видно, что процедуру зашифрования надо вставить в функцию MiniportSendPackets, либо если ее нет, то в MiniportSend, а расшифрования в функции ProtocolReceive, ProtocolTransferDataComplete, ProtocolReceivePacket, ProtocolSendComplete. Ниже следуют объяснения этого утверждения.

Зашифрование требуется перед тем, как пакеты передадутся драйверу сетевой карты для отправки в сеть, а это в промежуточном драйвере происходит в функции MiniportSendPackets, либо если ее нет, то в MiniportSend, в них и надо реализовывать процедуру зашифрования.

С расшифрованием дела обстоят гораздо сложнее, так как в процессе получения пакета из сети в промежуточном драйвере участвуют сразу несколько функций: ProtocolReceive, ProtocolTransferDataComplete, ProtocolReceivePacket, Minipor tRetumPacket, MiniportTransferData.

Так как в функцию ProtocolReceive может попасть не весь пакет, а только его начало, то расшифрование в этой функции осуществляется только в том случае, если в нее был передан весь пакет. Если в эту функцию попало только начало пакета, то будет вызвана функция NdisTransferData, которая заставит драйвер сетевой карты передать оставшуюся часть пакета, и тогда его расшифрование будет осуществляться в завершающей функции для функции NdisTransferData - функции ProtocolTransfer DataComplete, которая уже будет обладать полным пакетом.

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



Необходимость расшифрования пакетов ( в случае зашифрования первоначального пакета без его перекопирования), возвращаемых драйверу транспорта, в функции ProtocolSendComplete объясняется следующим образом. При отправлении данных из прикладной программы в сеть, переданные ею данные, проходя по стеку сетевых драйверов, не перекопируются в новые участки памяти, просто к ним добавляются разные сетевые заголовки, поэтому при зашифровании данных пакета в функции MiniportSendPackets или MiniportSend (если не создавался новый пакет и первоначальный не перекопировался в него) шифруются на самом деле данные в буфере, принадлежащем приложению, а оно вовсе может и не рассчитывать на это. Может даже произойти следующее: если ключ шифрования не изменять, то при повторной посылке данных в сеть, эти данные, наоборот, расшифруются и пойдут по сети в открытом виде. Чтобы избежать этой неприятности, необходимо, перед возвращением отправленного пакета драйверу транспорта со статусом операции отправления, расшифровать его.

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

При разработке драйвера шифрования важно помнить, что цепь NDIS-буферов в NDIS-пакете не всегда точно соответствует сетевым заголовкам (Ethernet, IP, TCP/UDP). NDIS-пакеты, передаваемые из драйвера протокола, например TCP/IP, в промежуточный драйвер для отправки, могут иметь очень разнообразную структуру, то есть разное количество NDIS-буферов и их содержимое. Вот лишь некоторые примеры (смотри Рисунок 31), которые удалось пронаблюдать с помощью программы-отладчика Soft Ice. (Разделение означает различные NDIS-буфера, заметьте, что Ethernet заголовок уже пристроен к пакету драйвером протокола ТСРЛР.)

При разборе пакета перед зашифрованием (чтобы определить, например, следует ли его шифровать) нужно учитывать, что данные пользователя и сетевые заголовки могут находиться в разных участках памяти, а иногда и вместе (в пакетах протокола ARP_RARP).

При разработке драйвера шифрования полезно использовать анализатор протоколов, например, Network Monitor, наблюдая с его помощью содержимое перехваченных пакетов. Если его драйвер-перехватчик, под названием bh.sys, привязать к реальной сетевой карте, то перехваченные им пакеты будут зашифрованными. Если же его привязать к виртуальной сетевой карте, создаваемой промежуточным драйвером шифрования, то перехваченные им пакеты будут уже расшифрованными.


Структуры NDIS-пакетов



Рисунок . 31. Структуры NDIS-пакетов




Синхронизация



Синхронизация

NDIS предоставляет механизм спин-блокировок, который может быть использован, чтобы синхронизировать доступ к общим ресурсам между потоками, имеющими одинаковый приоритет. Если два потока исполняются с различными приоритетами, то NDIS обеспечивает механизм временного повышения приоритета потока с более низким приоритетом. Любая функция драйвера, разделяющая ресурсы с процедурой ISR этого же драйвера, должна иметь возможность повышения своего IRQL до DIRQL, этот механизм обеспечивается NDIS.

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

Спин-блокировка (Spin Lock). Поток должен сначала захватить спин-блокировку, прежде чем пытаться получить доступ к защищаемым ресурсам. Спин-блокировка удерживает все потоки, кроме одного потока-владельца спин-блокировкой, от использования ресурсов. Другие потоки ждут освобождения спин-блокировки. Сетевые драйверы должны минимизировать время захвата спин-блокировки.

Функции для работы со спин-блокировками:

VOID NdisAllocateSpinLock (IN PNDIS_SPIN_LOCK SpinLock);

VOID NdisAcquireSpinLpck (IN PNDIS_SPIN_LOCK SpinLock);

VOID NdisReleaseSpinLock (IN PNDIS_SPIN_LOCK SpinLock);

VOID NdisFreeSpinLock (IN PNDIS_SPIN_LOCK SpinLock);

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

Таймеры создаются и инициализируются путем вызова функции NdisMInitialize Timer, и устанавливаются с помощью вызова NdisMSetTimer, или (если это периодический таймер) с помощью вызова функции NdisMSetPeriodic Timer. Функция NdisMCancelTimer сбрасывает таймер.

События (Events). События обеспечивают механизм синхронизации исполнения потоков. Один поток инициализирует событие с помощью вызова NdisInitializeEvent. Другой поток (или тот же самый), исполняемый с приоритетом PASSIVE_LEVEL, может вызвать функцию NdisWaitEvent, чтобы усыпиться до момента возникновения события, либо до истечения определенного им промежутка времени (что произойдет быстрее). Событие устанавливается и сбрасывается с помощью функций NdisSetEvent и NdisResetEvent соответственно.



Сравнительный анализ способов реализации защиты



Сравнительный анализ способов реализации защиты

В табл. 11 дается сравнительный анализ реализаций средств защиты относительно следующих факторов:

1. Объем контролируемых данных. Этот фактор определяет, какие данные проходят через средство защиты, и, следовательно, могут им контролироваться и обрабатываться.

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

3. Возможность встраивания защиты. Этот фактор определяет, предоставляет ли операционная система возможность встраивания средства защиты и расширения своей функциональности на данном уровне.

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

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



Структура NDIS-пакетов



Структура NDIS-пакетов

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

Описатель NDIS-пакета является структурой NDIS_PACKET, содержащей, помимо прочих, следующие поля (см. Рисунок 28):

1. закрытые области данных для драйвера минипорта и драйвера протокола;

2. флаги;

3. число физических страниц, содержащих пакет;

4. полную длину пакета;

5. указатели на описатели первого и последнего буфера пакета.

Описатель буфера является структурой NDIS_BUFFER (в действительности, NDIS_BUFFER определяется как тип MDL), содержащей, помимо всего прочего, следующее:

1. начальный виртуальный адрес буфера;

2. смещение буфера относительно страницы;

3. длину буфера в байтах;

4. указатель на описатель следующего буфера (или NULL, если его нет).

Основные функции для работы с NDIS-пакетами и NDIS-буферами:

1) NdisAllocatePacketPool - размещает и инициализирует пространство для пула описателей пакетов;

2) NdisAllocatePacket - размещает и инициализирует описатель пакета;

3) NdisReinitializePacket - удаляет все присоединенные буфера из пакета и инициализирует его для повторного использования;

4) NdisCopyFromPacketToPacket;

5) NdisQueryPacket - возвращает информацию о «шкете;

6) NdisFreePacket;

7) NdisAllocateBufferPool - возвращает указатель, с помощью которого затем можно разместить описатели буферов, вызвав NdisAllocateBufFer;

8) NdisAllocateBuffer-создает описатель буфера;

9) NdisChainBufferAtBack (NdisChainBufferAtFront) - присоединяет описатель буфера в хвост (в начало) цепи описателей буферов, присоединенных к пакету;

10) NdisCopyBuffer; И) NdisFreeBuffer;

12) NdisGetFirstBufferFromPacket - возвращает указатель на буфер первый в цепи буферов пакета;

13) NdisUnchainBufferAtBack, NdisUnchainBufferAtFront.



Сравнительный анализ реализаций средств защиты



Таблица 11. Сравнительный анализ реализаций средств защиты

Реализация защиты на уровне:

Объем контролируемых данных

Сложность реализации

Возможность встраивания защиты

Прозрачность защиты

Возможности, предоставляемые ОС

Приложения

Только данные самого приложения

Низкая

Да

-

Минимальные

Собствен ной DLL

Данные приложений, использующих эту DLL

Низкая

Да

Минимальные

Системной DLL

Данные приложений, использующих сис темную DLL

Высокая

Нет

+

Зависит от способа реализации

Сетевого сервиса

Зависит от сетевого сервиса

Высокая

Нет

+

Зависит от способа реализации

"Родного" API

Данные всех прило- жений

Высокая

Нет

+

Максимальные

Драйвера файловой системы

Данные приложений, использующих соот- ветствующее сетевое API

Высокая

Да

+

Максимальные

Транс- портного драйвера

Данные приложений, использующих этот транспорт + приложений, взаимодействующих с др. транспорта напрямую

Высокая

Да

+

Максимальные

Драйвера Ndis.sys

Данные всех приложений

Высокая

Нет

+

Максимальные

Промежуточного драйвера

Данные приложений, использующих транспорты, привя- занные снизу к этому промежуточному драйверу + приложе- ний, взаимодейст- вующих с промежу- точным драйвером напрямую

Средняя

Да

+

Максимальные

Драйвера сетевого устройства

Данные всех приложений

Средняя

Да

+

Максимальные

Средства защиты уровня ядра могут использовать внутренние интерфейсы для взаимодействия с компонентами исполнительной системы. Модуль защиты, исполняющийся в режиме ядра, может повышать или понижать текущий IRQL процессора, маскируя тем самым прерывания с равными или меньшими IRQL.

Модуль защиты, исполняющийся в режиме пользователя, не может получить доступа к системным данным иначе, как, вызвав предоставляемые операционной системой функции, которые в свою очередь вызывают системные сервисы, осуществив предварительную проверку параметров вызова.

Следовательно, относительно этого фактора логично оценивать средства защиты по 2-х бальной системе: если средство защиты реализуется как компонент уровня ядра, то ему будут предоставлены максимальные возможности со стороны ОС, если средство защиты реализуется как компонент уровня пользователя, то - минимальные.

Если для реализации защиты на уровне системной DLL или сетевого сервиса был разработан компонент, исполняющийся в режиме ядра, то это средство защиты также может использовать максимальные возможности, предоставляемые ОС. Для реализации защиты на уровне «родного» API в любом случае должен использоваться компонент уровня ядра.

Необходимо реализовать программу, обеспечивающую безопасный



Таблица 12


Требования к системе безопасности
Рекомендуемый уровень реализации средства защиты
Необходимо реализовать программу, обеспечивающую безопасный обмен данными по компьютерной сети
Реализация на уровне приложения и DLL
Необходимо обеспечить безопасный обмен данными по сети между приложениями, использующими конкретное сетевое API или контроль доступа к сетевому API
Реализация на уровне системной DLL, предоставляющей этот API, или на уровне "родного" API, или на уровне соответствующего драй- вера файловой системы
Необходимо обеспечить контроль доступа к сетевым сервисам
Реализация на уровне сетевого сервиса (которая сводится к реализации защиты на уровне системной DLL rpcrt4 или "родного" API) или реализация на уровне сетевых драйверов для контроля адресов
Необходимо обеспечить безопасный обмен данными по сети между приложениями, использующими конкретный транспортный протокол, или анализ трафика по этому протоколу
Реализация на уровне драйвера ndis.sys или драйвера транспорта или промежуточного драйвера или драйвера устройства
Необходимо обеспечить безопасный обмен данными по сети между любыми приложениями, или анализ всего трафика в сети
Реализация на уровне драйвера ndis.sys или промежуточного драй- вера или драйвера устройства

Точка входа DriverEntry



Точка входа DriverEntry

Точка входа DriverEntry промежуточного драйвера должна по крайней мере:

1. вызвать NdisMInitializeWrapper и сохранить возвращенный описатель NdisWrapperHandle\

2. вызвать функцию NdisIMRegisterLayeredMiniport, чтобы зарегистрировать свои точки входа MiniportA3cc;

3. вызвать функцию NdisRegisterProtocol, чтобы зарегистрировать свои точки входа ProtocoLEcc (если драйвер привязывается к нижележащему NDIS-драйверу);

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

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

Точки входа MiniportXxx



Точки входа MiniportXxx

Возможные обязательные и необязательные функции минипортовой части промежуточного драйвера перечислены ниже (подробное их описание смотри в DDK):

1. MiniportHalt - эта функция вызывается библиотекой NDIS, например, когда нижележащая сетевая карта зависла, и NDIS останавливает драйвер сетевой карты, или, например, когда ОС выполняет контролируемое закрытие системы.

2. Miniportlnitialize - эта функция вызывается в результате вызова промежуточным драйвером функции NdisIMInitializeDevicelnstance, чтобы инициализировать ми-нипортовые операции для инициализируемой виртуальной сетевой карты.

3. MiiuportQuerylnformation - эта функция получает запросы вида ОID_ХХХ, организованные (или просто передаваемые далее) вышележащим драйвером, вызвавшем NdisRequest с типом запроса NdisRequestQuerylnformation.

4. MiniportReset. NDIS может вызвать эту функцию промежуточного драйвера по приказу вышележащего драйвера протокола, вызвавшего NdisReset. Обычно, однако, драйвер протокола не инициирует сброс. NDIS, главным образом, инициирует сброс нижележащего драйвера сетевой карты и вызывает функции промежуточного драйвера ProtocolStatus и ProtocolStatusComplete, для того, чтобы информировать промежуточный драйвер о том, что нижележащий минипорт сбрасывает свою сетевую карту.

5. MiniportSetlnformation. Эта функция обрабатывает запросы вида OID_ХХХ, сделанные (или просто передаваемые далее) вышележащим драйвером, который вызвал NdisRequest с типом запроса NdisRequestSet Information.

6. MiniportSend. NDIS вызывает эту функцию для передачи одного пакета для нижележащего драйвера сетевой карты (или драйвера устройства). Функция MiniportSend (или функция MiniportWanSend) требуется, если промежуточный драйвер не обеспечил функцию MiniportSendPackets. Но лучше, чтобы всегда обеспечивалась функция MiniportSendPackets, чем функция MiniportSend (если только промежуточный драйвер не располагается всегда между драйверами, передающими по одному пакету за раз, или, если он не привязывается к нижележащему WAN NIC драйверу).

7. MiniportSendPackets. Эта функция получает массив из одного или более указателей на описатели пакетов для передачи в сеть. Лучше, чтобы каждый промежуточный драйвер обеспечивал функцию MiniportSendPackets, чем MiniportSend, если только он не привязывается к нижележащему WAN NIC драйверу, и должен при этом обеспечить функцию MiniportWanSend. Функция MiniportSendPackets обеспечивает лучшую производительность в независимости от того, располагается ли промежуточный драйвер над драйвером сетевой карты, который может передавать по несколько пакетов за раз, или только по одному пакету. И вне зависимости от того, располагается ли промежуточный драйвер под драйвером протокола, отправляющим по несколько пакетов за раз, или только по одному пакету.

8. MiniportTransferData. Эта функция вызывается, чтобы передать оставшуюся часть полученного из сети пакета, ранее представленного в буфере lookahead, переданном промежуточным драйвером в функцию NdisMA!ja:Indicate Receive. Этот пакет может быть конвертированным пакетом, полученным перед этим в

функции промежуточного драйвера ProtocolReceive или ProtocolReceivePacket. Функция MiniportTransferData требуется, если промежуточный драйвер представляет вышележащим драйверам полученные из сети пакеты путем вызова любой зависимой от среды функции NdisMA'Jalndicate Receive, кроме NdisMWan IndicateReceive. Если промежуточный драйвер всегда представляет пакеты путем вызова NdisMIndicateReceivePacket, то ему не нужно обеспечивать функцию MiniportTransferData.

9. MiniportReturnPacket. Эта функция получает возвращенный описатель пакета, который перед этим был представлен вышележащему драйверу путем вызова NdisMIndicateReceivePacket, тем самым возвращается контроль над ресурсами, представленными вышележащему драйверу. После того как каждое такое представление было обработано вышележащим драйвером, описатель пакета, размещенный промежуточным драйвером, и ресурсы, которые он описывает, будут возвращены функции MiniportReturnPacket, Функция Miniport ReturnPacket не нужна, если промежуточный драйвер всегда представляет пакеты наверх путем вызова зависящей от среды функции NdisMA3cdndicate Receive, или, если он всегда устанавливает статус в блоке данных ООВ, ассоциированном с каждым описателем пакета, в NDIS_STATUS_ RESOURCES, прежде чем вызвать NdisMIndicateReceivePacket.

10. MiniportCheckForHang. Эта функция вызывается через интервал времени, определенный библиотекой NDIS или промежуточным драйвером. Обычно, промежуточным драйверам не обязательно реализовать функцию MiniportCheckForHang, потому что подробные драйверы не могут удостовериться, что нижележащая сетевая карта зависла. Возможно, промежуточному драйверу следует обеспечить эту функцию, если он располагается над драйвером, не поддерживающим среду NDIS, чье состояние не доступно библиотеке NDIS.



Точки входа промежуточного NDIS- драйвера



Точки входа промежуточного NDIS- драйвера

Промежуточный NDIS-драйвер обычно экспортирует функции MiniportXxx на своем верхнем уровне и функции ProtocolXxx на своем нижнем уровне (см. Рисунок 29).

Реже промежуточный драйвер может экспортировать MiniportXxx функции на своем верхнем уровне, а в своей нижней части предоставлять частный закрытый интерфейс нижележащему драйверу, не являющемуся NDIS-драйвером. Например, промежуточный драйвер может управлять сетевыми запросами ввода/вывода для устройства, соединенного с последовательным портом. Подобный промежуточный драйвер будет экспортировать ряд функций MiniportXxx, чтобы взаимодействовать с NDIS в своей верхней части, а в своей нижней части использовать стандартные пакеты запроса ввода/вывода (IRP), чтобы взаимодействовать с драйвером последовательного устройства.

Если реализуемый промежуточный драйвер должен поддерживать ориентированный на соединение (Connection-Oriented) интерфейс нижнего уровня, либо поддерживать работу с NDISWAN, то необходимо реализовать как ряд дополнительных точек входа, так и изменить некоторые из описанных в этом разделе точек входа.



Точки входа ProtocolXxx



Точки входа ProtocolXxx

Возможные обязательные и необязательные функции протокольной части промежуточного драйвера перечислены ниже (подробное их описание смотри в DDK):

1. ProtocolBindAdapter - обязательная функция, вызываемая NDIS, чтобы попросить промежуточный драйвер привязаться к нижележащей реальной или виртуальной сетевой карте, чье имя передано в качестве параметра этой функции.

2. ProtocolUnbindAdapter - обязательная функция, вызываемая NDIS, чтобы закрыть соединение с нижележащей реальной или виртуальной сетевой картой, чье имя передано в качестве параметра этой функции.

3. ProtocolOpenAdapterComplete - обязательная функция. Если вызов функции NdisOpenAdapter промежуточным драйвером привел к возврату статуса NDIS_STATUS_ PENDING, то в последствии будет вызвана функция ProtocolOpenAdapterComplete, чтобы завершить присоединение.

4. ProtocolCIoseAdapterComplete - обязательная функция. Если вызов функции NdisCloseAdapter промежуточным драйвером привел к возврату статуса NDIS_ STATUS_PENDING, то в последствии будет вызвана функция ProtocolClose Adapter-Complete, чтобы завершить отсоединение.

5. ProtocoIReceive - обязательная функция. ProtocolReceive вызывается с указателем на буфер lookahead, содержащий данные, полученные из сети. Если этот буфер содержит не весь сетевой пакет, то ProtocolReceive вызывает NdisTransferData с описателем пакета для того, чтобы получить оставшуюся часть сетевого пакета. Если нижележащий драйвер вызвал NdisMIndicateReceive Packet для указания о получение сетевого пакета, то буфер lookahead, переданный ProtocolReceive, всегда будет содержать весь сетевой пакет.

6. ProtocolReceivePacket - необязательная функция. Обеспечивается, если промежуточный драйвер будет располагаться над драйвером сетевой карты, который передает сразу массив указателей на один или более описателей пакетов, или если драйвер сетевой карты передает дополнительные данные вместе с индикацией получения с помощью вызова NdisMIndicateReceivePacket. Если разработчик неуверен в среде, в которой будет исполняться его промежуточный драйвер, то эта функция должна быть обеспечена, потому что тогда промежуточный драйвер достигнет большей производительности, если нижележащий драйвер сетевой карты производит мультипакетную индикацию получения.

7. ProtocolReceiveComplete - это обязательная функция. Она вызывается, если обработка какого-либо из пакетов, попавших перед этим в ProtocolReceive, была отложена.

8. ProtocoITransferDataComplete - это обязательная функция, если ProtocolReceive когда-либо вызывает NdisTransferData. Если предыдущий вызов NdisTransferData с просьбой скопировать оставшуюся часть полученного пакета вернул статус NDIS_STATUS_PENDING, то ProtocoITransferDataCompIete вызовется, когда операция передачи оставшейся части будет завершена.

9. ProtocolResetComplete - это обязательная функция. Она вызывается, если операция сброса, начатая с помощью вызова функции NdisReset, вернувшей статус NDIS_STATUS_PENDING, была завершена. Обычно промежуточные драйверы не вызывают NdisReset, но драйверы выше их могут вызвать эту функцию, таким образом, промежуточный драйвер должен суметь передать такой запрос нижележащим NDIS-драйверам.

10. ProtocolRequestComplete - это обязательная функция. Она вызывается по завершении операции получения/установки параметров, начатой с помощью вызова функции NdisRequest, вернувшей статус NDIS_STATUS_PENDING.

11. ProtocolSendComplete - обязательная функция. Она вызывается для каждого пакета, переданного с помощью вызова функции NdisSend, вернувшей статус операции отправки NDIS_STATUS_PENDING. Если был отослан сразу массив пакетов путем вызова NdisSendPackets, то ProtocolSendComplete будет вызвана для каждого пакета из массива. Промежуточный драйвер может определить окончательный статус операции отправки только из параметра статуса у функции ProtocolSendComplete.

12. ProtocoIStatus - обязательная функция. Она вызывается библиотекой NDIS для сообщения об изменении статуса, инициированного нижележащим драйвером сетевой карты.

13. ProtocoIStatusComplete - обязательная функция. Она вызывается библиотекой NDIS, чтобы сигнализировать, что изменение статуса, о котором перед этим сообщалось в ProtocoIStatus, теперь завершилось.

14. ProtocolPnPEvent - это обязательная функция. NDIS вызывает ProtocolPn PEvent, чтобы сигнализировать о событии Plug and Play или о событии управления питанием.

15. ProtocolUnload - это необязательная функция. NDIS вызывает ProtocolUnload в ответ на требование пользователя удалить промежуточный драйвер. ProtocolUnload выполняет определенные драйвером операции удаления.



Возможности реализации средств защиты сетевой информации на пользовательском уровне



Возможности реализации средств защиты сетевой информации на пользовательском уровне

Рассмотрим особенности реализации средств защиты на различных уровнях сетевой архитектуры ОС Windows NT. Под реализацией защиты на каком-либо уровне понимается следующее: можно ли с помощью разработки компоненты этого уровня осуществить функции защиты сетевой информации, и возможно ли реализовать средство защиты, которое, воздействуя каким-либо образом на уже существующие компоненты данного уровня, сможет осуществлять функции защиты.

К функциям защиты сетевых ресурсов, прежде всего, относятся:

Шифрование сетевого трафика.

Пассивный мониторинг сетевых пакетов, либо, на более высоком уровне, пассивный мониторинг доступа к сетевым системным DLL и сетевым сервисам.

Активный мониторинг сетевых пакетов, то есть мониторинг и фильтрация нежелательных пакетов (на более высоком уровне — мониторинг и фильтрация запросов к сетевым системным DLL и сетевым сервисам).

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


Возможности реализации средств защиты сетевой информации на уровне ядра

С этого момента начинается анализ сетевых компонент, исполняющихся в привилегированном режиме - режиме ядра, и являющихся драйверами. Прежде чем переходить к дальнейшему рассмотрению того, как и куда сетевая архитектура Windows NT позволяет встраивать модули защиты на уровне ядра, необходимо вспомнить механизм драйверов-фильтров (см. раздел «Реализация драйверов-фильтров»), который может использоваться в качестве средства защиты на любом уровне в стеке драйверов.



Завершающие функции



Завершающие функции

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

Асинхронный ввод/вывод поддерживается с помощью использования завершающих функций. Когда драйвер протокола вызывает NDIS, чтобы отправить пакет (что приводит в результате к вызову функции MiniportSend в драйвере сетевой карты), драйвер сетевой карты может попытаться завершить эту передачу немедленно и вернуть в качестве результата соответствующее значение статуса операции. Для синхронных операций вероятным значением статуса могут быть NDIS_STATUS_SUCCESS (если операция завершилась успешно) и NDIS_STATUS_RESOURCES или NDIS_STATUS_ FAILURE в случае неудачи. А может, тут же вернуть управление со значением статуса операции NDIS_STATUS_PENDING.

Но операция отправки пакета в сеть может потребовать некоторого времени для ее завершения. За это время драйвер сетевой карты (или NDIS) поставит пакет в очередь, и будет ожидать до тех пор, пока сетевая карта не просигнализирует результат операции отправки. Функция MiniportSend драйвера сетевой карты может управлять этой операцией отправки асинхронно, путем возвращения значения статуса NDIS_STATUS_ PENDING. Когда драйвер сетевой карты завершит отправку, он вызовет завершающую функцию NdisMSendComplete, передав в эту функцию указатель на отосланный NDIS-пакет. Эта информация передается драйверу протокола, сигнализируя завершение операции отправки.

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



Зависимость способа реализации средства защиты от предъявляемых к нему требований



Зависимость способа реализации средства защиты от предъявляемых к нему требований

Выбор конкретного способа реализации системы защиты зависит от первоначально предъявляемых к этой системе требований.

В табл. 12 перечислены наиболее часто возникающие требования к средству защиты и рекомендуемый уровень его реализации в соответствии с табл. 11.

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

Разработка этих драйверов хорошо документирована, и ОС Windows NT предоставляет возможность их внедрения в стек сетевых драйверов без необходимости модификации уже существующих компонентов. Они могут взаимодействовать с компонентами исполнительной системы, ядром и HAL, получая тем самым максимальные возможности для реализации функций защиты. Эти драйверы способны контролировать весь сетевой трафик, через них проходят данные всех приложений, исполняющихся в системе.

В результате исследования сетевой архитектуры можно сделать следующие выводы:

Не на всех уровнях сетевой архитектуры операционная система Windows NT позволяет расширять свои возможности и, в частности, возможности по реализации защиты сетевой информации.

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

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