В этой статье я собираюсь рассказать о плагине для 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 ..." (а если не появится, то можно написать мне, и я попробую её добавить).
Прочее
Помимо этого, добавляются следующие опции:
- Поиск структуры по размеру и замена числа на
sizeof(Structure)
). Удобно для поиска структуры подходящего размера по числу байт, указанных операторуnew
или функцииmalloc
. - Быстрое изменение сигнатуры функции. Кликнув правой кнопкой по её объявлению, можно добавить/удалить возвращаемое значение, удалить аргумент, сбросить соглашение о вызове к __stdcall.
- Переход по двойному клику у виртуальных методов.
Восстановление структур
Одной из самых сложных и энергозатратных задач ревёрс-инжиниринга является понимание работы и реконструкция структур и классов. 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 соответственно. При поиске подходящей структуры учитываются типы полей — они должны точно совпадать, за исключением базовых типов (char
— BYTE
, int
— DWORD
— int *
) считающихся одинаковыми.
Для уже созданных классов (структур с виртуальными таблицами), в плагине есть возможность более удобной работы с ними. По пути View-> Open Subviews-> Classes можно открыть следующее окошко:
Здесь можно:
- Переименовать методы, в результате чего изменения будут произведены сразу в коде и виртуальных таблицах (сделано, чтобы избежать рассинхронизации)
- Изменить объявление методов
- Быстро конвертировать первый аргумент в this.
- Переместиться к функции в окне дизассемблера.
- Фильтровать информацию регулярными выражениями.
И еще, хотелось бы лишний раз напомнить, что при работе с классами очень удобно использовать плагин ClassInformer. Если в файле есть RTTI информация, то это поможет восстановить иерархия классов, а мой плагин возьмёт имена виртуальных таблиц, что поможет получить близкие к оригиналу имена классов.
Надеюсь эта статья поможет разобраться как пользоваться плагином. Найти его и сообщить о багах можно по адресу — https://github.com/igogo-x86/HexRaysPyTools. Также жду feature requests.
Автор: igogo_x86