Протоколирование обмена информацией между компьютером и внешним запоминающим USB-устройством

Протоколирование обмена информацией между компьютером и внешним запоминающим USB-устройством

Протоколирование обмена информацией между компьютером и внешним запоминающим USB-устройством

Факультет «Информатика и системы управления»
Кафедра «Программное обеспечение ЭВМ и информационные технологии»
Курсовой проект
по системному программированию
Расчётно-пояснительная записка
Тема
«Протоколирование обмена информацией между компьютером и внешним запоминающим USB‑устройством»

Оглавление
Введение. 3
1. Аналитический раздел. 4
1.1 Постановка задачи. 4
1.2 Архитектура Windows NT 5. 4
1.3 Шина USB.. 7
1.3.1 Внутренняя организация шины USB.. 8
1.4 Драйверная модель WDM.. 16
1.4.1 Драйверные слои. 17
1.4.2 Точки входа WDM-драйвера. 19
1.5 Пакет запроса ввода / вывода (IRP) 20
1.6 Уровни запроса прерываний. 24
1.7 Уведомление о завершении запроса нижестоящим драйвером. 25
1.8 Работа с файлами в режиме ядра. 26
1.9 Работа с реестром в режиме ядра. 29
1.10 MDL‑списки. 31
2. Конструкторский раздел. 32
2.1 Точки входа разрабатываемого драйвера. 32
2.1.1 Функция DriverEntry. 32
2.1.2 Функция AddDevice. 32
2.1.3 Функция DriverUnload. 33
2.1.4 Функция DispatchRoutine. 33
2.1.5 Функция DispatchInternalDeviceControl 33
2.2 Размещение кода драйвера в памяти. 35
2.3 Установка драйвера в системе. 35
3. Технологический раздел. 38
3.1 Выбор языка и средств программирования. 38
3.1.1 Драйвер-фильтр. 38
3.1.2 Управляющее приложение. 39
3.2 Структуры данных драйвера-фильтра. 39
3.2 Интерфейс управляющего приложения. 41
3.3 Тестирование драйвера-фильтра. 42
Заключение. 44
Список литературы и интернет-ресурсов. 45

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

1. Аналитический раздел

1.1 Постановка задачи
В соответствии с заданием на курсовую работу необходимо разработать программный комплекс, обеспечивающий слежение за обменом информацией между компьютером и внешним запоминающим USB‑устройством.
Перечислим требования, предъявляемые к программному комплексу
· Разрабатываемый комплекс должен отслеживать запросы на чтение и запись, приходящие к устройству;
· От программы не требуется определять, к каким файлам производилось обращение, а лишь то, какие данные считывались и записывались;
· Сохраняемая информация должна обладать структурированностью, таким образом, что при анализе лог-файла можно было определить, передавалась или принималась информация и в каком количестве;
· Программный комплекс не должен приводить к сбоям в работе операционной системы;
· Программа установки должна корректно обрабатывать попытку установки на одно устройство в двух экземплярах.

1.2 Архитектура Windows NT 5
Архитектура Windows NT 5 соответствует классическим представлениям о проектировании операционных систем. Наиболее распространены реализации данной ОС для платформы Intel x86 в одно- или многопроцессорных конфигурациях, однако существуют также версии для DEC Alpha и MIPS. Данная операционная система использует защищённый режим центрального процессора, реализует механизмы виртуальной памяти и многозадачности.
Исполняемый код в Windows NT 5 имеет два уровня привилегий код пользовательского режима и код режима ядра. Уровень привилегий накладывает определённые ограничения в пользовательском режиме не могут выполняться привилегированные инструкции процессора, не разрешено обращение к защищённым страницам памяти. Эти ограничения накладываются для обеспечения безопасности работы системы. Пользовательское приложение не должно иметь возможность в результате ошибки или преднамеренно вносить изменения в критические таблицы операционной системы или в память других приложений. В частности, такие ограничения запрещают пользовательскому приложению напрямую управлять внешними устройствами, потому что каждое из них является разделяемым ресурсом.
В Windows NT 5 обеспечение обмена данными и управление доступом к внешнему устройству как к разделяемому ресурсу возлагается на его драйвер. Ввод и вывод в драйверах осуществляется посредством IRP‑пакетов (Input/Output Request Packet). Запросы на ввод / вывод, посылаемые приложениями или другими драйверами, обрабатываются драйвером, после чего запрашивающей программе в том же пакете посылается статус завершения операции. Подробнее о пакетах ввода / вывода будет сказано далее, общий же принцип взаимодействия проиллюстрирован на Рис. 1.2.1.

Рис. 1.2.1 Архитектура ввода / вывода Windows NT 5
Применительно к поставленной задаче, из вышеизложенного следует, что полное протоколирования обмена данными с внешним устройством может быть осуществлено только на уровне драйвера.
Управление внешним устройством в общем случае сводится к заполнению его регистров необходимыми данными. Монопольный доступ драйвера к этим регистрам гарантируется операционной системой. Очевидно, что при данных обстоятельствах требуется, чтобы драйвер устройства выполнялся в режиме ядра.
Обобщённая классификация драйверов Windows NT 5 может быть представлена следующим образом
· Драйверы режима ядра
o Унаследованные драйверы;
o Драйверы файловой системы;
o Видеодрайверы;
o Драйверы PnP (Plug And Play)
§ Драйверы WDM.
· Драйверы пользовательского режима.

1.3 Шина USB
Спецификация USB была разработана консорциумом компаний, включая Intel и Microsoft. Целью нового стандарта было обеспечение организации недорогой среднескоростной шины в таких областях применения, как передача цифровых изображений, компьютерная телефония и мультимедийные игры. Текущими версиями спецификации USB является версии 1.1 и 2.0 (во вторую заложены более высокие скоростные характеристики).
Предельная скорость передачи данных по шине USB спецификации 1.1 составляет 12 Мбит/с (Full Speed). Медленные устройства используют низкую скорость передачи – 1,5 Мбит/с (Low Speed). Стандарт USB версии 2.0 поддерживает физическую скорость передачи до 480 Мбит/с (High Speed). Данные передаются последовательно по паре проводников. Питание для некоторых устройств доступно по отдельным проводникам питания и заземления (для устройств с небольшим энергопотреблением).
Устройства USB могут быть подключены 5-метровым кабелем (а практически – и более длинным). Использование USB‑хаба (hub – концентратор) позволяет увеличить дальность размещения устройств от хост-контроллера, а так же количество устройств, подключаемых к одной шине USB. Последовательно можно подключить до пяти хабов, обеспечив длину соединения 30 метров. К хост-контроллеру можно подключить до 127 устройств, шинный адрес которых устанавливается динамически при подключении устройств.
На рисунке 1.3.1 приведен пример конфигурации сети USB‑устройств

