How to exploit [1]: Starting with GDB.


Estos papers que pienso escribir irán enfocados para aquellos que tengan curiosidad sobre cómo explotar vulnerabilidades, descubrirlas, realizar ingeniería inversa, etc etc..

¿Requisitos?.
Simplemente tener conocimientos básicos de programación.. compiladores, arquitectura, todo lo que nos rodea y usamos habitualmente, y en otros casos lo usamos pero no tenemos idea que lo hacemos.. Un requisito mas podria ser el saber y querer entender ‘el porqué de las cosas’, no tirar un exploit a lo scriptkiddie. De todas maneras intentare enfocarme y detallar lo máximo que pueda. Para el resto, Google. Usare muchas herramientas como GDB, Objdump, IDA, GCC, Python,etc, etc..

Primero, lo aburrido, pero necesario para entender.
Vamos a familiarizarnos un poco con el código fuente, compilar, desensamblar, algunas herramientas que ayudan este proceso y todas esas cosas.
Para empezar tenemos preparado para esta prueba un pequeño código fuente  “analizaMe”,
esta codeado en C, y es de lo mas básico, pero me ayudará a explicarlo de manera rápida.. así que deberíamos compilarlo. ¿Compilar?.

Suponiendo que si llegan hasta acá, siguieron al pie los requisitos de tener conocimientos basicos de programacion, assembler, etc..  no sera muy dificil entender lo que refresco por encima a continuación. Para comprender realmente el significado de Compilar, tenemos que movernos al origen de los procesadores, pero resaltaremos lo importante, el procesador x86.
Intel desarrolló la cpu 8086 que fue el primer procesador x86.. luego creo otros mas avanzados como el 386, 486.. etc.. El procesador x86 que se refiere a los anteriormente mencionados, posee varios registros que son como las ‘variables’ internas para dicho procesador. Algunos de estos registros son los conocidos ‘EAX,ECX,EDX,EBX’, son registros usados para varios fines, entre ellos como contadores o acumuladores.. sirven como variables temporales para el procesador cuando está ejecutando instrucciones. Hay 4 registros mas a resaltar que son ‘EBP, ESP, ESI y EDI’.


Estos registros llevan el nombre de EBP puntero base, ESP puntero de pila, ESI indice de origen y EDI indice destino. El puntero EIP, es el puntero de instrucción, es quien nos ayuda y nos señala a la instrucción que el procesador está leyendo en ese preciso momento.

Bueno, como sabrán con el código fuente no vamos a poder hacer nada, hasta el momento en el que lo compilamos y lo convertimos en un archivo ejecutable.
Let’s Go!

El programa analizaMe:

#include <stdio.h>

int main() {
  int contar;
  for(contar=0; contar < 5; contar++)
  {
    printf("La variable contar vale: %i \n", contar);
  }
}


Bien, lo primero será explicar un poco este código, y luego compilarlo.
Básicamente y de manera rápida, este programa lo que hace es mostrar en pantalla un mensaje y el valor de la variable ‘contar’. Mientras sea la variable menor de 5 el mensaje será mostrado via la función ‘printf’. Cuando llega a 5, el programa finaliza. Analicemoslo mas en detalle..
(cada vez que vean $> es la manera de decirles que son comandos a tipear en la shell)

Vamos a compilar con GCC (es el GNU Compiler Collection), la función de los compiladores básicamente es la de convertir el código fuente en lenguaje máquina, la manera en la que entienden los procesadores.

$> gcc analizaMe.c -o analizaMe
$> chmod +x
$> ./analizaMe

Invocamos al compilador y le decimos que nos compile el codigo fuente en c. El resultado es un nuevo archivo llamado analizaMe. Le damos permisos de ejecución. Y luego lo ejecutamos..

El resultado es el siguiente:

La variable contar vale: 0
La variable contar vale: 1
La variable contar vale: 2
La variable contar vale: 3
La variable contar vale: 4

Ok, vemos el resultado del programa, pero veamos qué es lo que no esta a simple vista, como es que funciona este programa realmente..

Invocamos en la shell, a ojbdump, es una gran utilidad para desensamblar fácilmente binarios compilados.

$> objdump -D analizaMe -M intel |grep -A15 main.:

