martes, 11 de septiembre de 2007

Introducción a las herramientas de depuración de linux

En todos los sistemas operativos que merezcan ese nombre hay una serie de utilidades para depurar programas. Para comentar las de linux voy a basarme en el siguiente mini-programa.

#include <stdio.h>

int main(){
char cadena[1024];
printf("Introduce el pass: ");
scanf("%s", cadena);

if(!strcmp(cadena, "esteeselpass")) printf("Correcto\n");
else printf("Incorrecto\n");
}

Si compilamos ese fichero con gcc, obtendremos un ejecutable, yo le he llamado "prueba".

La primera utilidad es objdump, con ella se puede desensamblar ejecutables, bueno, eso y algunas cosas más.

objdump -d prueba > prueba.asm

Ahora en prueba.asm tenemos el fichero con el código desensamblado.

Intentemos averiguar el password del programa anterior, para ello hay que saber algo de ensamblador. Sabemos que en algún momento hay un salto condicional (je, jne, jz, jnz...) que es el que nos lleva a la situación de error, buscando estos saltos podemos ir rápido sobre el código y averiguar la zona en la que se produce la comprobación.

8048472:    e8 d9 fe ff ff           call   8048350 
8048477: 8d 85 f0 fb ff ff lea 0xfffffbf0(%ebp),%eax
804847d: 89 85 e0 fb ff ff mov %eax,0xfffffbe0(%ebp)
8048483: c7 85 dc fb ff ff e3 movl $0x80485e3,0xfffffbdc(%ebp)
804848a: 85 04 08
804848d: c7 85 d8 fb ff ff 0d movl $0xd,0xfffffbd8(%ebp)
8048494: 00 00 00
8048497: fc cld
8048498: 8b b5 e0 fb ff ff mov 0xfffffbe0(%ebp),%esi
804849e: 8b bd dc fb ff ff mov 0xfffffbdc(%ebp),%edi
80484a4: 8b 8d d8 fb ff ff mov 0xfffffbd8(%ebp),%ecx
80484aa: f3 a6 repz cmpsb %es:(%edi),%ds:(%esi)
80484ac: 0f 97 c2 seta %dl
80484af: 0f 92 c0 setb %al
80484b2: 89 d1 mov %edx,%ecx
80484b4: 28 c1 sub %al,%cl
80484b6: 89 c8 mov %ecx,%eax
80484b8: 0f be c0 movsbl %al,%eax
80484bb: 85 c0 test %eax,%eax
80484bd: 75 0e jne 80484cd
80484bf: c7 04 24 f0 85 04 08 movl $0x80485f0,(%esp)
80484c6: e8 b5 fe ff ff call 8048380
80484cb: eb 0c jmp 80484d9
80484cd: c7 04 24 f9 85 04 08 movl $0x80485f9,(%esp)
80484d4: e8 a7 fe ff ff call 8048380

En este código vemos que se hace un scanf, unas operaciones intermedias, luego hay un jne que nos lleva a un puts o a otro (salidas por pantalla diferentes según una condición).

Ahora que ya sabemos por que zona movernos, podemos usar gdb.

gdb es un debugger gnu para linux, ejecutamos:

gdb prueba

En el listado de arriba desensamblado se puede ver que la instrucción que hace la comparación de las cadenas introducida y correcta es la siguiente:

80484aa:    f3 a6                    repz cmpsb %es:(%edi),%ds:(%esi)

asi que dentro de gdb, ejecutamos:

break *0x80484aa

Con lo que la ejecución se parará justo en esa instrucción, ejecutamos:

run

info registers

Obtenemos el valor de edi y esi, en mi caso 0x80485e3 y 0xbf86a3d8, inspeccionamos lo que hay en esas direcciones de memoria:

x/s 0x80485e3
x/s 0xbf86a3d8

Nos aparecen el pass que hemos introducido, y el ¡correcto!

Hay otra forma de conseguir que el programa nos de "su aprobación":

80484bb:    85 c0                    test   %eax,%eax
80484bd: 75 0e jne 80484cd

Vamos a la instrucción justo anterior a la del salto, el test (0x80484bb) ponemos un breakpoint ahí, si ejecutamos:

info registers

Vemos que eax vale 1, asi que la condición nos llevará al error, pero vamos ha hacer lo siguiente:

set $eax=0

Después pulsamos la 'c' para continuar...y obtenemos el mensaje que queríamos obtener aún habiendo introducido mal la contraseña.