Рис. 1.3.1 Сеть USB‑устройств
Работа программиста, создающего драйвер внешнего (не находящегося на материнской плате) USB устройства сводится к тому, чтобы воспользоваться программным интерфейсом системных драйверов шины USB, общение с которым происходит при помощи пакетов, называемых URB (USB Request Block) пакетами. Работа с регистрами USB контроллеров на материнской плате теперь стала уделом узкого круга специалистов – разработчиков материнских плат и операционных систем. Всем остальным разработчикам USB‑устройств в операционной системе Windows предлагается достаточно развитый программный интерфейс WDM‑драйверов, которые берут на себя все аппаратно-ориентированные операции.
1.3.1 Внутренняя организация шины USB
Все операции по передаче данных по шине USB инициируются хостом. Периферийные устройства не могут сами начать обмен данными, они могут только реагировать на команды хоста. Рассмотрим общую схему обмена данными по шине USB.
Система USB разделяется на три логических уровня с определенными правилами взаимодействия. Устройство USB содержит интерфейсную, логическую и функциональную части. Хост тоже делится на три части интерфейсную, системную и программное обеспечение. Каждая часть отвечает только за определенный круг задач. Логическое и реальное взаимодействие между ними показано на рисунке 1.3.1.1.

Рис. 1.3.1.1 Взаимодействие компонентов USB
Таким образом, операция обмена данными между прикладной программой и шиной USB выполняется путем передачи буферов памяти через следующие уровни
· уровень клиентского программного обеспечения в хосте – обычно представляется драйвером устройства USB, обеспечивает взаимодействие пользователя с операционной системой с одной стороны и системным драйвером с другой;
· уровень системного программного обеспечения USB в хосте (USBD, Universal Serial Bus Driver) – управляет нумерацией устройств на шине, управляет распределением пропускной способности шины и мощности питания, обрабатывает запросы пользовательских драйверов;
· хост-контроллер интерфейса шины USB (HCD, Host Controller Driver) – преобразует запросы ввода / вывода в структуры данных, по которым хост-контроллер выполняет физические транзакции, работает с регистрами хост-контроллера.
Уровень клиентского программного обеспечения определяет тип передачи данных, необходимый для выполнения затребованной прикладной программой операции. После определения типа передачи данных этот уровень передает системному уровню следующее
· буфер памяти, называемый клиентским буфером;
· пакет IRP, указывающий тип необходимой операции. Непосредственной обработкой запроса занимается системный драйвер USB.
Уровень системного драйвера USB необходим для управления ресурсами USB. Он отвечает за выполнение следующих действий
· распределение полосы пропускания шины USB;
· назначение логических адресов устройств каждому физическому USB‑устройству;
· планирование транзакций.
Логическое устройство USB представляет собой набор независимых конечных точек, с которыми клиентское программное обеспечение обменивается информацией. Каждому логическому устройству USB назначается свой адрес, уникальный на данной шине USB. Каждая конечная точка логического устройства идентифицируется своим номером и направлением передачи (IN – передача к хосту, OUT – от хоста).
Транзакция на шине USB – это последовательность обмена пакетами между хостом и периферийным устройством, в ходе которой может быть передан или принят один пакет данных. Когда клиентское программное обеспечение передает IRP уровню системного драйвера, USB‑драйвер преобразует их в одну или несколько транзакций шины и затем передает получившийся перечень транзакций драйверу контроллера хоста.
Системный драйвер USB состоит из драйвера USB и драйвера хост-контроллера. Когда клиентский уровень передает IRP уровню системного обеспечения USB, USB‑драйвер преобразует их в одну или несколько транзакций шины и затем передает получившийся перечень транзакций драйверу контроллера хоста. Драйвер контроллера хоста принимает от системного драйвера шины перечень транзакций и выполняет следующие действия
· планирует исполнение полученных транзакций, добавляя их к списку транзакций;
· извлекает из списка очередную транзакцию и передает ее уровню хост-контроллера интерфейса шины USB;
· отслеживает состояние каждой транзакции вплоть до ее завершения.
При выполнении всех связанных с командным пакетом транзакций системный уровень уведомляет об этом клиентский уровень.
Уровень хост-контроллера интерфейса шины USB получает отдельные транзакции от драйвера контроллера хоста (в составе уровня системного обеспечения USB) и преобразует их в соответствующую последовательность операций шины. В результате этого USB‑пакеты передаются вдоль всей физической иерархии хабов до периферийного USB‑устройства.
Нижний уровень периферийного USB‑устройства называется уровнем интерфейса шины USB. Он взаимодействует с интерфейсным уровнем шины USB на стороне хоста и передает пакеты данных от хоста к периферийному устройству в формате, определяемом спецификацией USB. Затем он передает пакеты вверх – уровню логического USB‑устройства.
Средний уровень периферийного устройства USB‑устройства называется уровнем логического USB‑устройства. Каждое логическое USB‑устройство представляется набором своих конечных точек, с которыми может взаимодействовать системный уровень USB‑хоста. Эти точки являются источниками и приемниками всех коммуникационных потоков между хостом и периферийными USB‑устройствами.
Самый верхний уровень периферийного USB‑устройства называется функциональным уровнем. Этот уровень соответствует уровню клиентского обеспечения хоста. С точки зрения клиентского уровня, нижележащие уровни нужны для организации между ним и конечными точками прямых «каналов данных», которые идут вплоть до функционального уровня. А с точки зрения нашей схемы функциональный уровень выполняет следующие действия
· получает данные, посылаемые клиентским уровнем хоста из конечных точек каналов данных нижележащего уровня логического USB‑устройства;
· посылает данные клиентскому уровню хоста, направляя их в конечные точки каналов данных нижележащего уровня логического USB‑устройства.
Логически передача данных между конечной точкой и программным обеспечением производится с помощью выделения канала и обмена данными по этому каналу, а с точки зрения представленных уровней, передача данных выглядит следующим образом

