Четверг, 24 Май 02
софт журнал
Меню сайта
Категории каталога
секреты OS (win, *nix, freebsd e.t.c) [12]
Трутся спиной медведи, о земную ось...
securite [18]
компьютерная безопасность
разное [5]
всё что не вошло в первые 2 категории
Главная » Статьи » software » securite

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

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

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

В распределенных сетях (ботнетах) перезагрузка одного или нескольких узлов — вообще не проблема, к тому же после перезагрузки узел будет инфицирован вновь. Этот факт очень трудно обнаружить, ведь никаких изменений на диске нет! А сетевые соединения современные руткиты скрывают весьма эффективно. Прошли те времена, когда открытые порты обнаруживались тривиальным сканированием с соседней машины. Продвинутые руткиты не открывают никаких портов. Они садятся на сетевой интерфейс, контролируя трафик и модифицируя определенные поля в заголовках TCP/IP-пакетов, значения которых согласно RFC выбираются случайным образом. Скремблер скроет факт модификации (независимо от передаваемых руткитом данных мы получим такое же хаотичное распределение, как и на незараженной машине), а несимметричный шифратор предотвратит декодирование перехваченной информации. Даже если мы заведомо знаем, что руткит есть!

Откуда мы узнаем, что он есть? Объем трафика в норме, никаких изменений на диске не наблюдается (что кардинальным образом отличается от руткитов первого типа, которые обнаруживаются настолько тривиально, насколько это можно себе представить). Загружаемся с LiveCD и проверяем контрольные суммы всех файлов (или просто осуществляем побайтовое сравнение с дистрибутивом). Конечно, для серверов такой способ не очень-то пригоден — их вообще лучше не перезагружать, но сервера, критичные к перезагрузкам, обычно оснащены RAID-массивами с hot-plug'ом. Так что просто вытаскиваем один набор дисков из матрицы, ставим его на другую машину, проверяем контрольную сумму и делаем оргвыводы.

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

Прежде чем продвигаться вглубь, сразу выбросим на помойку несколько популярных, но безнадежно устаревших способов борьбы с руткитами. Чтение памяти ядра через /dev/[k]mem (при активом рутките!) — это курам на смех. Поиск следов компрометации при помощи GDB – из той же оперы. Руткиту ничего не стоит отследить обращение к любому файлу/устройству, «вычистив» следы своего пребывания или совершить «харакири» при запуске GDB. Чуть сложнее — ввести в заблуждение GDB, оставаясь при этом активным, живым и здоровым.

Достойных отладчиков ядерного уровня под никсы не существует. Ну, не то, чтобы совсем нет, но в штатный комплект поставки уж точно ни один не входит. Хорошо еще, если установка отладчика не требует перекомпиляции ядра, не говоря уже о перезагрузке. Самих же отладчиков довольно много: NLKD, KDB, LinIce, DDB, и ни один из них не обладает неоспоримыми преимуществами перед остальными. Кстати, для ловли руткитов иметь готовый к употреблению отладчик необязательно. Достаточно написать загружаемый модуль ядра, считывающий и передающий на прикладной уровень все критичные к перехвату структуры данных вместе с машинным кодом (естественно, ядро должно быть скомпилировано с поддержкой модульности). Что это за данные — мы сейчас выясним.
Магические аббревиатуры — GDT, LDT, IDT

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

Начнем с простого. С таблиц глобальных/локальных дескрипторов (Global/Local Description Table или, сокращенно, GDT/LDT), хранящих базовые адреса, лимиты и атрибуты селекторов. Чем они могут помочь руткиту? Ну, кое-чем могут. Linux/xBSD используют плоскую модель памяти, при которой селекторы CS (код), DS (данные) и SS (стек) «распахнуты» на все адресное пространство: от нуля до самых верхних его окраин. Создание нового селектора с базой, отличной от нуля, с последующей его загрузкой в один из сегментных регистров существенно затрудняет дизассемблирование руткита, особенно тех экземпляров, что выдраны из памяти чужой машины. Таблицы дескрипторов в распоряжении реверсера нет и не будет (руткит умер). Грубо говоря, мы вообще не можем определить, к каким данным осуществляется обращение, ведь база селектора неизвестна! Реверсеров и сотрудников антивирусных компаний такие руткиты просто доводят до бешенства, затягивая анализ, а вместе с ним и приготовление «вакцины».

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