(Le decimos a objdump que (-D) desensamble el binario ‘analizaMe’, (-M intel) que nos muestre el resultado con la sintaxis Intel ya que es más prolija y acostumbrado que la de AT&T (por default objdump nos arroja esta última por eso es que forzamos a que nos muestre con la de Intel)).. (grep -A15 main.:) invocamos a ‘grep’ y aplicamos una expresión para que nos muestre solo las primeras 15 líneas de la funciona principal, main. Podríamos no limitar a 15, pero arrojaría muchas líneas innecesaria para este momento. De este modo tenemos un primer y rapido vistaso del codigo desensamblado.

Veremos por ‘encima’ el tema de assembler ya que mi interés no es un tutorial de este, sino repasar para introducirlo con el Debugger. Hay muy buenos cursos/tutoriales de assembler, en udemy o internet como para profundizar.

[GDB]


Utilizaremos el crudo y puro gdb desde la línea de comandos con sintaxis INTEL. 
Para configurar GDB con sintaxis INTEL, vamos a hacer lo siguiente:

$> gedit /etc/gdb/gdbint 

Y luego pegamos al final la siguiente línea y guardamos el archivo:

set disassembly-flavor intel

La sintaxis de intel nos mostrará las operaciones de esta manera en gdb:

instrucciones “[destino],[origen]” 
Por ejemplo: “mov ebx, eax

En este ejemplo el valor del registro ‘eax’, sera movido al registro ‘ebx’. 
Recordar que las operaciones del origen y destino pueden ser un valor, 
una dirección de memoria, o un registro como en este caso.

Operaciones comunes que veremos en assembler para refrescar memoria:

MOV: Mueve contenido
INC: Incrementa de a 1 el valor
DEC: Resta de a 1 el valor
ADD: Suma  
SUB: Resta
CMP: Compara dos registros o un registro y dirección de memoria.
JMP/JNE etc: Todos los que empiecen con J.. son saltos condicionales e incondicionales. 
Hay mas pero como dije, son los básicos y el paper no está orientado a un manual de assembler.

El debugger es una herramienta que nos permite ejecutar programas y analizarlos. Ya sea para localizar errores o entender un poco mas a fondo que es lo que hace una determinada aplicación.

GDB dispone de un comando muy particular y uno de los mas útiles a la hora de verificar la memoria.
Este comando se llama ‘examine’ y como su nombre indica, nos sirve para examinar la memoria.
GDB puede examinar detenidamente cada aspecto de la ejecución de un programa, ejecutarlo, detenerlo, verificar los registros, direcciones de memoria, lo que se nos ocurra.
(Lo practico de GDB también es que la mayoría de los comandos se pueden ‘abreviar’, por ejemplo examine se puede acortar a una simple ‘x’ y este se reconoce como un comando)

El comando examine, tiene varias maneras de arrojar la información que requerimos ver en una determinada direccion de memoria. Podemos especificar el formato en el que se muestra usando alguna de las siguiente abreviaturas:

x = Muestra el resultado en Hexadecimal
u = Muestra el resultado en Decimal.
o = Muestra el resultado en Octal.
t = Muestra el resultado en Binario.

$> gdb -q ./analizaMe

De esta manera le pasamos a gdb el binario a debugguear.

(gdb) break main


Punto de interrupción 1 at 0x804840f (en mi caso esto nos responde gdb al poner un break)

Le decimos que genere un breakpoint en la función principal “main”. Un breakpoint no hace otra cosa que poner una barrera una vez que se ejecuta el programa, le dice al gdb que cuando llegue a nuestro breakpoint el programa se parará obligatoriamente ahi en ese break. Podemos poner varios break, de momento solo haremos uno en la función main.

(gdb) run


Breakpoint 1, 0x0804840f in main () (esto nos responde el gdb al encontrar el break que seteamos)

Corremos el programa desde el debugger con run o también con una simple ‘r’ y va a parar en el primer breakpoint que seteamos en la función “main”

(gdb) info register

eax            0x1    1
ecx            0xbffff344    -1073745084
edx            0xbffff2d4    -1073745196
ebx            0xb7fc1000    -1208217600
esp            0xbffff2a8    0xbffff2a8
ebp            0xbffff2a8    0xbffff2a8
esi            0x0    0
edi            0x0    0
eip            0x804840f    0x804840f <main+3>
eflags         0x246    [ PF ZF IF ]
cs             0x73    115
ss             0x7b    123
ds             0x7b    123
es             0x7b    123
fs             0x0      0
gs             0x33    51