Рис. 1.3.1.2 Уровни передачи данных
Конечная точка (Endpoint) – это часть USB‑устройства, которая имеет уникальный идентификатор и является получателем или отправителем информации, передаваемой по шине USB. Проще говоря, это буфер, сохраняющий несколько байт. Обычно это блок данных в памяти или регистр микроконтроллера. Данные, хранящиеся в конечной точке, могут быть либо принятыми данными, либо данными, ожидающими передачу. Хост также имеет буфер для приема и передачи данных, но хост не имеет конечных точек.
Конечная точка имеет следующие основные параметры
· частота доступа к шине;
· допустимая величина задержки обслуживания;
· требуемая ширина полосы пропускания канала;
· номер конечной точки;
· способ обработки ошибок;
· максимальный размер пакета, который конечная точка может принимать или отправлять;
· используемый конечной точкой тип посылок;
· направление передачи данных.
Любое USB‑устройство имеет конечную точку с нулевым номером (Endpoint Zero). Эта точка позволяет хосту опрашивать устройство с целью определения его типа и параметров, выполнять инициализацию и конфигурирование устройства.
Кроме нулевой точки, устройства, обычно, имеют дополнительные конечные точки, которые используются для обмена данными с хостом. Дополнительные точки могут работать либо только на прием данных от хоста (входные точки, IN), либо только на передачу данных хосту (выходные точки, OUT).
Нулевая точка устройства доступна после того, как устройство подключено к шине, включено и получило сигнал сброса по шине (bus reset). Все остальные конечные точки после включения питания или сброса находятся в неопределенном состоянии и недоступны для работы до тех пор, пока хост не выполнит процедуру конфигурирования устройства.
Спецификация шины определяет четыре различных типа передачи данных для конечных точек
· управляющие передачи (Control Transfers) – используются хостом для конфигурирования устройства во время подключения, для управления устройством и получения статусной информации в процессе работы. Протокол обеспечивает гарантированную доставку таких посылок;
· передачи массивов данных (Bulk Data Transfers) – применяются при необходимости обеспечения гарантированной доставки данных от хоста к функции или от функции к хосту, но время доставки не ограничено;
· передачи по прерываниям (Interrupt Transfers) – используются в том случае, когда требуется передавать одиночные пакеты данных небольшого размера. Каждый пакет требуется передать за ограниченное время. Операции передачи носят спонтанный характер и должны обслуживаться не медленнее, чем того требует устройство;
· изохронные передачи (Isochronous Transfers) – применяются для обмена данными в «реальном времени», когда на каждом временном интервале требуется передавать строго определенное количество данных, но доставка информации не гарантирована (передача данных ведется без повторения при сбоях, допускается потеря пакетов).
Канал (pipe) – это логическое соединение между конечной точкой устройства и ПО хоста. Существует две модели каналов
· потоковый канал (или просто поток, streaming pipe) – это канал для передачи данных, структура которых определяется клиентским ПО. Потоки используются для передачи массивов данных, передачи данных по прерываниям и изохронной передачи данных. Поток всегда однонаправленный. Один и тот же номер конечной точки может использоваться для двух разных потоковых каналов – ввода и вывода. Передачи данных в потоковых каналах подчиняются следующим правилам
· запросы клиентских драйверов для разных каналов, поставленные в определенном порядке друг относительно друга, могут выполняться в другом порядке;
· запросы для одного канала будут исполняться строго в порядке их поступления;
· если во время выполнения какого-либо запроса происходит серьезная ошибка (STALL), поток останавливается;
· канал сообщений (message pipe или control pipe) – это канал для передачи данных, структура которых определяется спецификацией USB. Каналы этого типа двунаправленные и применяются для передачи управляющих посылок. Каналы сообщений строго синхронизированы – спецификация USB запрещает одновременную обработку нескольких запросов нельзя начинать передачу нового сообщения, пока не завершена обработка предыдущего. В случае возникновения ошибки передача сообщения может быть прервана хостом, после чего хост может начать передачу нового сообщения.
Основными характеристиками каналов являются
· полоса пропускания канала;
· используемый каналом тип передачи данных;
· характеристики, соответствующие конечной точке направление передачи данных и максимальный размер пакета.
Полоса пропускания шины делится между всеми установленными каналами. Выделенная полоса закрепляется за каналом, и если установление нового канала требует такой полосы, которая не списывается в уже существующее распределение, запрос на выделение канала отвергается. Архитектура USB предусматривает внутреннюю буферизацию всех устройств, причем, чем большей полосы пропускания требует устройство, тем больше должен быть его буфер. Шина USB должна обеспечивать обмен с такой скоростью, чтобы задержка данных в устройстве, вызванная буферизацией, не превышала нескольких миллисекунд.
Канал сообщений, связанный с нулевой конечной точкой, называется Основным каналом сообщений (Default Control Pipe или Control Pipe 0). Владельцем этого канала является USBD, и он используется для конфигурирования устройства. Основной канал сообщений поддерживает только управляющие передачи. Остальные каналы (они называются клиентскими каналами, Client Pipe) создаются в процессе конфигурирования устройства. Их владельцами являются драйверы устройств. По клиентским каналам могут передаваться как потоки, так и сообщения с помощью любых типов передач.
Набор клиентских каналов, с которыми работает драйвер устройства, называется интерфейсом устройства или связкой клиентских каналов.
1.4 Драйверная модель WDM
WDM (Windows Driver Model) – новая модель архитектуры драйверов, предложенная Microsoft для Windows 2000, хотя эта архитектура развивалась, начиная с Windows 3.11, продолжая развиваться и в Windows 98 и Windows NT, но по-настоящему полной она стала только в Windows 2000.
С точки зрения WDM, существует три типа драйверов
· Драйвер шины – драйвер, обслуживающий контроллер шины, адаптер, мост или любые другие устройства, имеющие дочерние устройства. Для каждого типа шины в операционной системе имеется свой драйвер;
· Функциональный драйвер – основной драйвер устройства, предоставляющий его функциональный интерфейс. Этот драйвер обязателен кроме тех случаев, когда ввод-вывод осуществляется драйвером шины или драйвером фильтров шины. Функциональный драйвер по определению обладает наиболее полной информацией о своем устройстве. Обычно только этот драйвер имеет доступ к специфическим регистрам устройства;
· Драйвер фильтра – драйвер, поддерживающий дополнительную функциональность устройства (или существующего драйвера) или изменяющий запросы ввода / вывода и ответы на них от других драйверов. Таких драйверов может быть несколько, хотя их присутствие необязательно. Они могут работать как на более высоком уровне, чем функциональный драйвер или драйвер шины, так и на более низком.
В среде WDM один драйвер не может контролировать все аспекты устройства драйвер шины информирует диспетчера PnP об устройствах, подключенных к шине, в то время как функциональный драйвер управляет устройством.
1.4.1 Драйверные слои
Согласно перечисленным выше типам драйверов, существует три типа объектов
· Объекты физических устройств (PDO, Physical Device Object) – эти объекты создаются для каждого физически идентифицируемого элемента аппаратуры, подключенного к шине данных;
· Объекты функциональных устройств (FDO, Functional Device Object) – подразумевает единицу логической функциональности устройства;
· Объекты фильтров устройств (FiDO, Filter Device Object) – предоставляют дополнительную функциональность.
В Windows NT 5 последовательность загрузки драйверов устройств такая
1. Во время загрузки операционной системы производится загрузка шинных драйверов для каждой известной системе шины (список шин создается при установке операционной системы и сохраняется в реестре);
2. Вызывается DriverEntry, а затем AddDevice для каждого шинного драйвера. В AddDevice создается FDO для драйвера системной шины. Затем на созданный FDO отправляется запрос IRP_MN_START_DEVICE;
3. Шинный драйвер составляет список всех устройств, подключенных к шине. Для каждого найденного устройства создается объект PDO;
4. На каждый PDO посылается запрос IRP_MN_QUERY_DEVICE_RELATION, в ответ на который шинный драйвер возвращает идентификаторы всех найденных устройств;
5. На эти PDO посылают запрос IRP_MN_QUERY_ID, в ответ на который драйвер системной шины сообщает идентификаторы этих устройств;
6. Получив идентификаторы, система пытается найти и загрузить драйверы устройств;
7. Найдя драйвер для устройств, система загружает его в память, вызывая его DriverEntry. Потом вызывается AddDevice, где создается FDO для устройства. Если устройств, управляемых этим драйвером, несколько, то AddDevice будет вызвана для каждого устройства. Если в реестре зарегистрированы дополнительные фильтры, то они также загружаются в память. Затем система посылает на FDO запрос IRP_MN_START_DEVICE;
8. Происходит посылка на FDO запроса IRP_MN_QUERY_DEVICE_RELATIONS. Если устройство само является шиной или держит на себе другие устройства, которыми само не управляет, то для устройства на нем повторяется вся последовательность действий, начиная с пункта 5.
Функция AddDevice, вызываемая для каждого FDO, вызывает IoCreateDevice и IoAttachDeviceToStack, обеспечивая построение стека устройств. Стек устройств обеспечивает прохождение запросов от пользовательских программ до аппаратного (нижнего) уровня драйверов (Рис. 1.4.1.1).

