GDB orientado a Pentesting – Parte 2

Continuamos con la segunda parte de esta serie de artículos sobre cómo usar GDB orientado a Pentesting. Tal y como vimos en el GDB orientado a Pentesting – Parte 1 de esta serie,

GDB es un depurador que podemos utilizar para distintas tareas de las cuales las que nos interesan desde el punto de vista del pentesting son aquellas orientadas al análisis de un programa en tiempo de ejecución, de forma que podamos controlar el flujo del programa, así como consultar o modificar valores de variables y registros durante la ejecución del mismo.

En artículo anterior, que nos sirvió de introducción, vimos cómo podemos analizar un programa que ha sido compilado con gcc incluyendo sus símbolos de depuración. En esta ocasión vamos a ver cómo podríamos depurar un programa que no incluye los símbolos de depuración, un escenario mucho más real, ya que cuando tengamos que analizar un programa o una App móvil, no incluirá estos símbolos, lo que dificultará de alguna forma su análisis.

En esta ocasión, siguiendo con ejemplos básico de programas en C, vamos a utilizar uno muy sencillo que realiza la suma de dos números, que el usuario introduce durante le ejecución del programa. Este programa, llamado sumaNumeros, básicamente funciona así:

GDB orientado a Pentesting - Parte 2

¿Fácil, verdad? Introducimos dos sumandos, y el programa nos muestra el resultado de sumarlos. Pero claro, aquí no estamos para jugar a las matemáticas, nuestra intención podría ser, por ejemplo, modificar los valores de los sumandos o el resultado final de la suma. Pasemos a depurar nuestro programa con GDB.

gdb ./sumaNumeros

En primer lugar, prestamos atención al mensaje de GDB cuando se ejecuta, y es que al no haber compilado este programa con la opción –ggdb del compilador gcc, los símbolos de depuración no están disponibles.

GDB orientado a Pentesting - Parte 2

En primer lugar, antes de crear nuestros breakpoints, vamos a echar un vistazo a las funciones del programa:

info functions

GDB orientado a Pentesting - Parte 2

Podemos observar que además de la función principal main, tenemos otra función creada por el programador, llamada resultado. Interesante, tendríamos dos posibles puntos donde colocar los breakpoints.

Comencemos creando un breakpoint (tal y como vimos en el artículo anterior) en la función main.

break main

Y a continuación vamos a ejecutar el programa con el comando run:

run

GDB orientado a Pentesting - Parte 2

Una vez ejecutado y alcanzada la función main en el flujo del programa, éste se para al llegar al primer y único breakpoint, en la dirección 0x0804849f, en la funión main().

Bien, si no tenemos símbolos de depuración, y no podemos acceder a las posibles variables del programa, ¿cómo podríamos consultar los valores de éstas e intentar modificarlos? No nos queda más remedio que desensamblar el código. En GDB usaremos el comando disassemble.

disassemble main

GDB orientado a Pentesting - Parte 2

En este caso, por defecto GDB muestra la información de ensamblador en formato ATT, podemos cambiar el formato a Intel de la siguiente forma:

set disassembly-flavor intel

De esta forma, los que estéis más acostumbrados a ensamblador o al menos hayáis tocado algo de este lenguaje, seguro que os sentís algo más identificados.

Volvemos a ejecutar el comando para desensamblar el código:

disassemble

GDB orientado a Pentesting - Parte 2

Ya lo tenemos en formato Intel, que aunque no haya mucha diferencia, os aseguro que sí la hay 😉

Prestemos atención a los rectángulos rojos de la imagen anterior, del primer disassemble, y podréis ver algunas cosas interesantes, por ejemplo, las llamadas a funciones con “call”. El primer call llama a una función que hace referencia al printf de C para mostrar en pantalla, y el siguiente call hace referencia a scanf para solicitar al usuario que introduzca algo. En la dirección 0x080484ed también podemos ver un add, que podéis imaginar se encargará de sumar los valores.

Pero, ¿dónde se encuentran esos valores? Pues bien, digamos que la CPU tiene unos espacios donde almacena información relativa al programa. Dentro de estos espacios, llamados registros, nos podemos encontrar unos de propósito general (GPR, General Purpose Registers) y que son, entre otros, eax, ebx, ecx, edx

Esto significa que en algún momento durante la ejecución del programa, estos registros en un punto determinado (breakpoint), almacenarán unos valores, por ejemplo los que introduzca el usuario o el resultado de una operación.

Llegados a este punto, tendríamos que tener ya en mente, que cuando el programa llegue a la dirección 0x080484ed para ejecutar el add para sumar los dos valores, éstos estarán almacenados en unos registros de la CPU.

Por lo tanto, vamos a crear otro breakpoint justo cuando se haga la operación de la suma, que se encuentra en la dirección 0x080484ed. Importante, destacar que cuando se crea un breakpoint sobre una dirección y no una función, hay que poner un asterisco (*) delante de la dirección:

break *0x080484ed

Llegados a este punto tendríamos creados dos breakpoints:

GDB orientado a Pentesting - Parte 2

Por lo tanto, vamos a ejecutar el programa con run, alcanzaremos el primer breakpoint en la función main, continuaremos con c, alcanzaremos el segundo breakpoint donde realiza el add de los sumandos y aquí, en este punto, es donde vamos a visualizar los valores de los sumandos. Para ello utilizaremos:

info registers

GDB orientado a Pentesting - Parte 2

Podemos comprobar que edx almacena el primer sumando y eax el segundo sumando. También podemos mostrarlos con el comando print:

print $eax

GDB orientado a Pentesting - Parte 2

Tal y como vimos en el artículo anterior, podríamos modificar el valor con el comando set:

set $edx = 5000

Provocando que el resultado del programa, no sea el esperado.

GDB orientado a Pentesting - Parte 2

En el próximo artículo de esta serie veremos cómo hacer un simple crack a un programa protegido por un código.

Como siempre, espero que haya sido de vuestro interés.

¡Hasta la próxima!