Специально для паблика SMARTRHINO.

0x00. Знакомство

В наших статьях по реверс-инжинирингу (1, 2) периодически упоминается такой инструмент как IDAPython. Пришло время поговорить про него подробнее, дать советы, поделиться опытом.

Для кого. Для тех, кто уже умеет работать в IDA Pro, но ни разу не писал скрипты на IDAPython. Если вы уже имеете опыт написания скриптов под IDAPython, то вряд ли найдёте здесь что-то новое.

Чего здесь не будет. Не будем учить программировать на Python, не будем учить базовой работе в IDA Pro.

Как очевидно из названия, IDAPython - это всего-навсего интерпретатор Python, встроенный в дизассемблер IDA как инструмент автоматизации . Функции IDAPython являются “обёртками” над IDC (внутренний С-подобный язык автоматизации IDA Pro).

К сожалению, до сих пор IDAPython имеет скудную документацию, и часто ответы на вопросы по API нужно искать в исходниках или получать опытным путём. Модули IDAPython находятся в поддиректории python дизассемблера IDA (обычно это C:\Program Files\IDA 7.0\python\).

Подразумеваем работу в IDA Pro версии 7.0. IDAPython является плагином для IDA и идёт сразу в “коробочке” (нет необходимости его устанавливать). В версиях IDA до 7.4 используется Python 2.7 64-bit.

Примечание

Начиная с 7-й версии в IDAPython обновили API, а с версии 7.4 отключили поддержку старого API. Модуль idc_bc695.py обеспечивает обратную совместимость, но в какой-то момент его перестанут поддерживать. В сети в статьях и советах до 2017 года используется старый API, поэтому будьте внимательны и сверяйтесь с текущей документацией.

0x01. Первое свидание

Поскольку IDAPython - это средство автоматизации, то исследователь должен понимать, как сделать то или иное действие вручную, чтобы это действие потом можно было автоматизировать.

Распространенными случаями для такой автоматизации могут быть:

  • переименование функций;
  • комментирование кода/данных;
  • преобразование кода/данных (изменение типов данных, добавление в код перекрёстных ссылок);
  • поиск каких-нибудь хитрых шаблонов кода/данных;
  • патчинг кода.

Естественно, пространство для творчества велико.

Принятые обозначения и соглашения

При работе с IDAPython принимается ряд условных обозначений:

  • eaEffective Address – адрес в базе IDA, к которому применяется та или иная функция
  • функция here() возвращает текущий адрес, где установлен курсор в Disassembly-окне. Тип результата функции – long.
  • большинство функций импортируются из модуля idc, этот модуль импортирован по умолчанию. Некоторые функции находятся в других модулях, в этом случае подключение модуля будет указано явно;
  • в качестве “подопытного” будет использоваться файл прошивки Носорога в формате ELF.

Как выполнить код

Выполнять IDAPython-код можно несколькими способами:

  • короткие скрипты в командной строке IDA;
  • запуск скрипта через меню File - Script file (Alt + F7);
  • запуск скрипта через меню File - Script command (Shift + F2);
  • запуск IDA Pro из командной строки с параметром -S.

Рассмотрим особенности каждого способа.

Командная строка IDA CLI

Командная строка расположена внизу главного окна IDA. Слева находится кнопка, позволяющая выбрать язык для вводимых команд – нужно кликнуть по ней и выбрать Python.

Особенности IDA CLI
  • Ввод осуществляется построчно как в интерактивном режиме Python.
  • Для блоков кода (функции, циклы, условия) отступы не добавляются автоматически, поэтому не забываем их добавить.
  • Поддерживается автодополнение вводимых функций по клавише Tab.
  • Автодополнение работает только для имён функций без указания имени модуля.
  • Функции help и dir работают как в обычном Python и позволяют узнать информацию об объекте.
  • Вывод результатов команд осуществляется в окне Output window (Alt + 0).
Пример

Функция Segments из модуля idautils возвращает генератор, выдающий начальные адреса сегментов внутри базы IDA.

Python>import idautils
Python>help(idautils.Segments)
Help on function Segments in module idautils:

Segments()
Get list of segments (sections) in the binary image
@return: List of segment start addresses.

Python>for ea in idautils.Segments():
Python> print("%08x %s" % (ea, idc.get_segm_name(ea)))
Python>
08000000 .isr_vector
080000c0 .text
08006f9c .rodata
08007a14 .init_array
08007a18 .fini_array
20000000 .data
200001f8 .bss
200006c8 ._user_heap_stack
200017f8 abs

Script File

Поскольку в IDA 7.0 используется Python 2.7, то необходимо указать кодировку файла (или использовать только латиницу в комментариях).

