miércoles, 1 de febrero de 2012

Introducción a la ingeniería inversa x86 (Parte I)

Una de las temáticas que más me han llamado la atención y por varios motivos menos e tocado el mundo de la seguridad de la información, son la ingeniería inversa y el desarrollo de exploits. Me parece una tarea realmente interesante y con una gran cantidad de profesionales detrás de ella. Por ese y algún que otro motivo, he decidido realizar una serie de entradas en relación a dichos temas y así poder descubrir un poco como funciona todo este mundo.

Teniendo en cuenta mi desconocimiento del lenguaje ensamblador y de la arquitectura x86 empezaremos por lo más fácil. Así que iremos creando pequeños códigos en lenguaje C e interpretando en ensamblador que se está haciendo.

Pero antes de meternos de lleno, recordaremos muy por encima algunos registros básicos, las instrucciones principales y el funcionamiento de la pila.

Registros:

Utilizados para facilitar la tarea en el procesado de las instrucciones, cómo para almacenar datos que se utilizaran posteriormente por las mismas. Estos son alguno de los registros básicos que existen:

  1. EAX (Accumulator register): Utilizado tanto para realizar cálculos, cómo para el almacenamiento de valores de retorno en "calls".
  2. EDX (Data register): Extensión de EAX, utilizada para el almacenamiento de datos en cálculos más complejos.
  3. ECX (Count register): Utilizado en funciones que necesiten de contadores, como por ejemplo bucles.
  4. EBX (Base register): Se suele utilizar para apuntar a datos situados en la memoria.
  5. ESI (Source index): Utilizado para la lectura de datos.
  6. EDI (Destination index): Utilizado para la escritura de datos.
  7. ESP (Stack pointer): Apunta a la cima de la pila “stack”.
  8. EBP (Base pointer: Apunta a la base de la pila “stack”.

Instrucciones

Son acciones predefinidas en el lenguaje ensamblador. Algunas de las más habituales de ver son:

  1. Push: Guarda el valor en la pila.
  2. Pop: Recupera valor de la pila.
  3. Mov (dst, src): Copia el operador “src” en el operador “dst”.
  4. Lea (reg, src): Copia una dirección de memoria en el registro destino (ej: EAX).
  5. Add (o1, o2): Suma los dos operadores y los almacena en el operador uno.
  6. Sub (o1, o2): Resta el valor del segundo operador sobre el primero y lo almacena en el primer operador.
  7. Inc: Incrementa en 1 el valor del operador indicado.
  8. Dec: Decrementa en 1 el valor del operador indicado.
  9. And: El resultado es 1 si los dos operadores son iguales, y 0 en cualquier otro caso.
  10. Or: El resultado es 1 si uno o los dos operandos es 1, y 0 en cualquier otro caso.
  11. Cmp: Compara dos operadores.
  12. Jmp: Salta a la dirección indicada.
  13. Call: Llama/Salta a la dirección/función indicada.
  14. Nop: Not Operation.

PILA (Stack)

La PILA o Stack es un conjunto de direcciones de memoria encargadas de almacena información de llamadas a funciones, variables locales, direcciones de retorno a funciones anteriores, entre otras tareas. La PILA es dinámica, por lo que va cambiando su tamaño dependiendo de la función a la cual se encuentre asociada, dispone de una estructura “First In, Last Out”, por lo que lo último que entra será lo primero en salir, delimitada siempre por su cima (ESP) y por su base (EBP).

Para entender correctamente el funcionamiento de la PILA vamos a ver un ejemplo. Imaginemos que disponemos de dos funciones “main()” e “imprimir_dato()”, donde la segunda se llamada dentro del código de la primera.

image

Vamos a ver como queda la Stack justo antes de realizar la llamada a printf de la “función 1”.

image

Ahora vamos a visualizar el estado de la pila justo antes de la llamada al printf de la “función 2”.

image

Si os fijáis, la Stack a medida que va habiendo funciones dentro de funciones, se van apilando de forma decreciente la nueva PILA sobre la anterior, dejando entre medias la famosa “dirección de retorno” encargada en este caso de volver la aplicación al punto exacto de la “función 1” después de haber llamado a “función 2”.

Por lo tanto, podemos concretar que cada función que se esté ejecutando en una aplicación tendrá asociada su “propia” Stack.

Existen multitud de registros e instrucciones más, simplemente he nombrado algunas de las que más vamos a ver. A medida que vayamos viendo nuevas las iremos comentando y explicando para poder entender correctamente el código.

Hasta aquí el POST de hoy, en la próxima entrega de la serie veremos ya más código profundidad y como interpretarlo.

Un Saludo!!

No hay comentarios:

Publicar un comentario en la entrada