P : ¿Por qué es necesario otro descompilador, especialmente uno dañado?
R : Una triste verdad es que la mayoría de los descompiladores que existen están paralizados. Muchos no son capaces de descompilar construcciones triviales, otros no pueden descompilar más avanzados, aquellos que aparentemente pueden manejarlos, están paralizados al soportar sólo arquitecturas y sistemas operativos aburridos. Y casi todos están escritos de tal manera que modificarlos o agregar una nueva arquitectura es complicado. Un descompilador es una herramienta de ingeniería inversa, pero, irónicamente, si desea utilizar un descompilador típico de manera productiva o adaptarlo a sus necesidades, primero necesitará realizar ingeniería inversa al descompilador, y eso puede llevar fácilmente meses (o años). .
La parte central de un descompilador (y de cualquier marco de transformación de programas) es la representación intermedia (IR). Un descompilador debería funcionar en IR y debería tomarlo como entrada, y la conversión del ensamblador de una arquitectura particular a este IR debería estar bien desacoplada de un descompilador o, de lo contrario, se necesita un esfuerzo extraordinario para agregar soporte para otra arquitectura (lo que a su vez limita base de usuarios de un descompilador).
La descompilación es una tarea compleja, por lo que debería ser fácil comprender el proceso de descompilación. Esto significa que la IR utilizada por un descompilador debe ser amigable para los humanos, por ejemplo usar una sintaxis familiar para los programadores, mapear lo más directamente posible a un ensamblador de máquina típico, etc.
Los requisitos anteriores deberían ser bastante obvios por sí solos. Si no, se pueden aprender de los libros sobre el tema, por ejemplo:
"El escritor del compilador también necesita mecanismos que permitan a los humanos examinar el programa IR fácil y directamente. El interés propio debería garantizar que los escritores del compilador presten atención a este último punto".
(Keith Cooper, Linda Torczon, "Ingeniería de un compilador")
Sin embargo, los proyectos de descompilación, incluidos los de OpenSource, violan rutinariamente estos requisitos: están estrechamente acoplados con arquitecturas de máquinas específicas, no permiten la alimentación de IR y, a menudo, no los exponen ni documentan en absoluto para el usuario.
ScratchABlock es un intento de decir "no" a tales prácticas y desarrollar un marco de descompilación basado en los requisitos anteriores. Tenga en cuenta que ScratchABlock puede considerarse un proyecto de aprendizaje/investigación y, más allá de las buenas intenciones y las críticas a otros proyectos, puede no ofrecer demasiado a un usuario ocasional (actualmente, o posiblemente no ofrecer nada). Sin duda, también se le puede criticar en muchos aspectos.
ScratchABlock se publica bajo los términos de la Licencia Pública General GNU v3 (GPLv3).
ScratchABlock está escrito en lenguaje Python3 y probado con la versión 3.3 y superiores, aunque también puede funcionar con la 3.2 o inferior (no funcionará con las versiones heredadas de Python2). Hay algunas dependencias:
En Debian/Ubuntu Linux, estos se pueden instalar con sudo apt-get install python3-yaml python3-nose
. Alternativamente, puede instalarlos a través del administrador de paquetes pip
de Python (debería funcionar para cualquier sistema operativo): pip3 install -r requirements.txt
.
ScratchABlock utiliza el ensamblador PseudoC como IR. Es un lenguaje ensamblador expresado tanto como sea posible utilizando la sintaxis familiar del lenguaje C. La idea es que cualquier programador de C lo entendería intuitivamente (ejemplo), pero hay un esfuerzo continuo para documentar PseudoC de manera más formal.
Tenga en cuenta que, basándose en los requisitos descritos en la sección anterior del documento, y siguiendo el conocido "paradigma Unix", ScratchABlock hace "una cosa": análisis y transformaciones en programas PseudoC, y explícitamente no se ocupa de convertir instrucciones de máquina de arquitecturas particulares. en PseudoC (al menos, por ahora). Eso significa que ScratchABlock no te obliga a usar ningún convertidor/elevador en particular; puedes usar el que quieras. Advertencia: necesitarías tener uno para usarlo. Consulte el final del documento para obtener algunas sugerencias al respecto.
El código fuente y los scripts de interfaz se encuentran en la raíz del repositorio. Los guiones más importantes son:
apply_xform.py
: un controlador central que permite aplicar una secuencia de transformaciones (o, en general, scripts de análisis/transformación de alto nivel) a un solo archivo o un directorio de archivos ("directorio de proyecto").
inter_dataflow.py
: controlador de análisis de flujo de datos (WIP) interprocedural (global).
script_*.py
: scripts de análisis/transformación de alto nivel para apply_xform.py
(conmutador --script
).
script_i_*.py
: scripts de análisis para inter_dataflow.py
.
run_tests
: el corredor del conjunto de pruebas de regresión. La mayor parte del conjunto de pruebas es de alto nivel y consiste en ejecutar apply_xform.py con diferentes pasadas en los archivos y verificar los resultados esperados.
Otros subdirectorios del repositorio:
tests_unit
: pruebas unitarias clásicas para módulos de Python, escritas en Python.
tests
: el conjunto de pruebas principal. Si bien es de naturaleza integracional, generalmente prueba una pasada en un archivo simple, por lo que sigue la filosofía de prueba unitaria. Las pruebas se representan como archivos de entrada PseudoC, mientras que los resultados esperados, como PseudoC con anotaciones de bloques básicos y (cuando corresponda) CFG en formato .dot. Observar estos casos de prueba, intentar modificarlos y ver el resultado es la mejor manera de aprender cómo funciona ScratchABlock.
docs
: una colección cada vez mayor de documentación. Por ejemplo, hay una especificación del lenguaje ensamblador PseudoC que sirve como representación intermedia (IR) para ScratchABlock y una encuesta de por qué no se seleccionó otro IR existente.
El enfoque actual de ScratchABlock es desarrollar una colección de algoritmos ("pases") relativamente poco acoplados para el análisis y la transformación de programas, cubrirlos con pruebas y permitir un fácil acceso a ellos por parte del usuario. La magia de la descompilación consiste en aplicar estos algoritmos en el orden correcto y el número de veces correcto. Luego, para mejorar el rendimiento de la descompilación, estos pases suelen requerir un acoplamiento más estrecho. Explorar esas direcciones es la siguiente prioridad después de implementar el inventario de pases como se describe anteriormente.
Algoritmos y transformaciones implementadas por ScratchABlock:
Algoritmos gráficos:
Formulario de asignación única estática (SSA):
Análisis de flujo de datos:
Propagación:
Eliminación de código muerto (DCE)
Reescritura:
Estructuración del flujo de control:
Formatos de salida:
La herramienta asociada de ScratchABlock es ScratchABit, que es un desensamblador interactivo destinado a realizar las tareas de nivel más bajo del proceso de descompilación, como la separación del código de los datos y la identificación de los límites de las funciones. ScratchABit generalmente funciona con una sintaxis de ensamblador de arquitectura nativa, pero para algunas arquitecturas (generalmente, RISC fieles), si hay un complemento adecuado disponible, puede generar una sintaxis PseudoC, que puede servir como entrada para ScratchABlock.