Как сделать из ядра Linux Windows?

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

Первая статья будет вводной с кратким обзором ключевых методов встраивания. Далее, мы подробнее рассмотрим отдельные методы встраивания, уделяя внимание техническим деталям.

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

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

Каждый из подходов имеет свои сильные и слабые стороны, определяющие границы применимости. Зачастую сильная сторона одного подхода является уязвимым местом другого. Так, к преимуществам встроенных средств защиты стоит отнести отсутствие необходимости проверки аутентичности защищаемой системы; это же является недостатком наложенных средств — как определить, что защищаемое аутентично? С другой стороны, интеграция встроенного средства защиты в защищаемую систему несет в себе ограничение на выбор поставщика компонентов операционной системы и самого средства защиты. Особенно остро эта проблема проявляется на российском рынке, где важную роль играет аттестация систем, и встроенные средства защиты не всегда соответствуют требованиям, определяемым регуляторами (см. требования ФСТЭК России).

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

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

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

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

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

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

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

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

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

Недостатком патчинга можно считать необходимое нарушение целостности компонентов целевой системы. Внесение изменений в код может быть легко обнаружено, что в некоторых контекстах является принципиальным ограничением. Примером подобного рода ограничений является, в частности, механизм защиты ядра ОС Windows — Kernel Patch Protection, более известный как PatchGuard. Одной из его особенностей является реализация контроля целостности ключевых компонентов ядра ОС, что сводит на нет большинство стандартных методов встраивания, основанных на модификации кода. При этом отключение данного средства для установки наложенного является крайним вариантом, т.к. в отсутствии дублирующего функционала во встраиваемом средстве защиты общая защищенность системы очевидно снижается.

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

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

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

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

© Habrahabr.ru