SlideShare una empresa de Scribd logo
Ingeniería Inversa de Código Fuente
                            José Enrique Alvarez Estrada (jeae@prodigy.net.mx)
                         Elvia Morales Turrubiates (emorales@pampano.unacar.mx)


Resumen
Nadie pone en duda la importancia de la ingeniería como disciplina del conocimiento humano. Pero pocas
personas saben que esta disciplina posee una contraparte: la llamada ingeniería inversa.
El objetivo de la ingeniería, es desarrollar la solución a un problema a partir de la nada. En cambio, la inge-
niería inversa propone solucionar ese mismo problema, pero a partir de alguna tecnología existente, cuyo fun-
cionamiento no se conoce (al menos no totalmente), pero que se sabe tiene el potencial de resolverlo total o
parcialmente.
Si bien no existe una licenciatura en ingeniería inversa, es un hecho que muchos ingenieros dedican su vida
profesional a esta disciplina. Pero, curiosamente, muy poco o nada se habla de ella en las currículas universi-
tarias, y jamás se enseña formalmente. De hecho, en muchos círculos el hablar de ingeniería inversa todavía
es un tabú, y si bien se sabe y se acepta que la mayor parte de las empresas la utilizan, nadie quiere reconocer-
lo abiertamente.
El objetivo de esta ponencia es divulgar la importancia de esta disciplina, presentando su estado del arte para
la rama particular de la ingeniería inversa de código fuente.

                                                            Las técnicas de transformación de programas se
Introducción                                                utilizan en muchas áreas de la ingeniería de
Se puede definir a la ingeniería inversa como el            software     incluyendo    la     construcción    de
proceso mediante el cual una tecnología o producto          compiladores, la visualización de software, la
se analiza, con el fin de conocer los componentes           generación de documentación y la actualización
que lo integran y la forma en que éstos interactúan,        automatizada del software. En todas estas
para lograr finalmente una comprensión cabal de             aplicaciones podemos distinguir dos aspectos
su modo de funcionamiento, con el objetivo                  principales, por ejemplo, uno donde el lenguaje
probable de construir una tecnología similar. La            fuente y destino son diferentes (las traducciones) y
ingeniería inversa toma un producto cuyo formato            otro aspecto cuando son iguales (la reformulación).
presenta un bajo grado de abstracción, y obtiene            Estos panoramas principales se pueden refinar en
una nueva presentación del mismo, con una                       un número de aspectos secundarios típicos
abstracción mayor.                                              basados en su efecto sobre el nivel de
Sin duda, cada rama del conocimiento ha                         abstracción de un programa y en la medida
desarrollado sus propias técnicas de ingeniería                 que preservan la semántica de un programa.
inversa. Por ejemplo, en el caso de la química, el
análisis espectrográfico de los componentes que
forman una substancia (digamos, un fármaco)                 Decompilación
puede utilizarse para crear una substancia similiar.
En la mecánica, el desensamble de un mecanismo              La decompilación es lo inverso de la compilación:
permite al ingeniero ver las partes que lo integran,        traducir un archivo ejecutable a un archivo en un
sus medidas, los materiales de que está hecho, etc.         lenguaje de más alto nivel. La decompilación es
de modo que esté en condiciones de crear un clon            útil si el código fuente original no está disponible.
del mismo.                                                  La decompilación completamente automatizada no
En el caso del software, la transformación de               es posible, pero aunque se pudiera llevar a cabo el
programas es el acto de cambiar un programa en              resultado obtenido no sería totalmente objetivo:
otro. El lenguaje en el cual el programa es                 este problema es teóricamente equivalente a “The
transformado y el programa que resulta se                   Halting Problem”, y por tanto la decompilación es
denominan lenguaje fuente y lenguaje destino,               computacionalmente irresoluble.
respectivamente.                                            La decompilación no puede ser exitosa para todos
                                                            los programas, pues es difícil alcanzar una
completa separación de datos y del código en           “dependiente o específica del compilador en
máquinas con arquitectura Von Neumann.                 cuestión” y otra forma “genérica o general”.
Además, si se logra un cierto grado de éxito, el
                                                       La primera aproximación intenta ir del ejecutable
programa generado carece de nombres de
                                                       al fuente, basándose en el análisis de la salida
funciones y variables significativos, pues éstos no
                                                       generada por un compilador especifico. Esto por lo
se almacenan normalmente en un archivo
                                                       general da mejores resultados con respecto a
ejecutable (excepto cuando son almacenados con
                                                       generar código fuente que se asemeje al código
propósitos de depuración, situación que se
                                                       fuente original. Esta aproximación es limitada, en
denomina “almacenar la tabla de símbolos en el
                                                       el sentido de que tiene que construirse un
código objeto”).
                                                       decompilador para cada compilador individual.
Algunas personas creen que solamente es posible
lograr que la decompilación recupere los códigos
fuentes en lenguaje ensamblador, pero ya en sí éste    Decompilación Dependiente
no es un problema trivial, nuevamente debido a su
equivalencia al problema de la detención de la         En la decompilación dependiente o específica del
máquina de Turing. Sin embargo, existen en la          compilador, se puede seguir el siguiente proceso:
práctica aproximaciones que se ocupan del              1.   El ejecutable tiene que examinarse para ver si
desensamblaje y la decompilación. Los más                   está comprimido y si es así, tiene que
acertados hasta la fecha hacen uso de información           descomprimirse. O simplemente excluir ese
adicional -conocimiento acerca del compilador               tipo de ejecutable, advirtiéndole la razón al
empleado- o requieren la intervención humana en             usuario. Revisar el aviso de copyright para
las partes más complejas del proceso del                    verificar que el compilador y la versión sean
desensamblaje. Puede incluso ser posible para               las correctas. Una sola versión del
ciertos     decompiladores,       el   decompilar           decompilador puede ampliarse a múltiples
automáticamente una fracción grande de                      versiones del mismo compilador usando los
programas de código máquina provenientes del                archivos que contengan los datos críticos para
mundo real, no sólo ejercicios académicos.                  cada uno.
Sea como fuere, esta traducción se alcanza                  Se debe examinar el ejecutable para depurar
generalmente en varias fases en las cuales un               información y conservar esa información,
lenguaje de alto nivel primero se traduce a una             posiblemente en un archivo temporal. Esto
representación intermedia. La selección de la               puede ayudar a detectar librerías y recuperar
instrucción entonces traduce la representación              nombres de funciones y variables. Se puede
intermedia a instrucciones de máquina. El proceso           construir una lista ligada que permita observar
de la compilación generalmente también implica              qué partes del programa se conocen
un número de pasadas al código fuente. Por                  (completamente o parcialmente), y por lo
ejemplo, normalizaciones al programa, tales como            tanto, cuáles no se conocen. Cada nodo debe
poner las instrucciones en forma canónica,                  tener el tipo de memoria (código/dato),
simplificaciones algebraicas y varias formas de             nombre de la función, contenido si está
optimización de programas.                                  disponible y tipo de código (librería/usuario).
Las técnicas de la decompilación fueron utilizadas     2.   Como siguiente paso se debe determinar el
inicialmente en los años 60 para ayudar en la               punto de inicio del ejecutable, esto podría ser
migración de programas de una plataforma a otra.            el código de inicio de C que se traduciría como
Desde entonces, se han utilizado para ayudar en la
recuperación del código fuente perdido, en la          int main (int argc,
depuración (debugging) para eliminar errores de                  char *argv[],...)
programas, en la localización de virus, compresión     Más importante, el código de inicio indica dónde
de programas, recuperación de vistas de alto nivel     se han inicializado las variables y cuáles son sus
–diagramas de análisis y diseño- de programas, y       valores. Como no se puede conocer, inicialmente,
más.                                                   el tamaño o tipo de tales variables, examinar estos
                                                       datos puede ayudar a encontrar los tipos flotantes,
                                                       los dobles, las cadenas de caracteres, y quizá los
Aproximaciones a la                                    enteros. Buscando por patrones de repetición de
Decompilación                                          entradas de tipo/tamaño, se podría (tentativamente)
                                                       identificar arreglos de tipo estándar y de
Existen dos formas distintas de atacar el proceso de   estructuras. Se puede diferenciar entre arreglos y
decompilación: una forma que pudiéramos llamar
variables múltiples escalares en una            línea   5.   Concentrarse en una función a la vez, no
examinando el código que las referencia.                     siguiendo las llamadas a funciones. El código
                                                             de entrada a la función informa acerca de las
3.   El siguiente paso es desensamblar, siguiendo
                                                             variables automáticas, y el examen del código
     el flujo del programa principal, observando las
                                                             puede revelar el tamaño y probablemente
     direcciones de las funciones llamadas y las
                                                             también el tipo. El mismo tipo de análisis
     direcciones de inicio de las instrucciones
                                                             aplicado al área de las variables inicializadas
     ejecutadas. Las áreas de confusión (donde un
                                                             se puede utilizar para localizar estructuras.
     goto es dirigido a la mitad de una
     instrucción) pueden ser ensambladas en línea            El segundo paso para una función es
     (y marcarlas como tal) siguiendo siempre la             identificar llamadas a funciones y el código
     ejecución del programa. Se emite una                    que le pasa parámetros a la función. Para el
     advertencia cuando es probable que lo                   paso de valores que no son solamente
     realizado no sea correcto. Luego se puede ver           operaciones de carga/descarga, se puede crear
     si las funciones invocadas corresponden a               un nuevo nodo de código que contenga el
     cualquiera en la lista de información.                  código que genera el valor que es cargado.
     Entonces, se repite recursivamente para cada            Para funciones donde los tipos de parámetros
     rutina llamada, revisando la lista de                   son conocidos, se puede ajustar la información
     información, y comparando el código con la              acerca de las variables involucradas (por
     biblioteca para ese compilador. Cualquier               ejemplo, identificar una variable de archivo
     módulo de bilbioteca conocido puede                     “FILE *”). Esta es una forma de
     eliminarse previa consideración, excepto                concordancia de patrones, pero debe realizarse
     cuando una función de biblioteca llama a otras          antes de avanzar para que sea más fácil.
     funciones; entonces, se puede también
                                                             El paso tres es la identificación de plantillas,
     eliminar las funciones llamadas, etc.
                                                             para cualquiera de las plantillas que se han
     Conociendo qué es lo que hacen las diferentes
                                                             determinado para el compilador. Conforme se
     interrupciones (INT), se pueden delimitar
                                                             van identificando los segmentos de código y el
     áreas que sean de datos. Cuando este proceso
                                                             código fuente, se crea un nodo de código, el
     se haya realizado (y éste involucra varios
                                                             cuál agrega el código fuente generado al
     pasos), se desarrolla un enorme árbol de
                                                             binario. La identificación de plantillas debe
     información acerca del ejecutable, y se puede
                                                             realizarse recursivamente para identificar
     producir un archivo de lenguaje ensamblador
                                                             ciclos anidados, etc. El código ajustado para
     intermedio que tenga la biblioteca descubierta
                                                             los ciclos los identifica como: for, while ó
     (incluso si no había información depurada),
     variables estáticas inicializadas identificadas         do, si no el ciclo no interesa y puede elegirse
     junto con sus valores iniciales.                        de forma arbitraria. Esto conducirá a
                                                             identificar sentencias switch, case, if y
4.   Una vez que se tiene la versión                         llamadas de funciones, convertidas en código
     desensamblada, junto con nuestra lista de               en línea por las instrucciones del
     información, se puede comenzar el proceso               preprocesador “#pragma inline”. Cuando
     real de decompilación. Se puede usar una lista          se encuentra código controlado por #pragma,
     ligada de ramificación; cada nodo indica el             debe colocarse el enunciado pragma en el
     nombre de la función (determinada de la                 código fuente; La localización actual depende
     información depurada o sintetizada), y un               de que si el pragma puede utilizarse como un
     apuntador a la lista de código para esa función.
                                                             bloque de código local o para el archivo entero
     El nodo de la lista de código indica el tipo de
                                                             (y hay un #pragma correspondiente que lo
     código contenido (fuente, binario, o
     ensamblado en línea), un apuntador a memoria            desactiva).
     que contiene el fuente del ASCII, y un                  El paso cuatro busca el código en línea que
     apuntador a un binario no desensamblado.                tiene una representación directa del fuente y
     Como el código es desensamblado, las                    agrega el código fuente al nodo de código. Ese
     entradas en la lista de código se dividen y se          es el código que reúne una o más variables, las
     mezclan; cuando la función completa es                  modifica, y pone uno o más valores de retorno.
     desensamblada, hay una única entrada en la              Se debe poner atención a las fuentes de
     lista de código. El binario se retiene hasta que        variables de registro (desde la lista de
     la     función    ha     sido    desensamblada          automáticas) de manera que el fuente pueda
     completamente.                                          identificar correctamente la variable original.
                                                             El condicional en las instrucciones if puede
                                                             determinarse en este punto, ya que ahora es
posible identificar el código (inmediato) de la     equivalencias. Se generan problemas cuando en el
     comparación.                                        código original se fuerza el cambio de tipo
                                                         (casting). También los tipos enumerados y otros
6.   Cualquier cosa que se haya dejado en este
                                                         tipos que son equivalentes a los tipos estándares,
     punto probablemente puede convertirse en
                                                         no pueden recuperarse.
     código en línea del ensamblador. En cualquier
     caso, ésta es la última posición para retornar.     La detección de los tipos arreglos y tipos apuntador
                                                         no es difícil: los tipos estructurados, especialmente
                                                         cuando se utilizan uniones, generan problemas
Decompilación Independiente                              extra.
La segunda aproximación se caracteriza por
analizar la semántica del ejecutable, y de este
análisis se deriva un archivo fuente equivalente, sin
                                                         Reglas de Traducción de
hacer uso de información acerca de qué compilador        Código para Lenguajes de
fue utilizado para generar el ejecutable. El código
fuente generado no puede asemejarse en todo al           Alto Nivel
original. La ventaja es que el método trabaja para
cualquier compilador que pudiera haberse                 El ensamblador es, a fin de cuentas, un lenguaje de
utilizado.                                               programación como cualquier otro. Teóricamente,
                                                         si se cuenta con la definición de la gramática del
1.   Se asume que se inicia con la salida de             mismo, pudiera construirse un parser que
     ensamblador del desensamblador, que también         reconociera el código fuente, lo estructurase en
     contiene las etiquetas propias. Se podría leer el   forma de un árbol de análisis sintáctico, y
     archivo entero, dentro de la memoria, y             posteriormente recorrer dicho árbol para generar
     agrupar instrucciones en bloques, basados en        código en C o algún otro lenguaje de alto nivel.
     las etiquetas.
                                                         En la práctica, para poder realizar la ingeniería
2.   Decodificar cada instrucción en más                 inversa de código de Ensamblador, se deben hallar
     operaciones primitivas, para así evitar todos       las estructuras de control básicas presentes en el
     los diferentes modos de direccionamiento. La        código fuente de ensamblador, y traducirlas a un
     mayoría de las instrucciones sólo se modifican      lenguaje de mayor nivel. El problema es que la
     en registros o direcciones de memoria.              gramática de ningún ensamblador toma en
3.   A partir de esto, debe ser posible determinar       cuenta dichas estructuras de control. Esto se
     qué registros se leen y escriben por cada           debe a que la mayor parte de las instrucciones del
     bloque (asumiendo que las direcciones de            ensamblador      se   mapean       directamente    a
     memoria se refieren a variables C). Basado en       instrucciones de la capa ISA, y por ello la
     ello se puede hacer un análisis de flujo, para      estructura gramatical del lenguaje ensamblador
     encontrar si hay algún registro de variables        tiende a tomar la forma de un conjunto de líneas,
     detectadas (mapped).                                cada una de las cuáles está compuesta por una
                                                         etiqueta opcional (para identificarla en caso de
4.   Si esto se ha llevado a cabo, se puede              saltos o llamados a subrrutinas), una instrucción de
     determinar cuáles son las expresiones que son       ensamblador directamente traducible a lenguaje
     calculadas por las instrucciones para cada          máquina, y un comentario opcional.
     almacén a una variable (también las basadas
     en registros o memoria). Se necesitara la           Bajo estas circunstancias, es responsabilidad
     normalización de esta expresión.                    completa del programador de lenguaje
                                                         Ensamblador, instrumentar las estructuras de
5.   Ahora, se tiene que reconocer las instrucciones     control secuenciales, selectivas y repetitivas
     de alto nivel que resultaron en la estructura del   mediante combinaciones más o menos extensas de
     bloque, y los saltos entre ellos. Desde aquí es     instrucciones de este lenguaje, usando para ello
     posible empezar a generar el código que             tantas líneas de código como sea necesario♦. La
     solamente tratará operaciones con bytes,            problemática estriba en traducir un código que no
     words, longwords y flotantes (doubles).             necesariamente se pensó y programó con buenas