Рис. 1.4.1.1 Стек устройств

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

1.4.2 Точки входа WDM-драйвера
WDM‑драйверы отличаются от унаследованных драйверов тем, что должны содержать дополнительные точки входа для поддержки PnP, и, в целом, они более логичны по структуре. Приведем список точек входа и кратко охарактеризуем их назначение
NTSTATUS DriverEntry (
IN PDRIVER_OBJECT DriverObject, // указатель на объект драйвера
IN PUNICODE_STRING RegistryPath) // путь к подразделу регистра,
// относящегося к драйверу
Эта функция выполняется при загрузке драйвера операционной системой. В WDM‑драйверах на DriverEntry возложены обязанности по регистрации всех остальных точек входа драйвера.
NTSTATUS AddDevice (
IN PDRIVER_OBJECT DriverObject, // указатель на объект драйвера
IN PDEVICE_OBJECT PhysicalDeviceObject) // указатель на родительский PDO
В драйверах, поддерживающих PnP, через эту точку входа менеджер PnP посылает драйверу уведомление об обнаружении устройства, за которое должен отвечать драйвер. Функция AddDevice должна создать объект устройства с помощью вызова IoCreateDevice и при необходимости присоединить его к стеку устройств с помощью IoAttachDeviceToDeviceStack.
NTSTATUS DriverUnload (
IN PDRIVER_OBJECT DriverObject) // указатель на объект драйвера
Вызывается при выгрузке драйвера. В этой функции должны осво бождаться все затребованные драйвером ресурсы. Драйверы WDM‑модели выполняют эти действия в обработчике запросов IRP_MJ_PNP с субкодом IRP_MN_REMOVE_DEVICE, то есть при удалении устройства из системы.
Следует выделить отдельный класс точек входа драйвера, которые предназначены для обработки IRP‑пакетов с различными кодами операций. Эти точки входа регистрируются при загрузке драйвера в функции DriverEntry. Регистрация производится путем заполнения элементов массива MajorFunction адресами диспетчеризуемых функций. Индексом в этом массиве являются коды IRP_MJ_XXX, то есть описанные числами типы пакетов IRP. Диспетчер ввода / вывода, ориентируясь на заполнение этого массива, вызывает нужные функции драйвера.
Поскольку для драйвера важны только адреса рабочих процедур, то все рабочие процедуры могут иметь совершенно произвольные имена.
1.5 Пакет запроса ввода / вывода (IRP)
Пакеты ввода / вывода (IRP‑пакеты) используются для передачи запросов к драйверу от его клиентов. Они являются структурами данных переменной длины, и состоят из стандартного заголовка, содержащего общую учетную информацию, и одного или нескольких блоков параметров, называемых ячейками стека ввода / вывода (I/O Stack Location).
Приведем структуру заголовка IRP‑пакета
Таблица 1.5.1. Структура заголовка IRP‑пакета.

Поля
Описание

IO_STATUS_BLOCK IoStatus
Статус запроса

PVOID AssociatedIrp. SystemBuffer
Указатель на системный буфер для случая, если устройство поддерживает буферизованный ввод / вывод

PMDL MdlAddress
Указатель на MDL‑список в случае, если устройство поддерживает прямой ввод / вывод

PVOID UserBuffer
Адрес пользовательского буфера для ввода / вывода

BOOLEAN Cancel
Индикатор того, что IRP‑пакет должен быть аннулирован

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

Поля
Описание

UCHAR MajorFunction
Код IRP_MJ_XXX, описывающий назначение операции

UCHAR MinorFunction
Субкод операции

PDEVICE_OBJECT DeviceObject
Указатель на объект устройства, которому был адресован данный объект IRP

PFILE_OBJECT FileObject
Файловый объект для данного запроса, если он задан

union Parameters (трактовка определяется значением MajorFunction)

struct Read
Параметры для IRP типа IRP_MJ_READ ULONG Length ULONG Key LARGE_INTEGER ByteOffset

struct Write
Параметры для IRP типа IRP_MJ_WRITE ULONG Length ULONG Key LARGE_INTEGER ByteOffset

struct DeviceControl
Параметры для IRP типа IRP_MJ_DEVICE_CONTROL ULONG OutputBufferLength ULONG InputBufferLength ULONG IoControlCode PVOID Type3InputBuffer

Приведем графическое представление структуры IRP‑пакета

Рис. 1.5.1 Структура IRP пакета
Общение с USB‑накопителями в ОС Windows NT 5 на уровне драйверов, как уже было сказано в разделе 1.3.1, происходит посредством передачи URB‑пакетов. Указатели на URB‑пакеты содержат ячейки стека IRP‑пакета, доступ к этим указателям осуществляется следующим образом

PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp);
PURB Urb = IrpSp->Parameters. Others. Argument1;

Приведем частичное объявление структуры из справочной документации Microsoft. Отметим только поля, использование которых необходимо в рамках данной курсовой работы
typedef struct _URB {
union {
struct _URB_HEADER UrbHeader;
struct _URB_SELECT_INTERFACE UrbSelectInterface;
struct _URB_SELECT_CONFIGURATION UrbSelectConfiguration;
struct _URB_BULK_OR_INTERRUPT_TRANSFER UrbBulkOrInterruptTransfer;
}
} URB, *PURB;
Поле UrbHeader хранит информацию о коде URB‑пакета, по которому можно определить, какая операция запрашивается.
Поля UrbSelectInterface и UrbSelectConfiguration служат для запроса по выбору интерфейса и конфигурации устройства, которые будут использоваться при работе с устройством. Пакеты этой структуры отправляются хостом к устройству в начале его работы, при конфигурировании.
Поле UrbBulkOrInterruptTransfer несет наиболее важную в рамках данной курсовой работы информацию – указатели на блоки ввода / вывода USB‑устройства. Приведем описание структуры _URB_BULK_OR_INTERRUPT_TRANSFER
struct _URB_BULK_OR_INTERRUPT_TRANSFER {
struct _URB_HEADER Hdr;
USBD_PIPE_HANDLE PipeHandle;
ULONG TransferFlags;
ULONG TransferBufferLength;
PVOID TransferBuffer;
PMDL TransferBufferMDL;};
Поля этой структуры описаны в следующей таблице
Таблица 1.5.3 Поля структуры URB_BULK_OR_INTERRUPT_TRANSFER

Поле
Описание

struct _URB_HEADER Hdr
Стандартный заголовок URB‑пакета, содержащий код запроса

USBD_PIPE_HANDLE PipeHandle
Дескриптор канала, на который передаются данные

ULONG TransferFlags
Флаги, определяющие направление передачи данных и способ обработки ошибок

ULONG TransferBufferLength
Длина передаваемого блока данных в байтах

PVOID TransferBuffer
Указатель на передаваемый буфер. Буфер находится в нестраничной памяти

PMDL TransferBufferMDL
Указатель на MDL‑список, несущий передаваемую информацию. Буфер находится в страничной памяти

