В этой статье я собираюсь рассказать о плагине для IDA Pro, который написал прошлым летом, еще находясь на стажировке в нашей компании. В итоге, плагин был представлен на ZeroNights 2016 (Слайды), и с тех пор в нём было исправлено несколько багов и добавлены новые фичи. Хотя на GitHub я постарался описать его как можно подробнее, обычно коллеги и знакомые начинают пользоваться им только после проведения небольшого воркшопа. Кроме того, опущены некоторые детали внутренней работы, которые позволили бы лучше понять и использовать возможности плагина. Поэтому хотелось бы попытаться на примере объяснить, как с ним работать, а также рассказать о некоторых проблемах и тонкостях.


HexRaysPyTools, как можно догадаться из названия, предназначен для улучшения работы декомпилятора Hex-Rays Decompiler. Декомпилятор, создавая псевдо-С код, существенно облегчает работу ревёрсера. Основым его достоинством, выделяющим инструмент на фоне других, является возможность трансформировать код, приводя его к удобному и понятному виду, в отличие от ассемблерного кода, который, даже при самом лучшем сопровождении, требует некоторой доли внимания и сосредоточенности для понимания его работы. У Hex-Rays Decompiler, как и самой IDA Pro, есть API, позволяющий писать расширения и выходить за рамки стандартного функционала. И хотя API очень широк, и в теории позволяет удовлетворить самые изысканные потребности разработчика дополнений, у него есть несколько существенных недостатков, а именно:


  • Слабая документированность. Для того, чтобы найти подходящие функции или классы, самый эффективный способ — произвести поиск регулярными выражениями по файлам, угадывая ключевые слова
  • Произвольные названия функций и структур — часто их название не говорит ни о чём
  • Deprecated методы, для которых не предложено замены
  • Общая запутанность работы. Например, для изменения типа аргумента у функции, нужно написать 8 строк кода и задействовать 3 класса. И это не самый странный пример
  • API для языка Python не совсем совпадает с тем, что для C++. idaapi содержит новые методы, наткнуться на которые получилось чисто случайно
  • Есть неочевидные вещи: например, IDA Pro будет падать, если не отключать Garbage Collector для объектов, добавленных с помощью idaapi в абстрактное синтаксическое дерево (оно всегда строится, когда происходит декомпиляция, и в нём можно изменять объекты или вставлять свои).

Чтобы разобраться во всем этом, помогали рабочие примеры, собранные в Интернете (что-то интересное нашлось даже в китайском сегменте). Так что теперь, если кто-то захочет создать что-то своё для декомпилятора, можно обратиться еще и к исходным кодам моего плагина.


Перейдем к описанию плагина. В HexRaysPyTools можно выделить две отдельные категории — это помощь по трансформации дефолтного вывода Hex-Rays Decompiler к удобному виду и реконструкция структур и классов.


Работа с кодом


Изначально, после запуска декомпилятора клавишей F5, IDA Pro выдаёт не очень понятый код, состоящий преимущественно из стандартных типов и имён переменных. И несмотря на то, что местами пытается угадать типы, создать массивы или назвать эти переменные (которым повезло оказаться аргументами у стандартных функций), получается это не очень хорошо. В целом, задача ревёрсера — привести в адекватный вид декомпилированный код. К сожалению, есть вещи, которые невозможно сделать, не прибегая к IDA SDK. Например, отрицательные обращения к полям структур, которые всегда выглядят безобразно (порой превращаясь в массивы с отрицательными индексами), а также длинные условные вложения, тянущиеся из левого верхнего угла в правый нижний. Кроме этого, очень не хватает горячих клавиш и опций для более быстрой трансформации кода. По мере получения информации в процессе анализа программы приходится изменять сигнатуры функций, переименовывать переменные и изменять типы. Всё это требует большого количества манипуляций мышкой и копирований-вставок. Перейдем к описанию того, что предлагает плагин для решения этих проблем.


Отрицательные смещения


Очень часто встречаются при реверсинге драйверов или ядра Windows или модулей ядра Linux. Например, несколько разных структур может быть расположено в двусвязном списке с использованием структуры LIST_ENTRY. При этом у каждой структуры обращение к двусвязному списку может производиться из произвольного поля.