При работе с кодом и данными иногда возникает необходимость преобразовать операнды из одного представления в другое. Для этого есть функции с префиксом op_, вот некоторые из них:

  • op_bin – преобразование операнда в двоичный вид;
  • op_dec – преобразование операнда в десятичный вид;
  • op_oct – преобразование операнда в восьмеричное число;
  • op_hex – представление операнда в hex-виде;
  • op_chr – представление операнда в виде символа;
  • op_seg – представление операнда в виде ссылки на сегмент;
  • op_stkvar – преобразование операнда в стековую переменную;
  • op_stroff – преобразование операнда в поле структуры;
  • op_enum – представление операнда в виде ENUM-константы;
  • op_plain_offset – представление операнда в виде ссылки на объект.

Можно сформировать модуль для организации таких преобразований и выполнить его через меню File - Script file (Alt + F7):

# coding: utf-8

''' File: transforms.py
    Функции преобразования отображения операндов
'''

START = 0x08000038
END = 0x08000084


def make_offsets32(start_ea, end_ea):
    ''' Преобразование данных в ссылки на объекты'''
    for ea in xrange(start_ea, end_ea, 4):
        idc.op_plain_offset(ea, 0, 0)


def make_dwords(start_ea, end_ea):
    ''' Преобразование данных в 32-битные числа в hex-представлении '''
    for ea in xrange(start_ea, end_ea, 4):
        idc.op_hex(ea, 0)


if __name__ == '__main__':
    make_dwords(START, END)

По аналогии с выполнением скрипта в Python, весь код, находящийся в теле модуля выполняется. В данном случае будет выполнена функция make_dwords с адреса START по адрес END. При этом, все функции, которые есть в загруженном файле, становятся доступны для выполнения в IDA CLI.

Для упрощения доступа к недавним скриптам в IDA есть окно Recent scripts (меню View – Recent scripts Alt + F9):

Важно: если вы разрабатываете и отлаживаете IDAPython-утилиту, которая состоит из нескольких модулей, то при обновлении вспомогательного модуля придётся перезапускать IDA, потому что Python 2 не умеет перезагружать модули, а IDA не умеет отдельно перезагружать свои плагины.

Script Command

Меню File - Script command (Shift + F2) позволяет написать и сохранить скрипт в текущей базе IDA. Это удобно, если предполагается, что некие действия могут выполняться много раз в текущей базе. В случае, если базу нужно передать коллегам, скрипты также автоматически будут переданы.

Код написанный в этом окне сохраняется автоматически. Для выполнения кода нужно нажать кнопку Run.

Запуск IDA c ключом -S

IDA Pro, как и многие приложения, поддерживает запуск с ключами через командную строку. Информацию по всем ключам запуска можно прочитать во встроенной справке (по клавише F1) в разделе Command line switches.

Среди прочих, есть ключи, позволяющие выполнить скрипт при запуске:

C:\Program Files\IDA 7.0\ida.exe" -A -Sscript_name.py rhino.idb , где

  • -A задаёт режим автономной работы без дополнительных диалоговых окон
  • -S служит для запуска скрипта script_name.py однократно при открытии IDB-файла.

Ввод/вывод

При взаимодействии с пользователем есть следующие особенности:

  • оператор print выводит результат в окно Output window (Alt + 0);
  • двойной клик по строке в окне Output window позволяет перейти в Disassembly-окно по адресу или имени объекта, если такой существует в базе IDA. Ниже приведён вывод из Output window с указанием случаев, когда будут и не будут выполняться переходы в Disassembly-окне.

    Python>here()
    134239060         # 1. при двойном клике здесь не будет перехода
    Python>hex(here())
    0x8005354L        # 2. при двойном клике здесь не будет перехода
    Python>"%08x" % here()
    08005354          # 3. при двойном клике будет выполнен переход
    Python>get_name(here())
    aErrorWrongHead   # 4. при двойном клике будет выполнен переход
    
  • функции input/raw_input не работают;
  • для пользовательского ввода нужно использовать ask_-функции модулей ida_kernwin и idaapi.

Отладка скриптов

В привычном понимании отладка скриптов под IDAPython невозможна в силу того, что скрипты должны иметь доступ к “внутренностям” IDA Pro. Имеющиеся рецепты неактуальны для IDA 7 версии.

Актуальными средствами отладки для IDAPython-скриптов можно назвать логирование и вывод промежуточных значений (в народе – “отладка принтами”).

IDAPyHelper

Автодополнение в командной строке помогает, если вы точно знаете или хотя бы предполагаете имя функции, к которой хотите обратиться. Для более наглядного выбора модуля и функции IDAPython можно воспользоваться скриптом IDAPyHelper, который выводит имена доступных модулей и их функций.

IDAPython Cheatsheet

Для упрощения работы с IDAPython мы сделали свою шпаргалку популярных функций. Особенность шпаргалки - цветовое представление типов аргументов и результатов функций.

0x02. Комментирование вызова функции

От общих слов – к конкретным примерам.

Удобным приёмом при исследовании бинарного кода является комментирование отдельных участков. Интересно, что если оставить комментарий в строке с вызовом функции, то этот комментарий будет отображаться в окне ссылок на функцию (Xrefs по клавише X), как это показано на рисунке ниже.

