martes, 21 de febrero de 2012

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

Tras aprender que es y de que está compuesto el registro de estado y ver algunas operaciones matemáticas, hoy continuaremos con alguno de los códigos que habitualmente nos encontraremos al realizar ingeniería inversa sobre un binario.

Vamos a analizar el siguiente código:

image

Si os fijáis este caso es similar al código que analizamos en la segunda entrega de la serie, una de las diferencias claves en este código es la nueva instrucción JG (Jump if Greater) que podemos ver en la dirección 0x004013EA. Como se ha podido observar se trata de una instrucción de salto que se efectuará en el caso que la comparación anterior sea mayor al valor indicado.

Debido a que nos encontraremos una gran cantidad de instrucciones de salto cuando se está realizando ingeniería inversa sobre un binario vamos a recordar algunas de ellas y como funcionan.

image

Como habréis podido observar los saltos no tienen ningún misterio ya que únicamente es necesario acordarse de su función su correspondiente valor en el registro de estado. Por lo que, cuando se cumpla la condición de los flags se realizara el salto a la dirección indicada en la instrucción.

Operaciones Lógicas

Ya que vimos en el anterior post como realizar cálculos matemáticos mediante las instrucciones SUB, IMUL y DIV, hoy veremos como se realizan cálculos lógicos a nivel de bit a través de las instrucciones AND, NOT, OR y XOR.

(AND dst, src) Devuelve un valor verdadero, siempre que los valores dst y src sean verdaderos.

image

(NOT dst) Niega el valor dst, y lo almacena en el mismo operador.

image

(OR dst, src) Devuelve un valor verdadero siempre que dst o src sean verdaderos.

image

(XOR dst, src) Devuelve un valor verdadero si únicamente uno de los valores src o dst es verdadero.

image

Y os preguntaréis, ¿Y todo esto para que nos sirve? Lo veremos de una forma práctica pronto en la última publicación de la serie de introducción a la ingeniería inversa en la arquitectura x86.

Un Saludo!!

miércoles, 8 de febrero de 2012

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

En la entrada anterior vimos algunos ejemplos de código en C y su equivalente en ensamblador. Hoy continuaremos con algunos ejemplos más que nos permitan entender más como funcionan los registros y ensamblador.

Pero antes de comenzar y ya que en el anterior post vimos un ejemplo de salto (JLE) debido a un bucle vamos a profundizar un poco más en el asunto.

Si recordáis la instrucción JLE (Jump if Less than or Equal) realizaba el salto a la dirección indicada si la condición se cumplía. ¿Pero como sabe si esta se cumple o no? Aquí es donde entra el registro de estado y sus FLAGS, el cual sirve para indicar el estado actual de la máquina y el resultado de algún procesamiento mediante bits de estado, entre los cuales podemos encontrar los siguientes:

  1. OF (overflow): Indica desbordamiento del bit de mayor orden después de una operación aritmética de números con signo (1=existe overflow; 0=no existe overflow).
  2. SF (Sign): Contiene el signo resultante de una operación aritmética (0=positivo; 1=negativo).
  3. PF (paridad): Indica si el número de bits 1, del byte menos significativos de una operación, es par (0=número de bits 1 es impar; 1=número de bits 1 es par).
  4. CF (Carry): Contiene el acarreo del bit de mayor orden después de una operación aritmética.
  5. ZF (Zero): Indica el resultado de una operación aritmética o de comparación (0=resultado diferente de cero; 1=resultado igual a cero).

Como ya habréis intuido, los flags ZF, SF y OF serán los más utilizados en comparadores y saltos. Retornamos al ejemplo del post anterior donde teníamos algo similar al siguiente código:

image

Teniendo en cuenta que se está ejecutando la instrucción JLE, nos interesa saber con que registros de estado trabaja la misma (http://faydoc.tripod.com/cpu/jle.htm).

0F 8E

JLE

Jump near if less or equal (ZF=1 or SF<>OF)

Como se ha podido observar, la instrucción JLE trabaja con los registros ZF, SF y OF, por lo que mientras la condición se cumpla nos encontraremos dentro del bucle. Para verlo todo más claro, vamos a ver la secuencia en ensamblador y como afecta al registro de estado.

image

Como veis, hasta que NO se ha cumplido la sentencia (ZF=1 o SF <> OF) no se ha salido del bucle. De este modo en la vuelta diez el bucle dejará de ejecutarse.

Una vez entendido como funcionan algunos de los registros de estado, continuamos con alguna de las equivalencias entre lenguaje C y ensamblador.

4 - Operaciones matemáticas

Vamos a ver como realiza ensamblador algunos cálculos matemáticos básicos.

image

Tal y como se puede observar en la captura anterior, se trata de un simple código que calcula la suma, resta, multiplicación y división de dos variables. Voy a dividir el código en cuatro partes para visualizarlo con más facilidad.

Empezamos con una simple suma:

image

Utilizando la instrucción LEA carga el contenido de EAX+EDX en EAX, por lo que ya tenemos nuestro resultado en un registro para posteriormente imprimirlo por pantalla.

En el siguiente código se encuentra la equivalencia a la resta.

image

En este caso se utiliza la instrucción SUB para restar el contenido de EAX a ECX, y almacenarlo nuevamente en ECX.

En el siguiente código se encuentra la equivalencia a la multiplicación.

image

Para realizar la multiplicación de dos variables, se utiliza la instrucción IMUL, el cual multiplica el primer valor por el segundo, almacenando el resultado de la operación en el primer valor.

En el siguiente código se encuentra la equivalencia a la división.

image

Para realizar la división de dos variables son necesarias dos instrucciones:

  1. CDQ se encarga de convertir el valor de 32 bits almacenado en EAX, en uno de 64 bits almacenado en EDX:EAX.
  2. IDIV se encarga de dividir el valor almacenado en EAX con el valor que se le pasa a la instrucción.

Después de conocer parte del registro de estado, como es utilizado para realizar bucles y algunas operaciones matemáticas básicas, doy por finalizado el post de hoy. Pronto volveré con algo más de esta serie de introducción a la ingeniería inversa :)