Огромное количество руткитов модифицирует таблицу дескрипторов прерываний (Interrupt Description Table или, сокращенно, IDT), позволяющую им перехватывать любые прерывания и исключения, в том числе и системные вызовы, реализованные на некоторых системах именно как прерывания. Но о сисколлах мы еще поговорим, а пока лишь отметим, что модификация IDT позволяет руткиту перехватывать обращения к страницам, вытесненным на диск (при обращении к ним возникает исключение Page Fault). А также перехватывать другие исключения, например, общее исключение защиты (General Protection Fault), отладочное и пошаговое исключение (отличный способ борьбы с отладчиками), не говоря уже о прерываниях, поступающих от аппаратных устройств — клавиатуры, сетевой карты и прочего оборудования, прямое обращение к которому очень полезно для сокрытия «преступной» деятельности.

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

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

В зависимости от типа и версии ОС системные вызовы реализуются по-разному. Самый древний механизм — это далекий вызов по селектору семь, смещение ноль — CALL FAR 0007h:00000000h (или, то же самое, но в AT&T синтаксисе — lcall $7,$0). Он работает практически на всех x86-клонах UNIX'а, однако практического значения не имеет, поскольку им пользуются только некоторые ассемблерные программы в стиле «hello, world!», ну и… вирусы, также написанные на ассемблере.

Стандартом де-факто стал программный вызов прерывания 80h (INT 80h), работающий как в Linux, так и во FreeBSD. Как руткит его может перехватить? Посредством модификации таблицы дескрипторов прерываний, переназначая вектор 80h на свой собственный код. Однако это не единственный вариант. Стандартно INT 80h передает управление на функцию system_call, адрес которой можно определить по файлу System.map, если он, конечно, не удален администратором по соображениям безопасности, — тогда руткит либо читает вектор 80h через SIDT, либо находит system_call эвристическим путем, поскольку она, как и любой другой обработчик прерывания, содержит довольно характерный код. Вставив в начало (или середину) этой функции команду перехода на свое тело, руткит будет получать управление при всяком системном вызове. Следовательно, мы должны считать код функции system_call из памяти, сравнив его с оригиналом, который можно позаимствовать из неупакованного ядра, выдернутого из дистрибутивного диска (как это сделать, мы уже неоднократно рассказывали).

После выполнения системного вызова управление получает другая интересная функция — ret_from_sys_call, идущая следом за system_call и также, как и system_call, присутствующая в System.map. Ее перехватывают многие руткиты, что вполне логично, поскольку «вычистить» следы своего пребывания лучше всего после отработки системного вызова (а не до него). Популярные руководства по поиску руткитов об этом почему-то забывают, а зря! Функцию ret_from_sys_call следует проверять в первую очередь, сравнивая ее код с кодом оригинальной ret_from_sys_call, ну или просто дизассемблируя его на предмет наличия посторонних переходов.

Начиная с версии 2.5, ядро Linux поддерживает механизм быстрых системных вызовов, реализуемый командами SYSENTER/SYSEXIT (Intel) и SYSCALL/SYSRET (AMD). Он существенно облегчает перехват и делает его трудно заметным. Команда SYSENTER передает управление с 3-го кольца прикладного уровня на ядерный уровень, используя специальные MSR-регистры, а конкретно: IA32_SYSENTER_CS содержит селектор целевого сегмента, IA32_SYSENTER_EIP — целевой адрес перехода, IA32_SYSENTER_ESP — новое значение регистра ESP при переходе на ядерный уровень. При этом селектор стека равняется (IA32_SYSENTER_CS + 08h). SYSCALL работает практически аналогичным образом, только MSR регистры другие: STAR, LSTAR и CSTAR (подробнее об этом можно прочитать в описании самой команды SYSCALL в спецификации от AMD, ну или от Intel, с учетом, что она поддерживает эту команду в той же манере, в какой AMD поддерживает SYSENTER).

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

