F : Warum besteht Bedarf an einem weiteren Dekompiler, insbesondere einem verkrüppelten?
A : Eine traurige Wahrheit ist, dass die meisten Dekompilierer verkrüppelt sind. Viele sind nicht in der Lage, triviale Konstrukte zu dekompilieren, andere können fortgeschrittenere nicht dekompilieren, und diejenigen, die scheinbar damit umgehen können, sind durch die Unterstützung nur der langweiligen Architekturen und Betriebssysteme verkrüppelt. Und fast alles ist so geschrieben, dass es kompliziert ist, es zu optimieren oder eine neue Architektur hinzuzufügen. Ein Dekompiler ist ein Werkzeug für das Reverse Engineering. Wenn Sie jedoch einen typischen Dekompiler produktiv nutzen oder an Ihre Bedürfnisse anpassen möchten, müssen Sie ironischerweise zuerst den Dekompiler selbst rückentwickeln, und das kann leicht Monate (oder Jahre) dauern. .
Der zentrale Teil eines Dekompilers (und jedes Programmtransformations-Frameworks) ist die Intermediate Representation (IR). Ein Decompiler sollte mit IR arbeiten und diese als Eingabe verwenden, und die Konvertierung des Assemblers einer bestimmten Architektur in diese IR sollte gut von einem Decompiler entkoppelt sein, andernfalls ist es außerordentlicher Aufwand, Unterstützung für eine andere Architektur hinzuzufügen (was wiederum Einschränkungen bedeutet). Benutzerbasis eines Dekompilers).
Die Dekompilierung ist eine komplexe Aufgabe, daher sollte ein einfacher Einblick in den Dekompilierungsprozess möglich sein. Dies bedeutet, dass die von einem Dekompilierer verwendete IR benutzerfreundlich sein sollte, beispielsweise eine Syntax verwenden sollte, die Programmierern vertraut ist, so direkt wie möglich einem typischen Maschinenassembler zugeordnet werden sollte usw.
Die oben genannten Anforderungen sollten für sich genommen ziemlich offensichtlich sein. Wenn nicht, können sie den Büchern zu diesem Thema entnommen werden, z. B.:
„Der Compiler-Autor braucht außerdem Mechanismen, die es Menschen ermöglichen, das IR-Programm einfach und direkt zu untersuchen. Aus Eigeninteresse sollte sichergestellt werden, dass Compiler-Autoren diesem letzten Punkt Beachtung schenken.“
(Keith Cooper, Linda Torczon, „Engineering a Compiler“)
Allerdings verstoßen Decompiler-Projekte, darunter auch OpenSource-Projekte, regelmäßig gegen diese Anforderungen: Sie sind eng mit bestimmten Maschinenarchitekturen verbunden, erlauben keine IR-Einspeisung und stellen sie dem Benutzer häufig überhaupt nicht zur Verfügung oder dokumentieren sie nicht.
ScratchABlock ist ein Versuch, „Nein“ zu solchen Praktiken zu sagen und ein Dekompilierungs-Framework zu entwickeln, das auf den oben genannten Anforderungen basiert. Beachten Sie, dass ScratchABlock als Lern-/Forschungsprojekt betrachtet werden kann und über gute Absichten und Kritik an anderen Projekten hinaus einem Gelegenheitsbenutzer möglicherweise nicht allzu viel bietet – derzeit oder möglicherweise überhaupt. Es kann sicherlich auch in vielerlei Hinsicht kritisiert werden.
ScratchABlock wird unter den Bedingungen der GNU General Public License v3 (GPLv3) veröffentlicht.
ScratchABlock ist in der Python3-Sprache geschrieben und mit Version 3.3 und höher getestet, funktioniert jedoch möglicherweise auch mit 3.2 oder niedriger (funktioniert nicht mit älteren Python2-Versionen). Es gibt ein paar Abhängigkeiten:
Unter Debian/Ubuntu Linux können diese mit sudo apt-get install python3-yaml python3-nose
installiert werden. Alternativ können Sie diese über Pythons eigenen pip
Paketmanager installieren (sollte für jedes Betriebssystem funktionieren): pip3 install -r requirements.txt
.
ScratchABlock verwendet den PseudoC- Assembler als IR. Es handelt sich um eine Assemblersprache, die weitestgehend in der bekannten C-Sprachsyntax ausgedrückt wird. Die Idee ist, dass jeder C-Programmierer es intuitiv verstehen würde (Beispiel), es gibt jedoch fortlaufende Bemühungen, PseudoC formeller zu dokumentieren.
Beachten Sie, dass ScratchABlock basierend auf den im vorherigen Abschnitt des Dokuments beschriebenen Anforderungen und dem bekannten „Unix-Paradigma“ „eine Sache“ ausführt – Analysen und Transformationen an PseudoC-Programmen, und sich ausdrücklich nicht mit der Konvertierung von Maschinenanweisungen bestimmter Architekturen befasst in PseudoC (zumindest vorerst). Das bedeutet, dass ScratchABlock Sie nicht dazu zwingt, einen bestimmten Konverter/Lifter zu verwenden – Sie können jeden verwenden, den Sie möchten. Vorsichtsmaßnahme: Sie müssten eines haben, um es verwenden zu können. Einige diesbezügliche Hinweise finden Sie am Ende des Dokuments.
Quellcode und Schnittstellenskripte befinden sich im Stammverzeichnis des Repositorys. Die wichtigsten Skripte sind:
apply_xform.py
– Ein zentraler Treiber, der es ermöglicht, eine Folge von Transformationen (oder allgemein Analyse-/Transformationsskripts auf hoher Ebene) auf eine einzelne Datei oder ein Dateiverzeichnis („Projektverzeichnis“) anzuwenden.
inter_dataflow.py
– Interprozeduraler (globaler) Datenflussanalysetreiber (WIP).
script_*.py
– Analyse-/Transformationsskripts auf hoher Ebene für apply_xform.py
(Schalter --script
).
script_i_*.py
– Analyseskripte für inter_dataflow.py
.
run_tests
– Der Regressions-Testsuite-Runner. Der Großteil der Testsuite ist auf hoher Ebene und besteht aus der Ausführung von apply_xform.py mit verschiedenen Durchgängen für Dateien und der Überprüfung der erwarteten Ergebnisse.
Weitere Unterverzeichnisse des Repositorys:
tests_unit
– Klassische Unit-Tests für Python-Module, geschrieben in Python.
tests
– Die Haupttestsuite. Obwohl es von Natur aus integrativ ist, testet es normalerweise einen Durchgang an einer einfachen Datei und folgt daher der Unit-Test-Philosophie. Tests werden als PseudoC-Eingabedateien dargestellt, während die erwarteten Ergebnisse als PseudoC mit grundlegenden Blockanmerkungen und (sofern zutreffend) CFG im .dot-Format dargestellt werden. Wenn Sie sich diese Testfälle ansehen, versuchen, sie zu ändern und das Ergebnis zu sehen, können Sie am besten lernen, wie ScratchABlock funktioniert.
docs
– Eine wachsende Sammlung von Dokumentationen. Beispielsweise gibt es eine Spezifikation der PseudoC-Assemblersprache, die als Zwischendarstellung (IR) für ScratchABlock dient, und eine Umfrage, warum keine andere vorhandene IR ausgewählt wurde.
Der aktuelle Ansatz von ScratchABlock besteht darin, eine Sammlung relativ lose gekoppelter Algorithmen („Pässe“) für die Programmanalyse und -transformation aufzubauen, sie mit Tests abzudecken und einen einfachen Benutzerzugriff darauf zu ermöglichen. Die Magie der Dekompilierung besteht darin, diese Algorithmen in der richtigen Reihenfolge und mit der richtigen Häufigkeit anzuwenden. Um die Leistung der Dekompilierung zu verbessern, erfordern diese Durchgänge dann normalerweise eine engere Kopplung. Die Erkundung dieser Richtungen ist die nächste Priorität nach der oben beschriebenen Implementierung der Bestandsaufnahme von Pässen.
Von ScratchABlock implementierte Algorithmen und Transformationen:
Graphalgorithmen:
Statisches Einzelaufgabenformular (SSA):
Datenflussanalyse:
Vermehrung:
Eliminierung von totem Code (DCE)
Umschreiben:
Strukturierung des Kontrollflusses:
Ausgabeformate:
Das Partnertool von ScratchABlock ist ScratchABit, ein interaktiver Disassembler, der die untersten Aufgaben des Dekompilierungsprozesses ausführen soll, wie die Trennung von Code von Daten und die Identifizierung von Funktionsgrenzen. ScratchABit arbeitet normalerweise mit einer nativen Architektur-Assembler-Syntax, aber für einige Architekturen (normalerweise originalgetreue RISCs) kann es, wenn ein geeignetes Plugin verfügbar ist, eine PseudoC-Syntax ausgeben, die als Eingabe für ScratchABlock dienen kann.