Автор: Крис Касперски
Агрессивное развитие руткитов до сих пор остается безнаказанным и продолжается столь же активно, не встречая никакого существенного сопротивления со стороны защитных технологий, большинство из которых хорошо работает только на словах и ловит общедоступные руткиты, взятые с rootkits.com или аналогичных ресурсов. Руткиты, написанные «под заказ», обнаруживаются значительно хуже, если вообще обнаруживаются. Причем даже такие продвинутые технологии детекции, как удаленное сканирование портов, оказываются бессильными перед новейшими версиями руткитов, которые реально палятся только руками, хвостом и головой.
Мыщъх постоянно держит включенным honeypot на базе VMware, засасывающий кучу малвари. Ее анализ указывает на неуклонный рост количества руткитов, обитающих исключительно в памяти и не записывающих себя на диск, в результате чего у них отпадает необходимость в сокрытии файлов и ветвей реестра, прямо или косвенно ответственных за автозагрузку. Они не создают новых процессов, предпочитая внедряться в адресное пространство уже существующих. Они не открывают новых портов, перехватывая входящий трафик с помощью сырых сокетов или внедряясь в сетевые драйверы (например, в TCPIP.SYS или NDIS.SYS).
В результате ни в реестре, ни в файловой системе не происходит никаких изменений, а значит, ничего прятать не приходится! Естественно, перезагрузка убивает руткиты такого типа наповал, и потому многие администраторы полагают, что никакой опасности нет. Не так уж сложно перезагрузить сервер при возникновении подозрений на его компрометацию. Однако именно установка факта компрометации является первоочередной и самой сложной задачей, стоящей перед администратором. Если сервер действительно был скомпрометирован, то необходимо выяснить, как именно он был скомпрометирован! В противном случае повторные атаки не заставят себя ждать, не говоря уже о том, что после удаления малвари требуется как минимум изменить пароли на все ресурсы, иначе хакер сможет обойтись и без руткита, используя ранее перехваченные пассы.
Сам по себе руткит обычно не представляет никакой угрозы, и открывать удаленный шелл типа backdoor сейчас уже немодно. А вот завести новую учетную запись и добавить IP-адрес «своего» proxy-сервера в список доверенных адресов — это не только проще, но и надежнее, поскольку, в отличие от backdoor'а, это не обнаруживается ни антивирусами, ни другими средствами защиты.
Таким образом, задача сводится к ответу на вопрос: утекли ли наши пароли на сторону или нет. К сожалению, в общем случае задача не имеет решения. Руткит, обитающий в оперативной памяти и существующий короткое время, обнаружить практически невозможно, тем более ручными методами.
Так что мы будем рассматривать лишь долгоживущие руткиты, которых в настоящий момент большинство.
Антивирусы и другие автоматизированные средства
Руткит, известный антивирусу, элементарно обнаруживается путем сканирования почтовых вложений или сетевых пакетов, однако даже в этом случае у хакера имеется масса способов обломать рога антивирусу. Допустим, руткит забрасывается через дыру в браузере, некорректно обрабатывающем TIFF-файлы. Тогда атакующему остается всего лишь заманить жертву на ссылку вида https://www.xxxx.com, чтобы антивирус пропустил нужные сетевые пакеты мимо своих ушей.
Что же касается поиска активных руткитов, то, даже если они известны антивирусу, у них остаются все шансы уйти от возмездия, особенно если антивирус знаком руткитам. Вот, например, существует такая интересная утилита, как Rootkit Revealer от Марка Руссиновича. По утверждению ее создателя, она обнаруживает все руткиты, представленные на wwwrootkits.com, что не соответствует действительности - тривиальная проверка выявляет большое количество малвари, отслеживающей запуск Rootkit Revealer'а и модифицирующей его код в памяти таким образом, чтобы он ничего не показывал. Естественно, подобная техника работает только со строго определенными версиями Rootkit Revealer'а (руткит должен знать точное расположение машинных команд в памяти). А поскольку у разработчиков малвари нет никакого желания отслеживать выход новых версий, они ограничиваются атакой типа WM_X, сводящейся к манипуляции элементами пользовательского интерфейса путем посылки соответствующих сообщений (Window Messages), удаляющих обнаруженные руткиты из списка, отображаемого Rootkit Revealer'ом, что работает со всеми версиями. Но в лог-файл обнаруженные руткиты все-таки попадают.
К тому же Rootkit Revealer находит только те руткиты, которые: а) модифицируют реестр и/или файловую систему; б) скрывают следы своего присутствия. Если хотя бы одно из этих условий не выполняется, руткит не будет обнаружен. Анализ кода некоторых руткитов показывает, что они отслеживают появление окна Rootkit Revealer'а и прекращают свою маскировку на время его работы. Разработчикам защитных утилит уже давно пора взять полиморфизм на вооружение - пока они будут обнаруживаться руткитами, ни о какой защите и речи быть не может! Антивирус не должен иметь постоянной сигнатуры (равно как и окон с заранее известными заголовками)!
Мир руткитов не исчерпывается теми демонстрационными экземплярами, что выложены на wwwrootkits.com. Суди сам: разработка качественного руткита - сложная инженерная задача, и за один вечер такие руткиты не пишутся. Торговать руткитами (в силу их полулегального положения) отваживаются только самые нуждающиеся (или отчаявшиеся). Так какой же резон выкладывать руткит в общественный доступ? Разве что для того, чтобы заявить о себе и посостязаться в крутости с другими хакерами. Но! Профессиональные программисты уже давно миновали стадию самоутверждения и, вместо того чтобы работать за идею, предпочитают кодить за деньги по индивидуальным заказам (по крайней мере, будет на что нанимать адвоката).
Реюз (то есть повторное использование кода) в таких руткитах практически не встречается, и в антивирусные базы попадают лишь немногие. Как уже говорилось выше, правильно спланированная атака предполагает самоуничтожение руткита по истечении некоторого времени. Да и как его ловить, если он существует только в оперативной памяти?! Можно, конечно, передавать антивирусным компаниям дамп ядра операционной системы, но тут есть три но. Во-первых, какая антивирусная компания будет в нем ковыряться? Во-вторых, это же сколько трафика потратит! В-третьих, в дампе кроме руткита может находиться тьма секретной информации, которую разглашать крайне нежелательно, например пароли.
Важно понять, что, в отличие от вирусов и червей, распространяющихся от компьютера к компьютеру и рано или поздно попадающих в антивирусные капканы, настоящие руткиты существуют в единичных экземплярах, и потому обнаружить их могут лишь проактивные технологии, например эвристический анализ. Однако, если заказчик руткита хоть немного дружит с головой, он обязательно проверит, палится ли руткит последними версиями антивирусов при самом строгом режиме эвристики (при котором антивирус ругается даже на честные программы), и, если да, возвратит его назад на доработку.
Поэтому в качестве рабочего тезиса необходимо принять, что руткиты антивирусами не обнаруживаются, как бы нам ни промывали мозги создатели антивирусов.
Удаленное сканирование портов
В эпоху расцвета backdoor'ов удаленное сканирование портов считалось абсолютно надежным методом обнаружения руткитов. Действительно, как бы руткит ни маскировал сетевые соединения и какие бы системные вызовы ни перехватывал, все это воздействует лишь на локальную машину. Да, конечно, можно обмануть и tcpdump, и netstat, но… только локально. Всякая же попытка сканирования зараженного компьютера с соседней машины немедленно выявит открытые порты, если они, разумеется, там есть. И руткит никак не может этому противостоять.
Однако зачем маскировать факт открытия портов, если никакие порты можно вообще не открывать, а использовать уже открытые? Мыщъх исследовал несколько руткитов, которые путем перехвата системных функций мониторили HTTP-трафик и передавали на хакерский узел через 80-й порт информацию о текущем номере последовательности TCP/IP-соединения, чтобы хакер мог послать левый пакет (с командами для руткита), который бы воспринимался системой как правильный. А чтобы соединение с текущим web-узлом не разрывалось, хакер посылал ему еще один пакет, предотвращающий срыв синхронизации номера последовательности. Другими словами, руткит передавал/принимал данные в контексте существующего TCP/IP-соединения, инициированного компьютером-жертвой, и потому сканирование портов ничего подозрительного не выявляло, а вот внимательный анализ TCP/IP-пакетов показал, что пакеты, переданные руткитом хакеру, имели IP-адрес, отличный от IP-адреса целевого узла, с которым и было установлено соединение.
Однако не стоит обольщаться - не все руткиты такие простые, и при желании трафик можно спрятать так, что его никто и никогда не найдет. На сайте Жанны Рутковской выложены готовые утилиты, прячущие сам факт присутствия постороннего трафика и вдобавок шифрующие его алгоритмом RSA, благодаря которому разбор логов tcpdump'а становится пустой тратой времени.
Выдвигаем следующий тезис: руткиты не открывают новых портов, а генерируемый ими трафик ни локальными, ни удаленными сниферами не обнаруживается.
Свет в конце тоннеля или встречный?
Извечный вопрос: как быть, что делать?! Побороть новые руткиты старыми средствами уже не удается, а новых средств, к сожалению, нет. Поэтому приходится возвращаться к скомпрометированной машине и искать руткит непосредственно на ней. Весь вопрос в том, как найти произвольный руткит, если о нем заранее ничего не известно? Ни сигнатур, ни других опознавательных признаков у нас нет.
К счастью, существует не так уж много методов перехвата системных функций, и все они оставляют за собой вполне осязаемые следы, обнаружить которые можно даже без глубоких знаний особенностей реализации операционной системы и ассемблера. Естественно, никаких гарантий у нас нет, но все-таки ручной поиск намного надежнее автоматизированного, пускай он и требует определенной квалификации. Запустить антивирус может и домохозяйка, что нивелирует разницу между ней и опытным хакером, поэтому лучше развивать свои собственные способности, тренировать «нюх», чем доверять безопасность компьютера чужим дядям.
Руткиты принято классифицировать по двум основным критериям: по месту обитания (ядро или прикладной уровень) и способу внедрения (например, перехват функций путем битхака). Такая классификация очень условна, и в реальной жизни сплошь и рядом встречаются гибридные варианты, одновременно работающие как на уровне ядра, так и на прикладном уровне. Забавно, но руткиты, полностью работающие на прикладном уровне, обнаружить сложнее всего, поскольку им доступно огромное количество методик внедрения в чужие процессы, а для манипуляций с трафиком никаких функций вообще перехватывать не нужно - достаточно воспользоваться сырыми сокетами. Но руткиты прикладного уровня - это «не круто» и вообще «не по понятиям». Взор хакеров устремлен в ядро, в котором можно делать все что угодно. Вот только методик перехвата системных функций там - раз, два и обчелся, а потому обнаружение ядерных руткитов представляет собой довольно простую задачу.
Мы будем говорить именно о руткитах уровня ядра, семейство которых делится на два подтипа: одни внедряются путем правки машинного кода, вставляя в начало (редко - в середину) функции команду JMP или CALL для перехода на свое тело; а другие модифицируют структуры данных, например таблицу системных вызовов, хранящую указатели на функции. В NT оба подтипа руткитов встречаются приблизительно с одинаковой частотой. А в Linux/xBSD в основном преобладает второй подтип, что связано с тем фактом, что ядро NT экспортирует NativeAPI-функции как обычная динамическая библиотека (DLL), а чтобы найти NativeAPI-функции в Linux/BSD, следует очень постараться. Да только зачем стараться, если таблица системных вызовов у нас под рукой?!
Существует множество утилит, проверяющих целостность таблицы системных вызовов и восстанавливающих ее в случае необходимости, но мне не известна ни одна утилита, проверяющая целостность самих системных функций, внедрение в которые существенно усиливает жизнестойкость руткита (механизм PatchGuard, реализованный в x86-64 версиях NT, мы не рассматриваем, поскольку его очень легко обойти).
Собственно говоря, при всем различии NT и Linux/BSD техника поиска руткитов одна и та же. Первым делом нам необходимо заполучить дамп ядра или запустить ядерный отладчик. Теоретически руткиты могут перехватывать любые операции, в том числе и попытку сохранения дампа. В NT для этого им достаточно перехватить NativeAPI-функцию KeBugCheckEx и, прежде чем возвратить ей управление, вычистить все следы своего пребывания в оперативной памяти. Технически реализовать это несложно. Понадобится не больше пары сотен строк ассемблерного кода, но… мне не известен ни один руткит, реально делающий это. Так же можно обхитрить и ядерный отладчик. Устанавливаем всем хакнутым страницам атрибут только на исполнение (если ЦП поддерживает бит NX/XD) или ставим страницу в NO_ACCESS, а при возникновении исключения смотрим, пытаются ли нас прочесть или исполнить. И если нас читают, то это явно отладчик, для обмана которого временно снимаем перехват. Но это всего лишь теория. На практике она еще никем не реализована, и когда будет реализована — неизвестно.
Увы, абсолютно надежных способов детекции руткитов не существует, и на любую меру есть своя контрмера. Но не будем теоретизировать, вернемся к реально существующим руткитам, а точнее, к получению дампа памяти. В NT в «Свойствах системы» (<Win-Pause>) необходимо выбрать «Полный дамп», затем запустить «Редактор реестра», открыть ветвь HKLM\System\CurrentControlSet\Services\i8042prt\Pa rameters и установить параметр CrashOnCtrlScroll (типа REG_DWORD) в любое ненулевое значение, после чего нажатие <Ctrl> с последующим двойным нажатием <Pause> вызовет голубой экран с кодом E2h (MANUALLY_INITIATED_CRASH). К сожалению, чтобы изменения реестра вступили в силу, необходимо перезагрузить машину, прибив при этом руткит, который мы пытаемся найти, так что эту операцию следует осуществлять заблаговременно.
Кстати говоря, последовательность <Ctrl-Scroll Lock-Scroll Lock> срабатывает, даже если машина ушла в нирвану и уже не реагирует на <Ctrl-Alt-Del>. Причем, в отличие от RESET, комбинация <Ctrl-Scroll Lock-Scroll Lock> выполняет сброс дисковых буферов, что уменьшает риск потери данных, поэтому CrashOnCtrlScroll стоит настроить и в том случае, когда мы не собираемся охотиться на руткиты.
В тех случаях, когда CrashOnCtrlScroll не настроен, а перезагрузка не приемлема, можно взять любой драйвер из NTDDK и вставить в начало DriverEntry какую-нибудь недопустимую операцию: деление на ноль, обращение к памяти по нулевому указателю и т.д. Тогда при загрузке драйвера немедленно вспыхнет голубой экран, а на диск будет сброшен полный дамп памяти ядра со всей малварью, в нем содержащейся.
В Linux ручной сброс дампа осуществляется при нажатии <Alt-SysRq-C> (при этом ядро должно быть откомпилировано с параметром CONFIG_MAGIC_SYSRQ, равным «yes», или должна быть выполнена команда «echo 1 > /proc/sys/kernel/sysrq»).
В xBSD-системах комбинация <Ctrl-Alt-Esc> (кстати говоря, измененная в некоторых раскладках клавиатуры) вызывает всплытие ядерного отладчика (аналог <Ctrl-D> для SoftICE в NT), который, к сожалению, по умолчанию не входит в ядро, и потому его необходимо предварительно перекомпилировать, добавив строки «options DDB» и «options BREAK_TO_DEBUGGER» в файл конфигурации ядра. Если же последняя опция не обозначена (о ней часто забывают), то в отладчик можно войти из консоли командой «sysctl debug.enter_debugger=ddb».
Полученный дамп ядра можно анализировать любой сподручной утилитой, благо недостатка в них ощущать не приходится. Например, в NT для этой цели обычно используется WinDbg, но мыщъх предпочитает исследовать систему вживую с помощью SoftICE, ближайшим аналогом которого в мире Linux является LinICE.
Значит, нажимаем мы <Ctrl-D> (SoftICE), <Ctrl-Q> (LinICE) или <Ctrl-Alt-Esc> (xBSD) и оказываемся в ядре. Далее пишем «u имя_функции» и последовательно перебираем имена всех функций (ну или не всех, а самых соблазнительных для перехвата), список которых под NT можно получить командой «dumpbin.exe ntoskrnl.exe /export > output.txt» (где dumpbin.exe – утилита, входящая в состав Microsoft Visual Studio и Platform SDK). А под Linux/xBSD эту же задачу можно решить, изучив символьную информацию несжатого и нестрипнутого ядра.
В начале нормальных, неперехваченных функций должен находиться стандартный пролог вида «PUSH EBP/MOV EBP, ESP» или типа того. Если же туда воткнут JMP или CALL, то с вероятностью, близкой к единице, данная функция кем-то перехвачена. А вот кем - это вопрос. Кроме руткитов перехватом занимаются антивирусы, брандмауэры и другие программы, поэтому, прежде чем отправляться на поиск малвари, необходимо хорошо изучить особенности своей системы со всеми установленными приложениями.
Продвинутые руткиты внедряют JMP/CALL не в начало функции, а в ее середину, чтобы не вызывать подозрений. На самом деле, проанализировав код хакнутой функции, легко убедиться в некоторой его ненормальности. Левый JMP/CALL просто не вписывается в алгоритм! Однако, чтобы прийти к подобному заключению, необходимо не только знать ассемблер, но и иметь опыт дизассемблирования. К счастью, продвинутые руткиты встречаются достаточно редко, и подавляющее большинство из них внедряется в самое начало.
Просмотрев все функции и убедившись в отсутствии следов явного перехвата, приступаем к изучению таблицы системных функций, которая под SoftICE вызывается командой NTCALL, а под Lin-Ice – командой D sys_call_table. Поскольку функции, перечисленные в таблице, не экспортируются ядром NT, то в отсутствие символьной информации (которую можно получить с сервера Microsoft с помощью утилиты SymbolRetriver от NuMega) SoftICE отображает имя ближайшей экспортируемой функции плюс смещение. А потому мы не можем быстро сказать: перехвачена данная функция или нет, и нам придется набирать команду «u адрес_функции», чтобы посмотреть, что там находится: нормальный, неперехваченный пролог или JMP/CALL. В никсах информация о символах присутствует по умолчанию и подобных проблем не возникает.
Естественно, помимо описанных существуют и другие методики перехвата, используемые руткитами, однако они довольно сложны для понимания и требуют предварительной подготовки, а потому здесь не рассматриваются.