1 puntos por GN⁺ 2024-08-24 | 1 comentarios | Compartir por WhatsApp
  • Esta extensión de Ghidra exporta partes de un programa como archivos objeto, y los archivos exportados incluyen metadatos válidos como símbolos y tablas de reubicación, por lo que pueden volver a procesarse dentro del toolchain
  • Sus usos principales son el parcheo binario avanzado, portabilidad de software, conversión de formatos de archivo, creación de bibliotecas y la reimplementación de un programa dividiéndolo en varios archivos objeto dentro de proyectos de decompilación
  • Las combinaciones compatibles son COFF x86/x86_64, ELF x86/x86_64/MIPS y OMF x86; no son compatibles COFF MIPS, OMF x86_64 ni OMF MIPS
  • El usuario selecciona un rango de direcciones a extraer en el Listing de Ghidra, ejecuta el analizador Relocation table synthesizer y luego invoca el exporter de archivos objeto reubicables desde File > Export Program…
  • La síntesis de la tabla de reubicación depende de la precisión de la base de datos de Ghidra, por lo que si hay información incorrecta o faltante, las reubicaciones pueden romperse o faltar; es más seguro volver a ejecutar el analizador justo antes de exportar

Qué hace esta extensión

  • Object file exporter extension for Ghidra es una extensión de Ghidra que permite exportar partes de un programa como archivos objeto
  • El archivo objeto generado incluye metadatos válidos como símbolos y tablas de reubicación
  • Gracias a estos metadatos, el archivo objeto exportado puede reutilizarse directamente en el toolchain para procesamiento adicional

Casos de uso

  • Advanced binary patching
    • En lugar de hacer coincidir manualmente la parte original y la modificada, se puede usar el linker para que encajen entre sí
  • Software ports
    • Se puede separar del programa el código independiente del sistema y reemplazar el resto
  • Se pueden convertir programas o archivos objeto de un formato de archivo a otro
  • Extracción de partes de un programa y creación de bibliotecas
    • Se pueden extraer partes de un programa para reutilizarlas en otro contexto
  • En proyectos de decompilación, se puede dividir el programa en varios archivos objeto y reimplementarlo con un enfoque Ship of Theseus

Arquitecturas y formatos de archivo objeto compatibles

  • La matriz de compatibilidad es la siguiente
    • COFF: compatible con x86, x86_64 / no compatible con MIPS
    • ELF: compatible con x86, x86_64, MIPS
    • OMF: compatible con x86 / no compatible con x86_64 ni MIPS

Compilación e instalación

  • Procedimiento de compilación por CLI
    • Clonar el repositorio
    • Establecer la variable de entorno GHIDRA_INSTALL_DIR con el directorio de instalación de Ghidra
    • Ejecutar gradle buildExtension
    • El archivo generado de la extensión de Ghidra se crea en el directorio dist/
  • Para descargar el paquete desde el repositorio Maven de GitHub se requiere autenticación
    • Crear un GitHub classic token con permiso read:packages y agregar githubToken=ghp_xxx en ${GRADLE_USER_HOME}/gradle.properties
    • O bien ejecutar gradle installStandaloneDeps para compilar e instalar la dependencia vendorizada del submodule
  • Procedimiento de instalación
    • Descargar la extensión desde la releases page o compilarla localmente
    • Instalar la extensión en Ghidra desde File > Install Extensions…
    • Activar el plugin RelocationTableSynthesizedPlugin en File > Configure > Experimental dentro de la ventana CodeBrowser

Flujo de uso y puntos a considerar

  • Procedimiento básico de uso
    • Seleccionar en la vista Listing el conjunto de direcciones que se va a extraer
    • Ejecutar el analizador Relocation table synthesizer, que se ofrece en modo one-shot
    • Invocar el exporter de archivos objeto reubicables desde File > Export Program…
  • Las reubicaciones reconstruidas pueden verse en Window > Relocation table (synthesized)
  • El reporte de evaluación detallado puede activarse configurando la opción Evaluation report policy del analizador Relocation table synthesizer en el cuadro de diálogo Analysis > Auto Analyze...
  • No es necesario hacer ingeniería inversa de antemano sobre todo el programa antes de usar la extensión
  • El delinking exitoso generalmente depende mucho de los metadatos en la parte que se va a exportar y en las referencias externas
    • Funciones y punteros usados como ubicaciones de reubicación
    • El footprint de los símbolos usados como objetivos de reubicación
    • Las referencias entre ambos
  • El analizador Relocation table synthesizer depende de la exactitud de la base de datos de Ghidra
    • La información inexacta o faltante puede provocar reubicaciones rotas o ausentes durante el análisis
  • El exporter de archivos objeto depende de los resultados del análisis de Relocation table synthesizer
    • Si hay dudas, se debe ejecutar el analizador justo antes de exportar el archivo objeto para confirmar que la tabla de reubicación esté actualizada