Este comando “info register” nos permite ver el estado actual de los registros.
Como ven obtenemos un status de todos los registros de ese preciso momento.

(gdb) info register eip

eip            0x804840f    0x804840f <main+3> 

Podemos filtrar a algún registro en particular escribiendo al lado como en el ejemplo ‘eip’.
Ademas, como les conte antes podemos ‘abreviar’ este comando asi: “i r eip”
Vemos como EIP contiene el valor 0x804840f.

(gdb) x/x $eip
0x804840f <main+3>:    0x83f0e483 

(ahora examinamos la memoria a la que esta señalando el registro EIP usando la direccion almacenada en EIP).

Vemos que donde arriba EIP contenía ‘0x804840f’, examinamos este registro y contiene lo siguiente: 0x83f0e483.

De esa manera usamos el ‘examine’ abreviado con la primer ‘x’, luego le decimos que nos arroje la info en hexadecimal con la segunda ‘x’. Y el registro ‘eip’. Fijense que en este caso hay que poner el registro luego de un $. A diferencia de el info register que no lo anteponemos.

Tambien se puede anteponer un número al formato del comando examine, para examinar varias unidades en esas direcciones objetivos que tengamos. Por ejemplo:

(gdb) x/4x $eip
0x804840f <main+3>:    0x83f0e483    0x44c720ec    0x00001c24    0x19eb0000

Las unidades tienen un tamaño predeterminado de 4 bytes. Conocidas como WORD.
De la misma manera que podemos cambiar el formato que nos arroja el resultado con ‘examine’ ya sea en hexadecimal, octal, binario o decimal, también podemos cambiar el tamaño de las unidades que estamos examinando, agregando una letra de tamaño al final de la de formato y estos son:

b: byte
h: half-word (2 bytes)
w: word (4 bytes)
g: giant o qword (8 bytes)

Veamos un ejemplo práctico:

1) (gdb) x/4xb $eip
0x804840f <main+3>:    0x83    0xe4    0xf0    0x83

2) (gdb) x/4xh $eip
0x804840f <main+3>:    0xe483    0x83f0    0x20ec    0x44c7

3) (gdb) x/4xw $eip
0x804840f <main+3>:    0x83f0e483    0x44c720ec    0x00001c24    0x19eb0000

4) (gdb) x/4xg $eip
0x804840f <main+3>:    0x44c720ec83f0e483    0x19eb000000001c24
0x804841f <main+19>:    0x042444891c24448b    0xe8080484e82404c7

1- Como vemos en el ejemplo uno, le decimos a gdb que nos examine el registro $eip, y nos arroje en 4 unidades, en hexadecimal, y en tamaño byte.
2- En el segundo de la misma manera pero ya con 2 bytes.
3- Igual pero con 4 bytes.
4- Por ultimo igual, pero con 8 bytes.

  • Resumen de este paper
  1. Ya sabemos, compilar un código fuente, gcc analizaMe.c.
  2. Sabemos invocar el gdb y pasarle un binario para debuggearlo: gdb -q ./analizaMe
  3. Sabemos que para poner un breakpoint y que se detenga el programa invocamos el comando ‘break’ + nombre de la función, o dirección en memoria donde queremos que pare.
  4. Sabemos que una vez que para en el breakpoint, podemos verificar el status de los registros mediante ‘info register’ o alguno en particular como en nuestro caso anterior ‘info register eip’.
  5. Sabemos que la herramienta ‘examine’ nos permite verificar todas la memoria, direcciones y valores que se nos ocurra y arrojar el resultado de la manera que mas comoda nos parezca, como en el ejemplo de ‘x/x $eip’ para ver el valor que contiene el registro EIP en ese momento y que nos arroje el resultado en hexadecimal.
  6. Sabemos como mostrar por unidades como “x/2x”
  7. Sabemos cómo mostrar examine en tamaño de unidades b,h,w,g (byte, 2bytes,4bytes, 8bytes).

Hasta el proximo paper…

shkz @ myhack.tech

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *