Decompilador
Ir a la navegaciónIr a la búsqueda
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).
Índice
• 1Introducción
• 2Diseño
o 2.1Cargador
o 2.2Desensamblado
o 2.3Secuencias idiomáticas
o 2.4Análisis del programa
o 2.5Análisis de tipos
o 2.6Estructuración
o 2.7Generación de código
• 3Aspectos legales
• 4Referencias
• 5Véase también
• 6Enlaces externos
Introducción[editar]
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[editar]
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[editar]
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[editar]
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[editar]
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[editar]
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[editar]
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[editar]
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[editar]
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.