В результате, когда мы смотрим на то, что получается в IDA Pro, видим следующую картину:



Такой вывод будет всякий раз, когда в исходных кодах программ используются макросы CONTIAINING_RECORD (windows) и container_of (linux). Эти макросы возвращают указатель на начало структуры по её типу, адресу и названию поля. И именно их плагин позволяет вставлять в дизассемблер. Вот как выглядит пример после его применения:



Еще с отрицательными смещениями можно встретиться при множественном наследовании, но это довольно изысканный пример, он редко встречается на практике.


Для того, чтобы вставить макрос в дизассемблер, нужно, чтобы подходящая в данном контексте структура существовала в Local Types или в одной из библиотек в Types Library (их может быть несколько). Мы кликаем по вложенной структуре правой кнопкой и выбираем Select Containing Structure. Далее определяем, где искать структуру, — либо в Local Types, либо в Types Library, и плагин составляет список подходящих структур. Для этого он анализирует, как указанная переменная используется в коде и определяет минимальную и максимальную границы, в которых может находиться поле типа этой переменной. Затем, используя эти сведения, проходит по всем структурам, содержащим поле, у которых все в порядке с границей. При поиске плагин смотрит вложенные структуры и объединения на любую глубину.


В примере выше у exe-файла есть символы, поэтому список подходящих структур получился довольно большой:



Помимо этого, существует ситуация, когда плагин автоматически может вставить макрос. Дело в том, что если есть явное присвоение указателя, IDA Pro догадывается (иногда неправильно) его вставить, но не распространяет его дальше в коде.


Без плагина:



С плагином:



Сильная вложенность


Пожалуй, лучше всего показать искусственный пример. Без плагина:



С плагином:



Подобное изменение будет произведено автоматически, если установлен плагин. Хотелось бы, чтобы можно было накладывать и вручную, но увы — то, что выдаёт декомпилятор, очень нестабильно в плане сохранения вносимых изменений в синтаксическое дерево.


Переименования


Идея в том, чтобы называть переменную или аргумент приходилось не более одного раза, а дальше все переименования совершались горячими клавишами или двумя кликами.


Часто IDA Pro создаёт дублированные переменные. Можно было бы, используя стандартную опцию "map to another variable", избавиться от них. Но это не всегда удобно при отладке, может быть ошибочно и к тому же невозможно откатить, не пересоздавая функцию заново.


Перебросить можно имя с одной переменной на другую, при этом добавляется символ "_":


До:



После:



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


Recasts


Существует множество ситуаций, когда есть взаимодействие между двумя некоторыми сущностями с разными типами, и нам нужно перенести тип одной сущности на другую. Под сущностями понимаются локальные, глобальные переменные, аргументы, функции, поля структур (с обращением по ссылке и без) и возвращаемые значения функции. Плагин позволяет это быстро произвести. Сложно показать картинкой, рекомендую в случае, если нужно перенести тип одной сущности на другую, кликнуть правой кнопкой мыши по ней и посмотреть на опции. Наверняка там появится "Recast ..." (а если не появится, то можно написать мне, и я попробую её добавить).


Прочее


Помимо этого, добавляются следующие опции:


  1. Поиск структуры по размеру и замена числа на sizeof(Structure)). Удобно для поиска структуры подходящего размера по числу байт, указанных оператору new или функции malloc.
  2. Быстрое изменение сигнатуры функции. Кликнув правой кнопкой по её объявлению, можно добавить/удалить возвращаемое значение, удалить аргумент, сбросить соглашение о вызове к __stdcall.
  3. Переход по двойному клику у виртуальных методов.

Восстановление структур


Одной из самых сложных и энергозатратных задач ревёрс-инжиниринга является понимание работы и реконструкция структур и классов. HexRaysPyTools выступает помощником в этом процессе. В чём, собственно, проблема? Средствами по умолчанию можно только залить уже готовое объявление структуры, поэтому приходится "ползать" по коду, пытаясь насобирать сведения о полях, вручную высчитывать смещения и записывать куда-то всю информацию (например, в блокнот). Но, если у нас размеры классов исчисляются сотнями байт и, в придачу, имеют множество методов и несколько виртуальных таблиц, всё становится гораздо сложнее.


