Archivo de la categoría: Ingeniería Inversa

Introducción a la ingeniería inversa en linux (II)

Esta es la parte 2, podéis ver la 1 aquí .

Debido a que hay personas a las que les ha parecido interesante la introducción a la ingeniería inversa en linux, voy a seguir publicando mis progresos en esta rama de la seguridad informática.

Lo que voy a hacer hoy,  va a ser analizar otro crackme (más complicado que el anterior) que he encontrado en la página http://www.crackmes.de .

Concretamente, el crackme que vamos a usar ahora lo podéis encontrar en esta dirección : http://www.crackmes.de/users/lord/easy_linux_crackme/ .

Bueno, una vez que lo habéis descargado y le habéis asignado permisos de ejecución vamos a proceder al análisis.

Lo primero que vamos a hacer es ejecutarlo y ver que pasa.

[Familia@linuxArriba inge]$ ./cm1eng
Password : 123456

[Familia@linuxArriba inge]$

Lo primero que podemos observar es que mi nombre de usuario es Familia, lo es xD .No nos muestra ningún mensaje de ningún tipo por lo que podemos pensar que no hemos acertado la contraseña. Ahora, vamos a aplicarle el comando strings para ver si está guardada la contraseña en texto plano.

[Familia@linuxArriba inge]$ strings cm1eng

Password :

Great you did it !:)

QTBXCTU

[Familia@linuxArriba inge]$

Y en este momento yo ya pensé que se acababa el crackme porque habíamos encontrado la contraseña (QTBXCTU), pero si la probáis veréis que el programa hace los mismo que con cualquier otra contraseña (bueno, no cualquier otra, por eso estamos intentando crackearlo).

Como no hemos tenido éxito , vamos a proceder a desensamblarlo.

Para desensamblar el binario vamos a usar el programa objdump que probablemente ya os venga instalado en vuestra distribución de linux. Para desensamblar, ejecutamos el siguiente comando: “objdump -d cm1eng

He pegado la salida del comando en pastebin porque quizás aqui no quedaba muy bien. Podéis verla aquí : http://pastebin.com/MbypxFXL .

Antes de continuar os voy a enseñar una cosa importante de ASM en linux. Cada vez que se hace la interrupción 0x80, lo que se hace es llamar a una syscall. Dicho muy mal y pronto, una syscall es como una función de la API del sistema linux ( o cualquier unix-based system ). Por ejemplo, hay algunas syscalls que sirven para escribir (ya sea en la pantalla, en ficheros …) , otras para cambiar el permiso de archivos , para crear threads … Aquí podemos encontrar una lista con syscalls http://fxr.watson.org/fxr/source/kern/syscalls.c. Cada syscall tiene asignada un número que será muy importante para identificarla.

El procedimiento para llamar a una syscall en assembly sería algo así:

  1. Guardar en eax el número de la syscall (imaginemos que queremos usar  la syscall write, cuyo número es 4): “mov $4, %eax
  2. En ebx se guarda el primer parámetro (en nuestro caso es un file descriptor, y como nosotros queremos escribir en la standart output elegimos el 1): “mov $1 , %ebx
  3. En ecx el 2º parámetro. En la syscall write es el mensaje, osea que le pasamos la dirección de nuestra string : “mov $0xblabla, %ecx
  4. En edx el 3º parámetro (así consecutivamente). En write() sería la longitud del mensaje, si el mensaje fuera hola sería: “mov $4,%edx
  5. Llamar a la interrupción 0x80. “int 0x80

Osea, siempre que veamos un  “int 0x80” tenemos que saber que se está haciendo una syscall y tenemos que ver que es lo que hacen esas syscalls.

Bueno, ahora que podemos analizar el código vemos que al principio hay dos syscalls, que son write() y read() y por la ejecución del programa sabemos que esa es la parte que se encarga de mostrarnos el “Password :” y de leer la contraseña que pongamos. Si no me creéis podéis mirar en las direcciones de memoria que vienen ahí.  Luego vemos esto :

mov    $0x8049126,%esi

Si desde el gdb miramos que hay en esa dirección de memoria (printf “%s\n” , 0x8049126 o x/s 0x8049126) veremos que se encuentra la string “QTBXCTU” que intentamos meter antes como contraseña. En la siguiente línea se copia el contenido de %esi (la string) al registro %edi y en la siguiente, se pone a 0 el registro %ebx que veremos que va a funcionar como un contador.

Poniéndonos a mirar el resto del código , podemos notar que desde la dirección 0x80480b6 hasta 0x80480c3 hace lo siguiente :

  1. :Cifra
  2. Guarda el byte número %ebx de la cadena de %esi en %al
  3. %al = %al xor 0x21
  4. Guarda %al en su sitio correspondiente en %edi (posición %ebx)
  5. %ebx = %ebx+1
  6. Si %ebx es igual 7 continúa, si es distinto vuelve a Cifra.

