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

Продолжаем делиться советами и рецептами по IDAPython. Начало тут.

0x03. Получить аргумент функции

В прошлый раз для создания комментария в месте вызова функции мы сделали функцию get_function_arg:

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

Важно напомнить, что этот вариант доступа к аргументу функции подходит для архитектуры, где аргументы передаются через регистры. Если работа ведётся в рамках архитектуры x86, то в большинстве случаев аргументы передаются в функцию через стек. Тогда для получения n-го аргумента из стека нужно посчитать инструкции push перед вызовом функции:

def get_function_arg_value(ea, narg):
    """ Поиск n-го аргумента функции (нумерация с нуля).
    	Аргументы передаются через стек.
    """
    i = 0
    while True:
        ea = idc.prev_head(ea)
        if idc.print_insn_mnem(ea) == "push":
            if i == narg: break
            i += 1

    res = idc.get_operand_value(ea, 0)
    return res

0x04. Переименовать функцию по строке лога

Рассмотрим часто встречающуюся задачу – переименование функции по информации из строки лога (такое может быть, например, при использовании функции assert). Для эксперимента можно взять изменённую прошивку Носорога без информации об именах функций. При анализе имеющейся текстовой информации бросаются в глаза строки “sendMsg error %s”, “recvMsg error” и “freeMsg error”.

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

Если провести дальнейший анализ, то можно уставить, что все эти строки передаются в качестве первого аргумента в функцию sub_8006690:

Переименуем ее в x_printf.

В строке по адресу 0x08005034 в регистр R0 заносится адрес строки “sendMsg error %s\r\n”, но стоит обратить внимание вот на какой нюанс. Если выполнить запрос значения второго операнда в этой строке, то мы получим адрес 0x08005040:

Python>"%08x" % get_operand_value(here(), 1)
08005040

Связано это с особенностями архитектуры ARM и компилятора:

  • длина инструкции фиксирована (в данном случае – 2 байта);
  • чтобы загрузить 32-битное значение в регистр, само значение записывается ниже кода функции, а в инструкции используется короткое смещение относительно счетчика команд (PC);

IDA учитывает эти особенности, анализирует код и смещение, и автоматически подставляет в код ссылку на строку.

Учитывая всё это, алгоритм переименования функций будет таким:

  • получить все кодовые ссылки на функцию x_printf функцией idautils.CodeRefsTo;
  • получить значение первого аргумента функции (регистр R0) функцией idc.get_operand_value (воспользуемся слегка изменённой функцией get_function_arg, которую реализовали ранее);
  • получить адрес строки функцией idc.get_wide_dword;
  • получить строку функцией idc.get_strlit_contents;
  • проверить, что строка имеет нужный формат;
  • получить из строки имя функции и переименовать функцию используя idc.set_name.

В итоге получим скрипт:

# coding: utf-8

import idautils

PRINTF = 0x08006690

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) == idc.o_mem:
        res = idc.get_operand_value(ea, 1)
    else:        
        res = idc.BADADDR

    return res


def get_func_name(str_log):
    """ Получить из строки лога имя функции """
    words = str_log.split(' ')
    if len(words) > 1 and words[1].startswith("error"):
        return words[0]
    else:
        return ""


def rename_by_log_str(log_func):
    """ Переименовать функции, которые вызывают log_func со строкой логирования """
    for ea in idautils.CodeRefsTo(log_func, 0):
        arg = get_function_arg(ea, 0)
        if arg == idc.BADADDR:
            continue
        str_addr = idc.get_wide_dword(arg)
        str_log = idc.get_strlit_contents(str_addr)
        func_name = get_func_name(str_log)
        if func_name:
            print("0x%08x - 0x%08x - %s" % (arg, str_addr, func_name))


if __name__ == '__main__':
    rename_by_log_str(PRINTF)

Поскольку строка лога в разным приложениях может иметь разный формат, то разумно проверку формата строки и получение имени функции вынести в отдельную функцию (в нашем случае – get_func_name)

0x05. Раскраска кода и данных

IDA предоставляет функции для работы с цветом фона в окне листинга. Изменение цвета добавляет наглядности, а следовательно упрощает исследование. Например, изменённый цвет фона инструкций, которые выполнялись в режиме отладки, облегчит понимание логики в коде со множеством ветвлений.

Рассмотрим в этот раз простой пример – для кода и данных установить разный цвет фона.

Основные функции для работы с цветом фона:

  • idc.get_color(ea, what) – получить цвет фона элемента
  • idc.set_color(ea, what, color) – установить цвет фона элемента
    • цвет представляется моделью RGB и задаётся hex-числом в формате 0xBBGGRR (голубой-зелёный-красный)
    • аргумент what задаёт что раскрашивать:
      • idc.CIC_ITEM = 1 – отдельная строка листинга
      • idc.CIC_FUNC = 2 – полностью функция
      • idc.CIC_SEGM = 3 – полностью сегмент

Так, код

for i, ea in enumerate(xrange(0x08005196, 0x080051AE, 2)):
    idc.set_color(ea, CIC_ITEM, 0x0f << (2 * i))

раскрасит строки листинга в диапазоне от 0x08005196 до 0x080051AE в разные цвета:

Как упоминалось выше, при работе с ARM-кодом можно увидеть, что ниже кода функции находятся глобальные адреса объектов, если таковые используются в данной функции:

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

Для решения задачи нужно выполнить следующие шаги:

  • получить список сегментов – для этого воспользуемся генераторомidautils.Segments() , который возвращает стартовые адреса сегментов;
  • из всех сегментов получить только сегменты с кодом – для этого воспользуемся функцией idc.get_segm_attr(segm, attr), которая возвращает атрибут сегмента; нас интересует тип сегмента (атрибут SEGATTR_TYPE) SEG_CODE;
  • пройти по всем элементам каждого сегмента – воспользуемся генераторомidautils.Heads(start, end), который возвращает адреса начальные адреса элементов (инструкций, данных) в интервале адресов от start до end;
  • с помощью функций idc.get_full_flags(ea) и is_code(flags) проверить, содержит ли выбранный адрес ea код;
  • выполнить раскрашивание элемента сегмента.

Этот алгоритм можно представить следующим кодом:

BLUE = 0xF2D0AF
PINK = 0xAFD0F2

def colored_code():
    """ Раскрасить код и данные разными цветами в сегментах кода """

    code_segmnets = filter(lambda segm: \
                          idc.get_segm_attr(segm, SEGATTR_TYPE) == SEG_CODE,\
                          idautils.Segments())

    for segm in code_segmnets:
        end = idc.get_segm_end(segm)
        for ea in idautils.Heads(segm, end):
            flags = idc.get_full_flags(ea)
            if idc.is_code(flags):
                idc.set_color(ea, CIC_ITEM, BLUE)
            else:
                idc.set_color(ea, CIC_ITEM, PINK)

Примечание. Обратите внимание, что функция is_code(flags) принимает битовое поле флагов, а не адрес. Для получения флагов адреса необходимо использовать функцию get_full_flags(ea).

После выполнения функции colored_code сегменты кода примут вид:

Итак, в этот раз мы научились перебирать сегменты и элементы кода (функции idautils.Segments и idautils.Heads), раскрашивать код, а также запрашивать содержимое строки (idc.get_strlit_contents) и числа (idc.get_wide_dword). Продолжение следует…

Помимо шпаргалки решили сделать справочник по функциям IDAPython на русском языке (пока в процессе).

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