Saltar a contenido

GNU Debugger (GDB)

Debugging es una habilidad importante tanto para desarrolladores como para pentesters. Debugging es un término utilizado para encontrar y eliminar problemas (es decir, bugs) en nuestro código, de ahí el nombre de-bugging. Cuando desarrollamos un programa, con mucha frecuencia nos encontraremos con bugs en nuestro código. No es eficiente seguir cambiando nuestro código hasta que haga lo que esperamos. En su lugar, realizamos debugging configurando breakpoints y observando cómo actúa nuestro programa en cada uno de ellos y cómo cambia nuestra entrada entre ellos, lo que debería darnos una idea clara de lo que está causando el bug.

Los programas escritos en lenguajes de alto nivel pueden establecer breakpoints en líneas específicas y ejecutar el programa a través de un debugger para monitorear cómo actúan. Con Assembly, trabajamos con código máquina representado como instrucciones en ensamblador, por lo que nuestros breakpoints se establecen en la ubicación de memoria donde se carga nuestro código máquina, como veremos.

Para depurar nuestros binarios, usaremos un debugger bien conocido para programas Linux llamado GNU Debugger (GDB). Existen otros debuggers similares para Linux, como Radare y Hopper, y para Windows, como Immunity Debugger y WinGDB. También hay debuggers potentes disponibles para muchas plataformas, como IDA Pro y EDB. En este módulo, utilizaremos GDB. Es el más confiable para binarios Linux ya que está desarrollado y mantenido directamente por GNU, lo que le da una excelente integración con el sistema Linux y sus componentes.


Installation

GDB está instalado en muchas distribuciones de Linux, y también viene instalado por defecto en Parrot OS y PwnBox. En caso de que no esté instalado en tu máquina virtual, puedes usar apt para instalarlo con los siguientes comandos:

sudo apt-get update
sudo apt-get install gdb

Una de las grandes características de GDB es su soporte para plugins de terceros. Un excelente plugin que está bien mantenido y tiene buena documentación es GEF. GEF es un plugin gratuito y de código abierto para GDB diseñado específicamente para ingeniería inversa y explotación de binarios. Esto lo convierte en una gran herramienta para aprender.

Para agregar GEF a GDB, podemos usar los siguientes comandos:

wget -O ~/.gdbinit-gef.py -q https://gef.blah.cat/py
echo source ~/.gdbinit-gef.py >> ~/.gdbinit

Getting Started

Ahora que tenemos ambas herramientas instaladas, podemos ejecutar gdb para depurar nuestro binario HelloWorld usando los siguientes comandos, y GEF se cargará automáticamente:

gdb -q ./helloWorld
...SNIP...
gef

Como podemos ver en gef➤, GEF se carga cuando GDB se ejecuta. Si alguna vez tienes problemas con GEF, puedes consultar la Documentación de GEF, y probablemente encontrarás una solución.

En adelante, con frecuencia estaremos ensamblando y enlazando nuestro código en ensamblador y luego ejecutándolo con gdb. Para hacerlo rápidamente, podemos usar el script assembler.sh que escribimos en la sección anterior con el flag -g. Este ensamblará y enlazará el código, y luego lo ejecutará con gdb, de la siguiente manera:

./assembler.sh helloWorld.s -g
...SNIP...
gef

Info

Una vez que GDB esté iniciado, podemos usar el comando info para ver información general sobre el programa, como sus funciones o variables.

Consejo: Si queremos entender cómo funciona cualquier comando dentro de GDB, podemos usar el comando help CMD para obtener su documentación. Por ejemplo, podemos probar ejecutando help info

Functions

Para comenzar, usaremos el comando info para verificar qué functions están definidas dentro del binario:

gef  info functions

All defined functions:

Non-debugging symbols:
0x0000000000401000  _start

Como podemos ver, encontramos nuestra función principal _start.

Variables

También podemos usar el comando info variables para ver todas las variables disponibles dentro del programa:

gef  info variables

All defined variables:

Non-debugging symbols:
0x0000000000402000  message
0x0000000000402012  __bss_start
0x0000000000402012  _edata
0x0000000000402018  _end

Como podemos ver, encontramos el message, junto con algunas otras variables predeterminadas que definen segmentos de memoria. Podemos hacer muchas cosas con las funciones, pero nos centraremos en dos puntos principales: Disassembly y Breakpoints.


Disassemble

Para ver las instrucciones dentro de una función específica, podemos usar el comando disassemble o disas junto con el nombre de la función, de la siguiente manera:

gef  disas _start

Dump of assembler code for function _start:
   0x0000000000401000 <+0>: mov    eax,0x1
   0x0000000000401005 <+5>: mov    edi,0x1
   0x000000000040100a <+10>:    movabs rsi,0x402000
   0x0000000000401014 <+20>:    mov    edx,0x12
   0x0000000000401019 <+25>:    syscall
   0x000000000040101b <+27>:    mov    eax,0x3c
   0x0000000000401020 <+32>:    mov    edi,0x0
   0x0000000000401025 <+37>:    syscall
End of assembler dump.

Como podemos ver, la salida que obtuvimos se asemeja mucho a nuestro código en ensamblador y a la salida de desensamblado que obtuvimos con objdump en la sección anterior. Debemos centrarnos en lo principal de este desensamblado: las direcciones de memoria para cada instrucción y operandos (es decir, argumentos).

Tener la dirección de memoria es fundamental para examinar las variables/operandos y configurar breakpoints para una determinada instrucción.

Podrás notar durante el debugging que algunas direcciones de memoria están en la forma de 0x00000000004xxxxx, en lugar de sus direcciones crudas en memoria 0xffffffffaa8a25ff. Esto se debe al $rip-relative addressing en los ejecutables independientes de posición PIE, en los que las direcciones de memoria se utilizan en relación con su distancia desde el puntero de instrucción $rip dentro de la RAM virtual del programa, en lugar de usar direcciones crudas de memoria. Esta característica puede desactivarse para reducir el riesgo de explotación de binarios.

A continuación, revisaremos los conceptos básicos de debugging con GDB utilizando breakpoints, examinando datos y avanzando paso a paso a través del programa.