Рассмотрим на примере, как помогает в данном случае плагин. Когда-то (исключительно ради самообразования :D) мной был создан бот для онлайн-игрушки. В процессе наткнулся на защиту, шифрующую пакеты, не позволявшую модифицировать код в памяти и мешающую хукать вызов шифрующей функции (которая была жестко обфусцирована). Для того, чтобы обойти её, нужно было распарсить класс, отвечающий за обмен данными между клиентом и сервером и научиться, используя его, вызывать отправку пакетов и считывать полученные, расшифрованные пакеты за несколько вызовов от функций защиты. Тогда это было непростой задачей, но с плагином всё делается довольно просто.


Вот так выглядит метод, принимающий пакеты. this и v1 являются указателями на объект класса, gepard_1 — это функция, заменяющая recv



Если заглянуть внутрь функций sub_41AF50 и sub_41AFF0, можно увидеть довольно много кода, обращающегося к разным полям. И даже это — лишь часть функционала, ответственного за создание и отправку пакетов, поэтому разобраться в назначении полей может быть непросто. Плагин помогает автоматически проанализировать большое количество кода, и из собранной информации составить некий каркас структуры, которая в дальнейшем анализе может быть изменена исследователем и использована для автоматического создания нового типа. Для начала надо открыть Structure Builder через Edit->Plugins->HexRaysPyTools. Это окошко будет содержать собранную информацию, предоставлять возможность для редактирования имен полей и разрешения конфликтов, а также просмотра виртуальных таблиц и сканирования виртуальных функций.


Есть 3 возможных способа собирать информацию о полях:


1) Можно кликнуть правой кнопкой по переменной и, нажав Scan Variable, запустить сканирование в пределах одной функции. При сканировании будет рассматриваться то, как происходит обращение к переменной и, если оно под падает под паттерн обращения к полю, такая информацию будет запомнена. Если другой переменной присваивается значение первой (причем их типы не обязательно должны совпадать), она так же подключается к сканированию (и отключается, если ей будет присвоено новое значение). Вот какой результат будет, если применить этот метод к переменной this в функции выше:



Хотя мы запускали сканирование для переменной this, информация о v1 также была собрана.
Жёлтым отмечены разного рода обращения к полям, и нужно выбрать, какой вариант подходит больше всего, отключив все остальные. До тех пор, пока есть конфликты, создать финальную структуру будет нельзя. Красный — это смещение, начиная с которого производится сканирование. Его можно сдвинуть с помощью кнопки Origin для того, чтобы просканировать потенциальную подструктуру. Например, можно зайти в функцию sub_41AF50 и, сдвинув указатель, собрать немного новой информации:



Если дважды кликнуть по столбцу Offset интересующего поля, можно увидеть список всех обращений к этому полю и быстро переместиться в дизассемблере к месту обращения. Поэтому есть смысл как можно больше покрывать сканированиями все места использования восстанавливаемой структуры. Больше информации о полях — проще разобраться, что зачем нужно. Сканировать каждую переменную может быть весьма утомительно, поскольку указатель на структуру может путешествовать по большому количеству функций, поэтому есть другой способ сбора информации.


2) Кликнув правой кнопкой по переменной, можно выбрать опции "Deep Scan Variable". Основной процесс сканирования будет таким же, как и у первого способа, только теперь, если указатель на структуру будет передаваться как аргумент функции, будет запущено рекурсивное сканирование данного аргумента. Warning! Здесь есть одна проблема — декомпилятор не всегда распознает правильно аргументы функции, которую он еще не декомпилировал, поэтому приходится рекурсивно заходить и декомпилировать каждую функцию, которая потенциально может содержать указатель на нашу структуру как аргумент. Этот процесс запускается автоматически, и происходит только один раз за сессию для каждой функции. Поэтому первые процессы глубокого сканирования могут занять некоторое время (порядка пары минут).


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



Запустив здесь сканирование, получаем следующее:



3) Начинать сканировать лучше всего там, где структура впервые возникает, чтобы максимально покрыть её использование в коде. Плагин предоставляет возможность просканировать все переменные, которым присваивается результат, возвращаемый конструктором.


