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


Выравнивание структур


В изучаемой программе могут использоваться структуры, которые используют "нестандартное" выравнивание, скажем, по одному байту. Такие структуры можно без проблем создать в виде структур (Shift + F9), а в случае использования вида локальных типов нужно воспользоваться директивой pragma, а точнее pragma pack. pragma pack (n) задает выравнивание членов структур (чаще всего по умолчанию используется выравнивание 4 или 8 байт).


Например, если мы имеем дело с такой структурой


struct test_s
{
  char ch1;
  QWORD qword;
};

и знаем, что ее члены идут один за другим (т.е. сам экземпляр структуры занимает 5 байт), то при ее наложении на данные получим такую картину:


https://habrastorage.org/webt/mp/74/al/mp74al9nkprcsv7leihgqun6x3m.png


Видно, что между ch1 и qword образовалось неиспользуемое пространство, на месте которого по задумке должен был быть сам qword. Но если перепишем структуру следующим образом:


#pragma pack(1)
struct test_s
{
  char ch1;
  QWORD qword;
};

то получим корректный результат:


https://habrastorage.org/webt/8l/it/s6/8lits68hs1glqr0g4iatqacxl9a.png


Текстовый поиск


В декомпиляторе нет Ctrl+F, но оказывается, что Alt+T, работающий в окне дизассемблера, точно так же работает и в окне декомпилятора.


https://habrastorage.org/webt/-g/f5/5i/-gf55icmkkhcnsqx1mx4kjgpjxg.png


https://habrastorage.org/webt/qg/5b/cz/qg5bczp6gu-uo0tl1rbiu7vitxq.png


Segments > Rebase


Если вы с первого раза не угадали правильный адрес загрузки прошивки, то можете воспользоваться функцией переноса idb на другой адрес.


Например, мы загрузили прошивку в IDA по нулевому адресу. По адресу 0x04 находится указатель на функцию, который сейчас указывает в область памяти, которой не сопоставлено ни одного сегмента в idb.


https://habrastorage.org/webt/mj/n7/bv/mjn7bvxnomvd0ylscpm11hsusva.png


Зайдем в Edit>Segments>Rebase и перенесем нашу прошивку на адрес 0x8000000.



В результате у нас появился нормальный xref:


https://habrastorage.org/webt/iy/-l/rh/iy-lrhiqnzt7qfotqyo2iysssgi.png


Бряки


В IDA, как и в большинстве отладчиков, можно ставить условные бряки, т.е. они будут срабатывать только если выполняется определенное условие. Условие представляет собой конструкцию на языке IDC или Python, в которой во время отладки доступны глобальные переменные с соответствующими именами для доступа к каждому из регистров.


Также во время отладки таким образом можно менять значения регистров и содержимое памяти.


Выделение участков данных и экспорт (Alt+L — Shift+E)


Для выделения большого сегмента данных можно воспользоваться сочетанием клавиш Alt+L, чтобы начать выделение региона и управлять границами выделяемого региона с помощью мыши или клавиш управления курсором. Выделенные данные можно экспортировать в один их удобных форматов (строка, hex-последовательность, массив байт C) и помощью сочетания клавиш Shift+E.



Удаленная отладка


IDA поддерживает удаленные GDB и Windbg-сервера, но также в ней есть возможность удаленной отладки с помощью своих собственных серверов (находятся в каталоге IDADIR\dbgsrv).


https://habrastorage.org/webt/6s/1h/mh/6s1hmhcrc_icisa4viun1owbrz0.png


Строковые литералы в декомпиляторе


Часто может встречаться ситуация, когда read-only данные перемешаны в одном сегменте с read-write данными (например, при использовании старых компиляторов, компиляторов для RTOS и др.). При этом при использовании декомпилятора можно наблюдать следующее:



Как мы видим, вместо строковых литералов подставляются названия переменных. Это отрицательно сказывается на читабельности псевдокода. Для исправления этой неурядицы можем изменить тип строковых переменных с char на const char.