Поддержка SYSENTER/SYSCALL не отменяет INT 80h, по-прежнему присутствующую в ядре и вызываемую из старых прикладных библиотек некоторых ассемблерных программ, ну и, конечно, вирусов, работающих на прикладном уровне! Так что руткитам теперь приходится перехватывать и то, и другое, хотя перехват SYSENTER/SYSCALL намного более перспективен (INT 80h используется все реже и реже).

А вот разработчики FreeBSD от INT 80h отказываться пока не собираются, и хотя существует патч от David'а Xu, написанный в конце 2002 года и переводящий систему на SYSENTER/SYSCALL (people.freebsd.org/~davidxu/sysenter/), по умолчанию он не включен в стабильный релиз. Впрочем, сторонние составители дистрибутивов его активно используют (взять, к примеру, DragonFlyBSD).
Модификация таблицы системных вызовов

Указатели на системные вызовы перечислены в таблице sys_call_table, адрес которой можно найти все в том же System.map или вычислить эвристическим путем (удаление System.map'а не слишком-то усиливает безопасность).

Подмена указателя на оригинальный системный вызов указателем на код руткита — это классика перехвата. Элементарно обнаруживается путем сравнения оригинальной таблицы системных вызовов, выдернутой из неупакованного ядра дизассемблером, с ее «живой» сестрицей, прочитать которую можно либо отладчиком, либо «руками» – командой mov, вызываемой из загружаемого модуля ядра. Оба способа абсолютно ненадежны и выявляют только пионерские руткиты. «Зверюшки» посерьезнее сбрасывают страницы, принадлежащие таблице системных вызовов, в NO_ACCESS. В результате, при обращении к ним процессор выбрасывает исключение, подхватываемое руткитом, который смотрит, откуда пришел вызов на чтение — если это функция system_call, то все ОК, если же нет, то руткит возвращает подложные данные, и таблица системных вызовов выглядит, как сама невинность. Конечно, перед чтением можно проверить атрибуты страницы, но весь фокус в том, что функция определения атрибутов страниц реализована как системный вызов, находящийся в той же самой таблице, контролируемой руткитом. Упс! Приехали! Ладно, перед чтением мы назначим свой собственный обработчик исключений, который выручит нас только в том случае, если руткит не модифицировал IDT. Решение заключается в «ручном» разборе страничного каталога, формат которого описан в руководствах по системному программированию на процессоры Intel/AMD и представляет собой намного более простую задачу, чем это кажется поначалу.

Естественно, кроме таблицы системных вызовов необходимо проверить и целостность самих системных вызовов, помня о том, что руткиты могут внедрять команду перехода на свое тело не только в начало функции системного вызова, но также в ее конец или середину, хотя для этого им придется тащить за собой дизассемблер длин инструкций. Тем не менее, «серединный перехват» — стандарт де-факто для всех серьезных руткитов.
Дайте мне мыло, веревку или… дропер!

Сотрудники антивирусных компаний получают вирусы/руткиты из трех основных источников. Первый – свои собственные HoneyPot'ы, второй – малварь, присланная коллегами (другими антивирусными компаниями), третий (самый плодотворный) — файлы, полученные от пользователей.

В какой-то момент вирусописателям надоело, что их творения разносят в пух и прах в считанные дни, когда на создание руткита и его отладку уходят многие недели — обидно, да? Вот они и стали искать пути, как затруднить анализ малвари, и ведь нашли! Специальная программа, называемая дропером (от английского to drop – бросать), «сбрасывает» основное тело малвари на целевой компьютер, при этом оно шифруется ключом, сгенерированным на основе данных о конфигурации системы, и наружу «торчит» только расшифровщик (зачастую, полиморфный). Как нетрудно догадаться, малварь такого вида работает только на том компьютере, на который она попала «естественным» путем, а всякая попытка запуска ее на другой машине ничего не дает! Ничего, абсолютно!

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

Категория: securite | Добавил: Dorian (08 Сентябрь 14)
Просмотров: 740 | Комментарии: 1 | Рейтинг: 0.0/0 |
Всего комментариев: 0
Добавлять комментарии могут только зарегистрированные пользователи.
[ Регистрация | Вход ]
Форма входа
Поиск
Друзья сайта
Статистика

Онлайн всего: 1
Гостей: 1
Пользователей: 0
Copyright MyCorp © 2024