Un Saludo!!

sábado, 4 de febrero de 2012

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

Continuando con la serie de introducción a la ingeniería inversa, y una vez visto un pequeño resumen de algunos de los registros, comentado algunas de las instrucciones más vistas y un resumen de cómo funciona la Stack, pasamos a ver como podemos interpretar código en ensamblador y no perdernos en el intento.

Vamos a ver unos cuantos ejemplos de códigos en lenguaje C y su equivalencia en lenguaje ensamblador. (Para el desensamblado de la aplicación podéis utilizar cualquiera de las muchísimas herramientas que existen: OllyDbg, IDA Pro, Immunity Debugger, gdb, radare, etcétera, en futuros posts haré una pequeña mención a cada una de ellas detallando sus comandos y funciones básicas).

1 - Hello World

Comenzaremos con el típico “Hello World”.

image

Tal y como se puede observar, el código es bastante simple de entender.

  • 0x004013C6: Hasta dicha dirección nos encontramos con el prologo de la función (inicialización de la misma).
  • 0x004013C9: Nos encontramos con la instrucción CALL que seguramente la habrá introducido el compilador para llamar a vete a saber tu qué ;)
  • 0x004013CE: Se puede observar como se copia (MOV) el valor 0x00403064 asociada a la cadena “Hello World” en ESP (cima de la pila), por lo que ya tenemos en la PILA el valor que necesitamos.
  • 0x004013D5: Se llame a la función PUTS, la cual cogerá de la cima de la pila el valor que va a imprimir por pantalla (“Hello World”).
  • 0x004013DA: Tenemos el epílogo, donde se restaura la pila y retornamos a la función anterior.

2 - Hello World (Argumentos)

Pasamos a ver como se manejan los argumentos que le pasamos a una función.

image

Ya que el código es similar al anterior pasaremos a detallar lo más relevante.

  • 0x004013CE y 0x004013D1: Copia el valor de “EBP+arg_4” que equivale a “argv” en EAX, acto seguido incrementa el valor de EAX en 4 para así poder acceder a “argv[1]” donde se encuentra la dirección que apunta al valor que se ha pasado por argumento a la aplicación.
  • 0x004013D4, 0x004013D6 y 0x004013D9: Se copia el contenido de la dirección almacenada en [EAX] a EAX. Y para finalizar copiamos EAX en la cima de la pila para que la función PUTS pueda imprimir por pantalla el texto introducido.

Aunque parezca lioso al leerlo, os recomiendo debuggear paso a paso la aplicación fijándoos bien en como se van rellenando los registros y la pila.

3 - Función FOR

Vamos a ver como podemos detectar que se está realizando un bucle en nuestro código.

image

Si os fijáis, podemos definir claramente cuatro partes de código que realizan funciones distintas.

  1. Prologo y declaración de variables.
  2. (loc_4013E7): Encargado de preparar los parámetros y llamar a la función printf. De esta parte podemos destacar la instrucción INC, la cual incrementará en uno el valor indicado [esp+28].
  3. (loc_4013FF): Parte encargada de verificar el estado del bucle. En esta parte nos encontramos con una nueva instrucción JLE (Jump if Less than or Equal) saltar si es menor o igual, que junto a la instrucción CMP anterior se forma la estructura de comparar el valor [esp+28] con 9, si el resultado es menor o igual la instrucción JLE nos hará saltar a (loc_4013E7), de lo contrario continuará con la zona 4.
  4. Epílogo de la función encargado de restaura la pila y retornamos a la función anterior.

De este modo, ya podremos identificar un bucle en código ensamblador.

Os dejo también el flowgraph que nos ofrece IDA PRO, el cual de forma gráfica nos ayuda a interpretar mucho más rápido que función está realizando el código analizado.

image

Esto es todo por hoy, en las próximas entregas seguiremos interpretando algo más de código para familiarizarnos aún más con todo este lio ;)

Un saludo!!

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!!