Tras eso, vemos que guarda nuestra cadena (la que hemos insertado nosotros, sabemos cual es debido a que la pasa como parámetro en la syscall read (3)) en el registro %esi y luego guarda la cadena ya modificada.  Luego compara los primeros 7 bytes de las cadenas y si coinciden, veremos el cartel del chico bueno, si no coinciden saldrá directamente.

Tras ver esto, hay dos soluciones :

  1. Poner un breakpoint en alguna dirección de memoria en la que ya se haya modificado la cadena y ver la cadena.
  2. Cifrar nosotros la cadena a mano(quien dice a mano dice hacerse un programita para verlo)

Yo como me imagino que no tenéis ningún problema con la programación pero que puede ser distinto con el manejo de debuggers voy a optar por la primera.  La dirección en la que voy a poner el breakpoint va a ser en 0x80480c5 porque es ahí donde va cuando %ebx es igual a 7 y por lo tanto ya ha acabado la rutina de cifrado.

Abrimos el programa con el gdb ( “gdb cm1eng” ) y para poner el breakpoint lo hacemos así:

(gdb) break *0x80480c5

Breakpoint 1 at 0x80480c5

(gdb) run #corremos el programa

Nos saldrá de nuevo el prompt para meter la contraseña y cuando metamos la que sea llegará al breakpoint. En ese momento, la string a la que se va a comparar nuestra string ya está modificada y solo tenemos que ver su contenido. Si miráis el código, veréis que esa string se encuentra en la dirección 0x8049126.

(gdb) x/s 0x8049126

0x8049126: “pucybut”

Fuck yeah! Ya tenemos ahí nuestra contraseña de nuevo. Si nos vamos a ejecutar el programa:

[Familia@linuxArriba inge]$ ./cm1eng
Password : pucybut

Great you did it !:)
[Familia@linuxArriba inge]$

 

Finalmente hemos conseguido reventar la protección de este crackme.

Espero que les haya gustado esta nueva parte y en la próxima parte aprenderemos como parchear binarios de este tipo para poder conseguir el cartel de chico bueno sin siquiera saber la contraseña.

Anuncios

Introducción a la ingeniería inversa en linux (I)

Esta es la parte 1, podéis ver la 2 aquí .

La ingeniería inversa es un tema que siempre me ha llamado la atención aunque nunca me he sentido muy cómodo en ella.  Esta mañana me propuse el reto de crear un crackme a modo de ejemplo y conseguir crackearlo. Lo he conseguido (a muchos no les parecerá algo muy difícil, pero para mi es un mundo) y voy a mostraros como lo he hecho.

Antes de nada, para seguir esta introducción necesitaremos una distribución de linux (obvio) , gdb (suele venir instalado en todas las distros) y el paquete gcc-g++ para compilar nuestro crackme. Serán casi necesarios conocimientos de C/C++ y de ASM será útiles aunque no obligatorios.

Empecemos. Este va a ser el código de nuestro primer crackme :

#include <iostream>
#include <string.h>

using namespace std;

int main()
{
char palabra[50];

cout << ” Cual es la contrasenia ?? ” << endl;
cin >> palabra;

if (!strcmp(palabra,”alfffamola”))
{
cout<< ” Yeeeeahh!! Tu tambien molas” <<endl;
}
else
cout<< “Pues esa no es la contrasenia” <<endl;

return 0;
}

Es verdaderamente sencillo y me imagino que no hacer falta que lo explique.  Bueno, nos disponemos a compilar con ” g++ crackme.cpp -o crackme y ya tenemos nuestro crackme ejecutable.

Veamos, nuestro objetivo va a ser encontrar donde está la “contraseña”, en este caso “alfffamola” . Para eso, nos vamos al debugger :

$ gdb crackme

Y lo primero que vamos a hacer va a ser desensamblar la función main, lo hacemos de esta forma :

