RootKit принципы и механизмы работы
Методы перехвата функций в режиме пользователя (user mode)
1. Модификация машинного кода прикладной программы
2. Модификация таблицы импорта
3. Перехват функций LoadLibrary и GetProcAddress
4. Методика, сочетающая методики 2 и 3
5. Модификация программного кода функции
6. Модификация библиотек DLL на диске
Перехват функций в режиме ядра (kernel mode)
Методики обнаружения RootKit в системе
Данный цикл статей посвящен достаточно актуальной в настоящий момент теме технологиям, применяемым разработчиками вредоносного программного кода. В последнее время появилось множество вредоносных программ, которые нельзя считать вирусами, поскольку они не обладают способностью к размножению. В этой публикации речь пойдет о RootKit, клавиатурных шпионах, троянских и шпионских программах.
Введение
ермин «RootKit» исторически относится к среде UNIX, где под ним понимается набор утилит, которые хакер устанавливает на взломанном им компьютере после получения первоначального доступа. Как правило, это хакерский инструментарий (снифферы, сканеры) и троянские программы, замещающие основные утилиты UNIX. RootKit позволяет хакеру закрепиться во взломанной системе и скрыть следы своей деятельности.
В системе Windows термином «RootKit» принято называть программу, которая внедряется в систему и перехватывает системные функции или производит замену системных библиотек. Благодаря перехвату и модификации низкоуровневых API-функций такая программа достаточно хорошо маскирует свое присутствие в системе, защищая себя от обнаружения пользователем и антивирусным ПО. Кроме того, многие такие программы могут маскировать наличие в системе любых описанных в конфигурации RootKit процессов, папок и файлов на диске, ключей в реестре. Многие RootKit устанавливают в систему свои драйверы и сервисы (они, естественно, тоже являются невидимыми).
В последнее время угроза RootKit становится все более актуальной, так как разработчики вирусов, троянских программ и шпионского программного обеспечения начинают встраивать RootKit-технологии в свои вредоносные программы. Одним из классических примеров может служить троянская программа Trojan-Spy.Win32.Qukart, которая маскирует свое присутствие в системе при помощи RootKit-технологии (данная программа интересна тем, что ее RootKit-механизм прекрасно работает в Windows 95/98/Mе/2000/XP).
Для эффективной борьбы с RootKit необходимо понимание принципов и механизмов ее работы. Условно все RootKit-технологии можно разделить на две категории работающие в режиме пользователя (user mode) и в режиме ядра (kernel mode). RootKit первой категории основаны на перехвате функций библиотек пользовательского режима, а второй на установке в систему драйвера, осуществляющего перехват функций уровня ядра.
Далее в статье при описании методов перехвата функций речь идет именно о RootKit, однако нужно помнить, что рассмотренные методики универсальны и применяются множеством полезных программ и утилит.
Методы перехвата функций в режиме пользователя (user mode)
писания методик перехвата функций снабжены схемами их работы, при этом красная пунктирная стрелка показывает вмешательство RootKit в процесс работы программы, а красные стрелки показывают отклонения в логике работы, вызванные вмешательством RootKit.
Перехват функций позволяет RootKit модифицировать результаты их работы. Например, перехват функции поиска файла на диске позволяет исключить из результатов поиска маскируемые файлы, а перехват функций типа ntdll.ZwQuerySystemInformation дает возможность замаскировать запущенные процессы и загруженные библиотеки.
Принцип вызова функции
Прежде чем перейти к рассмотрению принципов работы RootKit пользовательского режима, необходимо кратко описать принцип вызова функций, размещенных в DLL. Существует два базовых способа:
- Раннее связывание (статически импортируемые функции). Этот метод основан на том, что компилятору известен перечень импортируемых программой функций. Опираясь на эту информацию, компилятор формирует так называемую таблицу импорта EXE-файла. Таблица импорта это особая структура (ее местоположение и размер описываются в заголовке EXE-файла), которая содержит список используемых программой библиотек и список импортируемых из каждой библиотеки функций. Для каждой функции в таблице имеется поле для хранения адреса, но на стадии компиляции адрес неизвестен. В процессе загрузки EXE-файла операционная система анализирует его таблицу импорта, загружает все перечисленные в ней DLL и производит занесение в таблицу импорта реальных адресов функций этих DLL. У раннего связывания есть существенный плюс: на момент запуска программы все необходимые DLL оказываются загруженными, а таблица импорта заполненной и все это делается операционной системой без участия программы. Но отсутствие в процессе загрузки указанной в его таблице импорта DLL (или отсутствие в DLL требуемой функции) приведет к ошибке загрузки программы. Кроме того, очень часто нет необходимости загружать все используемые программой DLL в момент запуска программы. На рис. 1 показан процесс раннего связывания: в момент загрузки происходит заполнение адресов в таблице импорта (шаг 1), в момент вызова функции из таблицы импорта берется адрес функции (шаг 2) и происходит собственно вызов функции (шаг 3).
- Позднее связывание. Отличается от раннего связывания тем, что загрузка DLL производится динамически при помощи функции Windows API LoadLibrary. Эта функция находится в kernel32.dll, поэтому если не прибегать к хакерским приемам, то kernel32.dll придется загружать статически. При помощи LoadLibrary программа может загрузить интересующую ее библиотеку в любой момент времени. Соответственно для получения адреса функции применяется функция kernel32.dll GetProcAddress. На рис. 1 шаг 4 соответствует загрузке библиотеки при помощи LoadLibrary и определению адресов при помощи GetProcAddress. Далее можно вызывать функции DLL (шаг 5), но, естественно, при этом таблица импорта не нужна. Чтобы не вызывать GetProcAddress перед каждым вызовом функции из DLL, программист может однократно определить адреса интересующих его функций и сохранить их в массиве или в некоторых переменных.
Рис. 1
Независимо от метода связывания, системе необходимо знать, какие функции экспортирует DLL. Для этого у каждой DLL имеется таблица экспорта, в которой перечислены экспортируемые DLL функции, их номера (ординалы) и относительные адреса функций (RVA).
1. Модификация машинного кода прикладной программы
В этом случае модифицируется машинный код (рис. 2), отвечающий в прикладной программе за вызов той или иной функции API. Эта методика сложна в реализации, так как существует множество языков программирования и версий компиляторов, а программист может реализовать вызов функций Windows API различными методами. Но теоретически подобное возможно при условии, что внедрение будет идти в заранее заданную программу известной версии. В этом случае можно проанализировать ее машинный код и разработать перехватчик.
Рис. 2
2. Модификация таблицы импорта
Данная методика описана в книге «Windows для профессионалов» Рихтера и является одной из классических. Идея методики проста: при внедрении RootKit находит в памяти системы таблицу импорта и исправляет адреса интересующих ее функций на адреса своих перехватчиков (естественно, она предварительно где-то запоминает правильные адреса). В момент вызова функции программа считывает ее адрес из таблицы импорта и передает по этому адресу управление. Методика универсальна, но у нее есть существенный недостаток (его хорошо видно на схеме, представленной на рис. 3) перехватываются только статически импортируемые функции. Но есть и плюс методика очень проста в реализации, причем есть масса примеров, демонстрирующих ее. Поиск таблицы импорта в памяти не представляет особой сложности, поскольку для этого существуют специализированные функции Windows API, позволяющие работать с образом программы в памяти. Исходный текст такого перехватчика на языке C занимает несколько листов печатного текста.
Рис. 3
3. Перехват функций LoadLibrary и GetProcAddress
Перехват функций LoadLibrary и GetProcAddress может быть выполнен любым методом, в классическом варианте применяется модификация таблицы импорта (рис. 4). Идея этого тоже проста: если перехватить GetProcAddress, то при запросе адреса можно выдавать программе не реальные адреса интересующих ее функций, а адреса своих перехватчиков. Как и в методике 2, программа «не почувствует разницы». При вызове GetProcAddress она получает адрес и выполняет вызов функции. Но здесь есть и минус невозможно перехватить статически импортируемые функции.
Рис. 4
4. Методика, сочетающая методики 2 и 3
В данной методике модифицируется таблица импорта, причем в обязательном порядке перехватываются функции LoadLibrary и GetProcAddress библиотеки kernel32.dll. В этом случае при вызове статически импортируемых функций искаженные адреса берутся из таблицы импорта, при динамическом определении адреса вызывается перехваченная функция GetProcAddress, которая возвращает адреса функций-перехватчиков. В результате у программы не остается шансов узнать правильный адрес функции (рис. 5).
Рис. 5
5. Модификация программного кода функции
Данная методика сложнее в реализации, чем подмена адреса. Суть ее состоит в том, что RootKit находит в памяти машинный код интересующих ее функций и модифицирует его (рис. 6). При таком способе перехвата функции уже нет надобности в модификации таблицы импорта запущенных программ и передаче программам искаженных адресов при вызове GetProcAddress. Что касается вызова функции, то все остается как есть, но за одним исключением теперь уже по правильному адресу внутри правильной DLL находится машинный код RootKit.
Рис. 6
Чаще всего вмешательство в машинный код перехватываемых функций минимально. В начале функции размещается не более двух-трех машинных команд, передающих управление основной функции-перехватчику. Для выполнения вызова модифицированных функций RootKit должна сохранить исходный машинный код для каждой модифицированной ею функции (естественно, сохраняется не весь машинный код функции, а измененные при перехвате байты). Именно такая методика перехвата реализована в широко известном HackerDefender и в библиотеке AFX RootKit (www.rootkit.com).
6. Модификация библиотек DLL на диске
Данная методика состоит в том, что системная библиотека модифицируется на диске. Методы модификации аналогичны вышеописанным, только изменения производятся не в памяти, а на диске. Эта методика не получила широкого распространения.
Перехват функций в режиме ядра (kernel mode)
ля понимания типовой методики перехвата функций в режиме ядра следует прежде всего описать принципы взаимодействия библиотек пользовательского режима и ядра. Рассмотрим их с помощью упрощенной схемы, приведенной на рис. 7.
Рис. 7
Основное взаимодействие с ядром производится через библиотеку ntdll.dll, большинство функций которой являются «переходниками», обращающимися к ядру через прерывание INT 2Eh (отметим, что прикладной программе ничто не мешает напрямую вызвать это прерывание). Дальнейшее обращение к функциям ядра основано на структуре, именуемой KeServiceDescriptorTable (SDT), расположенной в исполняемом файле ntoskrnl.exe. SDT это таблица, содержащая адреса точек входа сервисов ядра NT. Описание функций и методик перехвата можно найти в книге «Недокументированные возможности Windows 2000» Свена Шрайбера, там же приведена схема взаимодействия, послужившая прототипом для приведенной здесь схемы. Упрощенно можно сказать, что для перехвата функций нужно написать драйвер, который произведет модификацию таблицы SDT. Перед модификацией драйверу необходимо сохранить адреса перехватываемых функций и записать в таблицу SDT адреса своих обработчиков. Данный метод чем-то напоминает перехват прерываний в MS DOS или описанную выше методику 2.
Этот метод часто называют перехватом Native API, и, естественно, он работает только в Windows NT (и соответственно Windows 2000/XP и Windows Server 2003). Следует отметить, что перехват Native API осуществляет не только RootKit существует множество полезных программ, перехватывающих функции при помощи правки SDT. В качестве примера можно привести популярную утилиту RegMon от SysInternals и программу Process Guard.
Вышеописанный метод является наиболее простым, но далеко не единственным. Существует еще ряд способов, в частности создание драйвера-фильтра, который может применяться как для решения задач мониторинга (классический пример утилита FileMon от SysInternals), так и для активного вмешательства в работу системы. В частности, драйвер-фильтр может использоваться для маскировки файлов и папок на диске. Принцип работы такого драйвера основан на манипуляциях с пакетами запроса ввода-вывода (IRP).
Методики обнаружения RootKit в системе
ассмотрим базовые методики поиска RootKit:
- Сравнение двух снимков системы (например, списка файлов на диске). Первый снимок делается на проверяемой системе, второй после загрузки с CD или подключения исследуемого HDD к заведомо чистому компьютеру. Подобная методика гарантированно позволяет обнаружить любую RootKit, которая маскирует на диске свои файлы.
- Сравнение данных, возвращаемых API-функциями разного уровня и (или) получаемых низкоуровневыми методами (например, прямым чтением диска и анализом файлов реестра). Данная методика не требует перезагрузки исследуемого ПК и реализована в бесплатной утилите RootkitRevealer от SysInternals (http://www.sysinternals.com). Другим примером может служить утилита KLister (www.rootkit.com) для построения списка запущенных процессов, которая состоит из драйвера и консольной программы, использующей этот драйвер.
- Анализ в памяти функций основных библиотек на предмет изменения их машинного кода. Данная методика наиболее эффективна для борьбы с RootKit в пользовательском режиме. Она позволяет не только обнаружить перехват функций, но и восстановить нормальную работу поврежденных функций. Кроме того, сравнение снимков системы, полученных до и после восстановления функций API, во многих случаях позволяет обнаружить маскирующиеся процессы, сервисы и драйверы. Данная методика не требует перезагрузки, и один ее из вариантов реализован в созданной автором статьи утилите AVZ.
- Анализ и восстановление ServiceDescriptorTable. Эта методика позволяет бороться с рядом перехватчиков, работающих в режиме ядра (с перехватчиками, основанными на правке SDT). Практической реализацией этой методики является утилита SDTRestore (http://www.security.org.sg/code/sdtrestore.html). Однако восстановление SDT окажет воздействие на работу всей системы и может привести к очень неприятным последствиям: в лучшем случае к полному зависанию системы с выходом на BSoD, в худшем к непредсказуемому нарушению нормальной работы приложений, перехватывающих NativeAPI для реализации своих функций.
Заключение
писанные в статье базовые методики перехвата функций поясняют основные принципы работы RootKit. Однако разработчики RootKit-технологий не стоят на месте, в результате чего постоянно появляются новые разработки, подходы и методы. Практика показывает, что разработчики вредоносных программ (вирусов, троянских программ, шпионского ПО) все чаще начинают использовать RootKit-технологии, что существенно затрудняет обнаружение и удаление созданных ими вредоносных программ. Чаще всего применяются методики перехвата функций в режиме пользователя, но в последнее время появились весьма эффективные реализации с применением драйверов. В этом плане, по статистике автора, наиболее знаменит Backdoor.Win32.Haxdoor, который устанавливает в систему несколько драйверов, что позволяет ему весьма эффективно маскироваться от обнаружения пользователем.
***
В следующей статье цикла речь пойдет о клавиатурных шпионах их устройстве, принципах работы и методиках обнаружения.