Следует отметить, что один из указателей TransferBuffer или TransferBufferMDL равен NULL, то есть в пределах одного пакета передается только одна порция данных.
Задача протоколирования обмена информацией сводится к перехвату и сохранению буферов TransferBuffer и TransferBufferMDL.
1.6 Уровни запроса прерываний
В каждый момент времени центральный процессор находится на одном из уровней IRQL (Interrupt Request Level – уровень запросов прерываний). Уровни IRQL располагаются в порядке убывания от HIGHEST_LEVEL до PASSIVE_LEVEL. Каждому из прерываний (прерывания от внешних устройств, системные часы, и т.д.) соответствует свой уровень IRQL. Специальным действиям операционной системы также назначены IRQL. Они отмечены в нижней части приведённой таблицы
Таблица 1.6.1. Уровни запросов прерываний.

Уровень
Назначение

HIGHEST_LEVEL
Наивысший уровень. Все прерывания заблокированы

POWER_LEVEL
Прерывания по отказу питания

IPI_LEVEL
Межпроцессорное взаимодействие

CLOCK2_LEVEL
Прерывание по системному таймеру 2

СLOCK1_LEVEL
Прерывание по системному таймеру 1

PROFILE_LEVEL
Прерывание по таймеру замера производительности

уровни DRQL
Обычные прерывания устройств

DISPATCH_LEVEL
Диспетчеризация потоков и выполнение отложенных процедур

APC_LEVEL
Выполнение асинхронного вызова процедуры

PASSIVE_LEVEL
Обычное исполнение кода потока

Общее правило обработки уровней запросов прерываний гласит, что прерывания с IRQL, меньшим, чем у выполняемого в данный момент кода, маскируются. Во время исполнения кода потока (пользовательского или системного) устанавливается наименьший IRQL = 0 (PASSIVE_LEVEL). Работа драйвера чаще всего выполняется на уровне IRQL = 2 (DISPATCH_LEVEL). Уровни, лежащие над ним, называются DIRQL (Device IRQL) и выставляются для обработчиков прерываний от внешних устройств (ISR – interrupt service routine). Даже во время выполнения ISR драйвера может произойти прерывание с большим IRQL, например, принадлежащее другому драйверу.
Чем выше текущий уровень IRQL исполняемого кода, тем меньше функций ему доступно. Так, например, диспетчер потоков работает на уровне DISPATCH_LEVEL, и, следовательно, не будет вызываться, пока на процессоре с уровнем большим или равным DISPATCH_LEVEL исполняется другой код. Таким образом, на уровнях DISPATCH_LEVEL и выше отключается переключение потоков. Функции ожидания диспетчерских объектов (события, мьютексы, семафоры) с отличным от нуля временем, обращение к файлам, подкачка отсутствующих в физической памяти страниц – всё это также становится недоступным. Для корректного сохранения запросов в файле фильтр в таких случаях должен применять специальную методику.
1.7 Уведомление о завершении запроса нижестоящим драйвером
При отслеживании обмена данными драйвер-фильтр может получать уведомления о том, что некоторый переданный запрос был завершён нижестоящим драйвером. Механизм уведомления заключается в том, что вызовом специальной функции IoSetCompletionRoutine фильтр обращается к стеку в пакете IRP. В позиции стека, следующей за текущей позицией, он устанавливает в специальном поле адрес функции завершения (completion routine). Затем при передаче пакета по цепочке позиция стека увеличивается.
Когда нижестоящий драйвер отправляет пакет запроса на завершение (вызовом IoCompleteRequest), подсистема ввода / вывода начинает просматривать стек внутри этого пакета от конца к началу. Если в какой-то позиции стека определена функция завершения, управление передаётся ей. Отработав, функция возвращает результат, сигнализирующий об успехе, ошибке или необходимости дальнейшей обработки запроса.
При первых двух вариантах подсистема ввода / вывода переходит к следующей позиции стека и продолжает просмотр, пока не будет достигнуто его начало. После этого запрос завершается нормальным образом.
При третьем же варианте просмотр стека немедленно прекращается и запрос не будет завершён. Эта возможность реализована для того, чтобы драйвер-фильтр мог выполнить какие-либо действия над пакетом запроса после того, как тот будет обработан в нижестоящем драйвере. После такой «дополнительной обработки» пакет снова должен быть отправлен на завершение.
1.8 Работа с файлами в режиме ядра
Поскольку протоколируемая информация должна сохраняться в файле на диске, следует рассмотреть основные функции уровня ядра, используемые при работе с файлами.
Для открытия файла из драйвера режима ядра используется универсальная функция ZwCreateFile. Универсальность этой функции состоит в том, что с ее помощью производится и открытие существующих файлов, и создание новых.
Специфика системной функции ZwCreateFile состоит в том, что она имеет протокольных параметров даже больше, чем пользовательский вызов CreateFile. Существенная часть входной информации об открываемом объекте поступает внутри структуры OBJECT_ATTRIBURTES, которую следует предварительно создать и заполнить соответствующими конкретными данными. Для ведения учетной информации открытого объекта используется структура данных IO_STATUS_BLOCK, которую следует предоставить при вызове (инициализировать ее не следует).
Представим основные параметры функции ZwCreateFile в следующей таблице

Таблица 1.8.1. Параметры функции ZwCreateFile

Тип параметра
Описание параметра

OUT PHANDLE pHandle
Указатель на переменную, куда следует поместить дескриптор открытого объекта

IN ACCESS_MASK DesiredAccess
Характеристика доступа к объекту. Для фалов чаще всего используются значения GENERIC_READ или GENERIC_WRITE

IN POBJECT_ATTRIBUTES pObjAttributes
Указатель на заполненную вызывающим кодом структуру данных, которая описывает имя, местоположение и некоторые другие характеристики открываемого объекта

OUT PIO_STATUS_BLOCK pIOStatus
Указатель на буфер, в котором будет размещена информация об открытом объекте в формате структуры IO_STATUS_BLOCK

IN PLARGE_INTEGER AllocationSize
Начальный размер файла в байтах. Ненулевое значение принимается во внимание только при создании и перезаписи файла

IN ULONG FileAttributes
Атрибуты открываемого файла. Типовым является значение FILE_ATTRIBUTE_NORMAL

IN ULONG SharedAccessFlags
Описывает, разрешен ли совместный доступ, например, FILE_SHARE_READ – для чтения

IN ULONG CreateDispositionFlags
Способ открытия файла, например, FILE_OPEN_IF – если не существует, создать

IN ULONG CreateOptions
Комбинация флагов создания, например, FILE_SYNCHONOUS_IO_NONALERT – все операции над файлом выполняются как синхронные (DesiredAccess должен включать флаг SYNCHRONIZE)

IN PVOID EaBuffer
Для драйверов устройств следует указывать NULL

IN ULONG EaLength
Для драйверов устройств следует указывать 0

Для заполнения структуры атрибутов объекта используется функция InitializeObjectAttributes. Опишем ее параметры в следующей таблице

Таблица 1.8.2. Параметры функции InitializeObjectAttributes

Тип параметра
Описание параметра

OUT POBJECT_ATTRIBUTES pObjAttributes
Указатель на переменную, куда следует поместить атрибуты объекта

