Изменения

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

6163 байта добавлено, 09:41, 18 апреля 2011
м
Этап 2 — Разбиение на блоки и анализ статических ветвлений
== Этап 2 — Разбиение на блоки и анализ статических ветвлений ==
@TODOНа этом этапе программу нужно представить в виде набора блоков, то есть, последовательностей команд, всегда выполняющихся друг за другом, и переходов между ними. То, что получится, называется «control flow graph», или граф ветвлений. На блоки ARM-программу разбить легко, хотя есть нюансы — в ARM-коде почти все инструкции могут быть условными. Соответственно, каждая последовательность условных команд с одним и тем же суффиксом — блок, а все условные переходы генерируются как раз из суффиксов. Чтобы выделить блоки, нужно сначала пройтись по всему бинарю и на каждый адрес сохранить флаги, а потом опять пройтись, но уже рекурсивно и начиная с точки входа программы. Новый блок в позиции нужно начать, если (флаг EPI_N):* Если на этот адрес обнаружены переходы.* Если по предыдущему адресу другой условный суффикс.* Если по предыдущему адресу условная инструкция, меняющая флаги.* Если по предыдущему адресу переход без ссылки (то есть переход, но не вызов функции). Также нужно сохранить информацию о переходе:* Со ссылкой/без ссылки.* Тип адреса:** Известный и вычислен** Динамический регистровый** Динамический из памяти (<tt><nowiki>LDR PC, [...]</nowiki></tt>)** Возврат из функции Переходы в ARM могут инициироваться следующими инструкциями:* B / BX / BL / BLX* MOV PC, …* Арифметико-логические инструкции типа ADD PC, …* LDR PC, […]. Перед этой инструкцией часто встречается MOV LR, PC — тогда такой переход надо считать вызовом функции.* LDMxx … {..PC..} / POP {..PC..}. Это, кстати, почти наверняка возврат из функции, компилятор совмещает его с восстановлением значений регистров из стека. Отследить на этом этапе '''все''' переходы мы не сможем, потому что адрес может вычисляться более или менее динамически, а мы пока что не смотрим на данные. Однако есть два плюса:* Большинство «динамических» переходов — вызовы функций. Управляющие конструкции, как правило, состоят из ближних переходов.* Даже если мы не знаем цель перехода, сам факт наличия перехода всё равно нам известен.Поэтому разбиение на блоки будет почти наверняка корректным. Единственное, что может повлиять на разбиение — неизвестный переход в середину блока. Далее нужно разобраться с условными суффиксами. Тут есть нюансы:# Как отобразить на граф последовательность условных блоков с разными суффиксами?# Как отобразить на граф переход на условную инструкцию? Для решения (1) нужно добавить все переходы, «перескакивающие» один или несколько условных блоков, начиная с последней безусловной инструкции. Для этого при обработке нужно сохранять номера условных блоков + номер последнего безусловного, и при встрече нового блока добавлять переходы на этот новый блок с каждого из сохранённых с условием, равным конъюнкции отрицаний суффиксов блоков, которые мы перескакиваем, и суффикса нового блока, если он условный. Для решения (2) нужно действовать похоже — переход на условную инструкцию заменяется на набор переходов, которые могут перескакивать условные инструкции. Правда, так мы всё равно ничего не сможем сделать, если первая инструкция функции — условная. В такой ситуации можно только вводить «псевдо-блок», не включающий в себя ни одной инструкции, в начало функции. Но такого кода компиляторы, к счастью, не генерируют, поэтому если не пытаться декомпилировать вирусы и кряки — всё будет нормально. В результате все инструкции переходов на графе у нас будут безусловными, а условными могут быть только переходы к условным блокам инструкций.
Пример графа ветвлений с этого этапа: