scieee AI-readable full text Open interactive document viewer

Generación de diagramas de flujo a partir de código objeto en ELF

López Cuesta, Mario

Abstract

Grado en Ingeniería Informática

Full text

Escuela de Ingeniería Informática TRABAJO FIN DE GRADO Grado en Ingeniería Informática Mención en Tecnología de la Informacíon Generación de Diagramas de Flujo a partir de código objeto en elf Autor: Mario López Cuesta Escuela de Ingeniería Informática TRABAJO FIN DE GRADO Grado en Ingeniería Informática Mención en Tecnología de la Informacíon Generación de Diagramas de Flujo a partir de código objeto en elf Autor: Mario López Cuesta Tutor: Valentín Cardeñoso Payo Agradecimientos Quiero agradecer a toda mi familia y en especial a mis padres por el apoyo, paciencia y comprensión durante todos mis años de educación. A mi pareja que me ha apoyado y animado siempre a continuar. A todos mis ami gos de la escuela con los que he pasado tantos ratos y siempre estaban dispuestos a echar una mano. A mi tutor Valentín Cardeñoso que me ha apoyado, ayudado y aconsejado desde que plantee la idea del proyecto hasta su finalización. Por lo ultimo, gracias a todos los profesores que me han formado durante mi etapa académica en la escuela de Ingeniería Informática. Resumen Este trabajo de fin de grado presenta el desarrollo de un aplicación de terminal destinada a procesar códi go objeto en formato elf con la finalidad de transformarlo en una representación abstracta para ayudar a su entendimiento. Para la representación del fichero elf se utilizarán gráficos de control de flujo del fichero eje cutable. Para facilitar la representación del código se ha analizado la estructura y composición de el formato elf. Para el desarrollo del programa se ha utilizado el lenguaje de programación RUST, se ha escogido este lenguaje debido a que es un lenguaje relativamente nuevo, desarrollado en gran parte gracias a la comunidad y proporciona un enfoque multiparadigma (programación funcional pura, por procedimientos, imperativa…) además de definirse como seguro y eficiente. Abstract This endofdegree project presents the development of a terminal application intended to process object code in elf format in order to transform it into an abstract representation to aid understanding. For the repre sentation of the elf file, flow control charts of the executable file will be used. To facilitate the representation of the code, the structure and composition of the elf format have been analyzed. The RUST programming language has been used to develop the program, this language has been chosen because it is a relatively new language developed by the community and provides a multiparadigm approach (pure functional programming, procedural, imperative …) in addition to being defined as safe and efficient programming language Índice general Índice de cuadros vi Índice de figuras vii I Objeto, Concepto y Método 1 1. Introducción 3 1.1. Motivación ............................................ 3 1.2. Estructuradelamemoria..................................... 4 2. Objetivos y Alcance 5 2.1. Objetivos ............................................. 5 2.1.1. Objetivo.......................................... 5 2.2. Alcance .............................................. 5 3. Metodología 7 3.1. Fasesycostes........................................... 7 3.2. GestióndeRiesgos ........................................ 8 3.3. Plantemporal........................................... 11 II Marco Conceptual y Contexto 13 4. Marco Conceptual 15 4.1. elf ................................................. 15 4.1.1. Estructura elf ....................................... 15 4.1.2. SeccionesComunes ................................... 16 4.1.3. Seccionesanalizadas................................... 18 4.2. Lenguaje de programación: RUST ................................ 18 4.2.1. Lenguaje RUST ...................................... 18 4.2.2. Historia de RUST ..................................... 18 4.2.3. Seguridad de memoria RUST ............................... 18 4.2.4. Selección de Lenguaje RUST ............................... 19 4.3. Conceptos............................................. 20 4.3.1. TiposdeCódigo ..................................... 20 4.3.2. Desensamblado y Decompilado de código . . . . . . . . . . . . . . . . . . . . . . . . 22 4.3.3. BloquesBásicos ..................................... 23 4.3.4. Formato de Datos de Depuración . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23 4.3.5. Tipos de gráficos de representación de un programa . . . . . . . . . . . . . . . . . . 24 i viii Parte I Objeto, Concepto y Método 1 Capítulo 1 Introducción Quedan ya muy lejos los tiempos en que las máquinas se programaban en lenguaje máquina o, incluso, en lenguaje ensamblador. En la actualidad, hay una inmensa variedad de lenguajes de programación de alto ni vel que requieren del uso de compiladores o intérpretes que se encarguen de trasladas a lenguaje máquina el código expresado en un lenguaje más cercano al que los programadores usan para comprender y formular sus soluciones. El proceso de compilación incluye, habitualmente, fases de optimización y generación de código que hacen que el código objeto final resultante se ajuste a unos flujos de ejecución del código que no coinciden exactamente con los que se expresan en el código fuente. En el ámbito académico como a nivel profesional, tanto desarrolladores como analistas están interesados y buscan soluciones para saber que es lo que de verdad se esta ejecutando en el procesador, ya sea para optimizar y hacer mas seguro el código propio, para comprender mejor el código de terceros o para analizar posibles piezas de malware, sin tener que leer código maquina directamente del fichero resultado de una compilación. Estos ficheros contienen lenguaje ensamblador que es un lenguaje intermedio difícil de interpretar para los humanos. De esta carencia surge la necesidad de las interpretaciones abstractas del código. Partiendo de ficheros que contienen código objeto de programas en formato elf con arquitectura de 64 bits, se quiere elaborar el grafo de ejecución o flujo asociado a cada programa de forma automática. Para ello se quie re diseñar, construir y probar una herramienta que permita a través del análisis estático del fichero producirá el desensamblado de código objeto elf con el que se generara una representación abstracta de la misma basada en grafos de los posibles flujos de ejecución que el programa puede tener. Solo será capaz de mostrar los posibles caminos, ya que flujo de ejecución solo es mostrado en tiempo de ejecución y depende de la entrada que el programa reciba. Esta herramienta facilitará el análisis de programas, tanto a nivel de detección de errores o fragmentos optimízales, cuando no se disponga del código fuente. 1.1 Motivación El código fuente del software que usamos a diario no siempre está disponible. Un desensamblador como IDA Pro es capaz de crear mapas de su ejecución para mostrar las instrucciones binarias que realmente ejecuta el procesador en una representación simbólica llamada lenguaje ensamblador. Este proceso de desmontaje per mite a los especialistas en software analizar los programas que se sospecha son de naturaleza nefasta, como el software espía o el malware. Sin embargo, el lenguaje ensamblador es difícil de leer y entender. Es por eso por lo que se han implementado técnicas avanzadas en IDA Pro para hacer que ese código complejo sea más legible. 3 1.2. ESTRUCTURA DE LA MEMORIA CAPÍTULO 1. INTRODUCCIÓN En algunos casos, es posible revertir el programa binario, a un nivel bastante cercano, al código fuente original que lo produjo. El mapa del código del programa se puede procesar posteriormente para una investigación más detallada. 1.2 Estructura de la memoria La memoria ha sido estructurada en tres partes divididas en capítulos, que estos entre las tres partes hacen un total de 10 capítulos, una sección de anexos y para concluir se encuentra la sección de bibliográfica. La Parte I denominada Objetivo, Concepto y Método con los capítulos 1 Introducción, 2 Objetivos y Alcan ce y3Metodología. En esta parte se presenta la idea del proyecto en general, describiendo la motivación, los objetivos a cumplir en el proyecto y el alcance que tendrá, también se describen los plazos en los que va a ser desarrollada, enunciando las distintas etapas y entregables que tendrá, y la metodología que va a ser seguida en la gestión del proyecto. La Parte II denominada Marco Conceptual y Contexto consta solo de dos capítulos 4 Marco Conceptual y 5Soluciones Existentes. En esta segunda parte se pone en contexto la idea del proyecto, describiendo los temas que van a ser tratado y conceptos teóricos que influyen en el desarrollo del proyecto. También se describen herramientas y proyectos similares o que tratan los mismos temas y que ya están disponibles para el publico general. La Parte III denominada Desarrollo del Sistema con los capítulos 6 Análisis, 7 Diseño, 8 Implementación, 9Pruebas, 10 Conclusiones. Esta es la parte mas extensa y es la que describe desde el principio hasta el final el proceso de creación de la herramienta. La sección de anexos cuenta con la descripción de algunas estructuras de código usadas, manuales de uso e instalación y cierto código utilizado para la realización de la memoria que no esta en el proyecto final o que es interesante de describir en la memoria. 4 Capítulo 2 Objetivos y Alcance 2.1 Objetivos En este capítulo se va a tratar los objetivos definidos para el TFG y especificar el trabajo necesario a reali zar,constituirá la sección alcance del proyecto. 2.1.1 Objetivo El objetivo principal de este TFG es el desarrollo de una aplicación de terminal para sistemas GNU/Linux encargada de procesar ficheros binarios tipo elf y generar a partir de ellos el gráfico de control de flujo de ejecución del fichero. Siendo este el objetivo final, para llegar a conseguirlo se definen los siguientes objetivos: Estudiar y analizar el funcionamiento de las herramientas existentes similares. Diseñar e implementar herramienta. Programa encargado de procesado del fichero y la generación de la representación gráfica. Diseñar plan de pruebas para determinar la validez de la herramienta. Comparar resultados con los de herramientas similares. Realizar manual de uso e instalación de la herramienta. Realizar estudio de la estructura del fichero elf e identificar partes necesarias para la realización del proyecto. Búsqueda de información de posibles formatos de salida y herramientas para representar grafos. 2.2 Alcance Con el fin de definir el alcance del proyecto es necesario especificar el trabajo que ha de ser realizado en el proyecto. Pera definir el trabajo necesario para llevar a cabo los objetivos redactados en la Sección 2 se han identificado las siguientes fases las cuales se han dividido en tareas a realizar. 1. Análisis y descripción 5 2.2. ALCANCE CAPÍTULO 2. OBJETIVOS Y ALCANCE 1.1. Planteamiento de la idea principal 1.2. Visión general del proyecto y selección de las tecnologías a usar. 1.3. Búsqueda de soluciones similares existentes 1.4. Planificación de fases, iteraciones y riesgos. 1.5. Familiarización con tecnologías y herramientas. 2. Planteamiento y descripción de los entregables 2.1. Licitación de requisitos 2.2. Búsqueda y descripción de casos de uso 2.3. Modelo de dominio análisis 2.4. Planteamiento y diseño de la arquitectura 2.5. Modelo dominio diseño 2.6. Diagrama de secuencia de casos de uso críticos 3. Desarrollo de entregables 3.1. Apertura y análisis del fichero 3.2. Desensamblado a partir de código crudo 3.3. División de las instrucciones en bloques básicos 3.4. Construcción de grafo a partir de bloques básicos 3.5. Construcción de gráfico visual a partir del grafo 4. Revisión 4.1. Prueba ante stackholders 4.2. Análisis de posibles mejoras y correcciones 4.3. Implementación de correcciones y mejoras 4.4. Elaboración de manual de usuario 6 Capítulo 3 Metodología Todo trabajo requiere un método y para este TFG se va a utilizar la metodología agile, en concreto SCRUM. El uso de SCRUM implica la asignación de los roles dentro de las personas encargadas dentro del proyecto, de bido a que es un proyecto individual una sola persona ejercerá todos los roles. Y las reuniones periódicas serán las realizadas con el profesor responsable del TFG. Junto con SCRUM se va a utilizar el desarrollo incremental iterativo, en el cual en cada iteración se ira añadiendo funcionalidades y documentación al proyecto. Las fases van a ser divididas en “pequeños proyectos”llevados a cabo en las iteraciones, los cuales tendrán las etapas: planificación, desarrollo, pruebas y documentación. Estos proyectos ampliarán la funcionalidad y corregirán errores de versión anteriores. 3.1 Fases y costes El desarrollo del TFG se ha dividido en 4 fases. En cada fase se elaborarán distintas partes del proyecto. Las fases su duración el tema que será tratado esta descrito en los Cuadros 3.1, 3.2, 3.3 y 3.4. Fase 01: Análisis y descripción inicial del proyecto Duración: 1 semana En esta fase se expondrá el planteamiento de la idea principal, junto con la visión general del proyecto, se escogerán las tecnologías a utilizar. También se buscará informático sobre el contexto y se familiarizará con las herramientas y tecnologías a usar. Cuadro 3.1: Fase 01 Fase 02: Planteamiento y descripción de los entregables Duración: 2 semana En esta fase se realizará el análisis del proyecto. Se realizará la investigación y especificación del problema y la definición de componentes. Cuadro 3.2: Fase 02 7 3.2. GESTIÓN DE RIESGOS CAPÍTULO 3. METODOLOGÍA Fase 03: Desarrollo de entregables Duración: 3 semana En esta fase se realizará el desarrollo del software basándose en la descripción realizada en la fase de análisis del proyecto junto con las pruebas. Cuadro 3.3: Fase 03 Fase 04: Revisión Duración: 1 semana Revisión de la memoria y verificación de la herramienta desarrollada. También se incluyen solucionar los posibles errores contenidos en el código y el desarrollo del manual de uso. Cuadro 3.4: Fase 04 3.2 Gestión de Riesgos En el desarrollo de cualquier proyecto pueden surgir contratiempos que retrasen o modifiquen el proyecto. Para evitar que el efecto de estos en el proyecto sea grave y disminuir su impacto se realiza un análisis previo de los obstáculos que pueden ser encontrado y su posible impacto en el proyecto. Junto con cada riesgo se des criben los planes de reducción de impacto y de contingencia. En las tablas 3.5 a la 3.13 se describen los riesgos que se han tenido en cuenta a la hora de desarrollar el proyecto. En la Figura 3.1 se muestra la relación entre la probabilidad de ocurrencia y las consecuencias sobre el proyecto. Figura 3.1: Matriz Consecuencias - Probabilidad Identificador R-01 Riesgo Periodización inadecuada de tareas Descripción Dar prioridad alta a tareas que no lo son llevando a gastar mas tiempo del necesario en tareas de baja prioridad Probabilidad Media Plan Reajustar tiempos de tareas menos importantes, utilizar el colchón de tiempo. Impacto Medio Cuadro 3.5: Riesgo 01 8 CAPÍTULO 3. METODOLOGÍA 3.2. GESTIÓN DE RIESGOS Identificador R-02 Riesgo Estimaciones erróneas Descripción Calculo erróneo del tiempo de desarrollo de una tarea. Lo que puede llevar a retrasar el resto de las tareas Probabilidad Alta Plan Priorizar tareas importantes y reajustar tiempos. Utilizar colchón de tiempo Impacto Muy Alto Cuadro 3.6: Riesgo 02 Identificador R-03 Riesgo Rediseño de alguno de los componentes o Cambios en Requisitos Descripción Cambio de los requisitos o funcionalidad de algún componente que lleve al rediseño de este Probabilidad Medio Plan Priorizar tareas, reajustar tiempos y volver a estructurar las tareas en el calendario Impacto Alto Cuadro 3.7: Riesgo 03 Identificador R-04 Riesgo Posibles tareas no planificadas. Descripción Aparición de tareas que no estaban previstas en la planificación del proyecto Probabilidad Media Plan Reajustar dependencias de tareas y estructurar de nuevo el tiempo que se va a dedicar a cada tarea Impacto Alto Cuadro 3.8: Riesgo 04 Identificador R-05 Riesgo Desarrollo de funciones software erróneas. Descripción Desarrollo de funcionalidades que no van a ser incorporadas o que no se ajustan al resultado deseado Probabilidad Baja Plan Priorizar tareas importantes y reajustar tiempos. Utilizar colchón de tiempo Impacto Medio Cuadro 3.9: Riesgo 05 9 4.1. ELF CAPÍTULO 4. MARCO CONCEPTUAL La estructura de los ficheros elf esta descrita en la figura 4.1 y consta de: Cabecero elf (elf Header): definido en la sección apéndices B.1. Este situado al principio del fichero y describe la organización de los datos dentro del fichero. Secciones (Sections): contienen los datos del fichero objeto: instrucciones, tabla de símbolos, información de reubicación, etc. Tabla de cabeceros del programa (Program Header Table): dice al sistema como se crea la imagen del proceso. Los ficheros usados para construir imágenes de proceso deben contenerla. Tabla de cabeceros de sección(Section Header Table): contiene la información que describe las secciones del fichero. Todas las secciones tienen una entrada en la tabla. Cada entrada contiene información como el nombre, tamaño , ubicación, etc. Figura 4.1: Estructura Archivo elf [23] Nombre Valor Significado ET_NONE 0x0 Sin tipo de archivo ET_REL 0x1 Archivo reubicable (Relocatable) ET_EXEC 0x2 Archivo ejecutable ET_DYN 0x3 Archivo de objeto compartido (Shared object) ET_CORE 0x4 Archivo core ET_LOOS 0xfe00 Específico del sistema operativo ET_HIOS 0xfeff Específico del sistema operativo ET_LOPROC 0xff00 Específico del procesador ET_HIPROC 0xffff Específico del procesador Cuadro 4.1: Valores e-type elf 4.1.2 Secciones Comunes En esta sección se describen las secciones comunes, que normalmente están incluidas en los ficheros ejecu tables elf [27]: 16 CAPÍTULO 4. MARCO CONCEPTUAL 4.1. ELF .bss(Block Started by Symbol): Esta sección contiene datos no inicializados que contribuyen a la imagen de la memoria del programa. Por definición, el sistema inicializa los datos con ceros cuando el programa comienza a ejecutarse. .comment Esta sección contiene información de control de versiones. .data y.data1: Estas secciones contiene datos inicializados que contribuyen a crear la imagen del pro grama en memoria. .debug: Esta sección contiene información para la depuración simbólica(Symbolic Debuggin). .dynamic:Esta sección contiene información sobre el enlazado dinámico(Dynamic Linkin) .dynstr: Esta sección contiene las cadenas necesarias para la vinculación dinámica, más comúnmente las cadenas que representan los nombres asociados con las entradas de la tabla de símbolos. .dynsym: Esta sección contiene la tabla de símbolos de enlace dinámico(Dynamic Linking Symbol Table). .fini: Esta sección contiene instrucciones ejecutables que contribuyen al código de terminación del pro ceso. Cuando un programa sale normalmente, el sistema se encarga de ejecutar el código de esta sección. .got: Esta sección contiene la tabla de compensación global. .hash: Esta sección contiene una tabla hash de símbolos. .init: Esta sección contiene instrucciones ejecutables que contribuyen al código de inicialización del pro ceso. Cuando un programa comienza a ejecutarse, el sistema se encarga de ejecutar el código de esta sección antes de llamar al punto de entrada del programa principal. .interp: Esta sección contiene el nombre de la ruta de un intérprete de programa. .line: Esta sección contiene información sobre el número de línea para la depuración simbólica, que des cribe la correspondencia entre la fuente del programa y el código de la máquina. .note: Esta sección contiene información en el formato de ”Sección de notas”. .plt: Esta sección contiene la tabla de vinculación de procedimientos. .relNAME y.relaNAME: Esta sección contiene información de reubicación como se describe a conti nuación. .rodata y.rodata1: Esta sección contiene datos de solo lectura que normalmente contribuyen a un seg mento que no se puede escribir en la imagen del proceso. .shstrtab: Esta sección contiene los nombres de las secciones. .strtab: Esta sección contiene cadenas, más comúnmente cadenas que representan los nombres asociados con las entradas de la tabla de símbolos. .symtab: Esta sección contiene una tabla de símbolos. .text: Esta sección contiene las instrucciones ejecutables del programa. .jcr: Esta sección contiene información sobre clases de Java que deben estar registradas .eh_frame: Esta sección contiene información usado por C++ 17 4.2. LENGUAJE DE PROGRAMACIÓN: RUST CAPÍTULO 4. MARCO CONCEPTUAL 4.1.3 Secciones analizadas De las secciones que suelen tener los archivos elf solo algunas contienen el código ejecutable. Las secciones que contienen el código son .init, .text y .finit, debido a que .init es el código de la secuencia de iniciación del proceso y .finit es el código que, en caso de ejecución correcta, cierra el proceso(secuencia de finalización) no son importantes a la hora de el flujo de ejecución del programa. Por lo que la principal sección a ser analizada es .data que contiene las instrucciones ejecutables del programa. 4.2 Lenguaje de programación: RUST 4.2.1 Lenguaje RUST RUST es un lenguaje de programación multiparadigma enfocado en el desempeño y en la seguridad, espe cialmente en la concurrencia segura. Proporciona uso seguro de memoria sin necesidad de utilizar un recolector de basura estas características están descritas en la Seccion 4.2.3. 4.2.2 Historia de RUST RUST [21] comenzó como un proyecto paralelo de Graydon Hoare, un empleado de Mozilla. En poco tiem po, Mozilla vio el potencial del nuevo lenguaje y comenzó a patrocinarlo, antes de revelarlo al mundo en 2010. A pesar de su relativa juventud, RUST ha aumentado constantemente en las filas de los lenguajes de pro gramación populares. De hecho, aunque ocupó el puesto 33 en julio de 2019, en julio de 2020 había subido al puesto 18 en el índice de la comunidad de programación TIOBE. De manera similar, según la Encuesta para desarrolladores de Stack Overflow, RUST ha sido el lenguaje ”más querido”desde 2016. 4.2.3 Seguridad de memoria RUST A menudo cuando se habla de programas se basa a seguridad de la memoria [11]. Esto significa que en cada ejecución que se realice no haya accesos invalidos a memoria. Dentro de la seguridad de memoria podemos encontrar distintos campos como: use after free, desreferencia de puntero nulo, uso de memoria no inicializada, double free obuffer overflow. Estas violaciones de memoria pueden ser usadas por atacantes para generar blo queos inesperados de servicio o para alterar el comportamiento del programa. Hay distintas formas de asegurar el uso seguro de la memoria, como los puntero inteligentes o los recolec tores de basura. Estas medidas, aunque efectivas pueden afectar al rendimiento del sistema. Para obtener un uso de la memoria seguro, aparte de ayudar al desempeño, RUST utiliza la propiedad (ownership). El sistema de propiedad de RUST logra seguridad de memoria minimizando los costes de rendimiento. Todo código escrito en RUST sigue ciertas normas de propiedad, que permiten al compilador administrarla sin incurrir en gastos mayores: 1. Cada valor tiene una variable, llamada propietario. 2. Solo puede haber un propietario a la vez. 3. Cuando el propietario se sale del alcance, el valor se eliminará. RUST libera la memoria asignada a esa variable 4. Los valores se pueden mover o tomar prestados entre variables. Estas reglas son aplicadas por una parte del compilador llamada verificador de préstamos. 18 CAPÍTULO 4. MARCO CONCEPTUAL 4.2. LENGUAJE DE PROGRAMACIÓN: RUST Cuando se saca un valor de una variable, el propietario anterior deja de ser válido. Si el programador intenta utilizar la variable no válida, el compilador rechazará el código. Esto se puede evitar creando una copia profunda de los datos o utilizando referencias. RUST no permite el uso de variables no inicializadas y punteros colgantes, lo que puede hacer que un pro grama haga referencia a datos no deseados. El compilador rastrea el alcance de las variables para garantizar que los “préstamos” sean válidos. Respecto a los buffer overflow, una de las mitigaciones mas simples es requerir una verificación de límites al acceder a los elementos, lo cual agrega una penalización de rendimiento en tiempo de ejecución. Para solucionar esto en RUST los buffer integrados en la biblioteca estándar requieren de verificación de límites para cualquier acceso aleatorio, también proporcionan API de iterador que pueden reducir el impacto de estas verificaciones de límites en múltiples accesos secuenciales. Estos garantiza que lecturas y escrituras fuera de los límites sean imposibles para estos buffers. Aprender sobre la seguridad de memoria y como el procesador “piensa” es muy útil a la hora de desarrollar código tanto seguro como eficiente. 4.2.4 Selección de Lenguaje RUST Uno de los principales objetivos del equipo de desarrollo de RUST es garantizar el uso seguro de la memo ria sin utilizar un recolector de basura [14]. Debido a que el uso inseguro de la memoria ha sido durante años un vector de entrada para atacantes en muchos lenguajes de programación. Esta gestión de memoria junto a al análisis estático del código incorporado en la base del lenguaje y el elaborado sistema de emisión de mensajes de error proporcionando consejos aplicables para solucionar los errores, hacen que los ejecutables y librerías escritas en RUST sea código de calidad, robusto y seguro. El análisis estático es parte del lenguaje mismo; lo que es un dolor de cabeza es convencerlo de aceptar código dudoso. Y así es como RUST mejora la calidad de tu software: por la fuerza, ja También es considerado una combinación de lenguajes de alto nivel y de bajo nivel, siendo así un lenguaje adecuado para la de sistemas, proporcionando un desempeño similar o superior a C en programas similares. RUST dispone de una gran biblioteca de librerías publicas que contiene código hecho y publicado por la comunidad en la que se encuentra numerables soluciones a problemas. Esta biblioteca se encuentra en crates.io, que en el momento de escribir este proyecto consta con mas de sesenta mil librerías publicadas. Cargo es el administrador de paquetes de RUST. Es una herramienta que permite a los paquetes de RUST declarar sus diversas dependencias y garantizar que siempre obtendrá una compilación repetible. Una de las principales ventajas de Cargo es que normaliza los comandos necesarios para la creación de programas y bibliotecas. El mismo comando puede ser usado para la compilación de distintos artefactos inde pendientemente de sus nombres. También evita la llamada directa a rustc, compilador de RUST que toma por entrada el código fuente del programa y produce como salida código binario ya sea como biblioteca o ejecutable, en su lugar se pueden hacer la llamada genérica a çargo build.el cual se ocupara de realizar la correcta llamada a rustc además de obtener automáticamente de un registro todas las decencias definidas anteriormente y de los arreglos necesarios para la compilación. 19 4.3. CONCEPTOS CAPÍTULO 4. MARCO CONCEPTUAL Para lograr los objetivos nombrados anteriormente Cargo hace cuatro cosas: 1. Introduce dos archivos de metadatos con varios bits de información del paquete. 2. Obtiene y crea las dependencias de su paquete. 3. Invoca rustc u otra herramienta de compilación con los parámetros correctos para compilar su paquete. 4. Introduce convenciones para facilitar el trabajo con paquetes de RUST. Es solo una ligera exageración decir que una vez que sepa cómo construir un proyecto basado en Cargo, sabrá cómo construirlos todos. 4.3 Conceptos 4.3.1 Tipos de Código Código Fuente (Source code): El código fuente [35] es cualquier conjunto de código, escrito en lenguaje legible para humanos, este lenguaje esta diseñado para facilitar su desarrollo a los programadores, quienes mediante declaraciones especifican las accionéis que debe realizar la computadora. El código fuente a menudo es transformado, mediante compiladores o ensambladores, en código objeto para ser ejecutado o también puede ser interpretado y ejecutado sin necesidad de compilarlo, dependiendo del tipo de lenguaje. Código Objeto (Object code): Código objeto [34] hace referencia al código es el objeto o resultado de un proceso de compilación. Es una secuencia de declaraciones o instrucciones en lenguaje de computadora, generalmente código maqui na o lenguaje intermedio. Es decir que provienen de la traducción de código fuente y se convierte en un fragmento del programa final y es específico de la plataforma de ejecución. Para que pueda ser utilizado el código objeto tiene que ser colocado en un archivo ejecutable, un archivo biblioteca o un archivo objeto. Los archivos objeto, a su vez, pueden ser vinculado para formar un archivo ejecutable o biblioteca. Un enlazador (linker) [47]es el programa encargado de juntar archivos de código objeto en un archivo ejecutable. Código intermedio, representación intermedia Estructura de dato o código que es utilizado internamente por un compilador o maquina virtual [32]co mo representación del código fuente, es utilizado por la mayoría de los compiladores modernos para la optimización, la traducción... Debe ser capaz de representar el código fuente sin perdida de información, independientemente de fuente o destino. El lenguaje intermedio [44] es un lenguaje de una máquina abstracta diseñada para ayudar a realizar el análisis de un programa, es usado por la mayoría de los compiladores modernos para la optimización, la 20 CAPÍTULO 4. MARCO CONCEPTUAL 4.3. CONCEPTOS traducción... El término proviene de la transformación en los compiladores donde transforman el código fuente en código intermedio para mejorarlo y posteriormente generar el código objeto o código maquina. Las di ferencias fundamentales con el código maquinan son que cada instrucción representa exactamente una operación fundamental y la información de la estructura de control puede no estar incluida en el juego de instrucciones. Bytecode: Bytecode [29], es un conjunto de instrucciones diseñado para una ejecución eficiente por parte de un in térprete de software. Es tratado como un archivo binario que contiene un programa ejecutable similar al código objeto. Es utilizado para reducir las dependencias respecto del hardware específico y facilitar la interpretación. También es utilizado como código intermedio en un compilador. Los programas que usan este tipo de código suelen ser interpretados por una maquina virtual (interprete de bytcode). El nombre del código de bytes proviene de conjuntos de instrucciones que tienen códigos de operación de un byte, entre 0 y 255, seguidos de parámetros opcionales. Los traductores dinámicos traducen el bytecode a código máquina inmediatamente antes de su ejecución para mejorar la velocidad de ejecución. La mayor ventaja que ofrece el bytecode [37] es su portabilidad: el mismo bytecode puede ser ejecuta do en diferentes plataformas y arquitecturas, siempre que consten de un interprete. El código de bytes a menudo se puede ejecutar directamente en una máquina virtual , o se puede compilar más en código de máquina para un mejor rendimiento. Lenguaje de máquina (Machine code): El lenguaje de máquina o código máquina [45] es el sistema de códigos directamente interpretable por la CPU. Lenguaje está compuesto por un conjunto de instrucciones que determinan acciones a ser tomadas por la máquina. Un programa consiste en una cadena de estas instrucciones más un conjunto de datos sobre el cual se trabaja. Estas instrucciones son normalmente ejecutadas en secuencia, pero el flujo del programa puede verse influenciado por instrucciones especiales de ”salto”que transfieren la ejecución a una dirección de memoria no secuencial. El código maquina es específico de la arquitectura de la máquina. Es un lenguaje de programación de bajo nivel [33], representación de nivel más bajo de un programa de computadora compilado o ensamblado, que se utiliza para controlar directamente la unidad central de procesamiento (CPU) de una computadora, es estrictamente numérico que está destinado a ejecutarse lo más rápido posible. Cada instrucción hace que la CPU realice una tarea muy específica. Lenguaje de programación primitivo y dependiente del hardware. Rara vez se escriben directamente en código de máquina en contextos modernos, debido a su complejidad y escasa legibilidad. El código fuente es traducido a código de máquina ejecutable por utilidades como compiladores, ensam bladores y enlazadores, con la excepción de los programas interpretados que no se traducen a código de máquina. 21 4.3. CONCEPTOS CAPÍTULO 4. MARCO CONCEPTUAL Código ensamblador (Assembly code): Lenguaje ensamblador (asm) [28], también denominado código máquina simbólico, es cualquier lenguaje de programación de bajo nivel en el que existe una correspondencia muy fuerte entre las instrucciones en el lenguaje y las instrucciones del código de máquina de la arquitectura, normalmente relación 1:1.Debido a esta dependencia cada lenguaje ensamblador está diseñado específicamente una arquitectura. El código de ensamblaje se convierte en código de máquina ejecutable mediante un programa de utilidad denominado ensamblador. 4.3.2 Desensamblado y Decompilado de código Un desensamblador difiere de un decompilador, en que este tiene como objetivo un lenguaje de alto nivel en vez de al lenguaje ensamblador. la salida de un desensamblador, el desensamblado, es a menudo formateada para la legibilidad humana en vez de ser adecuada para la entrada a un ensamblador, haciendo que este sea principalmente una herramienta de ingeniería inversa. Existen algunos programas como Ghidra oIDA que in cluyen ambas funcionalidades. Desensamblado, De código maquina a código ensamblador Desensamblado [40] describe la operación de tomar un archivo ejecutable y producir como salida un código en ensamblador referente a dicho archivo. El programa que lleva a cabo el proceso se denomina desensamblador. El código fuente en lenguaje ensamblador generalmente permite el uso de constantes y comentarios del programador. Estos son generalmente eliminados, por el ensamblador, del código ensamblado a código de má quina. De esta manera, durante el proceso de conversión de código ensamblador a código maquina conocido como ensamblado, se eliminan constantes y comentarios, debido a esto un desensamblador no puede rescatar los nombres de las variables o las funciones nombradas por el programador, comentarios ni código fuente. Algunos desensambladores hacen uso de la información de depuración simbólica presente en los archivos objeto tales como el elf, añadiendo constantes e información adicional. El desensamblado no es una ciencia exacta, es posible para un simple programa tener dos o más desen samblados razonables. Algunos de los desensambladores mas famosos son Interactive Disassembler (IDA), OllyDbg y radare2. Decompilación, de código ensamblador a código fuente Un decompilador [17] es un programa que recupera el código fuente de archivos ejecutables. Operación inversa a la compilación, Consiste en convertir el código máquina Código fuente. No hay decompiladores perfectamente operacionales [39], el éxito de la decompilación depende de la canti dad 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 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. 22 CAPÍTULO 4. MARCO CONCEPTUAL 4.3. CONCEPTOS 4.3.3 Bloques Básicos En el entorno de los compiladores, un bloque básico [36] es un conjunto de instrucciones lineales y sin ramificaciones. Los compiladores normalmente descomponen los programas en bloques básicos como primer paso en el proceso de análisis. Los bloques básicos forman los nodos en un gráfico de flujo de un programa. El código en un bloque básico tiene un punto de entrada, lo que significa que no hay código dentro de él es el destino de una instrucción de salto en cualquier parte del programa. Y un punto de salida, lo que significa que solo la última instrucción puede hacer que el programa comience a ejecutar código en un bloque básico diferente. Un bloque básico [9] es una secuencia de código en línea recta con un solo punto de entrada y solo una salida. En GCC, los bloques básicos se representan mediante el tipo de datos basic_block. El bloque básico [22] es una secuencia de código de línea recta que no tiene ramificaciones de entrada y salida excepto a la entrada y al final, respectivamente. El bloque básico es un conjunto de declaraciones que siempre se ejecutan una tras otra, en una secuencia. 4.3.4 Formato de Datos de Depuración El Formato de Datos de Depuración (Debugging data format) es una forma de almacenar información sobre un programa compilado para ser usada por los depuradores de alto nivel. Almacena información de variables, tipos, constantes, subrutinas, etc. para que pueda ser traducida al desensamblar un programa. La información es generada por el compilador y almacenada dentro del archivo ejecutable o biblioteca dinámica por el enlazador. A continuación, describimos algunos de los formatos de depuración mas comúnmente usados: DWARF [6]: Formato desarrollado junto a elf [38], pero independiente del mismo. Utiliza la estructura de datos DIE (Debugging Information Entry) formando una estructura en árbol. Un atributo DIE puede referirse a cual quier otro del árbol. Existen 2 tablas: Line Number Table, que asigna ubicaciones de código a ubicaciones de código fuente y viceversa, también especifica qué instrucciones son parte de prólogos de funciones y epílogos y la tabla Call Frame Information permite a los depuradores localizar frames en la pila de llamadas. Para ahorrar espacio, son representados como instrucciones bytecode para máquinas de estado finito simples STAB: El nombre proviene de las cadenas de la tabla de string [5], ya que los datos de depuración se guardaron originalmente como string en la tabla de símbolos del archivo de objeto a.out de Unix. Codifica las ins trucciones en strings. Ha evolucionado de formato simple a formato un de depuración bastante complejo y más que consistente. No esta estandarizado ni bien documentadas. Sun Microsystems ha realizado una serie de extensiones para las puñaladas. GCC ha realizado otras extensiones, mientras intentaba aplicar ingeniería inversa a las extensiones de Sun. COFF: 23 4.3. CONCEPTOS CAPÍTULO 4. MARCO CONCEPTUAL El formato COFF [5] (Common Object File Format) es usado en archivos ejecutables, código objeto y bibliotecas compartidas, usada en sistemas Unix, introducido en Unix System V y ampliamente usado antes de antes de ser reemplazado en gran medida por elf. Es la base para especificaciones extendidas como XCOFF y ECOFF. COFF y sus variantes siguen siendo usados en algunos sistemas Unixlike, en Microsoft Windows, en entornos EFI y en algunos sistemas embebidos. XCOFF: El formato de archivo de objeto común extendido (XCOFF) [12] es definido por IBM y utilizado en AIX. XCOFF combina COFF con el concepto de formato de módulo TOC proporcionando vinculación diná mica y reemplazo de unidades dentro de un archivo de objeto. Se utiliza una variación de XCOFF para archivos de objetos de 64 bits y archivos ejecutables. Es la definición formal de objeto de imagen de máquina y archivos ejecutables. El nombre predeterminado para un archivo ejecutable XCOFF es a.o. OMF [18]: Llamado módulo de objeto reubicable (OMF:Relocatable Object Module Format) [48] se utiliza princi palmente para software destinado a ejecutarse en microprocesadores Intel 80x86. Fue lanzada por Intel en 1981 bajo el nombre Object Module Format, y es el formato de archivo objeto más importante en DOS, Windows de 16 bits y OS / 2 de 16 y 32 bits. Define información de nombre público y número de línea para depuradores y también puede contener da tos de depuración en formato Microsoft CV, IBM PM o AIX. Pero solo proporciona soporte rudimentario para depuradores. 4.3.5 Tipos de gráficos de representación de un programa Gráficos de control de flujo (Control Flow Graph): Representación esquemática , en forma de grafo dirigido, de todos los caminos , representados como no dos, que pueden ser atravesados a través de un programa durante su ejecución. Las relaciones entre nodos representadas mediante arcos son los distintos caminos de ejecución que puede seguir el programa. Gráficos de llamadas ( Call Graph): Un gráfico de llamadas representa las relaciones de llamadas entre subrutinas en un programa de compu tadora. Cada nodo representa corresponde a una función o procedimiento y cada relación la llamada de un procedimiento a otro. Los ciclos dentro del grafo representas llamadas recursivas. Gráfico de dependencias (dependence graph): En matemáticas, informática y electrónica digital, un gráfico de dependencia es un grafo dirigido que representa las dependencias de varios objetos entre sí. 24 CAPÍTULO 4. MARCO CONCEPTUAL 4.3. CONCEPTOS Figura 4.2: Tipos de Gráficos de Representación del Flujo de un Programa 4.3.6 Diagramas de Control de Flujo Un diagrama de control de flujo [25] es un diagrama que describe un procesos, sistema o algoritmo infor mático mostrando todas las posibles vías de ejecución, pero sin mostrar cual será la vía que se ejecutara ni el orden en el que se encontraran. Son usados en diversos campos [19] para documentar, estudiar, planificar, etc. Comúnmente son usados para representar procesos complejos mediante diagramas claros y fáciles de comprender. Emplean diversas figuras geométricas (rectángulos, óvalos, diamantes ...) para representar el tipo de acción y flechas para unirlos y formar el flujo de ejecución. 4.3.7 Representación de Grafos de forma Gráfica (.dot) DOT [30] es un lenguaje descriptivo en texto plano usado para representación de gráficos . Los ficheros de DOT suelen usar la extensión .gv o .dot. Aunque .dot esta en desuso debido a también es usada por Microsoft Word para plantillas. Existen diversos programas para procesar DOT. Dependiendo si se quiere renderizan la descripción y gene ran gráficos (OmniGraffle, dot, neato, twopi, circo, fdp y sfdp) en diversos formatos de imagen, ejecutar cálcu los sobre los grafos (gvpr, gc, accyclic, ccomps, sccmap y tred) o proveer una interfaz para editarlos (GVedit, KGraphEditor, lefty, dotty o grappa). Aunque mayoría forman parte del paquete Graphviz o lo usan parcialmente para ejecutar cálculos. Graphviz [13] [42] es un programa de visualización de gráficos de código abierto. Describen descripciones de gráficos en un lenguaje de texto simple y crean diagramas en diversos formatos como SVG, PDF, Postscript, PNG, etc. o pueden mostrarlo en un navegador de gráficos interactivo. Incluye características útiles para diagra mas concretos, como opciones de colores, fuentes, diseños de nodos tabulares, estilos de línea, hipervínculos y formas personalizadas. Graphviz esta compuesto por un conjunto de herramientas y librerías que pueden generar o procesar archivos DOT. Entre las herramientas proporcionas se encuentran las siguientes: dot: Herramienta de línea de comandos para producir imágenes en capas de grafo dirigido. Esta es la herramienta predeterminada para usar si los bordes tienen direccionalidad. neato:Herramienta para usar si el gráfico no es grande y no sabe nada más al respecto. fdp: Motor de diseño para grafos. 25 Capítulo 6 Análisis En esta sección se expresa el sistema a desarrollar mediante los conceptos y requisitos encontrados durante el análisis de datos y el estudio previo realizado. El proyecto será descrito mediante la definición de los requi sitos, el modelo de dominio y enumerando los casos de uso. 6.1 Requisitos En esta sección se describen el análisis y especificación de requisitos, tanto los requisitos del usuario como las especificaciones del sistema y entorno en el que se utilizara la aplicación. La identificación de estos requi sitos será usada para definición de la arquitectura que será usada en el diseño del proyecto. 6.1.1 Requisitos Funcionales Los requisitos funcionales son aquellos que describe las funciones que debe cumplir el sistema, en los que cada función esta descrita por la entrada, comportamiento y salida del sistema. Para este proyecto se han encontrado y descrito los siguientes requisitos funcionales: RF  01: Aplicación de línea de comandos El sistema consistirá en una aplicación de línea de comandos. El software será llamado desde terminal con un comando y argumentos. RF  02: Entorno de compilación Linux El sistema contendrá las dependencias necesarias para ser compilado en sistemas Linux RF  03: Compilación con cargo El sistema estará configurado para que su compilación sea realizada con la herramienta cargo, propia del lenguaje RUST. RF  04: Ficheros binarios ELF 64 El sistema será capaz de procesar ficheros binarios ELF compilados para arquitectura 64bit. Estos fiche ros para procesar serán pasados como parámetro al llamar al ejecutable desde terminal. RF  05: Lenguaje DOT como salida La generación del gráfico de control de flujo será en lenguaje DOT, que contendrá el gráfico generado. 33 6.2. CASOS DE USO CAPÍTULO 6. ANÁLISIS RF  06: Formato de salida El sistema será capaz de devolver la salida como fichero o por la salida estándar, dependiendo de los parámetros de entrada. RF  07: Lenguaje de programación RUST El sistema estará escrito por completo en el lenguaje de programación RUST. RF  08: Ejecución sin parámetros El sistema al ser lanzado sin parámetros de entrada se mostrará un mensaje de ayuda al usuario con las opciones disponibles y el formato correcto de lanzar la ejecución del programa RF  09: Realizar desensamblado El sistema será capaz de desensamblar ficheros ELF y devolverlo. RF  10: Generación de comando Con la compilación del sistema será automáticamente insertado en la variable PATH para poder ser lan zado sin necesidad de acciones extra RF  11: Análisis estático El desensamblado y la obtención del grafo serán realizados mediante la ejecución de análisis estáticos del fichero. RF  12: Identificación de la arquitectura El sistema será capaz de identificar la arquitectura para la que esta compilado el ejecutable y comprobar si es una de las aceptadas. 6.1.2 Requisitos No Funcionales Los requisitos no funcionales describen características de funcionamiento con relación a la calidad del pro grama, como puede ser la seguridad o fiabilidad del sistema. En relación con esto se describen los siguientes requisitos no funcionales: RNF  01: Mensaje de ayuda Si la entrada contiene errores en los argumentos, el sistema no ejecutara nada y mostrara por la salida estándar un mensaje de ayuda para poder ejecutar el programa correctamente. RNF  02: Facilidad de uso El sistema debe ser fácil de usar y proporcionar ayuda al usuario para que realice su correcta ejecución. RNF  03: Manual de instalación Debe poseer una documentación de instalación. RNF  04: Funcionalidades documentadas Debe poseer una documentación de funciones que es capaz de realizar y como pueden ser realizadas. RNF  05: Cancelación de ejecución ante errores Si se detecta algún error durante la ejecución, esta se deberá detener y mostrar un mensaje que en la medida de lo posible ayude a identificar el error. 6.2 Casos de uso En esta sección se describen los casos de uso del proyecto. Los casos de uso describen las actividades que pueden ser llevadas a cabo, así como los actores que intervienen. 34 CAPÍTULO 6. ANÁLISIS 6.2. CASOS DE USO En el escenario del proyecto existe un único actor. El actor es denominado Usuario y es el encargado de interactúa con la terminal y el que indica los parámetros necesarios para que el programa sea ejecutado. Figura 6.1: Diagrama de Casos de Uso La Figura 6.1 describe la relación entre los actores y los casos de uso y estos han sido definidos en las Tablas 6.2, 6.1, 6.4 y 6.3. Debido a que los casos de uso Obtener desensamblado en fichero externo yGenerar Gráfico de control de flujo de ejecutable en fichero externo son muy similares a Obtener desensamblado salida estándar yGenerar Gráfico de control de flujo de ejecutable salida estándar, no se ha realizado la tabla que los describe. Caso de uso Obtener opciones Identificador CU-02 Actores Usuario Pre-condiciones Ninguna Post-condiciones Ninguna Descripción El usuario introduce el argumento destinado a generar la ayuda y el sistema imprime los posibles argumentos, su descripción y la forma de usarlos Secuencia 1El sistema es llamado por el usuario con el parámetro –help o -h 2El sistema comprueba los parámetros 2.1Si solo hay uno y es -h o –help continua el caso de uso 3El sistema imprime el mensaje de ayuda y el caso de uso finaliza. Excepciones 2.aEl sistema al comprobar los parámetros detecta el parámetro -h y mas parámetros, el sistema termina la ejecución, imprime la forma de llamar al programa y el caso de uso termina Cuadro 6.1: Caso de Uso CU-02 35 6.2. CASOS DE USO CAPÍTULO 6. ANÁLISIS Caso de uso Generar Gráfico de control de flujo Identificador CU-01 Actores Usuario Pre-condiciones El fichero a procesar es un fichero ELF de 64-bits Post-condiciones El sistema a generado un gráfico de control de flujo del fichero de entrada. Descripción Generar el gráfico de control de flujo de un ejecutable ELF de arquitectura 64bits y devolverlo en formato .dot por la salida estándar. Secuencia 1El sistema es llamado por el usuario con la ruta de un fichero ELF como único parámetro 2El sistema comprueba el resto de los parámetros y carga la configuración indicada por los mismos 3El sistema Comprueba si el argumento es un fichero 3.1Si el argumento es un fichero lo abre 4El sistema realiza un análisis estático y genera los bloques básicos y la matriz de adyacencia asociada. 5El sistema toma la lista de bloques básicos y la matriz de adyacencia y genera el grafo asociado. 6El sistema toma el grafo y lo traduce a lenguaje dot 7Imprime el resultado por la salida estándar (terminal) y el caso de uso finaliza. Excepciones 3.1.aEl sistema no es capaz de abrir el fichero, saca un mensaje de error con la descripción del error y el caso de uso finaliza Cuadro 6.2: Caso de Uso CU-01 Caso de uso Obtener información del fichero Identificador CU-04 Actores Usuario Pre-condiciones Ninguna Post-condiciones Ninguna Descripción El usuario introduce el argumento destinado a obtener información del fichero Secuencia 1El sistema es llamado por el usuario con los parámetros para obtener información del fichero 2El sistema comprueba los parámetros 2.1Si son correctos continua el caso de uso 3El sistema imprime el mensaje de ayuda y el caso de uso finaliza. Excepciones 2.aEl sistema imprime el error y la forma de llamar al programa y el caso de uso termina Cuadro 6.3: Caso de Uso CU-04 36 CAPÍTULO 6. ANÁLISIS 6.3. MODELO DE DOMINIO Caso de uso Generar desensamblado Identificador CU-03 Actores Usuario Pre-condiciones El fichero a procesar es un fichero ELF de 64-bits Post-condiciones El sistema a generado el desensamblado del fichero. Descripción Generar el desensamblado de un ejecutable ELF de arquitectura 64bits y en instrucciones asm en formato DAWRF por la salida estándar. Secuencia 1El sistema es llamado por el usuario con la ruta de un fichero ELF como único parámetro 2El sistema comprueba el resto de los parámetros y carga la configuración indicada por los mismos 3El sistema comprueba si el argumento es un fichero 3.1Si el argumento es un fichero lo abre 4El sistema realiza un análisis estático y genera el código desensamblado de manera secuencial 5Imprime el resultado por la salida estándar (terminal) y el caso de uso finaliza. Excepciones 3.1.aEl sistema no es capaz de abrir el fichero, saca un mensaje de error con la descripción del error y el caso de uso finaliza Cuadro 6.4: Caso de Uso CU-03 6.3 Modelo de dominio Representación conceptual de las clases candidatas que estén relacionadas con los requisitos descritos en el apartado 6.1. En el se muestran las clases y las relaciones existentes entre las mismas. El modelo de dominio se muestra en la Figura 6.2 y sus clases descritas a continuación: Path: Representa la ruta a un fichero objetivo. File: Describe el fichero ELF, esta compuesto por las secciones y el cabecero de programa. Section: Forma parte de File y representa las secciones dentro del fichero ELF, consta del cabecero de sección y el código en bruto de la sección. ProgramHeader: Forma parte de File y describe el cabecero de programa y sus partes. AdyacenceMatrix: Estructura usada para representar las relaciones entre los nodos del grafo a generar. BasicBlock: Estructura que representa un conjunto de instrucciones que se ejecutan de forma lineal sin saltos. Graph: Estructura usada para la representación del grafo a obtener dentro del proyecto. 37 6.3. MODELO DE DOMINIO CAPÍTULO 6. ANÁLISIS Decoder: Elemento encargado de realizar la decompilación. Figura 6.2: Modelo de Dominio 38 Capítulo 7 Diseño 7.1 Diseño En este capitulo se presenta el diseño planteado para el proyecto. Que consiste en establecer la arquitectura, las interfaces, los algoritmos y las estructuras de datos necesarias para el proyecto. Esto se consigue a través de la traducción de los requisitos. Para la descripción del diseño del proyecto se enuncia la arquitectura, que consiste en definición de alto nivel de la estructura del software, y los diagramas de secuencia y de flujo, que describirán el sistema y los algoritmos utilizados en la aplicación. 7.2 Arquitectura de la aplicación Para el desarrollo arquitectónico de la aplicación se utilizará el patrón de diseño MVC. Debido a que es una aplicación de línea de comandos la vista estará representada por la terminal utilizada. La función main de sempeñará la función de controlador y el modelo estará compuesto por las estructura y funciones utilizadas. La Figura 7.1 muestra una representación gráfica del patrón MVC con las conexiones usadas. Figura 7.1: Diagrama Flujo Comprobación Tipo de Instrucción de Bifurcación 39 7.3. DIAGRAMA DE FLUJO CAPÍTULO 7. DISEÑO Siguiendo el patrón controlador, la función main ejercerá como intermediario entre la terminal(vista) y las funciones(modelo). El controlador recibirá los parámetros de entrada y dependiendo de estos realizará las lla madas necesarias a las funciones para posteriormente, cuando las llamadas terminen, devolver el resultado a usuario por el método descrito en los parámetros de entrada. 7.3 Diagrama de Flujo En esta sección se incluyen los diagramas de flujo que representaran al sistema y algoritmos que describen el comportamiento del programa. Para este fin nos centramos en el diagrama de flujo general de la aplicación, que describirá el sistema, y el diagrama de flujo de la identificación de instrucciones de bifurcación, que es el algoritmo utilizado para identificar los bloques básico de código. Para la realización de estos diagramas se ha seguido el código de formas geométricas y colores indicado en la leyenda, que esta descrita en la Figura 7.2. Figura 7.2: Leyenda Diagramas de Flujo 7.3.1 Diagrama de flujo general En la Figura 7.3 se representa el diagrama de flujo de la aplicación, en el se describe el proceso de entrada, comprobación y carga de los parámetros de entrada, se indica el momento de apertura y procesamiento del fi chero, y la comprobación de la forma de devolución de datos expresada en los parámetros de entrada. 40 CAPÍTULO 7. DISEÑO 7.3. DIAGRAMA DE FLUJO Figura 7.3: Diagrama Flujo general 41 8.3. IMPLEMENTACIÓN CAPÍTULO 8. IMPLEMENTACIÓN 8.3.3 Renderizar Grafos Para la traducción de las estructuras creadas para representar grafos dentro del programa al lenguaje de dot se ha optado por la escritura de funciones propias en vez de utilizar librerías externas que pueden realizar dicho propósito como petgraph, debido a que tras probar su funcionamiento no encajan con la conversión que se bus caba, debido a que lo hacen de forma demasiado genérica para nuestra solución. Se ha construido una función a la cual se le pasan las opciones de representación de nodos deseadas y la representación del grafo usada en el programa, un vector de bloques básicos y su lista de adyacencia, imprime los nodos y sus relaciones en el lenguaje dot. Estructura del dígrafo en dot La estructura de código dot que se ha seguido para la representación de grafos se muestra en la Figura: 8.1 mediante un código de ejemplo. Figura 8.1: Código ejemplo de un grafo escrito en dot [1] Declaración del tipo de grafo, en nuestro caso como se representa el flujo mediante grafos dirigidos se utilizará la directiva ”digraph”seguida del nombre del grafo, en nuestro caso se utilizará el nombre del ejecutable a analizar. [2] Declaración de las propiedades generales de los nodos y aristas, para los nodos se usa la palabra node yedge para las aristas, seguidas de las variables y sus valores todo entre corchete. [3] Declaración de todos los nodos con su contenido. [4] Representación de las relaciones entre los nodos, a la izquierda el origen de la relación y a la derecha los nodos destino 8.3.4 Análisis estático de programas El análisis del fichero se ha realizado mediante análisis estático. Este tipo de análisis es el realizado sin la ejecución del programa. En nuestro caso se ha realizado un análisis automático del código objeto. En este proyecto el análisis estático consiste en la identificación de las secciones mediante los cabeceros de programa y de sección. Una vez identificadas las secciones y ubicadas dentro del fichero se extrae el código en bruto. El código en bruto consiste en una sucesión de bytes que representan las instrucciones, con estos bytes se identifican los opcode y con los opcode podemos identificar la instrucción y su tamaño. Una vez desensamblada la instrucción se mueve al byte siguiente a la instrucción y se vuelve a identificar el opcode. Esto se repite hasta terminar el código en bruto de la sección. 48 Capítulo 9 Pruebas Debido a que se esta utilizando la metodología agile SCRUM las pruebas se han realizado durante el desa rrollo de los distintos submódulos en los que se ha fragmentado el proyecto general. Para automatizar parte de las pruebas se ha escrito 2 fuzzer, que están descrito en la sección de Apéndices C.2, en el lenguaje de programa ción Python. Uno es un fuzzer simple que solo prueba el programa con distintas entradas validas, el segundo es un fuzzer algo mas complejo escrito para proporcionar entradas que pueden contener errores mediante arbitraria de bytes del fichero de entrada. 9.1 Casos de prueba A parte de las pruebas realizadas con los fuzzers anteriormente nombrados se han realizado las siguientes pruebas: 9.1.1 Casos de prueba ejecución correcta Identificador CP-01 Descripción Se pasará como argumento un fichero elf con arquitectura x64, como único argumento. Entrada Fichero elf x64 Salida Esperada Grafo, escrito en lenguaje dot, devuelto por la salida estándar que represente el flujo del fichero de entrada Cuadro 9.1: Caso de Prueba 01 49 9.1. CASOS DE PRUEBA CAPÍTULO 9. PRUEBAS Identificador CP-02 Descripción El programa será llamado sin ningún argumento Entrada Nada Salida Esperada Mensaje de ayuda para la llamada correcta del programa Cuadro 9.2: Caso de Prueba 02 Identificador CP-03 Descripción El programa será llamado con un único argumento que será –help o -h Entrada –help o -h Salida Esperada Argumentos aceptados por el programa y la forma correcta de invocar al programa con los argumentos Cuadro 9.3: Caso de Prueba 03 Identificador CP-04 Descripción Se invocará al programa con un fichero válido y el argumento –sections Entrada <Fichero elf x64>–sections o -S Salida Esperada Devuelve secciones de las que consta el programa programa Cuadro 9.4: Caso de Prueba 04 Identificador CP-05 Descripción Probamos la funcionalidad de obtención de la cabecera de una sección. Entrada <Fichero elf x64>–secciones_header=<sección>o -s=<sección> Salida Esperada Cabecera de la sección seleccionada(Section Header) Cuadro 9.5: Caso de Prueba 05 50 CAPÍTULO 9. PRUEBAS 9.1. CASOS DE PRUEBA Identificador CP-06 Descripción Se probará si es capaz de generar un fichero con la salida esperada Entrada Fichero elf x64 y una ruta y nombre de fichero de salida validos. <Fichero elf x64><path del fichero de salida> Salida Esperada Gráfico del fichero elf escrito en dot en el fichero de salida indicado en la entrada Cuadro 9.6: Caso de Prueba 06 Identificador CP-07 Descripción Se pasará como argumento un fichero elf con arquitectura x64. Entrada <Fichero elf x64>–dissasembly o -d Salida Esperada Desensamblado del fichero de entrada devuelto por la salida estándar Cuadro 9.7: Caso de Prueba 07 Identificador CP-08 Descripción Se probará la ejecución con mas de un argumento valido Entrada -d <Fichero elf x64><path del fichero de salida> Salida Esperada Desensamblado del fichero de entrada en el fichero de salida Cuadro 9.8: Caso de Prueba 08 9.1.2 Casos de prueba generan errores Identificador CP-09 Descripción Se pasará como argumento un fichero elf con arquitectura x84. Entrada <Fichero elf x86> Salida Esperada Error, fichero no soportado y mensaje de ayuda Cuadro 9.9: Caso de Prueba 09 51 9.1. CASOS DE PRUEBA CAPÍTULO 9. PRUEBAS Identificador CP-10 Descripción Se pasará como argumento un fichero ejecutable de Windows. Entrada <Fichero EXE> Salida Esperada Error, fichero no soportado y mensaje de ayuda Cuadro 9.10: Caso de Prueba 10 Identificador CP-11 Descripción Fichero de otro formato, por ejemplo, un fichero de imagen Entrada <Fichero jpg> Salida Esperada Error, fichero no soportado y mensaje de ayuda Cuadro 9.11: Caso de Prueba 11 Identificador CP-12 Descripción Se pasará como argumento una ruta de entrada no valida para el fichero elf a analizar Entrada <Fichero elf x64><path del fichero de salida> Salida Esperada Error, fichero no encontrado Cuadro 9.12: Caso de Prueba 12 Identificador CP-13 Descripción Se pasará como argumento un fichero elf y una ruta inexistente de salida o sobre la que no se poseen permisos Entrada <Fichero elf x64><path del fichero de salida> Salida Esperada Error, ruta de salida no accesible Cuadro 9.13: Caso de Prueba 13 Identificador CP-14 Descripción Se pasará como argumento un fichero elf y una ruta de salida hacia un fichero que ya existe Entrada <Fichero elf x64><path del fichero de salida> Salida Esperada Error, ruta de salida no accesible Cuadro 9.14: Caso de Prueba 14 52 Capítulo 10 Conclusiones En este capitulo se describen las conclusiones que han sido obtenidas durante el desarrollo del proyecto, los objetivos que han sido desarrollados y conseguidos y por ultimo el trabajo futuro y las siguientes versiones de la herramienta que serán llevadas a cabo. 10.1 Conclusiones En este TFG se ha realizado una herramienta capaz de generar gráficos de control de flujo para ficheros objeto elf compilados para arquitecturas de 64 bits mediante análisis estático y desensamblado. Se basa en la estructura y las secciones de los ficheros objeto para poder llevar a cabo su cometido. A continuación, se deta llan las aportaciones conseguidas durante la realización de proyecto. Se ha analizado la estructura de los ficheros objeto que siguen el estándar elf, la estructuras de datos usadas para almacenan la información y como están guardadas las instrucciones a ejecutar dentro del fichero. Gracias a esto se ha obtenido la información necesaria para la realización del proyecto. Se han analizado los distintos tipos de instrucciones, como identificarlas y las partes de las que están compuesta. Junto a esto se ha analizado el ïnstruction set”de AMD64 para poder identificar los opcodes de las instrucciones que se necesitan analizar y se ha descubierto el gran conjunto de instrucciones que los procesadores son capaces de procesar. Se han descubierto e investigado herramientas y frameworks destinados a la ingeniería inversa, descom posición, desensamblado y análisis de ficheros objeto de multitud de arquitecturas y estándares distintos. Que han proporcionado una idea general sobre las técnicas usadas para poder realizar la herramienta graphflow. Se ha realizado experimentación y aprendizaje sobre el lenguaje de programación RUST y sus herramien tas, dejando claro su potencial como lenguaje de programación general. Se ha investigado sobre las diferentes formas de representación de grafos de manera matemática y su traducción a código. Se ha analizado el lenguaje de representación de grafos DOT, frameworks y programas capaces de pro cesar este lenguaje y los diferentes motores de representación. Se ha estudiado y aprendido la complejidad de las aplicaciones de terminal. 53 10.2. TRABAJO FUTURO CAPÍTULO 10. CONCLUSIONES 10.2 Trabajo futuro Como no hay código sin vulnerabilidades parte del trabajo futuro será encontrarlas y arreglarlas. Como parte del trabajo futuro se enunciarán algunas de las posibles mejoras de la herramienta: Paralelización de las partes del programa que puedan aceptarla. Extender los tipos de archivos objetos aceptados, tanto de otras arquitecturas como de diferentes están dares. Transformación de aplicación de terminal a aplicación web usando la arquitectura API Rest. Construcción de interfaz gráfica de usuario, para convertir la herramienta en aplicación de escritorio. Integración con los motores proporcionados por Graphviz. A parte de estas posibles mejoras la herramienta puede ser completada implementando características que ayuden al análisis de ficheros objeto en otros campos diferentes a la obtención de grafos, como puede ser el análisis de estructuras de datos o la obtención de código decompilado. APENDICES 54 Appendices 55 Apéndice A Manual de Usuario A.1 Manual de instalación Esta guía esta hecha para la instalación en ordenadores con Ubuntu Linux, con otros sistemas operativos la instalación de los componentes previos puede variar. A continuación, se describe la instalación de los compo nentes necesarios antes de la instalación: Lenguaje de programación RUST Para instalar RUST nos descargamos el script de instalación proporcionado por equipo de RUST y lo ejecu tamos, mediante el comando: 1curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh Este script le mostrara 3 opciones como en la Figura A.1, para una instalación normal introducimos un 1 y continuamos Figura A.1: Mensaje Opciones Instalación Script RUST Este script instalara rls, rustsrc, rustanalysis, cargo, clippy, rustdocs y ruststd. Una vez el script ha terminado ya esta RUST instalado. Para poder usarlo hay que recargar la variable PATH, esto se puede hacer reiniciando la terminal o incluyendo el directorio de cargo en la variable PATH actual con el comando: 1source $HOME/.cargo/env 57 B.1. X64 INSTRUCTION SET APÉNDICE B. MANUAL DEL DESARROLLADOR B.1.3 Struct Utilizados Listing B.3: Struct AdyacenceMatrix 1pub struct AdyacenceMatrix{ 2node_list: Vec<u64>, 3child_list: Vec<u64>, 4ady_mat: Vec<Vec<u64>>, 5} Listing B.4: Struct BasicBlock 1struct BasicBlock{ 2init_dir: u64, 3code: String, 4child_list: Vec<u64>, 5} Listing B.5: Struct Graph 1struct Graph{ 2node_list: Vec<BasicBlock>, 3} 64 Apéndice C Código externo a la aplicación C.1 Código para pruebas Se ha escrito un programa simple en el lenguaje de programación C y compilado con GCC para realizar pruebas durante el desarrollo. El programa se muestra en el Código C.1 Listing C.1: Código para Pruebas 1#include <stdio.h> 2 3int main(){ 4int a = 20; 5for(int i=0; i<=0; i++){ 6a=a-1; 7a*2; 8 9} 10 return 0; 11 } C.2 Código Fuzzers En esta sección se presentan los fuzzer usados para hacer pruebas sobre el proyecto. Esta compuesto de 2 fuzzer, el primero Fuzzer C.2 toma los ficheros objeto dentro le la carpeta corpus dentro del proyecto, los ejecuta y muestra si se ha ejecutado correctamente. El segundo Fuzzer ?? es igual que le primero, pero cambia bytes aleatorios por otros generados aleatoriamente. Estos fuzzer están basados en los fuzzers desarrollados por Brandon Falk en ??. Listing C.2: Código Fuzzer 1import glob, subprocess, random, time, threading 2def fuzz(thr_id: int, inp: bytearray): 3assert isinstace(thr_id, int) 4assert isinstace(inp, bytearray) 5 65 C.2. CÓDIGO FUZZERS APÉNDICE C. CÓDIGO EXTERNO A LA APLICACIÓN 6tmpfn = f"tmpinput{thr_id}" 7with open(tmpfn, "wb") as fd: 8fd.write(inp) 9 10 sp = subprocess.Popen(["./graphflow, tmpfn"], 11 stdout=subprocess.DEVNULL, 12 stderr=subprocess.DEVNULL) 13 ret = sp.wait() 14 15 if ret != 0: 16 print(f"Exited with {ret}") 17 18 19 corpus_filenames =glob.glob("corpus/*") 20 corpus = set() 21 22 for filenames in corpus_filenames: 23 corpus.add(open(filenames,"rb").read()) 24 25 corpus =list(map(bytearray, corpus)) 26 start = time.time() 27 cases = 0 28 29 def worker (thr_id): 30 global start, corpus, cases 31 while True: 32 fuzz(thr_id, random.choice(corpus)) 33 cases += 1 34 elapsed = time.time() - start 35 fcps = float(cases) / elapsed 36 # fpcs = fuzzer cases per second 37 print(f"[{elapsed:10.4f}] cases {cases:10} | fcps {fcps:10.4f}") 38 39 for thr_id in range(192): 40 threadin.Thread(tarhet=worker, args=[thr_id]).start() 41 42 while threading.acite_count() > 0: 43 time.sleep(0.1) Listing C.3: Código Fuzzer con mutación 1 2 3import glob, subprocess, random, time, threading 4def fuzz(thr_id: int, inp: bytearray): 5assert isinstace(thr_id, int) 6assert isinstace(inp, bytearray) 7 8tmpfn = f"tmpinput{thr_id}" 9with open(tmpfn, "wb") as fd: 10 fd.write(inp) 11 12 sp = subprocess.Popen(["./graphflow, tmpfn"], 13 stdout=subprocess.DEVNULL, 14 stderr=subprocess.DEVNULL) 15 ret = sp.wait() 16 17 if ret != 0: 18 print(f"Exited with {ret}") 19 66 APÉNDICE C. CÓDIGO EXTERNO A LA APLICACIÓN C.2. CÓDIGO FUZZERS 20 21 corpus_filenames =glob.glob("corpus/*") 22 corpus = set() 23 24 for filenames in corpus_filenames: 25 corpus.add(open(filenames,"rb").read()) 26 27 corpus =list(map(bytearray, corpus)) 28 start = time.time() 29 cases = 0 30 31 def worker (thr_id): 32 global start, corpus, cases 33 while True: 34 35 # Inicio del mutacion del fichero de entrada 36 inp = bytearray(random.choice(corpus)) 37 38 for _in range (random.randint(1,8)): 39 inp[random.randint(0,len(inp) - 1)] = random.randint(0,255) 40 41 fuzz(thr_id, inp) 42 cases += 1 43 elapsed = time.time() - start 44 fcps = float(cases) / elapsed 45 # fpcs = fuzzer cases per second 46 print(f"[{elapsed:10.4f}] cases {cases:10} | fcps {fcps:10.4f}") 47 48 for thr_id in range(192): 49 threadin.Thread(tarhet=worker, args=[thr_id]).start() 50 51 while threading.acite_count() > 0: 52 time.sleep(0.1) 67 C.2. CÓDIGO FUZZERS APÉNDICE C. CÓDIGO EXTERNO A LA APLICACIÓN 68 Bibliografía [1] Daniel K. Lee Ben L. Titzer y Jens Palsberg. Avrora: Scalable Sensor Network Simulation with Precise Timing. 2005. url: http://compilers.cs.ucla.edu/avrora/papers/avrora_ ipsn2005.pdf (visitado 11-07-2021). [2] TIS Committee. Tool Interface Standard (TIS) Executable and Linking Format (ELF) Specification. 1995. url: https://refspecs.linuxbase.org/elf/elf.pdf (visitado 09-07-2021). [3] Khan Academy Dartmouth Computer Thomas Cormen y Devin Balkcom. Representar grafos. 2016. url: https://es.khanacademy.org/computing / computer - science / algorithms/graph-representation/a/representing-graphs (visitado 11-07-2021). [4] DragonJAR. Todo lo que debes saber sobre Radare2. 2021. url: https://book.rada.re/ first_steps/overview.html (visitado 10-07-2021). [5] Michael J. Eager Free Software Foundation. Introduction to the DWARF Debugging Format. 2012. url: http://dwarfstd.org/doc/Debugging%20using%20DWARF2012.pdf (visitado 10-07-2021). [6] Several Authors Free Software Foundation. DWARF Debugging Information Format Version 5. 2017. url: http://dwarfstd.org/doc/DWARF5.pdf (visitado 11-07-2021). [7] Alexey Frunze. StackOverflow assembly. 2013. url: https://stackoverflow.com/questions/ 15209993/what-does-opcode-ff350e204000-do?rq=1 (visitado 11-07-2021). [8] Linux Fundation. ELF Header Linux Fundation. 2020. url: https://refspecs.linuxfoundation. org/elf/gabi4+/ch4.eheader.html (visitado 10-07-2021). [9] GCC Gnu. GNU Basic Block. 2021. url: https://gcc.gnu.org/onlinedocs/gccint/ Basic-Blocks.html (visitado 10-07-2021). [10] grapheverywhere. Qué son los grafos. 2021. url: https://www.grapheverywhere.com/ que-son-los-grafos/ (visitado 11-07-2021). [11] Diane Hosfel. Memory Safety. 2019. url: https : / / hacks . mozilla . org / 2019 / 01 / fearless-security-memory-safety/ (visitado 11-07-2021). [12] IBM. XCOFF Object File Format. 2021. url: https://www.ibm.com/docs/en/aix/7.2? topic=formats-xcoff-object-file-format (visitado 10-07-2021). [13] Open Source Software Initiative. GraphViz: graph Visualization Tool. 1999. url: https:// graphviz.org/ (visitado 14-04-2021). [14] ionos. Rust: el lenguaje de programación moderno. 2020. url: https://www.ionos.es/ digitalguide/paginas-web/desarrollo-web/rust-lenguaje-de-programacion/ (visitado 09-07-2021). [15] Michael Kerrisk. elf(5) Linux manual page. 2021. url: https://man7.org/linux/manpages/man5/elf.5.html (visitado 10-07-2021). [16] Johannes Kinder. The Jakstab static analysis platform for binaries. 2012. url: http://www. jakstab.org/ (visitado 11-07-2021). 69 BIBLIOGRAFÍA BIBLIOGRAFÍA [17] lawebdelprogramado. Decompilador Definicion. 2021. url: https://www.lawebdelprogramador. com/diccionario/Descompilador/ (visitado 10-07-2021). [18] John R. Levine. Object Files. 1999. url: https://archive.ph/20130125220014/http: //www.iecc.com/linker/linker03.html (visitado 10-07-2021). [19] lucidchart. Qué es un diagrama de flujo. 2020. url: https://www.lucidchart.com/pages/ es/que-es-un-diagrama-de-flujo (visitado 11-07-2021). [20] maijin pancake maijin y serveral authors. The Official Radare2 Book, Overview. 2021. url: https://book.rada.re/first_steps/overview.html (visitado 10-07-2021). [21] Matt Milano. The Rust Programming Language: Its History and Why It Matters. 2020. url: https://www.talentopia.com/news/the-rust-programming-language-itshistory-and-why/ (visitado 09-07-2021). [22] palaksinghal9903. Basic Blocks in Compiler Design. 2021. url: https://www.geeksforgeeks. org/basic-blocks-in-compiler-design/ (visitado 10-07-2021). [23] Santiago Urueña Pascual. Layout of an ELF file. 2007. url: https://commons.wikimedia. org/wiki/File:Elf-layout--en.svg (visitado 09-07-2021). [24] RAE Real Academia Española. Definición grafo. 2021. url: https://dle.rae.es/grafo (visitado 11-07-2021). [25] Unknow. DIAGRAMAS DE FLUJO DE CONTROL. 2021. url: https://docs.google. com/presentation/u/1/d/1OL4LOSnJ5Qoe6QyUeyZTcr6rjq_JEdrRN7T2i21a2y0/ htmlpresent (visitado 10-07-2021). [26] Unknown. Digrafos. 2012. url: http://tareasmd.blogspot.com/2012/07/digrafos. html (visitado 11-07-2021). [27] Jeroen Ruigrok van der Werven. FreeBSD File Formats Manual ELF(5). 2020. url: https: //www.freebsd.org/cgi/man.cgi?query=elf&sektion=5 (visitado 09-07-2021). [28] Several Authors Wikipedia(EN). Assembly Language Wikipedia. 2021. url: https : / /en. wikipedia.org/wiki/Assembly_language (visitado 10-07-2021). [29] Several Authors Wikipedia(EN). Bytecode Wikipedia. 2021. url: https://en.wikipedia. org/wiki/Bytecode (visitado 10-07-2021). [30] Several Authors Wikipedia(EN). DOT: A graphical description language. 1999. url: https:// en.wikipedia.org/wiki/DOT_(graph_description_language) (visitado 14-04-2021). [31] Several Authors Wikipedia(EN). ELF Executable and Linkable Format. 2021. url: https:// en.wikipedia.org/wiki/Executable_and_Linkable_Format (visitado 09-07-2021). [32] Several Authors Wikipedia(EN). Intermediate Representation Wikipedia. 2021. url: https: //en.wikipedia.org/wiki/Intermediate_representation (visitado 10-07-2021). [33] Several Authors Wikipedia(EN). Machin Code Wikipedia. 2021. url: https://en.wikipedia. org/wiki/Machine_code (visitado 10-07-2021). [34] Several Authors Wikipedia(EN). Object Code Wikipedia. 2021. url: https://en.wikipedia. org/wiki/Object_code (visitado 10-07-2021). [35] Several Authors Wikipedia(EN). Source Code Wikipedia. 2021. url: https://en.wikipedia. org/wiki/Source_code (visitado 10-07-2021). [36] Several Authors Wikipedia(ES). Bloques Basicos Wikipedia. 2021. url: https://es.wikipedia. org/wiki/Bloque_b%C3%A1sico (visitado 21-04-2021). [37] Several Authors Wikipedia(ES). Bytecode Wikipedia. 2021. url: https://es.wikipedia. org/wiki/Bytecode (visitado 10-07-2021). 70 BIBLIOGRAFÍA BIBLIOGRAFÍA [38] Several Authors Wikipedia(ES). Debuggin DWARF. 2021. url: https://es.wikipedia. org/wiki/DWARF (visitado 10-07-2021). [39] Several Authors Wikipedia(ES). Decompilador Wikipedia. 2021. url: https://es.wikipedia. org/wiki/Decompilador (visitado 10-07-2021). [40] Several Authors Wikipedia(ES). Desensamblador Wikipedia. 2021. url: https://es.wikipedia. org/wiki/Desensamblador (visitado 10-07-2021). [41] Several Authors Wikipedia(ES). Ghidra, Wikipedia. 2021. url: https://en.wikipedia. org/wiki/Ghidra (visitado 11-07-2021). [42] Several Authors Wikipedia(ES). Graphviz, Wikipedia. 2021. url: https://es.wikipedia. org/wiki/Graphviz (visitado 10-07-2021). [43] Several Authors Wikipedia(ES). Interactive Disassembler (IDA), Wikipedia. 2019. url: https: //es.wikipedia.org/wiki/Interactive_Disassembler (visitado 11-07-2021). [44] Several Authors Wikipedia(ES). Intermediate Representation Wikipedia Español. 2021. url: https: //es.wikipedia.org/wiki/Lenguaje_intermedio (visitado 10-07-2021). [45] Several Authors Wikipedia(ES). Lenguaje Maquina Wikipedia. 2021. url: https://es.wikipedia. org/wiki/Lenguaje_de_m%C3%A1quina (visitado 10-07-2021). [46] Several Authors Wikipedia(ES). Lista de Adyacencia, Wikipedia. 2021. url: https://es. wikipedia.org/wiki/Lista_de_adyacencia (visitado 10-07-2021). [47] Several Authors Wikipedia(ES). Object Code Wikipedia. 2021. url: https://es.wikipedia. org/wiki/C%C3%B3digo_objeto (visitado 10-07-2021). [48] Several Authors Wikipedia(ES). Relocatable Object Module Format(OMF). 2020. url: https: //en.wikipedia.org/wiki/Relocatable_Object_Module_Format (visitado 10-07-2021). 71 BIBLIOGRAFÍA BIBLIOGRAFÍA 72