Destripando código

¡Buenas! hoy vamos a tratar con los bajos fondos de nuestra máquina, en este post veremos cómo se representa un simple código en el lenguaje C a bajo nivel, lo que nos permitirá aprender más sobre el trabajo que realiza nuestra CPU al ejecutar instrucciones.

Muchos de nosotros los programadores, simplemente nos centramos en el código a alto nivel, ya sea porque nuestro jefe quiere el trabajo para ayer, o bien porque no queremos hacer un esfuerzo extra por la misma cantidad de dinero, no nos paramos a ver cómo funciona ese código “por debajo”, desde aquí pretendo “alumbrar” un poco a aquellos que no sepan muy bien cómo va el tema.

Bien, por simpleza, trataremos con un código en C bastante simple, es el siguiente:

#include <stdio.h>
int main () {
   int i;
   for(i=0;i<10;i++){
      puts("Hello, world! \n");
   }
   return 0;
}

Como habreis podido comprobar, es un simple bucle que imprime “Hello, world!” 10 veces por pantalla, vamos a empezar por compilarlo:

gcc -g firstprog.c -o firstprog

donde firstprog.c será el archivo donde tenemos nuestro código, -g indica a gcc que incluya información adicional que luego “recogerá” el depurador gdb (ahora vamos con eso) y -o indica que se generará un archivo de salida llamado firstprog.

Si no tenemos instalado gcc, simplemente tecleamos:

sudo apt-get install gcc

Ya tenemos nuestro ejecutable, para ver que todo ha ido bien, abrimos un terminal y nos situamos en el directorio donde se encuentre, para después teclear:

./firstprog

Ante nosotros deberían aparecer 10 líneas con “Hello, world!”.

Sigamos, vamos a echar un vistazo al código en ensamblador que genera nuestro “programa”, para ello, vamos a utilizar objdump:

objdump -M intel -D firstprog | grep -A20 main.:

con -M estamos indicando que utilice sintaxis intel y con -D indicamos el archivo a examinar, por otra parte, la salida se pasa como entrada mediante un “pipe” al comando grep, que nos mostrará solo el código referente a la función ‘main’.

ensamblador

Ante nosotros aparecen 4 columnas, de izquierda a derecha:

– posiciones de memoria
– instrucciones en hexadecimal
– instrucciones en ensamblador
– operandos

Antes de explicar un poco en qué consiste el código, conviene ver los registros, para el que no sepa qué son, vendrían a ser unas ‘variables’ donde el procesador almacena los datos necesarios para realizar operaciones, si analizamos el código C con gdb (depurador de código) podremos acceder al contenido de los mismos, para ello, vamos a la consola y tecleamos lo siguiente:

gdb -q firstprog

Una vez ejecutado gdb:

set disassemble intel –> indicamos que utilice sintaxis intel
file firstprog –> lee los simbolos de firstprog
list –> Nos permitirá mostrar el código C de nuestro ejecutable

break main –> Establece un punto de ruptura antes de ejecutar la función main

run –> Ejecuta hasta llegar a main()
info register –> muestra info de los registros

Con esta última instrucción, gdb nos mostraría algo similar a esto:

registros

vemos que hay bastates, según la arquitectura de cada procesador pueden variar un poco, pero, por norma general, suele haber:

– registros de propósito general -> Utilizados por la CPU para realizar operaciones lógicas/aritméticas.

– Indices y apuntadores -> Apuntan o hacen referencia a direcciones de memoria (los acabados en ‘i’ y en ‘p’)

– FLAGS -> Para que nos entendamos, vendría a ser un registro con varias banderas que se activan al producirse algún evento, por ejemplo, cuando comparamos registros. (eflags)

– Puntero de instrucción o contador de programa-> Podría decirse que el más importante de todos, es el que “apunta” a la dirección de memoria en ejecución, vendría a ser cómo el niño que apunta con el dedo por donde va leyendo. (rip)

Para aprender más sobre registros, hay mucha info en la red, también puedes contactarnos.

Una vez que hemos hablado de los registros, vamos a examinar un poco el código ensamblador, pero para que nos sea más facil, en gdb, el comando ‘list’ nos muestra el código en C, mientras que ‘disassemble main’ nos muestra el código máquina de la función main, como en la siguiente figura:

