Изменения

ARM-декомпилятор

6002 байта добавлено, 09:44, 29 марта 2011
м
Нет описания правки
Задумавшись о "простеньком плагинчике" «простеньком плагинчике» для исключения тупой ручной работы в IDA - IDA — для "трассировки констант"«трассировки констант», пришёл к выводу, что я изобрёл <s>велосипед</s>декомпилятор, и что не такая уж это и сложная вещь - вещь — декомпилятор.
Весь его смысл в том Потом понял, что парсить вывод IDA — занятие тяжкое и естьприносящее малый профит, чтобы проанализировать возможные пути выполнения внутри каждой функции, выделить циклы и условные операторы, а потом запоминать выражения, записываемые в регистрыслучае ARM точно лучше использовать objdump, хотя он и подставлять их в последующие операциине является рекурсивным дизассемблером. КонечноВообще, не в случае ARM всё так ну очень просто, если так как любая инструкция занимает 4 байта и всегда выровнена на границу 4 байт. Поэтому натравленный на файл objdump всегда дизассемблирует его всерьёз писатькорректно, наверняка появятся и другие проблемытолько некоторые «инструкции», но основная суть всё равно здесь описанакоторые на самом деле не инструкции, нужно будет заменить на данные.
По сути 3 типа блоковПоэтому сначала делаем начальный анализ:* ЦиклДизассемблировать код objdump’ом и разобрать вывод на адреса, инструкции, их аргументы и комментарии — как уже сказано, это гораздо легче, чем парсить бешеные листинги из IDA.* УсловныйПредставить адреса, которые очевидно читаются/пишутся, данными:* Линейный* Взятые относительно PC и числа — сразу. Которые только читаются, можно сразу принять за константы и подставить в код (аналог <tt>LDR xx, =const</tt> в IDA).** Потом, уже в процессе декомпиляции, могут выявиться новые адреса с данными — нужно уметь их «сделать» данными в будущем. ''Чувствуете? Уже появляется «интерактивщина», аналогичная букве I в названии IDA.''* Выделяем очевидные функции:** Все константные адреса, на которые делается BL или BLX — это «ближние» (short) вызовы функций.** В будущем, при трассировке выражений, могут появиться ещё функции — нужно уметь их тоже «сделать» функциями. Ещё чуть-чуть интерактивщины.* Разобрать код на непрерывные блоки, забыть про условные суффиксы.* Построить граф ветвлений — литературное название также «Control Flow Graph», или «граф управляющей логики».** Граф тоже может меняться в процессе анализа.
Все 3 имеют Далее начинается более сложный анализ: * Пройтись по одной точке входавсем возможным ветвям выполнения, на каждом шаге сохраняя выражения для значений регистров и флагов сравнений, транслировать инструкции в выражения, а условия EQ/NE/… — в выражения сравнений. ТНаибольшая проблема здесь — '''циклы'''.е* Отобразить ветвления либо на if’ы, либо на циклы, либо, на крайний случай, на goto («кривые» конструкции). для их выделения начинаем * Можно выделять ещё не выделенные функции — ими можно сделать всё, на что (ещё?) не найдены BL-переходы, но что оканчивается записью LR в PC (BX LR / MOV PC, LR / LDM …), а начинается с какогоинструкции, к которой нет перехода с предыдущей. То есть когда предыдущая — либо данные (не инструкция), либо когда она содержит безусловный переход, обходящий следующую инструкцию. NOP’ы между предыдущей и началом функции не учитывать.* Можно выделять дальние вызовы (long call) функции — то есть, вызовы, в которых адрес функции сначала грузится в регистр, а потом вызывается из регистра; это несложно, потому что сложных выражений отслеживать не надо, в качестве адреса используется константа.* Можно осилить даже часть непрямых вызовов функций (по хз откуда взятому адресу).* Найти все ASCII-строки в бинарнике, и подставить в обращения к ним. Далее нужно перейти к выделению блоков — то узлаесть, дальшециклов, условных операторов: * линейныйКаждый цикл: добавляем ** Начинается с какого-либо узла, являющегося к тому же единственной точкой входа в этот цикл.** Должен включать в себя все пути, ведущие в любой внешний узел, если за текущим следует только 1 и тот возвращающиеся в любой внутренний узел (''обобщённое требование цикла'').** Если добавили какой-то узел, и получили две точки входа, то этот узел не разветвляетсяпринадлежит циклу.* * Если добавили какой-то узел и единственная точка входа изменилась, идём нафиг — циклиз изначального узла не растёт.*: если через текущий узел проходит Когда залезаем в цикл, добавляем в него этот переходы на начало и выход из цикла заменяем на continue и break для удобочитаемости.* Условный оператор:*: Если что-то откуда-то ветвится и это не цикл, потом добавляем также это условный оператор :)*: Транслировать его следует так:** Ищем точку сбора всех веток, выходящих из начального узла (узел, в котором все циклыони объединяются).** Залезаем в каждую ветку, проходящие через добавленные всё обрабатываем точно так же, как до этого, и точно так же (в обработке линейных участков и циклов тоже помечаем).** Помечаем пройденные узлыкак обработанные.* условный: если * Когда где-то видим уже обработанный узел раздваивается и через него не проходит цикл, можно добавить заменяем переход к нему дочерниена goto. То есть, if «без дублирования» кусков веток будет «нормальным», без goto.*: Пример «плохого» if’а: 0->1->3->4, 0->2->3, 0->4, 0->5->4. Всё это соответствует стандартной теории декомпиляции, см. например http://www.backerstreet.com/decompiler/creating_statements.php