IN PUNICODE_STRING ObjectName
Имя объекта, HANDLE которого создается

IN ULONG Attributes
Флаги атрибутов объекта, при открытии файла как правило используются флаги OBJ_CASE_INSENSITIVE и OBJ_KERNEL_HANDLE

IN HANDLE RootDirectory
Дескриптор корневой директории для объекта, описатель атрибутов которого создается. Если ObjectName полностью описывает путь к объекту, то значению RootDirectory присваивается NULL

IN PSECURITY_DESCRIPTOR SecurityDescriptor
Дескриптор безопасности. Если указано NULL, то применяется стандартный дескриптор

Запись в файл выполняется системной функцией ZwWriteFile

Таблица 1.8.3. Параметры функции ZwWriteFile

Тип параметра
Описание параметра

IN HANDLE FileHandle
Дескриптор открытого или модифицированного файлового объекта

IN HANDLE Event
Для драйверов устройств следует указывать NULL

IN PIO_APC_ROUTINE
Для драйверов устройств следует указывать NULL

IN PVOID ApcContext
Для драйверов устройств следует указывать NULL

OUT PIO_STATUS_BLOCK pioStatusBlock
В поле pIoStatusBlock->Information по завершении вызова находится число реально записанных байт

IN PVOID Buffer
Буфер с данными для записи

IN ULONG Length
Размер записываемой порции данных

IN PLARGE_INTEGER pByteOffset
Указатель на переменную где содержится смещение в файле от его начала, по которому следует производить запись

IN PULONG Key
Для драйверов устройств следует указывать NULL

Для закрытия дескриптора объекта следует применять функцию ZwCloseKey.
Следует отметить, что функции работы с файлами могут работать только на уровне IRQL, равном PASSIVE_LEVEL. Это приводит к необходимости применения специальной методики при протоколировании обмена данными с USB‑накопителем.
1.9 Работа с реестром в режиме ядра
Работа с реестром из драйвера уровня ядра необходима, так как именно в системном реестре хранится информация о настройках протоколирования. Информация о настройках хранится в ключе реестра, связанном с устройством, к которому подключается драйвер-фильтр. Имя этого устройства соответствует шаблону HKEY_LOCAL_MACHINESystemCurrentControlSetEnumUSBXXXXXXDeviceParameters.
Доступ ключу устройства в реестре в драйвере предоставляется функцией IoOpenDeviceRegistryKey. Перечислим ее параметры

Таблица 1.9.1. Параметры функции IoOpenDeviceRegistry

Тип параметра
Описание параметра

IN PDEVICE_OBJECT DeviceObject
Указатель на объект физического устройства, ключ которого должен быть открыт

IN ULONG DevInstKeyType
Параметр определяющий, связан ли открываемый ключ непосредственно с физ. устройством или его программным обеспечением

IN ACCESS_MASK DesiredAccess
Этот параметр определяет права доступа к ключу

OUT PHANDLE DevInstRegKey
Указатель на переменную, куда следует поместить дескриптор открытого ключа

Открыв основной ключ, следует получить доступ к вложенному в него ключу с параметрами протоколирования. Для этого используется функция ZwOpenKey. Перечислим ее параметры

Таблица 1.9.2. Параметры функции ZwOpenKey

Тип параметра
Описание параметра

OUT PHANDLE KeyHandle
Указатель на переменную, куда следует поместить дескриптор открытого ключа

IN ACCESS_MASK DesiredAccess
Этот параметр определяет права доступа к ключу

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

Открыв ключ собственных параметров драйверу необходимо считать настройки протоколирования. Для чтения значения параметров ключа реестра используется функция ZwQueryValueKey. Перечислим ее параметры
Таблица 1.9.3. Параметры функции ZwQueryValueKey

Тип параметра
Описание параметра

IN HANDLE KeyHandle
Дескриптор ключа, которому принадлежит считываемый параметр

IN PUNICODE_STRING ValueName
Строка юникод-символов, содержащая имя параметра ключа

IN KEY_VALUE_INFORMATION_CLASS KeyValueInformationClass
Этот параметр принимает одно их трех значений в зависимости от полноты информации о параметре KeyValueBasicInformation KeyValueFullInformation KeyValuePartialInformation

OUT PVOID KeyInformation
Указатель на буфер, выделенный вызывающим кодом, в который должна быть помещена запрашиваемая информация

IN ULONG Length
Длина предоставленного буфера

OUT PULONG ResultLength
Указатель на переменную, содержащую число реально записанных в KeyInformation байт

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

1.10 MDL‑списки
MDL‑список – это структура, хранящая отображение блока виртуальной памяти на физическую память. MDL‑список используется в разрабатываемом драйвере для хранения информации из URB‑пакетов, связанных с вводом / выводом USB‑устройства. Кроме того, обмен информацией с USB‑устройством в режиме прямого доступа к памяти ведется именно посредством MDL‑списков.
Перед использовании MDL‑списка в драйвере необходимо провести ряд подготовительных действий
· выделить область в страничной памяти с помощью вызова функции ExAllocatePool;
· вызвать функцию MmCreateMdl, создающую и инициализирующую MDL‑список;
· выполнить фиксацию страниц, описанных в MDL‑списке, в физической памяти с помощью вызова функции MmProbeAndLockPages.
После завершения использования MDL‑списка его следует освободить
· отменить фиксацию страниц страничной памяти в оперативной памяти вызовом функции MmUnlockPages;
· очистить MDL‑список, вызвав функцию IoFreeMdl;
· освободить выделенную под список страничную память вызовом ExFreePool.