Para la reconstrucción de tipos, primero se              técnicas estructuradas ni modulares, a un código
necesitan técnicas de resolución de tipos, siempre y
cuando los parámetros sean pasados en llamadas a         
                                                           En ello estriba la dificultad de aprender a
funciones, o cuando se asignan de uno a otro. La         programar en lenguaje Ensamblador: la creación de
manera de hacer esto es asignar un tipo único a          las estructuras de control es responsabilidad del
cada variable y parámetro, y derivar reglas de           programador, y no del compilador.
estructurado y modular en un lenguaje de alto          •   Bertelsons, Boris y Mathias Rasch. PC al
nivel.                                                     Límite, Programación Avanzada. Ed.
A diferencia del Ensamblador, un programa en               Computec-Marcombo. ISBN 970-15-0085-7.
lenguaje C puede construirse utilizando                •   Cifuentes, Cristina. Reverse Compilation
cualquier combinación sintácticamente correcta             Techniques. Tesis Doctoral, Australia, 1994.
de estructuras for, while, do..while, if,
switch, etc. La gramática de C cuenta con clases       •   Godfrey, J. Terry. Lenguaje Ensamblador
sintácticas para describir y reconocer cada una de         para     Microcomputadoras      IBM. Ed.
ellas, y crear el respectivo árbol de análisis             Prentice-Hall. ISBN 968-880-204-2.
sintáctico para después traducirlas a Ensamblador o    •   Lemone, Karen A. Fundamentos de
a lenguaje máquina.                                        Compiladores. Ed. CECSA. ISBN 968-26-
Y aquí es donde se encuentra el reto del estado del        1297-7.
arte de esta disciplina:                               •   O’Gorman, John. Systematic Decompilation.
Describir      gramáticas      que     sean    un          Tesis Doctoral. Irlanda, 1991.
superconjunto de las gramáticas habituales de
los lenguajes ensambladores, pero que agrupen          •   Pratt, Terrence y Marvin V. Zelkowitz.
las instrucciones de éstos con el objeto de                Lenguajes de Programación, Diseño e
reconocer estructuras de control secuenciales,             Implementación. Ed. Prentice-Hall. ISBN 0-
selectivas y repetitivas, llamados a funciones y           13-678012-1.
procedimientos, operaciones aritméticas y              •   Teufel, Schmidt y Teufel. Compiladores,
lógicas, todo ello traducible posteriormente a un          Conceptos Fundamentales. Ed. Addison-
lenguaje de alto nivel.                                    Wesley Iberoamericana. ISBN 0-201-65365-6.
Para ello, es necesario revisar la relación entre      •   Tischer, Michael y Bruno Jennrich. PC
instrucciones Ensamblador y estructuras de control         Interno, Programación de Sistemas. Ed.
de alto nivel, con el afán de definir reglas de            Computec-Marcombo. ISBN 970-15-0079-2.
reconocimiento de patrones estructurales, que
posteriormente puedan usarse para reconocer tales
patrones y traducir el código a lenguaje de alto
nivel.


Conclusiones
Se necesita formalizar la enseñanza de la ingeniería
inversa en las aulas de las Facultades e Institutos
de México, puesto que las empresas líderes interna-
cionales emplean a discreción tales técnicas, y no
conocerlas nos pone en desventaja.
Cuanto mayor sea el número de profesionales que
conozcan y apliquen la ingeniería inversa de códi-
go fuente, tanto mayor será nuestro conocimiento
respecto a las reglas de reconocimiento de patrones
estructurales que es necesario aplicar, en distintos
tipos de compiladores.


Bibliografía
•   Abel, Peter. Lenguaje Ensamblador y
    Programación para PC IBM y Compatibles.
    Ed. Pearson. ISBN 968-880-708-7.
•   Aho, Sethi y Ullman. Compiladores,
    Principios, Técnicas y Herramientas. Ed.
    Pearson. ISBN 968-444-333-1.

Más contenido relacionado

PDF
Ejercicios de evaluación de fundametnos de programacion en JAva
PPTX
3.creacion de componentes visuales
PPTX
Bibliotecas o libreria de c++
PPTX
Teoría de autómatas
DOCX
5.1 estructura de una clase.
PDF
04 7n1is trabajo diseno-dialogos
PPT
Visual basic.net
PPSX
Lenguaje c diapositivas
Ejercicios de evaluación de fundametnos de programacion en JAva
3.creacion de componentes visuales
Bibliotecas o libreria de c++
Teoría de autómatas
5.1 estructura de una clase.
04 7n1is trabajo diseno-dialogos
Visual basic.net
Lenguaje c diapositivas

La actualidad más candente (20)

PPTX
Sistema Operativo UNIX
PPT
Estructuras repetitivas
PPTX
Control de Flujo [Telecomunicaciones]
PDF
Ensamblador
PPTX
Diseño de Algoritmos
DOCX
Traductor y su estructura
PPTX
Automatas Finitos Deterministicos y No Deterministicos
PDF
Manual pseint
PPTX
Lenguaje de Transferencia de Registro
PDF
130447032 inferencia-estadistica-unidad-ii (1)
PPTX
Programacion Modular
DOC
Numeros Pseudoaleatorios
PPT
Operaciones de Entrada / Salida en C++
PDF
GRAMATICAS AMBIGUAS
PDF
automatas finitos
PPTX
Algoritmos de encaminamiento
DOCX
El ciclo de instruccion
PPTX
Tipos de datos para C++
PDF
Interprete sencillo utilizando ply con python 3
PPT
Automatas de pila
Sistema Operativo UNIX
Estructuras repetitivas
Control de Flujo [Telecomunicaciones]
Ensamblador
Diseño de Algoritmos
Traductor y su estructura
Automatas Finitos Deterministicos y No Deterministicos
Manual pseint
Lenguaje de Transferencia de Registro
130447032 inferencia-estadistica-unidad-ii (1)
Programacion Modular
Numeros Pseudoaleatorios
Operaciones de Entrada / Salida en C++
GRAMATICAS AMBIGUAS
automatas finitos
Algoritmos de encaminamiento
El ciclo de instruccion
Tipos de datos para C++
Interprete sencillo utilizando ply con python 3
Automatas de pila
Publicidad