Cómo funciona

  • Un archivo objeto se compone de tres partes
    • Bytes de secciones reubicables
    • Tabla de símbolos

      • Tabla de reubicación
      • Lo que hace el linker al crear un ejecutable a partir de varios archivos objeto
      • Coloca las secciones en memoria
      • Calcula las direcciones de los símbolos en el espacio de direcciones virtual
      • Aplica las reubicaciones a los bytes de las secciones según las direcciones finales de los símbolos
      • Normalmente, una vez que este proceso termina, la tabla de reubicación se descarta
      • Si no se conservan símbolos de depuración, también se descarta la tabla de símbolos, y solo quedan bytes de secciones no reubicables
      • Esta extensión puede reconstruir esos datos mediante un análisis cuidadoso, lo que permite volver a delink un programa en archivos objeto

1 comentarios

 
GN⁺ 2024-08-24
Opiniones de Hacker News
  • Me alegra verlo por aquí. Creo que es un proyecto realmente genial, y ayudé a agregar soporte para MS COFF.
    Dicho eso, mi PR inicial estaba bastante por debajo del soporte ELF que ya existía, así que si aparecen problemas probablemente sea culpa mía. Aun así, se ve que va mejorando.
    Todavía no lo he usado en nada grande, pero lo más divertido que hice fue deslinkear un ejecutable de Hello World compilado con Visual Studio 2003, volver a enlazarlo con GCC+glibc en Linux x86 y luego reenlazarlo otra vez con MinGW+msvcrt.
    Algo más grande que Hello World todavía es difícil y, sobre todo, como no conozco muy bien Ghidra, aún no encontré una buena forma de elegir el rango a deslinkear en binarios grandes.
    Justo hoy se fusionó un paquete derivado de Nixpkgs para esta herramienta, así que en NixOS unstable se puede instalar con ghidra.withExtensions. Está en ghidra-extensions.ghidra-delinker-extension.
    Solo que hace unos días salió una versión nueva y no hice rebase del PR, así que por ahora está en una versión anterior; pronto intentaré subir una actualización.

    • Una forma de hacer seguimiento de qué deslinkear es usar carpetas y fragmentos dentro del árbol del programa.
      Por ejemplo, si tienes un programa de Ghidra en el que ya averiguaste los nombres y rangos de varios archivos objeto que componían el ejecutable original, puedes hacer clic derecho en esa carpeta o fragmento > Select Addresses para seleccionarlo completo.
      Tanto el analizador de síntesis de reubicaciones como la exportación se pueden scriptar, o automatizar mediante el administrador de árbol del programa, lo que reduce la necesidad de seleccionar a mano el rango deseado y ejecutar manualmente el analizador y la exportación.
  • Se ve bastante interesante, y me dan ganas de volver a mirar un proyecto de ingeniería inversa de un juego que dejé de lado hace unos años.
    Sería bueno tener un ejemplo completo que muestre de principio a fin cómo usar esto y cómo aprovechar su salida.

  • Me pregunto cuánto trabajo implica determinar qué secciones del ejecutable hay que exportar.
    ¿Es realista exportar un juego Win32 relativamente moderno, de alrededor de 2008~2015, como archivo objeto y luego recompilarlo/enlazarlo en un ejecutable completo en cuestión de horas?

    • Mientras no cortes a la mitad una variable o una función, puedes exportar con bastante libertad, y no tienes que seguir necesariamente los límites originales de los archivos objeto.
      Qué exportar es otro tema: requiere conocimiento del programa. Con símbolos de depuración es mucho más fácil y, aun sin ellos, una vez que armas una base de datos de Ghidra lo bastante precisa como para poder exportar, normalmente empiezas a tener una idea de dónde está cada cosa.
      En el caso de uso del post enviado, el usuario abrió el primer issue a comienzos de julio y, hacia mediados de agosto, ya tenía un ejecutable reenlazado que funcionaba de manera funcionalmente idéntica.
      Dicho eso, en ese momento había muchos bugs por corregir en la exportación COFF y también partes del analizador i386 que necesitaban ajustes, así que espero que ahora otras personas se topen con menos de esos problemas.
      No sé cuánto tiempo tomaría, pero salvo que tengas símbolos de depuración y muchísima suerte, es probable que lleve más que unas horas. Un reverser experimentado quizá podría llegar en ese tiempo a algo que ejecute, pero también podría morirse a mitad de la primera pantalla de carga; es más bien el tipo de trabajo en el que no sabes cuándo va a terminar hasta que termina.
  • Se ve genial. Quizá algún día pueda ayudar a desarmar programas existentes más fácilmente para usarlos de la forma que uno quiera.
    Hay investigaciones como LLM Compiler de Meta o la decompilación usando LLM, y dividir programas y reemplazar partes podría ser un área interesante, rica en datos, para que los LLM exploren y se mejoren a sí mismos.
    Desde el punto de vista del aprendizaje también parece haber muchos tokens interesantes escondidos, y muchas cosas que se podrían hacer con ellos.

  • Sinceramente, suena a magia. Tengo que entender cómo es posible esto.

    • En pocas palabras, un archivo objeto está compuesto por tres partes: bytes de secciones reubicables, una tabla de reubicación y una tabla de símbolos.
      Cuando el linker crea un ejecutable a partir de varios archivos objeto, coloca las secciones en memoria, calcula las direcciones de los símbolos en el espacio de direcciones virtual y luego aplica las reubicaciones a los bytes de las secciones con base en las direcciones finales de los símbolos.
      El truco de delink consiste en encontrar dónde se aplicaron esas reubicaciones, revertirlas y volver a obtener bytes reubicables. Luego, a partir de lo revertido, crea la tabla de reubicación y la tabla de símbolos, lo empaqueta y sale un archivo objeto.
      La parte realmente difícil es el análisis para encontrar los puntos de reubicación. La mayor parte se le deja a Ghidra, pero hace falta convertir las referencias en puntos de reubicación. En x86 es relativamente fácil; en MIPS es una pesadilla. Creé esta extensión para automatizar también la recopilación de los datos necesarios para eso y la serialización del archivo objeto.
    • No será fácil en todas las arquitecturas de CPU, pero no es tan descabellado como parece. Básicamente, la diferencia entre un archivo objeto y un ejecutable o una biblioteca compartida no es tan grande.
      Salvo en las plataformas de Microsoft, en muchos casos el formato de archivo real es el mismo; por ejemplo, ELF.
      La gran diferencia son las reubicaciones. Los archivos objeto tienen información de reubicación detallada, pero los ejecutables normalmente no. Las imágenes ejecutables de Windows solo tienen las reubicaciones mínimas que indican las direcciones de código y datos que deben corregirse cuando se reubica el ejecutable, y la imagen en sí solo puede reubicarse como un todo respecto de la dirección base de la imagen.
      En cambio, los archivos objeto contienen reubicaciones a nivel de símbolo. Para reconstruir esta información con precisión, hay que asociar el desensamblado con información bastante precisa sobre los símbolos.
      Otra gran diferencia es que un archivo objeto todavía no ha sido enlazado. Ningún símbolo está resuelto. Esto, de hecho, es más fácil de arreglar: durante el delink, si un símbolo queda fuera del rango actual, por lo general se convierte en un símbolo no resuelto. Luego, al volver a enlazar, otro archivo objeto o biblioteca debe proporcionar ese símbolo para que se conecte de nuevo.
      Hay diferencias menores, como que no parezca haber un punto de entrada, pero no tienen mayor importancia.
      Por lo tanto, el límite donde recortar un archivo objeto desde una imagen ejecutable o un objeto compartido es prácticamente arbitrario. Al compilar originalmente, seguramente estaba dividido por unidades de traducción, pero en la etapa de enlace esos límites no importan demasiado. Claro que, si quieres una decompilación que coincida, equivocarte con los límites del archivo objeto puede hacerlo muy difícil, así que conviene descubrirlos si es posible.
      He trabajado con este problema en la práctica, pero podría equivocarme un poco en los detalles, así que tómalo solo como referencia. Intenté escribir una entrada de blog sobre archivos objeto, y ya hay algunas bastante buenas.
  • Me pregunto si este proceso es completamente seguro. Quisiera saber si siempre se garantiza el éxito o si el análisis opera de forma conservadora.
    Por ejemplo, si a un ELF le falta algún fragmento, dato o función, ¿delink falla?

    • Es complicado de responder.
      Mi analizador depende de una base de datos de Ghidra precisa, al menos para la parte que se quiere exportar. Me esforcé bastante por registrar en logs varios problemas que hay que corregir, pero no puede ver lo que no existe.
      En particular, las referencias faltantes y las variables recortadas no se detectan, y pueden derivar en comportamientos indefinidos extraños.
      Hay formas de rastrear algunos de estos problemas. El mejor método que he encontrado hasta ahora es volver a enlazar el ejecutable con otra dirección base y evitar mapear el rango de direcciones del programa original. Así, en los puntos de reubicación absoluta que se hayan pasado por alto se produce un error de segmentación y se pueden depurar. Eso sí, solo es posible cuando el objetivo tiene MMU.
      Las variables recortadas son especialmente difíciles de rastrear si no las sospechas, porque lo que se corrompe es la memoria que viene después de la variable recortada. También es difícil rastrear los casos en los que se confunde un entero con un puntero. El valor entero cambia según la dirección en la que se ubique el símbolo destino, lo que hace que el comportamiento del programa sea errático; el problema es especialmente grande en programas que se cargan muy abajo en el espacio de direcciones.
      Aun así, si la base de datos de Ghidra es lo suficientemente precisa, y vuelves a exportar al mismo formato de archivo objeto que se usó originalmente, usando la misma plataforma y la misma cadena de herramientas, se pueden delinkear con éxito megabytes de código y datos de programa. Si el linker lo hizo, creo que debería ser posible deshacerlo.
      En cambio, si empiezas a hacer delink cruzado que no coincide con la plataforma y la cadena de herramientas del programa original, como delinkear desde un ejecutable ELF i386 de Linux a un archivo objeto COFF para usarlo con una cadena de herramientas i386 de Windows, la historia cambia. Si la exportación puede representar las reubicaciones, quizá obtengas un archivo objeto reubicable que funcione, pero también tendrás que lidiar con incompatibilidades de ABI. Es posible, pero no lo recomendaría como primer proyecto.
      En resumen, según lo que hagas y la precisión de la base de datos de Ghidra, puede ir desde “simplemente funciona” hasta “rogarle misericordia a Cthulhu”.
    • Si la pregunta va por ese lado, no es completamente seguro, ni podría serlo.
      Por ejemplo, piensa en una función como int getSpecialArrayElement(char *array, uint64 key) { i = computeOffset(key); return array[i]; }.
      computeOffset puede ser arbitrariamente compleja y, si se quiere ofuscar, puede hacerse deliberadamente difícil de analizar. Nada impide que esta función acceda a una ubicación arbitraria de memoria.
      A menos que pruebes absolutamente todas las entradas posibles, no puedes saber si accede a un símbolo que el linker ya definió en memoria, y computeOffset también puede contener trampas de Turing que impidan ese intento.
  • Sería interesante conectarlo con una idea que antes solo había imaginado y nunca llegué a implementar: generar archivos de encabezado a partir de la información de depuración y, si hace falta, hacer que un LLM los ordene.

    • De hecho, hay algunos intentos de ese tipo. Para Microsoft Program Database existe esto:
      https://github.com/wbenny/pdbex
      En cuanto a la parte de ordenarlo con un LLM, todavía no parece haber muchos casos de gran éxito aplicando modelos LLM a la ingeniería inversa. Tal vez este sea un ámbito donde se hacen evidentes las limitaciones de la arquitectura de los LLM.
      No soy experto, pero si tuviera que apostar, en muchos casos de uso de ingeniería inversa me parecerían más interesantes los modelos de difusión.
      No es exactamente lo mismo, pero Binary Ninja tiene una función llamada Sidekick que intenta ordenar el desensamblado con un LLM. Personalmente no me impresionó mucho, aunque podría ser útil para alguien.
    • pahole genera archivos de encabezado C compilables a partir de información ELF DWARF.
      Aquí el LLM no parece tener mucha relevancia. O el archivo de encabezado contiene correctamente todos los tipos exportados por el ejecutable junto con sus valores originales y se puede usar, o está mal o incompleto. Hacer que un LLM invente algo más no ayuda.
      Ghidra también tiene una función básica para exportar estructuras de datos, y puede generarlas a partir de estructuras DWARF. Clic derecho -> Export to C header
    • Hace algunos años hice una herramienta que genera e inserta automáticamente un fuzzer consciente de tipos para API en C a partir de información DWARF: https://github.com/intel/fffc
      La generación de encabezados y la creación posterior de mutadores que podían ajustarse a restricciones de tipo formaban parte de eso.
      Si se le agrega la parte de LLM, parece posible que sirva para ponerles nombre a cosas como estructuras anónimas, aunque no sé si sea buena idea. Algo más interesante podría ser intentar que un LLM resuma en lenguaje natural las restricciones de tipo conocidas con fines de documentación.
    • Es un tema un poco distinto, pero alguna vez pensé en generar símbolos de depuración para el archivo objeto exportado basándome en el contenido de la base de datos de Ghidra, para mejorar la experiencia de depuración.
      Todavía no lo implementé porque hasta ahora pude arreglármelas sin eso. Además, suena como una madriguera bastante profunda, y la madriguera en la que ya estoy metido ya es suficientemente grande.
  • Se ve realmente genial y también está relacionado con una idea de modding de juegos que tenía pensada antes. La serie del blog sobre la descompilación de Tenchu también estuvo buena.

    • Algún día tengo que volver a este proyecto. Necesité tomarme un descanso después de demasiadas sesiones seguidas de seguimiento de versiones, y mientras tanto la side quest de deslinking no deja de crecer fuera de control.
  • No tengo un uso inmediato para esto en lo que estoy haciendo ahora, pero parece una herramienta que me habría sido muy útil antes.
    Ojalá pronto tenga tiempo u oportunidad de probarla.