Диссертация Кочарян С.Г — копия (1195359), страница 3
Текст из файла (страница 3)
Для определения функций, в код которых необходимо вставить фильтр пакетов, рассмотрим функции интерфейсов нижнего и верхнего уровня, поддерживаемые средой NDIS, которые участвуют в приёме и отправлении пакетов, и через которые эти пакеты проходят непосредственно:
Отправление:
Функция MiniportSendPackets - Она получает несколько указателей на NDIS-пакеты от драйвера транспорта. В этой функции необходимо просмотреть все пакеты и убрать из очереди пакеты, не прошедшие фильтрацию;
Функция MiniportSend получает по одному NDIS-пакету от драйвера транспорта, требуется в том случае, когда драйвер не обеспечивает функцию MiniportSendPackets;
Функция ProtocolSendComplete является завершающей для функций MiniportSendPackets и MiniportSend. Она получает в качестве параметра статус завершения операции отправления пакета и описатель отправленного пакета.
Получение:
Функция ProtocolReceive получает пакет от драйвера сетевой карты. Она должна скопировать полученный пакет во вновь созданный NDIS-пакет и передать его драйверу транспорта. В этой функции пакеты, не прошедшие фильтрацию просто пропускаются;
Функция ProtocolReceivePacket является не обязательной функцией и вовлекается в том случае, когда промежуточный драйвер располагается над драйвером сетевой карты, имеющей способность передавать сразу несколько пакетов, полученных из сети. Если такой уверенности нет, то эту функцию можно не регистрировать. Тогда все пакеты будут передаваться через ProtocolReceive.
В моём драйвере фильтрация осуществляется в MiniportSendPackets для отправляемых пакетов и в ProtocolReceive для получаемых пакетов.
3.2 Загрузка и инициализация драйвера
Загрузка любого драйвера в Windows начинается с точки в хода в драйвер DriverEntry, в которой выполняется начальная инициализация драйвера.
При загрузке выполняются следующие действия:
-
вызывает FireWallInit Которая выполняет начальную настройку моего драйвера;
-
вызывает NdisMInitializeWrapper которая возвращает NdisWrapperHandle;
-
вызывает NdisIMRegisterLayeredMiniport, что бы зарегистрировать свои точки входа MiniportXxx;
-
вызывает NdisRegisterProtocol, что бы зарегистрировать свои точки входа ProtocolXxx;
-
вызывает NdisIMAssociateMiniport, чтобы информировать библиотеку NDIS о том, что определённые раньше интерфейсы нижнего и верхнего уровня принадлежат одному и тому же промежуточному драйверу.
Более подробно стоит рассмотреть функцию FireWallInit. Она вызывается самой первой, вследствие чего в ней доступны стандартные функции ядра. Эта функция загружает начальные настройки драйвера, ещё до того, как он начнет выполнять свою работу. Для этого она вызывает функцию ReadSettings, которая и загружает данные из файла.
Загрузка осуществляется из файла, который имеет чётко заданную структуру (Рисунок 3.2). Заголовок содержит информацию о хранящихся данных в файле. Поле Sign определяет, действительно ли данный файл является файлом настройки и всегда должно быть равно 0x5746794D (ANSI строка «MyFW»). Следующие два поля (NumPraviloIPTableb и OffsetPraviloIPTable) отвечают за таблицу правил, первое определяет количество правил, а второе за смещение в байтах от начала файла, указывающее, где хранится таблица правил. Следующие четыре поля определяют список имён хостов и список URL. Поля HostTableDataSize и URLTableDataSize соответственно определяют размер списков в байтах. Элементы списка разделяются нулевыми байтами, а сам список заканчивается нулевой строкой. Поля OffsetHostTableData и OffsetURLTableData соответственно определяют смещение списков в байтах от начала файла.
На рисунке 3 отображена общая структура файла настроек.
Рисунок 3.2 – Общая структура файла настроек. НБ – нулевой байт.
Такая структура позволяет достаточно просто загружать настройки драйвера и упрощает его написание.
3.3 Механизм передачи информации на уровень пользователя
В ОС Windows невозможно напрямую вызывать функции из драйвера. Для общения программ и драйверов предусмотрена специальная система Пакетов запроса ввода-вывода (I/O Request Packet, IRP).
На пользовательском уровне для обращения к драйверу необходимо, открыть драйвер с помощью функции CreateFile, после чего воспользоваться функцией DeviceIoControl для передачи информации в драйвер. DeviceIoControl в качестве параметров принимает:
А) Код операции – этот код определяет, какая функция в драйвере будет вызвана. Код операции имеет строго определённый формат (Рисунок 4). для задания кода используется макрос CNL_CODE(DeviceType, Function, Method, Access).
Четыре части макроса служат следующим целям:
поле DeviceType — это 16-битный идентификатор типа устройства. В файле ntddk.h перечислен ряд типов устройств, в символьном виде представленных константами FILE_DEVICE_*. Microsoft резервирует диапазон с 0x0000 по 0x7FFF для внутреннего использования, а диапазон 0x8000-0xFFFF доступен для разработчиков. Мой драйвер определяет свой собственный идентификатор устройства и устанавливает его в значение 0x8000;
поле Access задает двухбитное значение для проверки возможности доступа, задающее требуемые права доступа для работы IOCTL. Возможны следующие значения: FILE_ANY_ACCESS, FILE_READ_ACCESS FILE_WRITE_ACCESS и комбинация двух последних: FILE_READ__ACCESS | FILE_WRITE_ACCESS;
поле Function — это 12-битный идентификатор, задающий операцию, которую должно выполнить устройство. Microsoft резервирует значения с 0x000 по 0x7 FF для внутреннего использования и оставляет для разработчиков диапазон 0x800 по 0xFFF. Идентификаторы функций IOCTL, распознаваемые устройством просмотра, берутся из последнего пула номеров;
поле Method состоит из двух бит, определяя один из четырех возможных методом передачи ввода-вывода с именами METH0D_BUFFERED(0), METHOD_IN_DIRECT (1), METH0D_OUT_DIRECT (2) и METHOD_NEITHER (3), расположенных в файле ntddk.h. Драйвер слежения для всех запросов использует способ METHOD_BUFFERED, обладающий высокой безопасностью, но и весьма медлительный, поскольку данные копируются из системного буфера в клиентский и обратно.
Макроопределение CTL_CODE(), генерирующее управляющие коды ввода-вывода:
#define CTL_C0DE(DeviceType, Function, Method, Access) \
(((DeviceType) « 16) | ((Access) « 14) | \((Function) « 2) | (Method))
На рисунке 3.4 показана схема управляющего кода устройства ввода-вывода.
Рисунок 3.4 – Схема управляющего кода устройства ввода-вывода
Б) входящий буфер указывает на данные которые будут переданы в драйвер, также необходимо передать размер этого буфера;
В) выходящий буфер указывает на данные которые будут переданы из драйвера, также необходимо передать размер этого буфера;
Г) функция возвратит количество записанных байтов в выходящем буфере.
После того как закончится работа с драйвером необходимо закрыть указатель на драйвер с помощью функции CloseFile.
На уровне ядра в драйвере установлен обработчик запросов ввода-вывода DeviceIOCTRL, который извлекает по одному запросу из стека, определяет код операции и вызывает соответствующие обработчики, которые приведены в таблице 3.1.
Таблица 3.1 – Функции IOCTRL реализованные в драйвере
Имя функции | ID | Код IOCTL | Описание |
MY_ICTRL_GetLog | 10 | 0x80002028 | Получение информации об уже обработаных сетевых пакетах. |
MY_ICTRL_WriteTablePravilo | 11 | 0x8000202C | Запись в таблицу правил драйвера. |
MY_ICTRL_ReadTablePravilo | 12 | 0x80002030 | Чтение из таблицы правил драйвера. |
MY_ICTRL_WriteTableHTTPHost | 14 | 0x80002038 | Запись списка запрещённых хостов. |
MY_ICTRL_WriteTableHTTPURL | 16 | 0x80002040 | Запись списка запрещённых имён файлов. |
MY_ICTRL_GetQueryPacket | 100 | 0x80002190 | Получение ожидающих пакетов. |
MY_ICTRL_GetQP_PacketDataSize | 101 | 0x80002194 | Получение размера пакета. |
MY_ICTRL_GetQP_PacketData | 102 | 0x80002198 | Получение всего пакета. |
MY_ICTRL_QP_PacketDel | 104 | 0x800021A0 | Уделение ожидающего пакета. |
MY_ICTRL_GetVersion | 0 | 0x80002000 | Получение строки с версией драйвера |
Эти функции стоит рассмотреть более подробно.
Функция IOCTL MY_ICTRL_GetLog предназначена для передачи на уровень пользователя информацию об обработанных пакетах в драйвере. За один вызов передаётся информация об одном пакете.
Она передаёт на уровень пользователя следующею информацию:
действие, которое предпринял драйвер к данному пакету;
направление, в которое был направлен пакет;
правило, которое было использовано;
– заголовок IP пакета;
– заголовок TCP или UDP пакета в зависимости от типа пакета;
– системное время, когда был обработан пакет.
После передачи пакета данная информация удаляется из памяти драйвера
Информация предаётся через выходящий буфер. Для подтверждения правильности передаётся размер переданных данных.
Функция IOCTL MY_ICTRL_WriteTablePravilo используется для записи правил в драйвер. Она передаёт сразу всю таблицу правил, которая замещает уже имеющиеся таблицу. Количество элементов определяется путём деления размера переданной информации на размер одного правила, если деление произошло с остатком, то драйвер игнорирует это действие как ошибочное.
В каждом правиле определяется, по каким критериям происходит проверка:
– направление;
– локальный IP адрес;
– удалённый IP адрес;
– локальный порт;
– удалённый порт;
протокол используемый в пакете.
А также действие, которое необходимо предпринять при совпадении критериев.
Информация предаётся через входящий буфер. Для проверки необходимо воспользоваться функцией IOCTL MY_ICTRL_ReadTablePravilo.
Также эта функция вызывает функцию WriteSettings для того, чтобы сохранить произведённые изменения в настройках в файл.
Функция IOCTL MY_ICTRL_ReadTablePravilo используется для чтения правил из драйвера. Она передаёт сразу всю таблицу правил. Количество элементов определяется путём деления размера переданной информации на размер одного правила, если деление произошло с остатком, то это действие ошибочное.
Функция IOCTL MY_ICTRL_WriteTableHTTPHost используется для передачи списка запрещённых имён хостов. Список представляет собой набор строк заканчивающихся нулём. Сам список заканчивается пустой строкой. Также необходимо передать полный размер списка.
Также эта функция вызывает фукцию WriteSettings для того, чтобы сохранить произведённые изменения в настройках в файл.
Функция IOCTL MY_ICTRL_WriteTableHTTPURL используется для передачи списка запрещённых имён файлов. Список представляет собой набор строк, заканчивающихся нулём. Сам список заканчивается пустой строкой. Также необходимо передать полный размер списка.
Также эта функция вызывает фукцию WriteSettings для того, чтобы сохранить произведённые изменения в настройках в файл.
Функция IOCTL MY_ICTRL_GetQueryPacket реализует механизм передачи пакетов на уровень пользователя. Для этого необходимо создать правило, в котором действие обозначается как «Запрос». Все пакеты, подпадающие под это, правило помещаются в специальную очередь и получат идентификатор.
После вызова этой функции, пакет помещается в очередь ожидающих пакетов, где к нему можно обратится, используя одну из следующих функций: MY_ICTRL_GetQP_PacketDataSize, MY_ICTRL_GetQP_PacketData и MY_ICTRL_QP_PacketDel.
Данная функция передаёт на уровень пользователя Идентификатор, время поступления пакета и направление пакета.
Функция IOCTL MY_ICTRL_GetQP_PacketDataSize передаёт размер всего пакета для определения, какого именно пакета в функцию передаётся идентификатор пакета. Данную функцию необходимо вызывать перед получением пакета с помощю функции MY_ICTRL_GetQP_PacketData для определения размера пакета.