2010-11-30 155 views
41

¿Hay alguna forma de hacerlo? He usado objdump pero eso no produce salida de ensamblaje que sea aceptado por cualquier ensamblador que yo sepa. Me gustaría poder cambiar las instrucciones dentro de un ejecutable y luego probarlo después.¿Cómo desensamblar, modificar y luego volver a montar un ejecutable de Linux?

+0

Er. Su Linux, que es de código abierto, que generalmente significa que el código fuente está disponible gratuitamente –

+0

@James Anderson No necesariamente es cierto para todos los ejecutables que pueda encontrar. – mgiuca

+0

@JamesAnderson, actualmente estoy buscando un código de "código abierto" que fue escrito para un compilador comercial y no se compila limpiamente bajo gcc. – avakar

Respuesta

26

No creo que haya ninguna manera confiable de hacer esto. Los formatos de código de máquina son muy complicados, más complicados que los archivos de ensamblaje. Realmente no es posible tomar un binario compilado (por ejemplo, en formato ELF) y producir un programa ensamblador fuente que se compilará en el mismo (o similar) binario. Para obtener una comprensión de las diferencias, compare la salida de la compilación GCC directa al ensamblador (gcc -S) versus la salida de objdump en el ejecutable (objdump -D).

Hay dos complicaciones principales que se me ocurren. En primer lugar, el código de la máquina en sí no es una correspondencia de 1 a 1 con el código de ensamblaje, debido a factores como las compensaciones del puntero.

Por ejemplo, considere el código C a ¡Hola mundo:

int main() 
{ 
    printf("Hello, world!\n"); 
    return 0; 
} 

Esto compila al código ensamblador x86:

.LC0: 
    .string "hello" 
    .text 
<snip> 
    movl $.LC0, %eax 
    movl %eax, (%esp) 
    call printf 

Dónde .LCO es una constante llamada, y printf es un símbolo en una tabla de símbolos de biblioteca compartida. Comparar con la salida del objdump:

80483cd:  b8 b0 84 04 08   mov $0x80484b0,%eax 
80483d2:  89 04 24    mov %eax,(%esp) 
80483d5:  e8 1a ff ff ff   call 80482f4 <[email protected]> 

En primer lugar, la .LC0 constante es ahora sólo en la memoria de compensación alguna parte alguna al azar - que sería difícil crear un archivo fuente de ensamblaje que contiene esta constante en el lugar correcto, ya que el ensamblador y el enlazador son libres de elegir ubicaciones para estas constantes.

En segundo lugar, no estoy completamente seguro de esto (y depende de cosas como código independiente de posición), pero creo que la referencia a printf no está realmente codificada en la dirección del puntero en ese código, pero Los encabezados ELF contienen una tabla de búsqueda que reemplaza dinámicamente su dirección en tiempo de ejecución. Por lo tanto, el código desensamblado no se corresponde exactamente con el código del ensamblaje de origen.

En resumen, el conjunto de fuente tiene símbolos mientras que el código máquina compilado tiene direcciones que son difíciles de revertir.

La segunda gran complicación es que un archivo fuente de ensamblaje no puede contener toda la información que estaba presente en los encabezados de archivo ELF originales, como qué bibliotecas vincular dinámicamente y otros metadatos colocados allí por el compilador original. Sería difícil reconstruir esto.

Como he dicho, es posible que una herramienta especial pueda manipular toda esta información, pero es poco probable que simplemente se pueda producir un código ensamblador que se pueda volver a ensamblar en el ejecutable.

Si está interesado en modificar solo una pequeña sección del ejecutable, le recomiendo un enfoque mucho más sutil que recompilar toda la aplicación. Use objdump para obtener el código de ensamblado de la (s) función (es) que le interesan. Convierta a mano la "sintaxis de ensamblaje de origen" (y aquí, desearía que hubiera una herramienta que realmente produjera el desensamblaje con la misma sintaxis que la entrada) y modificarlo como lo desee. Cuando haya terminado, recompile solo esas funciones y use objdump para descubrir el código de máquina para su programa modificado.Luego, use un editor hexadecimal para pegar manualmente el nuevo código de máquina sobre la parte correspondiente del programa original, teniendo cuidado de que su nuevo código tenga exactamente el mismo número de bytes que el código anterior (o todos los desplazamientos serían incorrectos)) Si el nuevo código es más corto, puede rellenarlo usando las instrucciones NOP. Si es más largo, puede estar en problemas y podría tener que crear nuevas funciones y llamarlas en su lugar.

+0

A menos que los símbolos se eliminen intencionalmente, muchos se conservan. Pruebe nm/bin/ls. – Praxeolitic

+1