В случае, если обращений к функции достаточно много, расставлять комментарии вручную становится не очень приятно. Такую операцию можно и нужно автоматизировать c использованием IDAPython.

Расставим комментарии в местах обращений к функции memcpy. Как видно из рисунка, в текущей базе известно 11 обращений к memcpy:

Общий алгоритм для добавления комментариев будет таким:

  • получить все кросс-ссылки на функцию memcpy;
  • в каждом месте обращения к memcpy составить список аргументов;
  • составить строку комментария;
  • добавить комментарий.

Для выполнения этих действий потребуются следующие функции и классы:

  • idautils.CodeRefsTo(func_ea, flow) – создаёт генератор, который возвращает объекты-ссылки из кода на указанный адрес. Параметр flow (возможные значения – 0 и 1) задаёт необходимость учитывать ссылки, которые сформированы за счёт простого перехода от инструкции к инструкции. В нашем случае flow = 0;
  • idc.prev_head(ea) – возвращает адрес предыдущей инструкции относительно указанного адреса, это необходимо для прохода вверх по коду для поиска аргументов;
  • idc.get_operand_value(ea, n) – возвращает значение n-го операнда по указанному адресу. Если операнд является регистром, то возвращается номер регистра в соответствии с текущей процессорной архитектурой. Для архитектуры ARM номера регистров достаточно очевидны:
    • R0 – 0;
    • R1 – 1;
    • R2 – 2
    • и так далее;
  • idc.get_operand_type(ea, n) – возвращает тип n-го операнда по указанному адресу. Основные типы операндов:
    • o_void (0) – инструкция без операнда (например, NOP);
    • o_reg (1) – регистр;
    • o_mem (2) – адрес в памяти;
    • o_phrase (3) – составной адрес [Base Reg + Index Reg];
    • o_displ (4) – составной адрес [Base Reg + Index Reg + Displacement];
    • o_imm (5) – число-константа;
    • o_far (6) – FAR-адрес;
    • o_near (7) – NEAR-адрес;
    • другие типы операндов зависят от архитектуры;
  • idc.print_operand(ea, n) – возвращает строковое представление операнда
  • idc.set_cmt(ea, comment, repeat) – добавление комментария по указанному адресу.

Аргументы функции

Поскольку в архитектуре ARM аргументы передаются в функцию через регистры (R0-R3), необходимо:

  • пройти вверх по коду от точки обращения к функции для поиска аргументов;
  • найти инструкции, где регистры в регистры R0, R1, R2 заносятся данные (функция memcpy принимает три аргумента);
  • если в регистры заносится число или адрес, то вернуть hex-представление этого числа, для всех остальных случаев вернуть просто текстовое представление операнда.

Комментарии в IDA

Комментарии в базе IDA бывают трёх типов:

  • Простые комментарии– отображаются только в той строке, где они установлены. Добавляются функцией idc.set_cmt(ea, comment, rpt) с аргументом repeat, равным 0 – – аналогия горячей клавише :.

  • Повторяемые комментарии (repeatable) – помимо основной строки отображаются еще и там, где есть ссылка на строку с комментарием. Добавляются функцией idc.set_cmt(ea, comment, rpt) с аргументом repeat, равным 1 – аналогия горячей клавише ;.

  • Многострочные комментарии в коде – устанавливаются функцией idc.update_extra_cmt(ea, n, comment) .

Для нашего случая подходят простые неповторяемые комментарии.

Код

# coding: utf-8

''' File: funcargs.py
    Добавление комментария в места вызова функции с аргументами
'''
 	 
import idautils 	 

MEMCPY = 0x08006B26


def get_function_arg(ea, narg):
    ''' Поиск n-го аргумента функции '''
    while True:
        ea = idc.prev_head(ea)
        if idc.get_operand_value(ea, 0) == narg:
            break
            
    if idc.get_operand_type(ea, 1) in (idc.o_imm, idc.o_mem):
        res = "0x%x" % idc.get_operand_value(ea, 1)
    else:   	 
        res = idc.print_operand(ea, 1)  
    return res
     	 

def set_comment_by_args(func_ea, nargs):
    ''' Добавление комментария к вызову функции '''
    for ea in idautils.CodeRefsTo(func_ea, flow=0):
        func_args = []
        for i in range(0, nargs):
            arg = get_function_arg(ea, i)	 
            func_args.append(arg)

        args = ', '.join(a for a in func_args)    
        comment = "(%s)" % args
        idc.set_cmt(ea, comment, 0)


if __name__ == '__main__':
    set_comment_by_args(MEMCPY, 3)

После выполнения кода комментарии будут отображаться в окне кросс-ссылок:

В этот раз мы провели базовое знакомство с инструментом IDAPython, написали несколько несложных функций. В следующий раз продолжим изучение IDAPython, примеров будет больше.

Есть вопросы? Спросить можно в чате SMARTRHINO.

Ссылки