Вопрос : Зачем нужен еще один декомпилятор, тем более урезанный?
О : Печальная правда заключается в том, что большинство декомпиляторов неэффективны. Многие не умеют декомпилировать тривиальные конструкции, другие не могут декомпилировать более продвинутые, те, которые, казалось бы, с ними справляются, увечны поддержкой только скучных архитектур и ОС. И почти каждый написан так, что его подправить или добавить новую архитектуру сложно. Декомпилятор — это инструмент для обратного проектирования, но по иронии судьбы, если вы хотите продуктивно использовать типичный декомпилятор или адаптировать его под свои нужды, сначала вам нужно будет перепроектировать сам декомпилятор, а это может легко занять месяцы (или годы). .
Центральной частью декомпилятора (и любой среды преобразования программ) является промежуточное представление (IR). Декомпилятор должен работать с IR и должен принимать его на вход, а преобразование ассемблера конкретной архитектуры в этот IR должно быть хорошо отделено от декомпилятора, иначе потребуются невероятные усилия, чтобы добавить поддержку другой архитектуры (что, в свою очередь, ограничивает база пользователей декомпилятора).
Декомпиляция — сложная задача, поэтому процесс декомпиляции должен быть легко понятен. Это означает, что IR, используемый декомпилятором, должен быть дружественным к человеку, например, использовать синтаксис, знакомый программистам, максимально точно сопоставлять его с типичным машинным ассемблером и т. д.
Вышеупомянутые требования должны быть совершенно очевидны сами по себе. Если нет, то их можно узнать из книг по этому вопросу, например:
«Составителю компилятора также нужны механизмы, которые позволяют людям легко и напрямую исследовать программу IR. Личный интерес должен гарантировать, что составители компиляторов обратят внимание на этот последний пункт».
(Кит Купер, Линда Торчон, «Разработка компилятора»)
Однако проекты декомпиляторов, в том числе OpenSource, обычно нарушают эти требования: они тесно связаны с конкретными машинными архитектурами, не позволяют вводить IR и зачастую вообще не раскрывают и не документируют их пользователю.
ScratchABlock — это попытка сказать «нет» такой практике и разработать структуру декомпиляции на основе приведенных выше требований. Обратите внимание, что ScratchABlock можно рассматривать как учебный/исследовательский проект, и, помимо благих намерений и критики других проектов, он может не предложить слишком многого обычному пользователю - в настоящее время или, возможно, вообще. Его, безусловно, можно критиковать во многих аспектах.
ScratchABlock выпускается на условиях GNU General Public License v3 (GPLv3).
ScratchABlock написан на языке Python3 и протестирован с версией 3.3 и выше, хотя может работать и с версией 3.2 или ниже (не будет работать с устаревшими версиями Python2). Есть несколько зависимостей:
В Debian/Ubuntu Linux их можно установить с помощью sudo apt-get install python3-yaml python3-nose
. Кроме того, вы можете установить их через собственный менеджер пакетов pip
Python (должен работать для любой ОС): pip3 install -r requirements.txt
.
ScratchABlock использует ассемблер PseudoC в качестве IR. Это язык ассемблера, максимально выраженный с использованием знакомого синтаксиса языка C. Идея состоит в том, что любой программист на C поймет это интуитивно (пример), но продолжаются попытки документировать PseudoC более формально.
Обратите внимание, что, основываясь на требованиях, описанных в предыдущем разделе документа, и следуя известной «парадигме Unix», ScratchABlock делает «одну вещь» — анализирует и преобразует программы PseudoC и явно не занимается преобразованием машинных инструкций конкретных архитектур. в PseudoC (по крайней мере, на данный момент). Это означает, что ScratchABlock не заставляет вас использовать какой-то конкретный преобразователь/подъемник — вы можете использовать любой, который вам нравится. Предостережение: вам понадобится один, чтобы использовать его. См. в конце документа некоторые подсказки по этому поводу.
Исходный код и интерфейсные скрипты находятся в корне репозитория. Наиболее важные сценарии:
apply_xform.py
— центральный драйвер, позволяющий применять последовательность преобразований (или, в общем, сценарии анализа/преобразования высокого уровня) к одному файлу или каталогу файлов («каталог проекта»).
inter_dataflow.py
— драйвер межпроцедурного (глобального) анализа потоков данных (WIP).
script_*.py
— сценарии анализа/преобразования высокого уровня для apply_xform.py
(переключатель --script
).
script_i_*.py
— сценарии анализа для inter_dataflow.py
.
run_tests
— средство запуска набора регрессионных тестов. Большая часть набора тестов является высокоуровневой и состоит из запуска apply_xform.py с различными проходами к файлам и проверки ожидаемых результатов.
Другие подкаталоги репозитория:
tests_unit
— Классические модульные тесты для модулей Python, написанные на Python.
tests
— основной набор тестов. Несмотря на интеграционный характер, он обычно тестирует один проход одного простого файла, что соответствует философии модульного тестирования. Тесты представлены в виде входных файлов PseudoC, а ожидаемые результаты - в виде PseudoC с аннотацией основных блоков и (где применимо) CFG в формате .dot. Просмотр этих тестовых примеров, попытка их модификации и наблюдение за результатом — лучший способ узнать, как работает ScratchABlock.
docs
— постоянно растущая коллекция документации. Например, есть спецификация ассемблерного языка PseudoC, служащего промежуточным представлением (IR) для ScratchABlock, и обзор того, почему не был выбран другой существующий IR.
Текущий подход ScratchABlock состоит в том, чтобы вырастить коллекцию относительно слабо связанных алгоритмов («проходов») для анализа и преобразования программ, покрыть их тестами и обеспечить к ним легкий доступ пользователей. Магия декомпиляции заключается в применении этих алгоритмов в правильном порядке и нужное количество раз. Затем, чтобы повысить производительность декомпиляции, эти проходы обычно требуют более тесной связи. Исследование этих направлений является следующим приоритетом после инвентаризации пропусков, как описано выше.
Алгоритмы и преобразования, реализованные ScratchABlock:
Алгоритмы графа:
Статическая форма единого задания (SSA):
Анализ потока данных:
Распространение:
Устранение мертвого кода (DCE)
Переписывание:
Структурирование потока управления:
Выходные форматы:
Партнерским инструментом ScratchABlock является ScratchABit, интерактивный дизассемблер, предназначенный для выполнения задач самого низкого уровня процесса декомпиляции, таких как отделение кода от данных и определение границ функций. ScratchABit обычно работает с синтаксисом ассемблера собственной архитектуры, но для некоторых архитектур (обычно верных RISC), если доступен подходящий плагин, он может выводить синтаксис PseudoC, который может служить входными данными для ScratchABlock.