Если зайти в функции sub_419890, которая впервые возвращает указатель на структуру, то можно увидеть, что используется паттерн-одиночка:



Количество вызовов этой функции очень велико:



Сканировать каждую переменную было бы утомительно, поэтому есть возможность запустить сканер для всех сразу, кликнув по заголовку функции и выбрав опцию "Deep Scan Returned Variables".


Вот результат применения на примере:



Можно заметить, что нашлась информация об обращении к полям 0x8 — 0x14. Так же нашлись виртуальные таблицы — они отображены жирным шрифтом и, дважды кликнув по ним, можно увидеть список виртуальных функций (а заодно и просканировать их как по одной, так и все сразу).


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


Вот, что получилось после непродолжительного анализа:



Подготовка создания структуры завершена. Теперь можно кликнуть "Finalize" и, внеся последние изменения, закончить её создание:



Далее, везде, куда "дотянулся" сканнер, переменным, которые являлись указателем на эту структуру, будет применён её новосозданный тип. Вот как преобразится функция отправки пакетов:



Помимо представленного, в Structure Builder можно создать или попытаться угадать подструктуры, выделив необходимое количество полей и нажав Pack или Recognize Shape соответственно. При поиске подходящей структуры учитываются типы полей — они должны точно совпадать, за исключением базовых типов (charBYTE, intDWORDint *), считающихся одинаковыми.


Для уже созданных классов (структур с виртуальными таблицами), в плагине есть возможность более удобной работы с ними. По пути View-> Open Subviews-> Classes можно открыть следующее окошко:



Здесь можно:


  1. Переименовать методы, в результате чего изменения будут произведены сразу в коде и виртуальных таблицах (сделано, чтобы избежать рассинхронизации)
  2. Изменить объявление методов
  3. Быстро конвертировать первый аргумент в this
  4. Переместиться к функции в окне дизассемблера
  5. Фильтровать информацию регулярными выражениями

И еще. Хотелось бы еще раз напомнить, что при работе с классами очень удобно использовать плагин ClassInformer. Если в файле есть RTTI-информация, это поможет восстановить иерархию классов, а плагин возьмёт имена виртуальных таблиц, что поможет получить близкие к оригиналу имена классов.


Надеемся, что эта статья поможет разобраться, как пользоваться плагином. Найти его и сообщить о багах можно по адресу — https://github.com/igogo-x86/HexRaysPyTools. Также ждем feature requests.

Поделиться с друзьями
-->

Комментарии (16)


  1. vit9696
    02.06.2017 14:33
    +2

    Спасибо, обязательно попробую на досуге, с таблицами виртуальных прямо то, что надо. Напомнило HexRaysCodeXplorer, который регулярно выручает, но как я понимаю, если взять вместе с ClassInformer у вас тут функционал покруче.

    Раз уж делаете трансформации кода, и есть предложения по feature requests, не добавите code folding? Я пользуюсь Hexlight, конечно, но иногда так и тянет убрать большой блок, чтобы не маячил перед глазами. Впрочем, настаивать не буду.


    1. igogo_x86
      02.06.2017 16:04
      +2

      Я задумывался об этом и есть идея, как реализовать. Но там кое-какие проблемы, например, как сделать адекватное автооткрытие при отладке. Впрочем в отладочном режиме можно пока что все в развёрнутом режиме держать. Попробую добавить, как с дипломом разберусь.


      1. mityada
        02.06.2017 16:50

        Есть "Collapse/uncollapse item" (https://www.hex-rays.com/products/decompiler/manual/interactive.shtml). Не то ли это что вам нужно?


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


        1. mityada
          02.06.2017 21:02
          +1

          Вот, добрался до IDA:

          image

          Тут сразу напрашивается feature request. Иногда в коде встречается слишком много inline функций. Приходилось разбирать бинарник где чуть ли не в каждой десятой строчке был заинлайнен деструктор строк. Удобно было бы скрыть их все автоматически. В идеале их хорошо бы вообще вынести отдельно, но это уже более сложная задача.


          1. vit9696
            02.06.2017 23:52

            Спасибо однако, сколько лет пользуюсь, никогда не догадывался залезть в ман/ткнуть непосредственно по if/while. Хотя реализация в очередной раз не самая приятная, можно было раскрытие хотя бы по двойному клику сделать, это прогресс.

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


            1. mityada
              03.06.2017 00:07

              Там же хоткей есть. Если не нравится стандартный — можно поменять. Мне кажется вполне удобно.


          1. igogo_x86
            03.06.2017 00:06
            +2

            Перестройка дерева довольно мудрено сделана в IDA — там нужно унаследовать класс, у которого паттерн Visitor. Затем, в процессе обхода всего дерева, распознать то, что хотим изменять и там же или отдельным классом-наследником Visitor перестроить дерево. Можно у меня посмотреть как это делается с отрицательными смещениями и переворачиванием условий в конструкции if. В общем, каждый такой случай, который хочется скрывать скорее всего придется программировать отдельно (впрочем можно этому научиться и, если потратив на это некоторое время, можно будет потом сэкономить гораздо больше, то это стоит того)


            Насчет folding. Ида прячет сразу всё, но хотелось бы всё же условие у for, while и if видеть и иметь возможность отдельно True и False ветки сворачивать.


  1. mityada
    02.06.2017 15:06
    +1

    Есть неочевидные вещи, например, IDA Pro будет падать, если не отключать Garbage Collector для объектов, добавленных с помощью idaapi в абстрактное синтаксическое дерево

    Какое-то время назад при реверсинге прошивки под ARM столкнулся с тем, что Hex-Rays не распознает одну инструкцию и оставляет ее в asm {}. Она использовалась повсюду и это очень сильно портило код. Попытался написать плагин, который бы заменял эти блоки asm {}. Но как только я стал добавлять свои узлы в дерево — IDA сразу падала. Никогда бы не подумал что проблема может быть в Garbage Collector.


    В итоге проблему решил, заменив везде инструкцию на аналогичную, с которой у Hex-Rays было все в порядке. Но тут можно сказать повезло, так как такой могло и не оказаться.


    1. igogo_x86
      02.06.2017 16:07
      +1

      Я сначала придумал костыль в виде хранения объектов в глобальном массиве, чтобы Garbage Collector их не трогал и их число постоянно росло в ходе сессии, но потом обнаружил, что у Swig-объектов создаваемых IDA, есть поле thisown и, если поставить туда False, то изменение дерево больше не приводит к крашам.


  1. mityada
    02.06.2017 15:20

    Можно было бы, используя стандартную опцию "map to another variable", избавиться от них. Но это не всегда удобно при отладке, может быть ошибочно и к тому же невозможно откатить не пересоздавая функцию заново.

    Почему невозможно? Есть же "unmap variable": https://www.hex-rays.com/products/decompiler/manual/cmd_map_lvar.shtml


    1. igogo_x86
      02.06.2017 16:07

      Действительно, как-то я его прозевал


  1. shachneff
    02.06.2017 15:32

    Здравствуйте! Сколько примерно времени заняло написание и отладка данного плагина?


    1. igogo_x86
      02.06.2017 16:08
      +2

      Где-то 2 — 2.5 месяца очень плотно сидел и затем на протяжении полугода иногда что-то фиксил и потихоньку добавлял.


  1. nanshakov
    02.06.2017 18:40
    +1

    Автор, это очень классно, я так не умею). А как вообще пишутся плагины для IDA?


    1. igogo_x86
      03.06.2017 00:09
      +2

      В блоге Hex-Rays многое можно почерпнуть. Вот например HelloWorld в мире плагинов Иды http://www.hexblog.com/?p=120#PLUGIN_KEEP


    1. d1g1
      04.06.2017 17:11
      +1

      Могу посоветовать:
      — «IDA Pro Book, 2nd Edition» https://www.nostarch.com/idapro2.htm
      — «The Beginner's Guide to IDAPython» hooked-on-mnemonics.blogspot.ru/2015/04/the-beginners-guide-to-idapython.html
      — «Gray Hat Python» https://www.nostarch.com/ghpython.htm
      — Читать код чужих плагинов