Родители лучше всего знают что можно делать их ребенку, а чего нельзя. Так и программист конкретной программы знает лучше других, какие действия должна выполнять программа, а какие нет. Но не всё так хорошо как хотелось бы. Вредоносные программы частенько вмешиваются в работу других программа. Последствия от эксплуатации уязвимостей в сетевых программах вообще не предсказуемы. Всё это так или иначе нарушает информационную безопасность не только на предприятиях, но и на компьютерах рядовых пользователей.
А как было бы хорошо, если бы программа сама знала какие действия ей не нужны и на всякий случай отключала их.
Антивирусы и прочие подобные системы пытаются брать на себя роль защитников, но вся защита сводится к набору правил, которые для многих одинаковы и не учитывают специфики защищаемых программ. Такой подход иногда приводит к неприятных ситуациям. Вот несколько примеров:
- Внедренный вредоносный кода в браузер — получает беспрепятственный доступ к сетевым функциям.
- Внедренный вредоносный кода в почтовую программу — беспрепятственно получит доступ к файлам почтовой базы, несмотря на то, что антивирус будет защищать эти файлы.
- Эксплуатация уязвимости в сетевых программах — иногда позволяет получить доступ к системе или повышение прав, несмотря на то, что пользователь может иметь минимальные права.
Но как уже выше говорилось — автора программы лучше всего знает, что должна делать его программа. Так почему ему не дать возможности самому поставить ограничения на определенные действия? Причем не для всей программы, а для конкретных потоков.
Продуктивность идеи
Благодаря этому можно добиться:
- Потоки обработки и визуализации информации не мог бы работать с сетью.
- Потоки работы с сетью не могли бы запускать другую программу или внедряться в чужой процесс.
- Вредоносный код внедренный в программу, не смог бы воспользоваться её уровнем доверия у антивирусов.
Если говорить с более реалистичной точки, то можно было бы:
- Браузер сам себе мог бы запретить запускать сторонние программы.
- Почтовая программа могла бы иметь доступ к файлам базы, а внедренный в ней код — нет.
- Любая программа смогла бы узнать о своей аномальной активности.
Особенности реализации и использования идеи
Для реализации идеи необходимо в ядре Windows:
- Сделать правки в двух системных структурах.
- Добавить один системный вызов.
- Написать кода на 50-100 строк.
В kernel32.dll добавить одну функцию управляющую защитой. Которая тоже не будет особо большой.
Данные действия под силу любому программисту (причем за один день). Но самая большая трудность в том, что Microsoft сама должна это сделать. Именно по этому такой механизм защиты остается по в мечтах.
Теоретическое отступление
Так как любые ограничения в пользовательском режиме (user mode) легко обходятся или снимаются, то необходимо всю защиту реализовывать в ядре.
Основной принцип вызова системных функций в Windows можно представить в виде следующей схемы:
После вызова sysenter / syscall / int 0x2E или call dword ptr fs:[0xC0] (в зависимости от версии windows) далее управление попадет в ядро, где функция KiSystemService просматривает таблицу адресов системных сервисов (SDT) и по номеру сервиса (передаваемого в регистре eax) находит адрес нужной функции и далее передаёт на неё управление. Таким образом происходит вызов нужной функции.
В системе используется два таблицы — одна отвечает непосредственно за работу систему и обрабатывается ядром, а вторая отвечает за работу GUI и обрабатывается драйвером win32.sys. Нас интересует именно первая таблица.
Благодаря такой архитектуре возможно поставить ограничение доступа на вызов любой системной функции. Для этого достаточно вставить простую проверку в функцию KiSystemService.
Место проверки мы уже обнаружили, теперь необходимо найти философский камень место под хранение матрицы доступа.
В операционных системах Windows, в ядре для описания потока используется структура KTHREAD, которая у каждого потока своя. Именно там можно будет хранить матрицу доступа. Но теперь это уже будет не матрица, а линей список (т.к. каждый поток будет хранить информацию только о себе).
Всё бы то хорошо, но есть еще и сеть, работа с которой организована через специальные драйвера. Но и тут проблема решается в обработке некоторых IRP запросов к устройствам \DeviceTcp, \DeviceUdp, \DeviceRaw что позволит также прозрачно проверять возможность доступа к сети из определенных потоков.
Как будем реализовывать защиту
Реализация защиты видится следующим образом:
- В структуре описания каждого потока есть таблица запретов состоящая из битов. В Windows 7 в SDT находится 360 функций, по этому массива на 512 бит(64 байта) хватит выше крыши.
- По умолчанию все биты сброшены (т.е. запретов нет)
- Также есть 64 битное поле ключа, о котором речь пойдет чуть позже.
- Также есть 32/64 битное поле хранящее адрес callback функции
- Итого имеем данных на 76/80 байта
- В ядро добавлен еще один системный вызов, который заполняет структуру по следующим правилам:
- Биты можно устанавливать всегда.
- Сбрасывать биты можно только указав спец ключ защиты.
- Спец ключ можно получить только один раз.
- Callback функцию можно установить только один раз или неограниченное кол-во, используя ключ.
- В kernel32.dll и ntdll.dll добавлена интерфейсная функция к системному вызову организующего управление защитой.
Ход работы защиты:
- В KiSystemService или обработчике IRP запросов сетевого драйвера получаем указатель на структуру PTHREAD
- Проверяем границы индекса (в случае с номером в SDT)
- Проверяем установлен бит или нет. Если установлен то вернуть STATUS_ACCESS_DENIED
- Если установлен бит и указана callback функция, то вызвать её. Тем самым уведомив процесс о том, что какой-либо поток попытался выполнить запрещенные действия.
Отдельное внимание хотелось бы уделить функции установки защиты. Ей прототип видится как:
DWORD QuerySystemSecurity(HANDLE hThread, DWORD Flag, QWORD* Key, VOID* Data);
- hThread — дескриптор потока для которого устанавливается защита. Или 0xFFFFFFFF в случае текущего потока.
- Flag — параметры передаваемые или запрашиваемые:
- Получить ключ.
- Установить запрет на функцию.
- Установить запрет на список функций.
- Установить запрет на группу функций. — к примеру работа с файлами или с процессами.
- Установить callback функцию.
- Снять запрет на функцию.
- Наследовать таблицу защиты или нет.
- Запретить снимать защиту даже если задан ключ.
- Запретить получение ключа.
- Key — адрес ключа или NULL.
- Data — данные в зависимости от Flag.
Так же в QuerySystemSecurity должна находиться таблица соответствия номеру запрещаемой функции и её номеру из SDT т.к. последний меняется от версии к версии.
Заключение
Благодаря одной простой функции и небольшой модификации ядра, можно получить довольно гибкую и быструю защиту. Программист сам сможет запрещать своим программам аномальные действия. Что очень было бы полезно в сетевых программах и в первую очередь в браузерах.
Защита на основе такого подхода смогла бы более гибко контролировать выполнение стороннего кода в программах (не только вредоносного, но и плагинов от других авторов). Если небольших разработчиков программ такая защита бы не заинтересовала, то крупные смогли бы легко её использовать для усиления защиты своих программ от вмешательства чужого кода.
Конечно такая защита может быть реализована не только самой Microsoft, но и сторонними разработчиками, но это уже будет костылем и работать не столь эффективно (из-за того что придется отказаться от хранения данных в PTHREAD) ну и не будет иметь такого глобального масштаба.
Но как всегда это лишь собственные мечты о существовании защиты, которые вряд ли будут воплощены Microsoft.
Автор: shevmax