Similar a Ingeniería Inversa de Código Fuente (20)

PPTX
1.introduccion a la programación
PDF
Tareasol
PPTX
Introduccion web paula
DOCX
Aplicar los pricipios de programacion en la solucion de problemas 33
PDF
PDF
PDF
Construcci__n_de_Compiladores_Principios_y_Pr__ctica__1ra_Edicion__Kenneth_Lo...
PDF
Introducción a la compilación en ambientes Unix.
PPTX
Lenguaje de programacion keneling gullo compu 1 []
ODP
Decompiladores
PPTX
Compiladores diapositivas
PDF
PPTX
Compilador 22 (1)
DOCX
Conceptos de compilador
DOCX
Conceptos De Compilador
PPTX
Lenguaje de programacion
PDF
Evolución de la programación
PPTX
Desarollo web nivel de introduccion
DOCX
Unidad 3
PPTX
Preguntas (1)
1.introduccion a la programación
Tareasol
Introduccion web paula
Aplicar los pricipios de programacion en la solucion de problemas 33
Construcci__n_de_Compiladores_Principios_y_Pr__ctica__1ra_Edicion__Kenneth_Lo...
Introducción a la compilación en ambientes Unix.
Lenguaje de programacion keneling gullo compu 1 []
Decompiladores
Compiladores diapositivas
Compilador 22 (1)
Conceptos de compilador
Conceptos De Compilador
Lenguaje de programacion
Evolución de la programación
Desarollo web nivel de introduccion
Unidad 3
Preguntas (1)
Publicidad

Más de José Enrique Alvarez Estrada (20)

ODP
Video Mapping con Open Source
ODP
Video Mapping con Open Source
PDF
BAT 2 CLI, CLI 2 COW, COW 2 GUI
ODP
ODP
Elon Musk: el verdadero Ironman [detrás de Tesla Motors, SpaceX y SolarCity]
ODP
Cómo el Open Source Cambió mi Vida
ODP
Breve historia de la propiedad industrial
ODP
Utilidad e impacto de las redes sociales versión 1.5
ODP
Taller "Small Data con SQL"
ODP
De profesor a emprendedor
ODP
¡Crea tu propio Lab de Ciencias con tu Computadora o Smartphone!
PDF
Las sinrazones de la Educación Superior
PDF
De aventón... Una historia de TERROR en [y con] el transporte público
ODP
Grammars and Syntax
ODP
Pensamiento STEM Guiado por Datos
PDF
De ingeniero a hacker... ¡y de hacker a maker! La necesidad de más práctica e...
ODP
Las apps en el Sector Educativo
ODP
Pirámide organizacional
ODP
De homo sapiens a homo deus
ODP
Inducción al Sistema de Posgrado y MIGA
Video Mapping con Open Source
Video Mapping con Open Source
BAT 2 CLI, CLI 2 COW, COW 2 GUI
Elon Musk: el verdadero Ironman [detrás de Tesla Motors, SpaceX y SolarCity]
Cómo el Open Source Cambió mi Vida
Breve historia de la propiedad industrial
Utilidad e impacto de las redes sociales versión 1.5
Taller "Small Data con SQL"
De profesor a emprendedor
¡Crea tu propio Lab de Ciencias con tu Computadora o Smartphone!
Las sinrazones de la Educación Superior
De aventón... Una historia de TERROR en [y con] el transporte público
Grammars and Syntax
Pensamiento STEM Guiado por Datos
De ingeniero a hacker... ¡y de hacker a maker! La necesidad de más práctica e...
Las apps en el Sector Educativo
Pirámide organizacional
De homo sapiens a homo deus
Inducción al Sistema de Posgrado y MIGA