В результате строковая переменная с исправленным типом будет действительно отображаться в псевдокоде как строковый литерал.



Второй вариант — это настроить декомпилятор на отображение всех строк как литералов (а не только неизменяемых). Для этого убираем галку "Print only constant strings literals" в настройках декомпилятора.



В результате получаем красивый вывод:



Код little endian, а данные big endian


Иногда встречается Little Endian код для ARM, в то время, как данные имеют формат Big Endian. В этом случае для корректного отображения данных надо зайти в General options > Analysis > Processor specific analysis options > Edit ARM architecture options и поставить галку "BE-8 code (ARMB)".



Бряки и gdbserver


То ли я работаю с такими версиями gdbserver, то ли лыжи не едут, но включенная по умолчанию в IDA "single stepping support" у меня не работает, и из-за этого при отладке не срабатывают вообще никакие бряки, кроме самой первой. В этом случае я захожу в настройки отладчика, выбираю "Set specific options" и убираю галку "Use stepping support".



Эта настройка не сохраняется в idb, и для того, чтобы не делать этого каждый раз, в конфигурационном файле dbg_gdb.cfg необходимо исправить параметр SINGLE_STEP.



И еще про gdbserver


И еще поворчу про работу с gdbserver (в этот раз — в случае присоединения к удаленному процессу). Если на целевой машине включен ASLR, то у IDA может не получиться понять реальное расположение образа отлаживаемого файла в памяти. В результате IDA будет пытаться работать с адресами, которые указывают в какое-то невалидное пространство памяти. Для борьбы с этим можно еще до присоединения к удаленному процессу сделать rebase idb на актуальный адрес, и только потом присоединяться к процессу.


Сбор мусора в IDAPython


Интерпретатор Python, встроенный в IDA работает всегда и не закрывается до закрытия самой IDA. Поэтому сущности вашего Python-скрипта или плагина не будут автоматически удаляться. Кроме очевидных последствий вроде внезапных для автора утечек памяти, это может привести и к более забавным побочным эффектам, как, например, во время использования модуля logging.


Если написать такой скрипт


import logging

def main():
    logger = logging.getLogger('test1-script')
    logger.setLevel(logging.DEBUG)
    formatter = logging.Formatter('[TEST 1] %(name)s - %(message)s')
    ch = logging.StreamHandler()
    ch.setFormatter(formatter)
    logger.addHandler(ch)
    logger.debug("Log message!")

if __name__ == "__main__":
    main()

и запустить его два раза подряд, увидим такой вывод:


https://habrastorage.org/webt/cq/mj/am/cqmjamal8rb0l5cfeit8tll53wu.png


Происходит это потому, что функция getLogger либо создает, либо получает из глобального кэша уже существующий объект класса Logger. Далее к полученному инстансу логгера (одинаковому при каждом запуске скрипта) добавляется еще один хэндлер. Проверить это можно просто: выводим кэш (словарь) логгеров и список хэндлеров:


https://habrastorage.org/webt/kf/4t/ni/kf4tnigeguq6rnaga8n7v4tnoig.png


Есть как минимум два способа бороться с этим:


logging.Logger.manager.loggerDict.clear()

или


reload(logging)

И последнее


Минутка внимания еще нескольким трюкам:


  1. Начиная с версии 7.4, объявления локальных переменных в декомпиляторе могут быть "слохпнуты" по умолчанию — это настраивается в файле hexrays.cfg.
  2. Также в этой версии можно "прыгать" между фигурными скобками в псевдокоде по клавише "%" (Shift+5).
  3. С помощью горячей клавиши "/" можно скопировать псевдокод в ассемблерный листинг в виде комментариев.

Источники


  1. Друзья-коллеги
  2. IDA Pro Book
  3. https://twitter.com/idatips
  4. Release Notes
  5. И другие.