(gdb) disas main
Dump of assembler code for function main:
0x080486d4 <+0>:     push   %ebp
0x080486d5 <+1>:     mov    %esp,%ebp
0x080486d7 <+3>:     and    $0xfffffff0,%esp
0x080486da <+6>:     sub    $0x50,%esp
0x080486dd <+9>:     movl   $0x80488a4,0x4(%esp)
0x080486e5 <+17>:    movl   $0x8049c00,(%esp)
0x080486ec <+24>:    call   0x80485c8 <_ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc@plt>
0x080486f1 <+29>:    movl   $0x80485f8,0x4(%esp)
0x080486f9 <+37>:    mov    %eax,(%esp)
0x080486fc <+40>:    call   0x80485e8 <_ZNSolsEPFRSoS_E@plt>
0x08048701 <+45>:    lea    0x1e(%esp),%eax
0x08048705 <+49>:    mov    %eax,0x4(%esp)
0x08048709 <+53>:    movl   $0x8049b60,(%esp)
0x08048710 <+60>:    call   0x80485d8 <_ZStrsIcSt11char_traitsIcEERSt13basic_istreamIT_T0_ES6_PS3_@plt>
0x08048715 <+65>:    movl   $0x80488c0,0x4(%esp)
0x0804871d <+73>:    lea    0x1e(%esp),%eax
0x08048721 <+77>:    mov    %eax,(%esp)
0x08048724 <+80>:    call   0x8048608 <strcmp@plt>
0x08048729 <+85>:    test   %eax,%eax
0x0804872b <+87>:    jne    0x8048753 <main+127>
0x0804872d <+89>:    movl   $0x80488cb,0x4(%esp)
0x08048735 <+97>:    movl   $0x8049c00,(%esp)
0x0804873c <+104>:   call   0x80485c8 <_ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc@plt>
0x08048741 <+109>:   movl   $0x80485f8,0x4(%esp)
0x08048749 <+117>:   mov    %eax,(%esp)
0x0804874c <+120>:   call   0x80485e8 <_ZNSolsEPFRSoS_E@plt>
0x08048751 <+125>:   jmp    0x8048777 <main+163>
0x08048753 <+127>:   movl   $0x80488e8,0x4(%esp)
0x0804875b <+135>:   movl   $0x8049c00,(%esp)
0x08048762 <+142>:   call   0x80485c8 <_ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc@plt>
0x08048767 <+147>:   movl   $0x80485f8,0x4(%esp)
0x0804876f <+155>:   mov    %eax,(%esp)
0x08048772 <+158>:   call   0x80485e8 <_ZNSolsEPFRSoS_E@plt>
0x08048777 <+163>:   mov    $0x0,%eax
0x0804877c <+168>:   leave
0x0804877d <+169>:   ret
End of assembler dump.
(gdb)

Y ahí podemos ver varias cosas importantes, main+24, refiere al primer cout; main+60, al cin;  luego en main+104 y en main+142 están los cout de “tu tambien molas” y “pues esa no es la contraseña”. Pero lo más impmortante de todo, es la llamada a strcmp en main+80. Ahí es donde se hace la comparación de las cadenas y se comprueba si la contraseña ingresada es válida.

Algunos conceptos que tenemos que tener claros son :

  • En ASM, los parámetros de las funciones se pasan moviendo las direcciones de estos a la pila, y luego ejecutando el call.  En ESP estará el primer parámetro y en ESP+4 estará el segundo parámetro (así consecutivamente).
  • En linux se usa el ASM AT&T osea, que :” mov %eax, %ebx ” copia %eax en %ebx y no al revés.

Nosotros tenemos que ver los parámetros que se le pasan a la función strcmp() porque ahí estarán la cadena que nosotros ingresemos y nuestra ansiada contraseña. Como la contraseña es el segundo parámetro, en el momento de llamar a strcmp() estará en ESP+0x4. Si nos ponemos a ver los movimientos que hay antes de la llamada a strcmp() podemos ver uno muy interesante : “movl   $0x80488c0,0x4(%esp)” . Esa instrucción es la que mueve una dirección a ESP+4, que es la dirección en la que tiene que estar la contraseña, por lo tanto, ¡en 0x80488c0 está nuestra querida contraseña! Vamos a comprobarlo :

(gdb) printf “%s\n” , 0x80488c0
alfffamola
(gdb)

Fuck Yeah! hemos encontrado nuestra contraseña sin siquiera ejecutar el programa. Ahora solo nos quedaría ejecutar el programa de nuevo e ingresar “alfffamola” para saber que nosotros también molamos.

También existe un método que es bastante sencillo pero con el que se aprende bastante menos que es usar el comando strings.

[braulio@linuxArriba inge]$ strings crackme
/lib/ld-linux.so.2
libstdc++.so.6
__gmon_start__
_Jv_RegisterClasses
_ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_
_ZSt4cout
_ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc
_ZNSt8ios_base4InitC1Ev
_ZSt3cin
_ZStrsIcSt11char_traitsIcEERSt13basic_istreamIT_T0_ES6_PS3_
_ZNSt8ios_base4InitD1Ev
_ZNSolsEPFRSoS_E
libm.so.6
libgcc_s.so.1
libc.so.6
_IO_stdin_used
__cxa_atexit
strcmp
__libc_start_main
GLIBC_2.0
GLIBC_2.1.3
GLIBCXX_3.4
PTRh@
[^_]
Cual es la contrasenia ??
alfffamola
Yeeeeahh!! Tu tambien molas
Pues esa no es la contrasenia

Y ahí se ven las strings estáticas del programa. Como ahí la única que llama la atención es alfffamola, esa sería la primera que probaríamos (al menos yo lo haría así).

Bueno, aquí es donde acaba esta pequeña introducción.