Ingeniería Inversa de Código Fuente

  • 1. Ingeniería Inversa de Código Fuente José Enrique Alvarez Estrada (jeae@prodigy.net.mx) Elvia Morales Turrubiates (emorales@pampano.unacar.mx) Resumen Nadie pone en duda la importancia de la ingeniería como disciplina del conocimiento humano. Pero pocas personas saben que esta disciplina posee una contraparte: la llamada ingeniería inversa. El objetivo de la ingeniería, es desarrollar la solución a un problema a partir de la nada. En cambio, la inge- niería inversa propone solucionar ese mismo problema, pero a partir de alguna tecnología existente, cuyo fun- cionamiento no se conoce (al menos no totalmente), pero que se sabe tiene el potencial de resolverlo total o parcialmente. Si bien no existe una licenciatura en ingeniería inversa, es un hecho que muchos ingenieros dedican su vida profesional a esta disciplina. Pero, curiosamente, muy poco o nada se habla de ella en las currículas universi- tarias, y jamás se enseña formalmente. De hecho, en muchos círculos el hablar de ingeniería inversa todavía es un tabú, y si bien se sabe y se acepta que la mayor parte de las empresas la utilizan, nadie quiere reconocer- lo abiertamente. El objetivo de esta ponencia es divulgar la importancia de esta disciplina, presentando su estado del arte para la rama particular de la ingeniería inversa de código fuente. Las técnicas de transformación de programas se Introducción utilizan en muchas áreas de la ingeniería de Se puede definir a la ingeniería inversa como el software incluyendo la construcción de proceso mediante el cual una tecnología o producto compiladores, la visualización de software, la se analiza, con el fin de conocer los componentes generación de documentación y la actualización que lo integran y la forma en que éstos interactúan, automatizada del software. En todas estas para lograr finalmente una comprensión cabal de aplicaciones podemos distinguir dos aspectos su modo de funcionamiento, con el objetivo principales, por ejemplo, uno donde el lenguaje probable de construir una tecnología similar. La fuente y destino son diferentes (las traducciones) y ingeniería inversa toma un producto cuyo formato otro aspecto cuando son iguales (la reformulación). presenta un bajo grado de abstracción, y obtiene Estos panoramas principales se pueden refinar en una nueva presentación del mismo, con una un número de aspectos secundarios típicos abstracción mayor. basados en su efecto sobre el nivel de Sin duda, cada rama del conocimiento ha abstracción de un programa y en la medida desarrollado sus propias técnicas de ingeniería que preservan la semántica de un programa. inversa. Por ejemplo, en el caso de la química, el análisis espectrográfico de los componentes que forman una substancia (digamos, un fármaco) Decompilación puede utilizarse para crear una substancia similiar. En la mecánica, el desensamble de un mecanismo La decompilación es lo inverso de la compilación: permite al ingeniero ver las partes que lo integran, traducir un archivo ejecutable a un archivo en un sus medidas, los materiales de que está hecho, etc. lenguaje de más alto nivel. La decompilación es de modo que esté en condiciones de crear un clon útil si el código fuente original no está disponible. del mismo. La decompilación completamente automatizada no En el caso del software, la transformación de es posible, pero aunque se pudiera llevar a cabo el programas es el acto de cambiar un programa en resultado obtenido no sería totalmente objetivo: otro. El lenguaje en el cual el programa es este problema es teóricamente equivalente a “The transformado y el programa que resulta se Halting Problem”, y por tanto la decompilación es denominan lenguaje fuente y lenguaje destino, computacionalmente irresoluble. respectivamente. La decompilación no puede ser exitosa para todos los programas, pues es difícil alcanzar una
  • 2. completa separación de datos y del código en “dependiente o específica del compilador en máquinas con arquitectura Von Neumann. cuestión” y otra forma “genérica o general”. Además, si se logra un cierto grado de éxito, el La primera aproximación intenta ir del ejecutable programa generado carece de nombres de al fuente, basándose en el análisis de la salida funciones y variables significativos, pues éstos no generada por un compilador especifico. Esto por lo se almacenan normalmente en un archivo general da mejores resultados con respecto a ejecutable (excepto cuando son almacenados con generar código fuente que se asemeje al código propósitos de depuración, situación que se fuente original. Esta aproximación es limitada, en denomina “almacenar la tabla de símbolos en el el sentido de que tiene que construirse un código objeto”). decompilador para cada compilador individual. Algunas personas creen que solamente es posible lograr que la decompilación recupere los códigos fuentes en lenguaje ensamblador, pero ya en sí éste Decompilación Dependiente no es un problema trivial, nuevamente debido a su equivalencia al problema de la detención de la En la decompilación dependiente o específica del máquina de Turing. Sin embargo, existen en la compilador, se puede seguir el siguiente proceso: práctica aproximaciones que se ocupan del 1. El ejecutable tiene que examinarse para ver si desensamblaje y la decompilación. Los más está comprimido y si es así, tiene que acertados hasta la fecha hacen uso de información descomprimirse. O simplemente excluir ese adicional -conocimiento acerca del compilador tipo de ejecutable, advirtiéndole la razón al empleado- o requieren la intervención humana en usuario. Revisar el aviso de copyright para las partes más complejas del proceso del verificar que el compilador y la versión sean desensamblaje. Puede incluso ser posible para las correctas. Una sola versión del ciertos decompiladores, el decompilar decompilador puede ampliarse a múltiples automáticamente una fracción grande de versiones del mismo compilador usando los programas de código máquina provenientes del archivos que contengan los datos críticos para mundo real, no sólo ejercicios académicos. cada uno. Sea como fuere, esta traducción se alcanza Se debe examinar el ejecutable para depurar generalmente en varias fases en las cuales un información y conservar esa información, lenguaje de alto nivel primero se traduce a una posiblemente en un archivo temporal. Esto representación intermedia. La selección de la puede ayudar a detectar librerías y recuperar instrucción entonces traduce la representación nombres de funciones y variables. Se puede intermedia a instrucciones de máquina. El proceso construir una lista ligada que permita observar de la compilación generalmente también implica qué partes del programa se conocen un número de pasadas al código fuente. Por (completamente o parcialmente), y por lo ejemplo, normalizaciones al programa, tales como tanto, cuáles no se conocen. Cada nodo debe poner las instrucciones en forma canónica, tener el tipo de memoria (código/dato), simplificaciones algebraicas y varias formas de nombre de la función, contenido si está optimización de programas. disponible y tipo de código (librería/usuario). Las técnicas de la decompilación fueron utilizadas 2. Como siguiente paso se debe determinar el inicialmente en los años 60 para ayudar en la punto de inicio del ejecutable, esto podría ser migración de programas de una plataforma a otra. el código de inicio de C que se traduciría como Desde entonces, se han utilizado para ayudar en la recuperación del código fuente perdido, en la int main (int argc, depuración (debugging) para eliminar errores de char *argv[],...) programas, en la localización de virus, compresión Más importante, el código de inicio indica dónde de programas, recuperación de vistas de alto nivel se han inicializado las variables y cuáles son sus –diagramas de análisis y diseño- de programas, y valores. Como no se puede conocer, inicialmente, más. el tamaño o tipo de tales variables, examinar estos datos puede ayudar a encontrar los tipos flotantes, los dobles, las cadenas de caracteres, y quizá los Aproximaciones a la enteros. Buscando por patrones de repetición de Decompilación entradas de tipo/tamaño, se podría (tentativamente) identificar arreglos de tipo estándar y de Existen dos formas distintas de atacar el proceso de estructuras. Se puede diferenciar entre arreglos y decompilación: una forma que pudiéramos llamar
  • 3. variables múltiples escalares en una línea 5. Concentrarse en una función a la vez, no examinando el código que las referencia. siguiendo las llamadas a funciones. El código de entrada a la función informa acerca de las 3. El siguiente paso es desensamblar, siguiendo variables automáticas, y el examen del código el flujo del programa principal, observando las puede revelar el tamaño y probablemente direcciones de las funciones llamadas y las también el tipo. El mismo tipo de análisis direcciones de inicio de las instrucciones aplicado al área de las variables inicializadas ejecutadas. Las áreas de confusión (donde un se puede utilizar para localizar estructuras. goto es dirigido a la mitad de una instrucción) pueden ser ensambladas en línea El segundo paso para una función es (y marcarlas como tal) siguiendo siempre la identificar llamadas a funciones y el código ejecución del programa. Se emite una que le pasa parámetros a la función. Para el advertencia cuando es probable que lo paso de valores que no son solamente realizado no sea correcto. Luego se puede ver operaciones de carga/descarga, se puede crear si las funciones invocadas corresponden a un nuevo nodo de código que contenga el cualquiera en la lista de información. código que genera el valor que es cargado. Entonces, se repite recursivamente para cada Para funciones donde los tipos de parámetros rutina llamada, revisando la lista de son conocidos, se puede ajustar la información información, y comparando el código con la acerca de las variables involucradas (por biblioteca para ese compilador. Cualquier ejemplo, identificar una variable de archivo módulo de bilbioteca conocido puede “FILE *”). Esta es una forma de eliminarse previa consideración, excepto concordancia de patrones, pero debe realizarse cuando una función de biblioteca llama a otras antes de avanzar para que sea más fácil. funciones; entonces, se puede también El paso tres es la identificación de plantillas, eliminar las funciones llamadas, etc. para cualquiera de las plantillas que se han Conociendo qué es lo que hacen las diferentes determinado para el compilador. Conforme se interrupciones (INT), se pueden delimitar van identificando los segmentos de código y el áreas que sean de datos. Cuando este proceso código fuente, se crea un nodo de código, el se haya realizado (y éste involucra varios cuál agrega el código fuente generado al pasos), se desarrolla un enorme árbol de binario. La identificación de plantillas debe información acerca del ejecutable, y se puede realizarse recursivamente para identificar producir un archivo de lenguaje ensamblador ciclos anidados, etc. El código ajustado para intermedio que tenga la biblioteca descubierta los ciclos los identifica como: for, while ó (incluso si no había información depurada), variables estáticas inicializadas identificadas do, si no el ciclo no interesa y puede elegirse junto con sus valores iniciales. de forma arbitraria. Esto conducirá a identificar sentencias switch, case, if y 4. Una vez que se tiene la versión llamadas de funciones, convertidas en código desensamblada, junto con nuestra lista de en línea por las instrucciones del información, se puede comenzar el proceso preprocesador “#pragma inline”. Cuando real de decompilación. Se puede usar una lista se encuentra código controlado por #pragma, ligada de ramificación; cada nodo indica el debe colocarse el enunciado pragma en el nombre de la función (determinada de la código fuente; La localización actual depende información depurada o sintetizada), y un de que si el pragma puede utilizarse como un apuntador a la lista de código para esa función. bloque de código local o para el archivo entero El nodo de la lista de código indica el tipo de (y hay un #pragma correspondiente que lo código contenido (fuente, binario, o ensamblado en línea), un apuntador a memoria desactiva). que contiene el fuente del ASCII, y un El paso cuatro busca el código en línea que apuntador a un binario no desensamblado. tiene una representación directa del fuente y Como el código es desensamblado, las agrega el código fuente al nodo de código. Ese entradas en la lista de código se dividen y se es el código que reúne una o más variables, las mezclan; cuando la función completa es modifica, y pone uno o más valores de retorno. desensamblada, hay una única entrada en la Se debe poner atención a las fuentes de lista de código. El binario se retiene hasta que variables de registro (desde la lista de la función ha sido desensamblada automáticas) de manera que el fuente pueda completamente. identificar correctamente la variable original. El condicional en las instrucciones if puede determinarse en este punto, ya que ahora es
  • 4. posible identificar el código (inmediato) de la equivalencias. Se generan problemas cuando en el comparación. código original se fuerza el cambio de tipo (casting). También los tipos enumerados y otros 6. Cualquier cosa que se haya dejado en este tipos que son equivalentes a los tipos estándares, punto probablemente puede convertirse en no pueden recuperarse. código en línea del ensamblador. En cualquier caso, ésta es la última posición para retornar. La detección de los tipos arreglos y tipos apuntador no es difícil: los tipos estructurados, especialmente cuando se utilizan uniones, generan problemas Decompilación Independiente extra. La segunda aproximación se caracteriza por analizar la semántica del ejecutable, y de este análisis se deriva un archivo fuente equivalente, sin Reglas de Traducción de hacer uso de información acerca de qué compilador Código para Lenguajes de fue utilizado para generar el ejecutable. El código fuente generado no puede asemejarse en todo al Alto Nivel original. La ventaja es que el método trabaja para cualquier compilador que pudiera haberse El ensamblador es, a fin de cuentas, un lenguaje de utilizado. programación como cualquier otro. Teóricamente, si se cuenta con la definición de la gramática del 1. Se asume que se inicia con la salida de mismo, pudiera construirse un parser que ensamblador del desensamblador, que también reconociera el código fuente, lo estructurase en contiene las etiquetas propias. Se podría leer el forma de un árbol de análisis sintáctico, y archivo entero, dentro de la memoria, y posteriormente recorrer dicho árbol para generar agrupar instrucciones en bloques, basados en código en C o algún otro lenguaje de alto nivel. las etiquetas. En la práctica, para poder realizar la ingeniería 2. Decodificar cada instrucción en más inversa de código de Ensamblador, se deben hallar operaciones primitivas, para así evitar todos las estructuras de control básicas presentes en el los diferentes modos de direccionamiento. La código fuente de ensamblador, y traducirlas a un mayoría de las instrucciones sólo se modifican lenguaje de mayor nivel. El problema es que la en registros o direcciones de memoria. gramática de ningún ensamblador toma en 3. A partir de esto, debe ser posible determinar cuenta dichas estructuras de control. Esto se qué registros se leen y escriben por cada debe a que la mayor parte de las instrucciones del bloque (asumiendo que las direcciones de ensamblador se mapean directamente a memoria se refieren a variables C). Basado en instrucciones de la capa ISA, y por ello la ello se puede hacer un análisis de flujo, para estructura gramatical del lenguaje ensamblador encontrar si hay algún registro de variables tiende a tomar la forma de un conjunto de líneas, detectadas (mapped). cada una de las cuáles está compuesta por una etiqueta opcional (para identificarla en caso de 4. Si esto se ha llevado a cabo, se puede saltos o llamados a subrrutinas), una instrucción de determinar cuáles son las expresiones que son ensamblador directamente traducible a lenguaje calculadas por las instrucciones para cada máquina, y un comentario opcional. almacén a una variable (también las basadas en registros o memoria). Se necesitara la Bajo estas circunstancias, es responsabilidad normalización de esta expresión. completa del programador de lenguaje Ensamblador, instrumentar las estructuras de 5. Ahora, se tiene que reconocer las instrucciones control secuenciales, selectivas y repetitivas de alto nivel que resultaron en la estructura del mediante combinaciones más o menos extensas de bloque, y los saltos entre ellos. Desde aquí es instrucciones de este lenguaje, usando para ello posible empezar a generar el código que tantas líneas de código como sea necesario♦. La solamente tratará operaciones con bytes, problemática estriba en traducir un código que no words, longwords y flotantes (doubles). necesariamente se pensó y programó con buenas Para la reconstrucción de tipos, primero se técnicas estructuradas ni modulares, a un código necesitan técnicas de resolución de tipos, siempre y cuando los parámetros sean pasados en llamadas a  En ello estriba la dificultad de aprender a funciones, o cuando se asignan de uno a otro. La programar en lenguaje Ensamblador: la creación de manera de hacer esto es asignar un tipo único a las estructuras de control es responsabilidad del cada variable y parámetro, y derivar reglas de programador, y no del compilador.
  • 5. estructurado y modular en un lenguaje de alto • Bertelsons, Boris y Mathias Rasch. PC al nivel. Límite, Programación Avanzada. Ed. A diferencia del Ensamblador, un programa en Computec-Marcombo. ISBN 970-15-0085-7. lenguaje C puede construirse utilizando • Cifuentes, Cristina. Reverse Compilation cualquier combinación sintácticamente correcta Techniques. Tesis Doctoral, Australia, 1994. de estructuras for, while, do..while, if, switch, etc. La gramática de C cuenta con clases • Godfrey, J. Terry. Lenguaje Ensamblador sintácticas para describir y reconocer cada una de para Microcomputadoras IBM. Ed. ellas, y crear el respectivo árbol de análisis Prentice-Hall. ISBN 968-880-204-2. sintáctico para después traducirlas a Ensamblador o • Lemone, Karen A. Fundamentos de a lenguaje máquina. Compiladores. Ed. CECSA. ISBN 968-26- Y aquí es donde se encuentra el reto del estado del 1297-7. arte de esta disciplina: • O’Gorman, John. Systematic Decompilation. Describir gramáticas que sean un Tesis Doctoral. Irlanda, 1991. superconjunto de las gramáticas habituales de los lenguajes ensambladores, pero que agrupen • Pratt, Terrence y Marvin V. Zelkowitz. las instrucciones de éstos con el objeto de Lenguajes de Programación, Diseño e reconocer estructuras de control secuenciales, Implementación. Ed. Prentice-Hall. ISBN 0- selectivas y repetitivas, llamados a funciones y 13-678012-1. procedimientos, operaciones aritméticas y • Teufel, Schmidt y Teufel. Compiladores, lógicas, todo ello traducible posteriormente a un Conceptos Fundamentales. Ed. Addison- lenguaje de alto nivel. Wesley Iberoamericana. ISBN 0-201-65365-6. Para ello, es necesario revisar la relación entre • Tischer, Michael y Bruno Jennrich. PC instrucciones Ensamblador y estructuras de control Interno, Programación de Sistemas. Ed. de alto nivel, con el afán de definir reglas de Computec-Marcombo. ISBN 970-15-0079-2. reconocimiento de patrones estructurales, que posteriormente puedan usarse para reconocer tales patrones y traducir el código a lenguaje de alto nivel. Conclusiones Se necesita formalizar la enseñanza de la ingeniería inversa en las aulas de las Facultades e Institutos de México, puesto que las empresas líderes interna- cionales emplean a discreción tales técnicas, y no conocerlas nos pone en desventaja. Cuanto mayor sea el número de profesionales que conozcan y apliquen la ingeniería inversa de códi- go fuente, tanto mayor será nuestro conocimiento respecto a las reglas de reconocimiento de patrones estructurales que es necesario aplicar, en distintos tipos de compiladores. Bibliografía • Abel, Peter. Lenguaje Ensamblador y Programación para PC IBM y Compatibles. Ed. Pearson. ISBN 968-880-708-7. • Aho, Sethi y Ullman. Compiladores, Principios, Técnicas y Herramientas. Ed. Pearson. ISBN 968-444-333-1.