Q : Pourquoi y a-t-il besoin d'un autre décompilateur, surtout s'il est paralysé ?
R : La triste vérité est que la plupart des décompilateurs sont paralysés. Beaucoup ne sont pas capables de décompiler des constructions triviales, d'autres ne peuvent pas décompiler des constructions plus avancées, ceux qui semblent pouvoir les gérer sont paralysés en ne prenant en charge que les architectures et les systèmes d'exploitation ennuyeux. Et presque tous sont écrits de telle manière que le peaufiner ou ajouter une nouvelle architecture est compliqué. Un décompilateur est un outil de rétro-ingénierie, mais ironiquement, si vous souhaitez utiliser un décompilateur typique de manière productive ou l'adapter à vos besoins, vous devrez d'abord procéder à une rétro-ingénierie du décompilateur lui-même, et cela peut facilement prendre des mois (ou des années). .
La partie centrale d'un décompilateur (et de tout cadre de transformation de programme) est la représentation intermédiaire (IR). Un décompilateur doit fonctionner sur IR et doit le prendre comme entrée, et la conversion de l'assembleur d'une architecture particulière vers cet IR doit être bien découplée d'un décompilateur, sinon il faut un effort extraordinaire pour ajouter le support d'une autre architecture (ce qui à son tour limite base d'utilisateurs d'un décompilateur).
La décompilation est une tâche complexe, il devrait donc y avoir un aperçu facile du processus de décompilation. Cela signifie que l'IR utilisé par un décompilateur doit être convivial, par exemple utiliser une syntaxe familière aux programmeurs, mapper aussi directement que possible à un assembleur de machine typique, etc.
Les exigences ci-dessus devraient être assez évidentes en elles-mêmes. Sinon, ils peuvent être tirés des livres sur le sujet, par exemple :
"Le rédacteur du compilateur a également besoin de mécanismes permettant aux humains d'examiner le programme IR facilement et directement. L'intérêt personnel devrait garantir que les rédacteurs du compilateur prêtent attention à ce dernier point."
(Keith Cooper, Linda Torczon, "Ingénierie d'un compilateur")
Cependant, les projets de décompilation, y compris ceux OpenSource, violent systématiquement ces exigences : ils sont étroitement couplés à des architectures de machines spécifiques, ne permettent pas d'introduire des IR et, bien souvent, ne les exposent pas ou ne les documentent pas du tout à l'utilisateur.
ScratchABlock est une tentative de dire « non » à de telles pratiques et de développer un cadre de décompilation basé sur les exigences ci-dessus. Notez que ScratchABlock peut être considéré comme un projet d'apprentissage/recherche et, au-delà des bonnes intentions et des critiques d'autres projets, peut ne pas offrir grand-chose à un utilisateur occasionnel - actuellement, ou peut-être pas du tout. Il peut certainement être critiqué à bien des égards.
ScratchABlock est publié sous les termes de la licence publique générale GNU v3 (GPLv3).
ScratchABlock est écrit en langage Python3 et testé avec la version 3.3 et supérieure, mais peut également fonctionner avec la version 3.2 ou inférieure (ne fonctionnera pas avec les anciennes versions de Python2). Il y a quelques dépendances :
Sur Debian/Ubuntu Linux, ceux-ci peuvent être installés avec sudo apt-get install python3-yaml python3-nose
. Alternativement, vous pouvez les installer via le propre gestionnaire de packages pip
de Python (devrait fonctionner pour n'importe quel système d'exploitation) : pip3 install -r requirements.txt
.
ScratchABlock utilise l'assembleur PseudoC comme IR. Il s'agit d'un langage assembleur exprimé autant que possible en utilisant la syntaxe familière du langage C. L'idée est que tout programmeur C le comprendrait intuitivement (exemple), mais des efforts sont en cours pour documenter PseudoC de manière plus formelle.
Notez que sur la base des exigences décrites dans la section précédente du document, et en suivant le "paradigme Unix" bien connu, ScratchABlock fait "une chose" - des analyses et des transformations sur les programmes PseudoC, et ne concerne explicitement pas la conversion d'instructions machine d'architectures particulières. en PseudoC (du moins, pour l'instant). Cela signifie que ScratchABlock ne vous oblige pas à utiliser un convertisseur/élévateur particulier - vous pouvez utiliser celui que vous voulez. Attention : vous en aurez besoin pour l'utiliser. Voir la fin du document pour quelques indices à cet égard.
Le code source et les scripts d'interface se trouvent à la racine du référentiel. Les scripts les plus importants sont :
apply_xform.py
- Un pilote central, permet d'appliquer une séquence de transformations (ou en général, des scripts d'analyse/transformation de haut niveau) à un seul fichier ou à un répertoire de fichiers ("répertoire du projet").
inter_dataflow.py
- Pilote d'analyse de flux de données interprocédural (global) (WIP).
script_*.py
- Scripts d'analyse/transformation de haut niveau pour apply_xform.py
(commutateur --script
).
script_i_*.py
- Scripts d'analyse pour inter_dataflow.py
.
run_tests
- Le programme d'exécution de la suite de tests de régression. La majorité de la suite de tests est de haut niveau, consistant à exécuter apply_xform.py avec différentes passes sur le(s) fichier(s) et à vérifier les résultats attendus.
Autres sous-répertoires du référentiel :
tests_unit
- Tests unitaires classiques pour les modules Python, écrits en Python.
tests
- La suite de tests principale. Bien que de nature intégrative, il teste généralement une seule passe sur un fichier simple, il suit donc la philosophie des tests unitaires. Les tests sont représentés sous forme de fichiers d'entrée PseudoC, tandis que les résultats attendus sont représentés sous forme de PseudoC avec annotation de blocs de base et (le cas échéant) CFG au format .dot. Examiner ces cas de test, essayer de les modifier et voir le résultat est le meilleur moyen d'apprendre comment fonctionne ScratchABlock.
docs
- Une collection croissante de documentation. Par exemple, il existe une spécification du langage assembleur PseudoC servant de représentation intermédiaire (IR) pour ScratchABlock et une enquête sur les raisons pour lesquelles un autre IR existant n'a pas été sélectionné.
L'approche actuelle de ScratchABlock consiste à développer une collection d'algorithmes relativement faiblement couplés ("passes") pour l'analyse et la transformation des programmes, à les couvrir de tests et à permettre aux utilisateurs d'y accéder facilement. La magie de la décompilation consiste à appliquer ces algorithmes dans le bon ordre et le bon nombre de fois. Ensuite, pour améliorer les performances de décompilation, ces passes nécessitent généralement un couplage plus serré. L’exploration de ces orientations est la prochaine priorité après la mise en œuvre de l’inventaire des laissez-passer décrit ci-dessus.
Algorithmes et transformations implémentés par ScratchABlock :
Algorithmes graphiques :
Formulaire d'affectation unique statique (SSA) :
Analyse du flux de données :
Propagation:
Élimination des codes morts (DCE)
Réécriture :
Structuration du flux de contrôle :
Formats de sortie :
L'outil partenaire de ScratchABlock est ScratchABit, qui est un désassembleur interactif destiné à effectuer les tâches de niveau le plus bas du processus de décompilation, comme la séparation du code des données et l'identification des limites des fonctions. ScratchABit fonctionne généralement avec une syntaxe d'assembleur d'architecture native, mais pour certaines architectures (généralement des RISC fidèles), si un plugin approprié est disponible, il peut générer une syntaxe PseudoC, qui peut servir d'entrée à ScratchABlock.