Creo que está exagerando algunas dificultades para que el código encuentre sus constantes. El problema principal es que ninguna sintaxis asm puede representar de manera única las codificaciones de longitud diferente para la misma instrucción. El ['objconv' disassembler] de Agner Fog (http://agner.org/optimize/) se desarmará en NASM, YASM, MASM o GNU como sintaxis. Ese resultado se puede volver a ensamblar en un binario similar, pero cualquier suposición que el código haya realizado sobre la alineación/las compensaciones podría haber cambiado. p.ej. el PLT (tabla de vinculación de procedimientos) necesita usar la codificación 'jmp rel32' para que el offset derecho pueda llenarse en tiempo de ejecución. –

+1

Actualización: GAS tiene prefijos que pueden solicitar un 'disp32' explícito incluso si la instrucción no lo necesita: https://stackoverflow.com/questions/47673177/where-are-gnu-assembler-instruction-suffixes-like- s-in-x86-mov-s-documentado. Además, NASM generalmente puede usar 'mov eax, [rdi + strict dword 0]' para forzar un disp32. El problema es que los desarmadores no incluyen estas anulaciones al desmontar instrucciones más largas de lo necesario. –

6

Para cambiar el código dentro de un ensamblaje binario, generalmente hay 3 formas de hacerlo.

  • Si es algo trivial como una constante, simplemente cambie la ubicación con un editor hexadecimal. Asumiendo que puedes encontrarlo para empezar.
  • Si necesita modificar el código, utilice LD_PRELOAD para sobrescribir alguna función en su programa. Eso no funciona si la función no está en las tablas de funciones.
  • Hack el código en la función que desea corregir a ser un salto directo a una función de carga a través de LD_PRELOAD y luego saltar de nuevo en la misma ubicación (Esta es una combi de los dos anteriores)

Por supuesto solo el segundo funcionará, si el ensamblado realiza algún tipo de autoverificación de integridad.

Editar: Si no es obvio, jugar con ensamblajes binarios es un material de desarrollo de MUY alto nivel, y le será difícil preguntar sobre esto aquí, a menos que sea realmente algo específico lo que pregunte.

0

Otra cosa que podría estar interesado debe hacer:

  • binario instrumentación - cambiar el código existente

Si está interesado, echa un vistazo a: Pin, Valgrind (o proyectos que hacen esto: NaCl - Nativo de Google Cliente, tal vez QEmu.)

0

Puede ejecutar el ejecutable bajo la supervisión de ptrace (en otras palabras, un depurador como gdb) y de esa manera, controlar la ejecución sobre la marcha, sin modificar el archivo real. Por supuesto, requiere las habilidades de edición habituales, como encontrar dónde están las instrucciones particulares que desea influir en el ejecutable.

4

@mgiuca ha respondido correctamente esta respuesta desde un punto de vista técnico. De hecho, desensamblar un programa ejecutable en una fuente de ensamblaje fácil de recompilar no es una tarea fácil.

Para añadir algunos bits a la discusión, hay un par de técnicas/herramientas que podría ser interesante para explorar, aunque son técnicamente complejos.

  1. estático/dinámico de instrumentación. Esta técnica implica analizar el formato ejecutable, insertar/eliminar/reemplazar instrucciones de ensamblaje específicas para un fin determinado, corregir todas las referencias a variables/funciones en el ejecutable y emitir un nuevo ejecutable modificado. Algunas herramientas que conozco son: PIN, Hijacker, PEBIL, DynamoRIO. Considere que la configuración de tales herramientas para un propósito diferente de para lo que fueron diseñadas podría ser complicado, y requiere la comprensión tanto de formatos ejecutables como de conjuntos de instrucciones.
  2. descompilación ejecutable completa. Esta técnica intenta reconstruir una fuente de ensamblaje completo a partir de un ejecutable. Es posible que desee echar un vistazo al Online Disassembler, que intenta hacer el trabajo.De todos modos, pierde información sobre diferentes módulos fuente y posiblemente funciones/nombres de variables.
  3. decompilación regenerizable. Esta técnica intenta extraer más información del ejecutable, mirando huellas digitales del compilador (es decir, patrones de código generados por compiladores conocidos) y otras cosas deterministas. El objetivo principal es reconstruir el código fuente de mayor nivel, como fuente C, desde un ejecutable. Esto a veces puede recuperar información sobre los nombres de funciones/variables. Tenga en cuenta que compilar fuentes con -g a menudo ofrece mejores resultados. Es posible que desee probar el Retargetable Decompiler.

La mayor parte de esto proviene de la evaluación de la vulnerabilidad y los campos de investigación de análisis de ejecución. Son técnicas complejas y, a menudo, las herramientas no se pueden utilizar de inmediato. Sin embargo, brindan una ayuda inestimable al intentar realizar ingeniería inversa en algunos programas.

1

miasma

https://github.com/cea-sec/miasm

Ésta parece ser la solución más prometedora de concreto. De acuerdo con la descripción del proyecto, la biblioteca puede:

  • Apertura/modificar/generación de PE/ELF 32/64 LE/a utilizar Elfesteem
  • Montaje/Desmontaje X86/ARM/MIPS/SH4/MSP430

por lo que debe, básicamente:

  • analizar el ELF en un represen internos tación (desmontaje)
  • modificar lo que quiere
  • generar una nueva ELF (montaje)

No creo que genera una representación textual desmontaje, es probable que tenga que caminar a través de las estructuras de datos de Python.

TODO encontrar un ejemplo mínimo de cómo hacer todo eso con la biblioteca. Un buen punto de partida parece ser example/disasm/full.py, que analiza un archivo ELF dado. La estructura clave de nivel superior es Container, que lee el archivo ELF con Container.from_stream. ¿TODO cómo volver a armarlo después?Este artículo parece hacerlo: http://www.miasm.re/blog/2016/03/24/re150_rebuild.html

Esta pregunta se refiere a si hay algún otro tipo de bibliotecas: https://reverseengineering.stackexchange.com/questions/1843/what-are-the-available-libraries-to-statically-modify-elf-executables

preguntas relacionadas:

I cree que este problema no es automatizable

Creo que el problema general no es totalmente automatizable, y la solución general es básicamente equivalente a "cómo aplicar ingeniería inversa" a un archivo binario.

Para insertar o eliminar bytes de una manera significativa, deberíamos asegurarnos de que todos los saltos posibles sigan saltando a las mismas ubicaciones.

En términos formales, necesitamos extraer el gráfico de flujo de control del binario.

Sin embargo, con ramas indirectos por ejemplo, https://en.wikipedia.org/wiki/Indirect_branch, no es fácil de determinar que gráfica, véase también: Indirect jump destination calculation

Cuestiones relacionadas