disassembly

Bien, empecemos a explicar, si nos situamos en la línea 4, en el operador ‘mov’ , vemos que está moviendo a rbp-4 el número 0 (DWORD = doble palabra, 32 bits, aunque normalmente nos solemos referir a una palabra por 32 bits), esta instrucción se correspondería con la asignación ‘i=0’ de nuestro código C.

Las líneas anteriores no son demasiado importantes para nosotros, simplemente son instrucciones del procesador para poder llevar a cabo lo demás.

Después del ‘mov’ nos encontramos con un ‘jmp’, quiere decir que salte a la posición 0x40053b, normalmente las instucciones que empiezan por ‘j’ modifican el flujo de la ejecución, si ahora nos situamos en la posición especificada anteriormente, veremos un ‘cmp’, es decir, una comparación, está comparando lo que hay en el registro rbp-4 con 9, traduciendo, el bucle for, internamente, hace la comparación de que si ‘i’ es menor que 9, ejecuta la instrucción de dentro, pero si no se cumple la condición, salta fuera y se sigue con la ejecución del programa, por ahora se puede comprobar cómo se corresponde el código C con el ensamblador.

Sigamos, justo debajo vemos un ‘jle’, ‘salta si es menor’, es decir, si el dato en ese registro es menor que 9, salta a 0x40052d, donde nos volvemos a encontrar otro ‘mov’, esta vez se mueve al registro ‘edi’ la dirección 0x4005f4, pero, ¿os imaginais qué puede haber en esa dirección?, vamos a comprobarlo:

gdb también nos permite la posibilidad de examinar direcciones de memoria, si nos fijamos en la parte de abajo de esta imagen:

ascii

con el comando x/s y el argumento 0x4005f4 nos muestra lo que hay en esa dirección, así es, el texto que queremos mostrar por pantalla.

Sigamos con la ejecución del programa, a continuación viene un ‘call’, como su propio nombre indica, haría una llamada a “puts”, la función que se encarga de mostrar el texto por pantalla, internamente, el contador de programa pasaría a apuntar a la dirección donde se almacena la función ‘puts’, para cuando termine volver a apuntar a la dirección inicial (estos cambios de direcciones se realizan con ayuda de la pila, instrucciones ‘push’ y ‘pop’ para introducir y extraer, respectivamente).

Una vez, hecho esto, nos encontramos con ‘add’, suma al registro rbp-4 la cantidad 1, es decir, estaríamos haciendo ‘i++’ en C, veamos la siguiente imagen:

incremento

al principio estoy mostrando el valor del registro rbp-4, como vemos es 0, pero conforme voy ejecutando instrucciones máquina con ‘nexti’, al final de la imagen se aprecia como el valor del registro a cambiado, acabamos de realizar el ‘i++’

Ahora volvemos a llegar a la instrucción de comparación, así 10 veces hasta que no se produzca el salto en la línea de ‘jle’, en las siguientes lineas estaríamos ejecutando instrucciones para salir del bucle y terminar la ejecución de la función main().

Como apunte, también podemos ver qué instrucción hay en una dirección de memoria, es interesante para el tema del puntero de instrucción, si queremos ver a qué dirección apunta (instrucción a ejecutar), veamos esta imagen:

registro instruccion

Como podemos ver, con ‘info register rip’, obtenemos el contenido del mismo, si luego examinamos el contenido de la dirección 0x400524 nos muestra la instrucción que va a ejecutarse.

Puede parecer algo complicado al principio, se necesita entrenar un poco el ojo, como consejo, cuando programeis, utilizad depuradores, observad cómo trabaja vuestro programa, qué es lo que hace internamente.

Espero que no haya sido un aburrimiento y que hayais aprendido un poco :), si teneis alguna duda, comentad.

HackSaludos!

Anuncios

Acerca de Darkvidhck

Estudiante de ingeniería informática, haciendo mis pinitos como desarrollador web, programador, gamer y Linuxero. Aficionado a la seguridad. Eterno viciado al conocimiento.
Esta entrada fue publicada en Programación y etiquetada , , , , , , . Guarda el enlace permanente.

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s