2. Конструкторский раздел
2.1 Точки входа разрабатываемого драйвера
Разрабатываемый драйвер является драйвером нижнего уровня. В стеке драйверов USB‑накопителя он находится непосредственно под драйвером устройства, если после его загрузки не произойдет установки какого-либо другого драйвера-фильтра нижнего уровня.
Разрабатываемый драйвер включает в себя следующие точки входа
· DriverEntry;
· AddDevice;
· DriverUnload;
· Функции обработки IRP‑пакетов
· обработка IRP‑пакетов с кодами IRP_MJ_INTERNAL_DEVICE_CONTROL – функция DispatchInternalDeviceControl;
· обработка IRP пакетов с прочими кодами – функция DispatchRoutine.
Рассмотрим каждую из них более подробно.
2.1.1 Функция DriverEntry
В этой функции происходит регистрация всех стандартных точек входа драйвера и обработчиков IRP‑пакетов. В разрабатываемом драйвере пакеты IRP c кодами, не равными IRP_MJ_INTERNAL_DEVICE_CONTROL обрабатываются функцией DispatchRoutine.
2.1.2 Функция AddDevice
Управление этой функции передается диспетчером ввода / вывода после того, как завершает свою работу DriverEntry. AddDevice создает функциональный объект устройства с помощью вызова IoCreateDevice и подключает его к стеку драйверов выбранного устройства (вызовом IoAttachDeviceToDeviceStack). Кроме того, в этой функции производятся действия по подготовке к протоколированию считываются настройки из системного реестра, выделяется буфер для сбора протоколируемой информации, создается лог-файл.
2.1.3 Функция DriverUnload
Функция DriverUnload необходима для того, чтобы сделать драйвер выгружаемым. В унаследованных драйверах на эту функцию возложен весь процесс выгрузки драйвера удаление символьных ссылок, объектов устройств драйвера, отключение прерываний от объектов, освобождение выделенной памяти. В WDM‑драйверах все эти действия возложены на функцию-обработчик пакетов с кодом IRP_MJ_PNP.
2.1.4 Функция DispatchRoutine
На эту функцию возложены обязанности по обработке IRP‑пакетов с различными кодами, хотя в разрабатываемом драйвере существует необходимость в обработке только двух типов запросов. Все запросы с кодом, отличным от IRP_MJ_PNP передаются по стеку драйверов без изменений. Запросы же IRP_MJ_PNP диспетчеризуются по суб-кодам в функции PnP_Dispatch. Необходимость диспетчеризации по суб-кодам запросов IRP_MJ_PNP вызвана тем, что драйвер не должен нарушать порядка работы операционной системы и обязан подчиняться PnP‑менеджеру, то есть в драйвере должны корректно обрабатываться события старта и удаления устройства.
2.1.5 Функция DispatchInternalDeviceControl
Запросы ввода / вывода к USB‑накопителю передаются в составе IRP‑пакетов с кодом IRP_MN_INTERNAL_DEVICE_CONTROL. Этот пакет содержит полную информацию о направлении и характере передаваемых данных. То есть для протоколирования обмена информацией с USB‑носителем следует перехватывать пакеты именно этого типа.
Для того чтобы перехватывать информацию, передаваемую в обоих направлениях, следует установить функцию обратного вызова диспетчера ввода / вывода. Методика установки этой функции была описана в разделе 1.7. При наличии этой функции разрабатываемый драйвер-фильтр получит возможность перехвата данных, передаваемых от устройства к хосту.
Для сохранения протоколируемой информации используется, как уже было сказано в разделе 1.10, MDL‑список. Этот MDL‑список создается в функции AddDevice. Объем памяти, выделяемой под список, совпадает с максимальным размером лог-файла, задаваемым в пользовательском приложении. После создания список фиксируется в страничной памяти, что предотвращает его выгрузку на жесткий диск во время работы драйвера. После этих подготовительных действий список используется в функции DispatchInternalDeviceControl – он заполняется перехватываемой информацией.
Запись накопленного буфера в лог-файл происходит при удалении устройства.
Такая методика выбрана из-за того, что функция DispatchInternalDeviceControl работает на уровне запроса прерываний, равном DISPATCH_LEVEL, что сильно затрудняет использование механизмов синхронизации, которые могли бы позволить перейти на уровень запроса прерываний, равный PASSIVE_LEVEL, где становятся доступными функции работы с файлами. Если бы это было достигнуто в разрабатываемом драйвере, то отпала бы необходимость выделения больших объемов нестраничной памяти для хранения протокола.
Запись файла на диск в момент удаления устройства возможна, так как это событие инициализируется PnP‑менеджером, запросы которого всегда происходят на уровне IRQL, равном PASSIVE_LEVEL.

2.2 Размещение кода драйвера в памяти
Некоторые функции драйвера, например те, которые выполняют инициализацию, выгодно выполнить и освободить память, занимаемую ими. В языке C есть специальная директива #pragma_alloc_text (<тип секции>, <имя размещаемой функции>), позволяющая управлять размещением кода. В качестве типа секции могут указываться значения «INIT» или «PAGE».
Функции с размещением в секции «INIT» выгружаются, и память, занимаемая ими, освобождается сразу по завершении их работы. В разрабатываемом драйвере в секции «INIT» размещена точка входа DriverEntry, поскольку она выполняется единожды при загрузке драйвера.
Точки входа AddDevice и DriverUnload располагаются в секции «PAGE», то есть в страничной памяти, поскольку они гарантированно вызываются на уровне привилегий, равном PASSIVE_LEVEL и, даже оказавшись выгруженными на диск, будут немедленно загружены в физическую память менеджером страничной памяти (который способен работать только на уровне PASSIVE_LEVEL).
Остальные же точки входа (DispatchRoutine и DispatchInternalDeviceControl) располагаются по умолчанию, в нестраничной памяти, поскольку их работа зависит от клиентского драйвера USB‑устройства, под которым в стеке драйверов располагается разрабатываемый драйвер-фильтр. Уровень привилегий его запросов слабо предсказуем и может быть равен DISPATCH_LEVEL. На этом уровне подкачка страниц невозможна, что при обращении к выгруженной функции приведет к краху системы.
2.3 Установка драйвера в системе
Для установки драйвера следует создать его ключ в системном реестре. Имя ключа должно иметь следующий вид
HKEY_LOCAL_MACHINESystemCurrentControlSetServicesdriver_name.
Последняя часть имени специфична для устанавливаемого драйвера. Создаваемый ключ должен содержать следующие параметры
· DisplayName – значение этого параметра описывает текст, используемый в служебных программах;
· ErrorControl – этот параметр предписывает операционной системе способ поведения в той ситуации, когда при загрузке драйвера произошла ошибка;
· ImagePath – описывает полный путь к файлу с исполняемым кодом драйвера;
· Start – описывает стадию загрузки операционной системы, когда следует загружать драйвер;
· Type – определяет тип драйвера.
Возможные значения указанных параметров можно узнать из документации MSDN.
Таким образом, первая стадия установки драйвера в систему заключается в том, что должен быть создан ключ драйвера в реестре, а сам драйвер скопирован в каталог, описываемый строкой ImagePath (как правило -%SystemRoot%System32Driversdriver_name.sys).
Далее должно быть выбрано устройство, на которое будет установлен фильтр. Выбранному устройству в системном реестре соответствует ключ с именем вида HKLMCurrentControlSetEnumUSBXXXYYY.
Последняя часть ключа (XXXYYY) определяется именем устройства. При установке драйвера фильтра нижнего уровня в разделе YYY создается строковый параметр LowerFilters, которому присваивается значение, совпадающее с именем драйвера, для которого был создан ключ в …Services. Таким образом, при подключении устройства к системе для него будет создан стек драйверов, в состав которого в качестве фильтра нижнего уровня будет загружен устанавливаемый драйвер.
Кроме того, в ключе, связанном с устройством, при установке драйвера создается дополнительный раздел MyFilterParams, который хранит два параметра
· MaxLogSize – максимальный размер лог-файла;
· LogFileName – имя лог-файла.

3. Технологический раздел
3.1 Выбор языка и средств программирования
Разрабатываемый программный комплекс состоит из двух частей
· Драйвера-фильтра;
· Управляющего приложения для установки фильтра и ввода параметров протоколирования.
Каждая из программ, осуществляет общение с операционной системой на разном уровне. Соответственно необходим разный подход к этим задачам и специальный подбор средств разработки.

