Decompilador
Un decompilador (del inglés "decompiler", a veces castellanizado descompilador) es un programa de ordenador que realiza la operación inversa a un compilador.[1] Esto es, traducir código o información de bajo nivel de abstracción (sólo diseñado para ser leído por un ordenador, ej código máquina) a un lenguaje o medio de mayor nivel de abstracción (usualmente diseñado para ser leído por un humano, ej cualquier lenguaje de programación de alto nivel).
Introducción
El término "decompilar" se aplica comúnmente a programas cuya función es la de traducir un código ejecutable a código fuente, donde:
- el programa ejecutable está en código máquina, que es un lenguaje de bajo nivel (de hecho, el nivel de abstracción más bajo que existe), de la salida de un compilador),
- el código fuente está en un lenguaje de alto nivel, más inteligible y fácil de modificar por las personas, con la ventaja de poderse portar a alguna otra máquina (aunque tal vez necesite algunos cambios). Tras este proceso, el fuente puede volver a ser compilado para producir nuevamente un ejecutable que se comportará como el original.
En comparación, un desensamblador traduce un ejecutable exclusivamente a lenguaje ensamblador que, como diferencia, aún depende del soporte hardware y sigue teniendo un nivel de abstracción mínimo (sólo superior al código máquina), pero resulta legible por humanos (y este código puede volver a ser ensamblado en un programa ejecutable).
Decompilar es el acto de utilizar un decompilador, aunque si es usado como nombre, puede referirse a la salida de un decompilador. Puede ser usado para recuperar código fuente, y es muy útil en casos de seguridad del ordenador, interoperatividad y corrección de errores.[2] El éxito de la decompilación depende de la cantidad de información presente en el código que está siendo decompilado y en la sofisticación del análisis realizado sobre él. Los formatos de bytecode utilizados por muchas máquinas virtuales (como la Java Virtual Machine o el lenguaje .NET (.NET Framework Common Language Runtime)) en ocasiones incluyen metadatos en el alto nivel que hacen que la decompilación sea más flexible. Los lenguajes máquina normalmente tienen mucho menos metadatos, y son por lo tanto mucho más difíciles de compilar.
Algunos compiladores y herramientas de post-compilación producen código ofuscado (esto quiere decir que intentan producir una salida que es muy difícil de decompilar). Esto hace que sea más difícil revertir el código del ejecutable.
Diseño
Los decompiladores pueden ser pensados como un conjunto de fases, en las que cada una de ellas contribuye de una forma específica en el proceso de decompilación.
Cargador
La primera fase de decompilación es el cargador, que recibe el código máquina o el archivo binario de un lenguaje intermedio. El cargador debería ser capaz de descubrir datos básicos sobre el programa, como por ejemplo la arquitectura (Pentium, PowerPC, etc), y el punto de entrada. En muchos casos, debería ser capaz de encontrar la función main de un programa en C, que es el comienzo del código escrito por el usuario. Esto excluye el código de inicialización de la ejecución, que no debería ser decompilado si es posible.
Desensamblado
La siguiente fase lógica es el desensamblado del código máquina, éste pasa a una representación máquina independiente (IR). Por ejemplo, la instrucción Pentium
mov eax, [ebx+0x04]
debería ser traducida a IR
eax := m[ebx+4];
Secuencias idiomáticas
Las secuencias de código máquina idiomáticas es un conjunto de secuencias de código cuya semántica no aparenta instrucciones semánticamente individuales. Tanto como parte de la fase de desensamblado, o como parte del análisis posterior, estas secuencias idiomáticas necesitan ser traducidas a su equivalente en código IR (representación máquina independiente). Por ejemplo, en lenguaje ensamblador x86:
cdq eax ; edx está almacenado en el registro de extensión de eax xor eax, edx sub eax, edx
podría ser traducido a:
eax := abs(eax);
Análisis del programa
Se pueden aplicar varios tipos de análisis al IR. De forma particular, la propagación de expresiones combina la semántica de muchas instrucciones en expresiones más complejas. Por ejemplo,
mov eax,[ebx+0x04] add eax,[ebx+0x08] sub [ebx+0x0C],eax
podría resultar en el siguiente código IR después de la progagación de expresiones:
m[ebx+12] := m[ebx+12] - (m[ebx+4] + m[ebx+8]);
La expresión resultante luce como lenguaje de alto nivel, y además ha eliminado el uso de los registros eax
. Análisis siguientes podrían eliminar el registro ebx
.
Análisis de tipos
Un buen decompilador debería implementar un análisis de tipos. Aquí, la forma en que se usan los registros o las regiones de memoria dan como resultado restricciones en el tipo de localidades. Por ejemplo, una instrucción and
implica que el operando es un entero; los programas no usan tales operaciones sobre valores de punto flotante (excepto en código especial de librerías) o en punteros. Una instrucción and
da como resultado 3 restricciones; ambos operandos pueden ser enteros, o uno entero y el otro un puntero (en este caso; la tercera restricción surge en el orden de los dos operandos cuando los tipos son diferentes).
Pueden reconocerse varias expresiones de alto nivel, al conocer las estructuras o los arreglos. De todas formas, es difícil de distinguir muchas de las posibilidades por la libertad del código máquina o también porque algunos lenguajes de alto nivel como C permiten casting y aritmética de punteros.
La sección anterior podría dar como resultado el siguiente ejemplo en código de alto nivel:
struct T1* ebx; struct T1 { int v0004; int v0008; int v000C; }; ebx->v000C -= ebx->v0004 + ebx->v0008;
Estructuración
La penúltima fase de la decompilación implica la estructuración del código IR en construcciones de alto nivel como ciclos while
y estructuras condicionales if/then/else
. Por ejemplo, el código máquina
xor eax, eax l0002: or ebx, ebx jge l0003 add eax,[ebx] mov ebx,[ebx+0x4] jmp l0002 l0003: mov [0x10040000],eax
podría traducirse como:
eax = 0; while (ebx < 0) { eax += ebx->v0000; ebx = ebx->v0004; } v10040000 = eax;
El código no estructurado es más difícil de traducir a código estructurado. Algunas soluciones replican código, o agregan variables booleanas. Véase el capítulo 6 de.[3]
Generación de código
La fase final es la generación de código de alto nivel. Tal como un compilador puede tener varios lenguajes destinos para generar código máquina de diferentes arquitecturas, un decompilador puede tener varios destinos de generación de código en diferentes lenguajes de alto nivel.
Justo antes de la generación del código, es deseable permitir la edición interactiva del código IR, tal vez haciendo uso de alguna interfaz gráfica de usuario. Esto puede permitir al usuario agregar comentarios, variables no genéricas y nombres de funciones. Aun así, esto también se puede agregar en ediciones posteriores a la decompilación. El usuario puede cambiar algunos aspectos estructurales, como por ejemplo, convertir un ciclo while
a un ciclo for
. Esto se puede cambiar con un simple editor de textos, o también se pueden usar herramientas de refactoring sobre el código fuente. El usuario también necesitará agregar información que no se pudo reconocer durante la fase de análisis de tipos, por ejemplo, modificar una expresión de memorias a un arreglo o a una estructura. Finalmente, se necesita corregir código IR incorrecto, o hacer cambios para hacer que la salida de código sea más legible.
Aspectos legales
La mayoría de los programas de ordenador están cubiertos por las leyes del copyright. Aunque determinar con precisión qué cubre el copyright y qué no, difiere entre países; la ley de copyright generalmente provee al autor (el/los programador/es o empleado/s) de una colección exclusiva de derechos sobre el programa. Estos derechos incluyen la posibilidad de realizar copias, incluyendo copias realizadas sobre la memoria RAM del ordenador. Como el proceso de decompilación implica la realización de múltiples copias, ha sido prohibido generalmente sin autorización del propietario de los derechos. Sin embargo, la decompilación es en ocasiones necesaria a la hora de buscar interoperatividad; las leyes de copyright en Europa y Estados Unidos que permiten en este caso la decompilación hasta un cierto límite.
En Estados Unidos, las leyes del buen uso del copyright han sido utilizadas satisfactoriamente en casos de decompilación. Por ejemplo, existió un caso entre Sega y Accolade, en el que finalmente se permitía a Accolade la decompilación de ciertas partes de código siempre que no copiasen el mecanismo de juego utilizado por las consolas de Sega.[4]
En Europa, la Directiva del Software de 1991 provee explícitamente derechos de decompilación siempre y cuando se busque la interoperatividad entre programas. El resultado de un acalorado debate entre proteccionistas de software y desarrolladores independientes de código, fue el Artículo 6, que permite la decompilación sólo si un número de condiciones se cumplen:
- Primero, el decompilador (en este caso referido al usuario que realiza la operación de decompilar) debe tener licencia para usar el programa que se desea decompilar.
- Segundo, la decompilación debe ser necesaria en los casos en los que se desee añadir interoperatividad ya sea al programa que se decompila o a programas que se van a comunicar con éste. La información sobre la interoperatividad de un programa no tiene porque estar disponible, pero sí que se debe proporcionar información sobre su funcionamiento mediante manuales y documentación de la API. Esta es una limitación importante. El propósito de esta importante limitación es principalmente incentivar a los desarrolladores a que documenten bien sus productos en el ámbito de la interoperatividad. Ver.[5]
- Tercero, el proceso de decompilación debe, si es posible, estar confinado a partes del programa que son relevantes para la interoperatividad. Como uno de los propósitos de la decompilación es adquirir el conocimiento sobre la estructura de un programa, esta tercera limitación es difícil de aplicar. Una vez más, la responsabilidad recae sobre el decompilador.
Además, el Artículo 6 prescribe que la información obtenida a través de la decompilación no debe ser utilizada para otros propósitos y no puede ser dada a un tercero.
Por encima de todo esto, el derecho proveído en el artículo 6 es interesante, puesto que específica hasta donde se puede llegar en la industria del software con los decompiladores. Pocos casos sobre estos derechos de decompilación se han visto en Europa. Esto puede ser interpretado de dos formas:
- El derecho de compilación no se usa frecuentemente y por lo tanto estas leyes son innecesarias.
- Estos derechos funcionan tan bien y son tan claros que no dan lugar a dudas que planteen disputas entre empresas de software.
En un reciente informe de la Directiva Europea del Software, la Comisión Europea apoyaba la segunda interpretación.
Referencias
- «Caracterización de los riesgos inherentes a la Ingeniería Reversa». Universidad Tecnológica Nacional. 2013.
- (en inglés) "Porqué Decompilación"
- C. Cifuentes. Técnicas de Compilación Inversa. Tesis PhD, Universidad de Tecnología de Queensland, 1994. (disponible como postscript comprimido)
- (en inglés) La legalidad de la decompilación
- (en inglés) B. Czarnota y R.J. Hart, Protección legal de programas de ordenador en Europa: una guía para la directiva. 1991, Londres: Butterworths.
Véase también
Enlaces externos
- Esta obra contiene una traducción derivada de «Decompiler» de Wikipedia en inglés, publicada por sus editores bajo la Licencia de documentación libre de GNU y la Licencia Creative Commons Atribución-CompartirIgual 4.0 Internacional.
- (en inglés) Legalidad de la descompilación
- (en inglés) Artículo de descompilación