IDA + Python = любовь: Что может дать встроенный Python в дизассемблере IDA?IDA — это интерактивный дизассемблер и отладчик одновременно. Не так давно в нем появился еще и декомпилятор Hex-Rays, позволяющий преобразовывать машинный код в читаемый листинг на С. Но если и этого оказывается мало, пора брать ситуацию под своей контроль. Как ни крути, IDA является дизассемблером №1 в мире и используется хакерами-реверсерами, вирусными аналитиками и много кем еще. Встроенные отладчик и декомпилятор, возможность удаленной отладки, удобный подход к анализу давно сделали этот пакет стандартом де-факто. Для расширения функционала IDA долгое время было два пути: подключаемые плагины (большое количество готовых к употреблению аддонов всегда доступно на www.openrce.org/ downloads/browse/IDA_Plugins), а также скрипты, написанные на собственном интерпретируемом языке IDC. На последних остановимся подробнее. Основы IDCС помощью IDC можно быстро разрабатывать сценарии, позволяющие автоматизировать множество рутинных действий. Язык вобрал в себя основные синтаксические элементы традиционного C, поэтому прост для освоения. Полная документация идет вместе с IDA, но для старта вполне достаточно уяснить несколько основных моментов:
Первый сценарийЧтобы подкрепить краткие теоретические знания практикой, попробуем теперь написать простую программу, которая будет перечислять все присутствующие в программе функции с указанием места, откуда они вызываются.
Алгоритм действий простой. Сначала с помощью ScreenEA() получаем адрес, на котором в текущий момент находится курсор, и передаем его функциям SegStart() и SegEnd() для определения границ сегмента. После чего в цикле, с начала и до конца сегмента, перебираем адреса всех функций с помощью NextFunction(). Она принимает на вход адрес текущей функции, возвращая адрес следующей или, если других вызовов не найдено, значение -1 (BADADDR). В цикле с помощью GetFunctionFlags() проверяем, является ли данный адрес адресом функции (если не является, возвращается значение -1). Посредством функции GetFunctionName() получаем имя функции и выводим его. В завершение отображаем также список мест, откуда она вызывается — для этого используется цикл с RfirstB() и RnextB(). Полученный код сохраняем в файл с расширением .idc — теперь им можно пользоваться. Чтобы вызвать скрипт надо перейти в меню «File } Script File...» (используй комбинацию клавиш <Alt+F7>) и выбрать наш сценарий.
Готово! IDA + Python = IDAPythonПолучилось несложно, но есть еще более простой способ разрабатывать сценарии — использовать для этого плагин IDAPython. О главной фишке несложно догадаться из названия: плагин встраивает в IDA полноценный интерпретатор Python, позволяя при этом создавать скрипты, обладающие полным доступом ко всем возможностям языка сценариев IDC.
Главное приемущество плагина — поддержка родной питоновской обработки данных и возможность использовать весь спектр различных модулей для Python. Также, аддон открывает доступ к значительной части функционала IDA SDK, позволяя тем самым писать сценарии для решения более сложных задач, которые сложно было бы реализовать на IDC. На официальном сайте (code.google.com/p/idapython) можно найти версию под свою операционную систему и версию IDA. Установка проста — достаточно скопировать папки plugins и python из архива в папку IDA. Надо позаботиться о том, чтобы в системе был установлен Python. Совместимая версия интерпретатора указывается в имени архива IDAPython (например, сборке idapython-1.4.3_ida6.0_py2.6_win32.zip требуется Python 2.6). С дистрибутивом распространяется полезная папка examples, которая поможет быстрее разобраться, что к чему. После установки IDAPython становятся доступными три модуля:
Все три модуля автоматически импортируются во все скрипты. Немного практикиДля старта нужны базовые знания Python'а и умение подглядывать в документацию по IDAPython. Чтобы ощутить, насколько проще осуществляется обработка данных на питоне, напишем скрипт для решения ранее озвученной задачи — отобразим на экране все вызываемые функции. Воспользуемся низкоуровневыми вызовами из модуля idaapi.
Как видишь, скрипт получился очень похожим на IDC-реализацию. Но, как говорится, совершенству нет предела: можно написать аналогичный сценарий, который будет еще короче. В этом поможет модуль idautils:
Вот где по-настоящему ощущается вся прелесть Python: сценарий вообще не нуждается в комментариях. Код с грамотно обозначенными названиями функций сам предельно ясно говорит, что он делает. Реальная задачаИ все-таки. Посмотреть список функций, вообще говоря, можно и стандартными средствами IDA. Так что мы всего лишь изобрели велосипед (хотя и познакомились с основными понятиями IDC и IDAPython). Но попробуем все-таки решить реально полезную задачу.
Программа из PEB (Process Environment Block) получает адрес библиотеки kernel32.dll и в ее таблице экспорта ищет по хэшу адреса LoadLibrary и GetProcAddress. С помощью этих двух вызовов можно подгружать остальные библиотеки и искать в них адреса нужных функций. Вообще функция GetProcAddress даже не нужна, так как, зная адрес библиотеки, можно самостоятельно получить адрес нужной функции из таблицы экспорта. Это популярный механизм, который нередко используется в малвари. Итак, вырисовывается вполне понятная задача — написать скрипт, который автоматизировал бы поиск названий функций и упростил таким образом жизнь реверсера. Чтобы разговор был предметным, напишем небольшое подопытное приложение, которое будет использовать вышеперечисленные техники маскировки. Весь функционал приложения будет сводиться к выводу сообщения «Hello, world!» при помощи функции MessageBox. Для этого нам потребуется всего три типа вызовов: GetKernelAddress() для получения адреса kernel32.dll, CalcHash() для вычисления хэша по имени функции, а также GetProcAddressEx() для получения адреса функции по хэшу. Полный исходный код тестового приложения ты найдешь на диске:
После компиляции код будет выглядеть примерно так:
Как видишь, никаких вызовов API-функций в чистом виде. Обращение к MessageBox превратилось в call [ebp+var_C] (по адресу 0040104C). Причем в случае настоящей малвари были бы зашифрованы еще и строки. На практике такой простой прием может значительно увеличить время анализа приложения. Как быть? Предположим, мы восстановили алгоритм работы программы и поняли, что значения, которые кладутся в стек по адресам 0040100E и 0040102D — это хэши функций. Но как узнать, что за функции скрываются за ними? В нашем текстовом образце — всего несколько вызовов, поэтому нет большой проблемы просто запустить отладчик и посмотреть, куда приведет нас call. Но что делать, когда таких вызовов сотни? Вот тут-то и понадобятся возможности IDAPython. Наше решениеПосле беглого анализа кода становится видно, что функция sub_401060 возвращает адрес kernel32.dll. А sub_4010E0, вызываемая в программе два раза, возвращает адрес функции из заданной библиотеки по хэшу. Заглянем внутрь. В теле функции присутствует всего один call (по адресу 00401185), после которого возвращаемое значение сравнивается с переданным хэшем. Очевидно, что это функция вычисления хэша по имени. Переименуем ее в calc_hash. Взглянув на код, становится ясно, что фрагмент является самодостаточным (то есть не вызывает внутри себя других функций) и легко извлекается из общего листинга. Это значит, что его можно «выдернуть» и встроить в нашу программу для подсчета хэшей. Но мы поступим по-другому. Вместо этого мы задействуем IDAPython и напишем скрипт, который с помощью calc_hash будет вычислять хэши всех экспортируемых имен в рамках текущей отладочной сессии. План таков:
Итак, этап первый. Подготавливаем функцию calc_hash для использования в скрипте. Так как она является самодостаточной, то мы можем извлечь ее тело из базы данных IDA и вызывать ее при помощи ctypes:
В принципе, реально использовать механизм Appcall для достижения той же самой цели. Выполнив в командной строке питона Python>hex(Appcall.calc_hash("LoadLibraryA")&0xfffffff f) мы могли бы получить значение вычисленного хэша 0x0C8AC8026L. Но так как функция calc_hash будет вызвана по крайней мере 9 тысяч раз (для библиотеки kernel32.dll), то мы будем использовать первый рассмотренный способ как более быстрый. Следующий этап — поиск экспортируемых имен. Тут я должен сделать небольшое замечание: отладочная сессия для этого должна быть активна. Другими словами, наша программа должна быть запущена под отладчиком. Мы предварительно ставим брейкпоинт, запускаем программу в IDA и, когда она остановится на бряке, запускаем на выполнение наш скрипт. Каждый раз при запуске отладочной сессии дебагер IDA Pro просит отладочный модуль предоставить список отладочных имен. По сути, отладочными именами являются экспортируемые имена всех загруженных модулей. Чтобы получить этот список программным путем, воспользуемся вызовом idaapi.get_debug_names(). Реализуем функцию fetch_debug_names, которая будет возвращать список имен в формате (адрес, имя функции, имя модуля):
Каждое возвращенное отладочное имя имеет следующий формат: Modulename_ApiName. Вот почему мы разделяем эту строку по символу «_». Теперь у нас есть функция для вычисления хэшей и список всех имен. Можно начинать генерировать хэши. Это уже третий этап:
После этого остается только вывести результат в красивом окне. В IDAPython есть возможность добавлять свои окна со списком, состоящим из чего-либо (как дефолтное окно со списком имен функций). Для этого нужно создать класс-наследник от типа Chooser2. Делается это довольно просто, поэтому подробно останавливаться на этом не буду. На диске есть пример и полная версия нашего скрипта, которую теперь ты можешь использовать. Хочу заметить, что в сценарии также реализован импорт вычисленных хэшей в Enums (список). Это выглядит так. Допустим, что мы встретили в коде очередной вызов функции по хэшу:
Теперь мы можем нажать <m> и выбрать соответствующую ему функцию, после чего вызов сразу примет удобочитаемый вид:
Удобно? Еще бы! Вот так, единожды решив задачу, можно использовать наработки во время реверсинга вновь и вновь. Конечно, одного прочтения статьи недостаточно, чтобы сходу начать разрабатывать сценарии для IDA. Но наша задача была в том, чтобы показать, как можно быстро и изящно заточить дизассемблер под себя и прокачать его функциональность.
Этим постоянно пользуются многие реверсеры и ресечеры, в том числе наши постоянные авторы. Info
Links
|