3.1.1 Драйвер-фильтр
От разрабатываемого драйвера-фильтра требуется высокая скорость работы и надежность. При его работе осуществляется множество манипуляций с памятью, операций с указателями, преобразований типов. Важно представлять структуру скомпилированного продукта, чтобы правильно представить себе логику его работы. Среди языков программирования, удовлетворяющих этим требованиям, известны языки С и ассемблер. Для написания драйвера предпочтение было отдано языку C. Такой выбор был сделан по следующим причинам
· Существует специальный компилятор C, поставляемый в составе пакета DDK, предназначенный специально для компиляции драйверов. Он содержит множество макроопределений и библиотек, позволяющих сделать процесс написания драйвера более легким. Microsoft рекомендует его как основную среду для разработки драйверов для Windows;
· Программы на ассемблере работают быстрее, чем программы, написанные на C. Но разница в скорости между этими языками не очень велика. Зато производительность труда при использовании C намного выше, чем при использовании ассемблера.

3.1.2 Управляющее приложение
Управляющее приложение было создано в среде разработки Borland C++ Builder, поскольку эта среда программирования предоставляет широкие возможности по созданию пользовательского интерфейса и ускоряет процесс разработки программных продуктов. Данная среда программирования содержит множество стандартных элементов оконного пользовательского интерфейса, использование которых позволило сделать управляющее приложение простым и понятным для пользователя. Для рассматриваемого приложения скорость работы и объем исполняемого файла не являются критичными факторами, поэтому выбор среды Borland C++ Builder можно считать вполне обоснованным.
3.2 Структуры данных драйвера-фильтра
Для сбора информации о вводе / выводе устройства используется структура BUFFER, объявленная в драйвере следующим образом
typedef struct _BUFFER
{
PVOID Buffer;
PMDL Mdl;
ULONG MaxSize;
ULONG CurrentSize;
} BUFFER, *PBUFFER;
Поясним значения полей структуры
· Buffer – указатель на буфер, хранящий информацию;
· Mdl – указатель на MDL‑список, хранящий отображение буфера на системные страницы в памяти;
· MaxSize – максимальный размер буфера в байтах;
· CurrentSize – текущий размер буфера в байтах.
В программировании считается дурным тоном создание переменных, глобальных для всего кода. В драйверах рекомендуется размещать эти глобальные переменные в структуре расширения устройства. Создание этой структуры, конечный вид которой определяется программистом, происходит при вызове IoCreateDevice, то есть при создании объекта функционального устройства драйвера. Указатель на расширения устройства может быть получен по указателю на объект устройства.
В разрабатываемом драйвере в расширении устройства расположена следующая информация
typedef struct _DEVICE_EXTENSION
{BUFFER UrbPackets;
HANDLE LogFileHandle;
BOOLEAN PreparedToLog;
PURB Urb;
ULONG UrbCount;
PDEVICE_OBJECT topDevObject;

} DEVICE_EXTENSION, *PDEVICE_EXTENSION;
Поясним назначение указанных переменных
· UrbPackets – буфер для протоколирования;
· LogFileHandle – дескриптор файла, в который записывается информация о вводе / выводе;
· PreparedToLog – флаг, указывающий на то, что подготовка к протоколированию прошла успешно;
· Urb – пакет, информация о котором переносится в буфер UrbPackets;
· UrbCount – число URB‑пакетов, прошедших через фильтр;
· topDevObject – объект устройства, находящийся в стеке под нашим устройством.

3.2 Интерфейс управляющего приложения
Управляющее приложение предназначено для установки драйвера-фильтра в системе и передачи ему параметров протоколирования через системный реестр.
Интерфейс управляющего приложения состоит из главного окна, представленного на рисунке 3.2.1

Рис. 3.2.2 Интерфейс управляющего приложения
В левой части окна расположен список, в котором отображаются имена устройств, присутствующих в системе.
Перед установкой драйвера-фильтра на некоторый USB‑накопитель, следует создать ключ драйвера в системном реестре. Для этого следует разместить файл драйвера MyUSBFlt.sys в корневом каталоге управляющего приложения и нажать кнопку «Создать ключ драйвера». По окончании операции будет выдано сообщение о ее результате.
Установка драйвера-фильтра допускается только на устройства типа «Запоминающее устройство USB». Для этого устройство следует выделить в списке, а затем ввести максимальный размер лог-файла и путь к нему. После этого следует нажать кнопку «Установить драйвер». В зависимости от результата установки будет выдано сообщение об успехе операции или ее неудаче.
Драйвер-фильтр начнет свою работу после того, как устройство, на которое он был установлен, будет перезапущено. Перезапуск можно осуществить путем выделения нужного устройства в списке и нажатия кнопки «Перезапустить устройство». Того же результата можно добиться, перезапустив устройство типа «Хост-контроллер» или «Корневой концентратор», к которому подключен рассматриваемый USB‑накопитель. Возможен также перезапуск устройства путем отсоединения от порта концентратора и последующего подключения к нему.
Удаление драйвера-фильтра из системы должно производиться в обратном порядке сначала удаляется драйвер с устройства, а затем ключ драйвера. Это связано с тем, что в случае, если будет удален ключ драйвера, а само устройство не будет освобождено от фильтра, то работа устройства будет блокирована. Но после полного удаления фильтра из системы и перезапуска устройства, его работа будет происходить в обычном режиме.
3.3 Тестирование драйвера-фильтра
Драйвер был протестирован с использованием стандартной тестирующей утилиты DriverVerifier, поставляемой в составе пакета DDK. С помощью этой утилиты были проведены следующие тесты
· Операции с пулами памяти;
· Корректность уровней IRQL, на которых выполняется код драйвера;
· Нехватка ресурсов;
· Нетипичные запросы к драйверу.
Все тесты прошли успешно. Память в системе распределялась правильно, ошибок с ней не возникало. На нехватку ресурсов драйвер реагировал корректно. Нетипичные запросы к драйверу не обрабатывались им.
Для отладки драйвера использовалась программа DebugView. Эта утилита позволяет осуществлять перехват отладочных сообщений, выдаваемых драйвером.

Заключение
В данной работе рассмотрен вопрос, связанный с разработкой драйверов устройств в системе Windows, и реализован драйвер-фильтр USB‑накопителя.
Разработанный драйвер предоставляет следующие возможности
· возможность установки на любой USB‑накопитель, присутствующий в системе;
· перехват информации ввода / вывода USB‑накопителя;
· запись перехваченной информации в файл на диске.
Драйвер-фильтр был протестирован с помощью тестовых утилит из состава пакета DDK и отвечает всем современным требованиям, накладываемым ОС Windows на характеристики драйверов.
Тем не менее, существуют пути по усовершенствованию разработанного программного комплекса. Например, существует возможность по созданию связки «драйвер уровня ядра – управляющее приложение пользовательского уровня», которая при наличии развитых механизмов синхронизации позволила бы получать информацию о вводе / выводе устройства в интерактивном режиме. Но это достаточно трудоемкая задача, выходящая за рамки курсовой работы.

Список литературы и интернет-ресурсов
1. Агуров П.В. Интерфейсы USB. Практика использования и программирования. – СПб. БХВ-Петербург, 2004. – 576 с.
2. Солдатов В.П. Программирование драйверов Windows. Изд. 2-е, перераб. и доп. – М. ООО «Бином-Пресс», 2004. – 480 с.
3. Материалы проекта «Windows Assembly Site» – www.wasm.ru.
4. Материалы сайта www.usb.org.
5. MSDN Library, Copyright 1987–2005 Microsoft Corporation.