GDB orientado a Pentesting – Parte 1
Introducción a GDB orientado a Pentesting
GDB o GNU Debugger es el depurador escrito por Richard Stallman en 1986 y utilizado en plataformas Unix. Es software libre y está distribuido bajo la licencia GPL.
Aunque se utiliza para depurar programas y trazar posibles errores en tiempo de ejecución, su potencial y características hacen que sea una herramienta muy utilizada para pentesters y expertos en ingeniería inversa.
Es por este motivo por el que desde hacking-etico.com nos hemos decidido a crear una pequeña serie de artículos dedicados a cómo usar esta poderosa herramienta en nuestros trabajos de pentesting que hemos titulado «GDB orientado al Pentesting«.
Con GDB podremos controlar el flujo de un programa, ingeniería inversa o hacer un análisis en tiempo real, también aplicable por ejemplo a entornos iOS del que también tenemos una serie de artículos, y complementaremos precisamente con análisis en tiempo real de una App de iOS.
Pero para poder llegar a ese punto, tenemos que empezar por lo básico, así que en este primer artículo de la serie, empezaremos a ver conceptos básicos de GDB.
Ejemplos prácticos – Binario con símbolos de depuración
Para nuestros ejemplos prácticos nos basaremos en sencillos programas en C y compilados con su gcc. En este punto hay que hacer mención a los símbolos de depuración. Este tipo de compilación, incluyendo estos símbolos, se utiliza cuando un programador quiere depurar su programa, tiene el código fuente y lo compila con estos símbolos, para que el proceso de depuración sea mucho más sencillo.
Supongamos que tenemos nuestro primer programa que queremos depurar, el típico helloWorld.c
#include<stdio.h>
main()
{
printf(«Hello World!\n\n»);
return 0;
}
Una compilación normal se podría hacer de la siguiente forma:
gcc helloWorld.c –o helloWorld_sin_debug
Tendríamos un binario, helloWorld_sin_debug, sin símbolos de depuración, lo que para el programador supondría un hándicap, teniendo en cuenta que es el desarrollador, tiene el código fuente y podría haberlo compilado con símbolos de depuración, que le facilitaría mucho esa labor de depuración. ¿Cómo?
gcc –ggdb helloWorld.c –o helloWorld_con_debug
Aparentemente los binarios helloWorld_sin_debug y helloWorld_con_debug son iguales, pero en el segundo ejemplo incluyen los símbolos de depuración, al haber indicado el parámetro –ggdb.
Es el momento de ejecutar nuestro depurador, y lo vamos a hacer con una llamada al programa helloWorld_con_debug, que recordemos incluye los símbolos de depuración:
gdb ./helloWorld_con_debug
En la última línea que carga gdb justo antes de mostrar el prompt de consola (gdb) precisamente indica que los símbolos se han leído correctamente desde el fichero del programa.
En el caso contrario, depurando un programa sin símbolos de depuración, obtendríamos un mensaje como este:
Reading symbols from /root/pocs/debugging/helloWorld_sin_debug…(no debugging symbols found)…done.
Cuando depuramos un programa que hemos compilado con símbolos de depuración tenemos a nuestra disposición una serie de comandos que nos pueden facilitar información interesante acerca del programa, como pueden ser sus variables o funciones, e incluso el código fuente del programa.
El comando list, nos permitirá ver el código fuente del programa.
Con el comando info podemos ver información acerca de variables o funciones del programa, en el caso de las funciones, la única función es main(). La sintaxis sería la siguiente: info functions
Modificar valores de variables en tiempo de ejecución:
Pasemos ahora a otro ejemplo, con un programa muy sencillo similar al anterior pero que en este caso muestra un Hello acompañado de un nombre que se le pasa como argumento al programa. El programa es helloName.c y lo vamos a compilar con símbolos de depuración.
#include<stdio.h>
int main(int args, char *argv[])
{
printf(«Hello %s!\n\n», argv[1]);return 0;
}
Tras compilarlo con símbolos de depuración, el binario queda como helloName_con_debug que vamos a depurar (observad que no se pasa el argumento del nombre):
gdb ./helloName_con_debug
(gdb)run Miguel
Con el comando run ejecutamos el programa pasándole como argumento el nombre, en este caso Miguel.
Podemos ver que el programa se ha ejecutado correctamente y ha mostrado el argumento que le hemos pasado junto al comando run.
Veamos el mismo programa pero ahora parándolo para ver cómo se ejecuta paso por paso, para ello utilizaremos los famosos breakpoints. Como su nombre indica nos permite poner un punto de parada, por ejemplo en una función del programa o directamente en una dirección de memoria.
Para ello utilizamos el comando break, y vamos a poner el punto de parada en la función main() del programa.
En primer lugar ejecutamos break main, y a continuación ejecutamos el programa con el comando run y pasando como argumento Miguel. Tras su ejecución, el programa alcanza el primer breakpoint (1) y se detiene.
Al haber compilado el programa con los símbolos de depuración, tenemos acceso a sus variables, como por ejemplo a argv[1], que en C almacena los argumentos pasados al programa.
(gdb) print argv[1]
$1 = 0xbffff6e7 «Miguel»
Con un simple print argv[1] podemos ver su valor: «Miguel».
Lo realmente interesante de todo esto, y ahí es donde seguro que se os ocurren ya algunas ideas desde el punto de vista del pentesting o de ingeniería inversa, es que sobre la marcha podemos cambiar el valor de esta variable, con el comando set.
(gdb) set argv[1]=»Aurelio»
(gdb) print argv[1]
$3 = 0x804a008 «Aurelio»
Ahora el valor de argv[1] es «Aurelio». Vamos a continuar el programa y vemos cuál es el resultado final. Para ello utilizamos el comando continue o también c.
Como podéis ver el potencial de GDB para depurar un programa, analizarlo en tiempo de ejecución o controlar su flujo es muy grande.
En próximos artículos veremos cómo analizar programas que no se han compilado con símbolos de depuración, lo que se acerca más al mundo real de los pentesters.
Como siempre, espero que sea de vuestro interés y nos vemos en el próximo artículo en nuestro blog.
¡Saludos!