Ingeniería inversa con radare [Parte 1]

Iniciado por sadfud, Septiembre 24, 2017, 03:34:53 AM

Tema anterior - Siguiente tema

0 Miembros y 1 Visitante están viendo este tema.

Buenas Underc0ders, este sera el primer post de un total entre 5 y 7 que tienen como objetivo enseñar como proceder en la ingenieria inversa con el framework radare2.


Lo primero que hay que hacer es instalar radare, las instrucciones de instalacion se encuentran en su repositorio de github.
No tienes permitido ver los links. Registrarse o Entrar a mi cuenta

Para los vagos:
Código: bash
git clone https://github.com/radare/radare2
cd radare2/sys
chmod +x ./install.sh
sudo ./install.sh


Antes de nada algunos recursos utiles:
Documentacion oficial de radare: No tienes permitido ver los links. Registrarse o Entrar a mi cuenta
Informacion sobre arquitecturas e ingenieria inversa: No tienes permitido ver los links. Registrarse o Entrar a mi cuenta

En esta parte vamos a trabajar sobre la arquitectura x86, con sintaxis intel, dejo una chuleta con las instrucciones



El binario sobre el que trabajaremos en esta parte es el conocido IOLI crackme 0x00 (Podeis descargar todos los niveles aqui No tienes permitido ver los links. Registrarse o Entrar a mi cuenta )

En el archivo comprimido hay un archivo README.txt que nos da pistas sobre los niveles, en este nivel por ser el primero usaremos la ayuda que nos da.

Respecto a este nivel nos dice que las "strings son nuestras amigas".

Si bien esta seria de posts trataran exlusivamente sobre ingenieria inversa con radare2, sin usar ninguna otra cosa, me gustaria en este primer nivel resolverlo de varias sencillas maneras sin r2.

El primer metodo seria usando strings como nos indica el txt con
Código: bash
strings crackme0x00
. Output:
Código: bash
/lib/ld-linux.so.2
__gmon_start__
libc.so.6
printf
strcmp
scanf
_IO_stdin_used
__libc_start_main
GLIBC_2.0
PTRh
IOLI Crackme Level 0x00
Password:
250382 #esta es la contraseña del primer nivel
Invalid Password!
Password OK :)
GCC: (GNU) 3.4.6 (Gentoo 3.4.6-r2, ssp-3.4.6-1.0, pie-8.7.10)
GCC: (GNU) 3.4.6 (Gentoo 3.4.6-r2, ssp-3.4.6-1.0, pie-8.7.10)
GCC: (GNU) 3.4.6 (Gentoo 3.4.6-r2, ssp-3.4.6-1.0, pie-8.7.10)
GCC: (GNU) 3.4.6 (Gentoo 3.4.6-r2, ssp-3.4.6-1.0, pie-8.7.10)
GCC: (GNU) 3.4.6 (Gentoo 3.4.6-r2, ssp-3.4.6-1.0, pie-8.7.10)
GCC: (GNU) 3.4.6 (Gentoo 3.4.6-r2, ssp-3.4.6-1.0, pie-8.7.10)
GCC: (GNU) 3.4.6 (Gentoo 3.4.6-r2, ssp-3.4.6-1.0, pie-8.7.10)
.symtab
.strtab
.shstrtab
.interp
.note.ABI-tag
.gnu.hash
.dynsym
.dynstr
.gnu.version
.gnu.version_r
.rel.dyn
.rel.plt
.init
.text
.fini
.rodata
.eh_frame
.ctors
.dtors
.jcr
.dynamic
.got
.got.plt
.data
.bss
.comment
crtstuff.c
__CTOR_LIST__
__DTOR_LIST__
__JCR_LIST__
completed.1
__do_global_dtors_aux
frame_dummy
__CTOR_END__
__DTOR_END__
__FRAME_END__
__JCR_END__
__do_global_ctors_aux
crackme0x00.c
_GLOBAL_OFFSET_TABLE_
__init_array_end
__init_array_start
_DYNAMIC
data_start
__libc_csu_fini
_start
__gmon_start__
_Jv_RegisterClasses
_fp_hw
_fini
__libc_start_main@@GLIBC_2.0
_IO_stdin_used
scanf@@GLIBC_2.0
__data_start
__dso_handle
__libc_csu_init
printf@@GLIBC_2.0
__bss_start
_end
_edata
strcmp@@GLIBC_2.0
__i686.get_pc_thunk.bx
main
_init


Vemos que solo con listar las strings del binario conseguimos la contraseña valida, vamos a ver ahora como hacer lo mismo desde el modulo rabin2 de radare2: rabin2 -z crackme0x00
Con el comando rabin2 llamamos al modulo, con el flag -z buscamos strings en el binario. Obtenemos el siguiente resultado:

Código: bash
└──╼ $rabin2 -z crackme0x00
vaddr=0x08048568 paddr=0x00000568 ordinal=000 sz=25 len=24 section=.rodata type=ascii string=IOLI Crackme Level 0x00\n
vaddr=0x08048581 paddr=0x00000581 ordinal=001 sz=11 len=10 section=.rodata type=ascii string=Password:
vaddr=0x0804858f paddr=0x0000058f ordinal=002 sz=7 len=6 section=.rodata type=ascii string=250382
vaddr=0x08048596 paddr=0x00000596 ordinal=003 sz=19 len=18 section=.rodata type=ascii string=Invalid Password!\n
vaddr=0x080485a9 paddr=0x000005a9 ordinal=004 sz=16 len=15 section=.rodata type=ascii string=Password OK :)\n


De nuevo obtenemos la contraseña en texto plano a traves de un analisis estatico del binario.

Vamos a ver otra forma, ahora si usando exclusivamente radare2, de obtener la contraseña en texto plano sin debuggear el software y luego veremos como modificar el flujo de ejecuccion para que acepte cualquier contraseña como valida (crackear).

Lo primero que haremos sera ejecutar radare y analizar completamente el binario:
radare2 crackme0x00 -AA

Ahora dentro de radare con el comando 'S' (sin las comillas simples) listamos las secciones del binario. Documentación del comando No tienes permitido ver los links. Registrarse o Entrar a mi cuenta
El formato del output que nos da es:
Numero de sección
Offset de inicio
Permisos de la seccion
Virtual adress (dirección virtual)
Size (tamaño)
Virtual size (tamaño virtual)
Nombre de la seccion

Código: bash
[00] . 0x00000154 -r-- va=0x08048154 sz=0x0013 vsz=0x0013 .interp
[01] . 0x00000168 -r-- va=0x08048168 sz=0x0020 vsz=0x0020 .note.ABI_tag
[02] . 0x00000188 -r-- va=0x08048188 sz=0x0030 vsz=0x0030 .hash
[03] . 0x000001b8 -r-- va=0x080481b8 sz=0x0020 vsz=0x0020 .gnu.hash
[04] . 0x000001d8 -r-- va=0x080481d8 sz=0x0070 vsz=0x0070 .dynsym
[05] . 0x00000248 -r-- va=0x08048248 sz=0x0059 vsz=0x0059 .dynstr
[06] . 0x000002a2 -r-- va=0x080482a2 sz=0x000e vsz=0x000e .gnu.version
[07] . 0x000002b0 -r-- va=0x080482b0 sz=0x0020 vsz=0x0020 .gnu.version_r
[08] . 0x000002d0 -r-- va=0x080482d0 sz=0x0008 vsz=0x0008 .rel.dyn
[09] . 0x000002d8 -r-- va=0x080482d8 sz=0x0020 vsz=0x0020 .rel.plt
[10] . 0x000002f8 -r-x va=0x080482f8 sz=0x0017 vsz=0x0017 .init
[11] . 0x00000310 -r-x va=0x08048310 sz=0x0050 vsz=0x0050 .plt
[12] * 0x00000360 -r-x va=0x08048360 sz=0x01e4 vsz=0x01e4 .text
[13] . 0x00000544 -r-x va=0x08048544 sz=0x001a vsz=0x001a .fini
[14] . 0x00000560 -r-- va=0x08048560 sz=0x0059 vsz=0x0059 .rodata
[15] . 0x000005bc -r-- va=0x080485bc sz=0x0004 vsz=0x0004 .eh_frame
[16] . 0x00000f0c -rw- va=0x08049f0c sz=0x0008 vsz=0x0008 .ctors
[17] . 0x00000f14 -rw- va=0x08049f14 sz=0x0008 vsz=0x0008 .dtors
[18] . 0x00000f1c -rw- va=0x08049f1c sz=0x0004 vsz=0x0004 .jcr
[19] . 0x00000f20 -rw- va=0x08049f20 sz=0x00d0 vsz=0x00d0 .dynamic
[20] . 0x00000ff0 -rw- va=0x08049ff0 sz=0x0004 vsz=0x0004 .got
[21] . 0x00000ff4 -rw- va=0x08049ff4 sz=0x001c vsz=0x001c .got.plt
[22] . 0x00001010 -rw- va=0x0804a010 sz=0x000c vsz=0x000c .data
[23] . 0x0000101c -rw- va=0x0804a01c sz=0x0004 vsz=0x0004 .bss
[24] . 0x0000101c ---- va=0x00000000 sz=0x01b9 vsz=0x01b9 .comment
[25] . 0x000011d5 ---- va=0x00000000 sz=0x00db vsz=0x00db .shstrtab
[26] . 0x00001738 ---- va=0x00000000 sz=0x0420 vsz=0x0420 .symtab
[27] . 0x00001b58 ---- va=0x00000000 sz=0x0219 vsz=0x0219 .strtab
[28] * 0x00000000 mr-x va=0x08048000 sz=0x05c0 vsz=0x05c0 LOAD0
[29] . 0x00000f0c mrw- va=0x08049f0c sz=0x0110 vsz=0x0114 LOAD1
[30] . 0x00000000 mrw- va=0x08048000 sz=0x0034 vsz=0x0034 ehdr
[31] . 0x00100000 -rwx va=0x00100000 sz=0xf0000 vsz=0xf0000 esil.ram


Esta información es bastante útil aunque para esta parte del curso no la usaremos.
Lo que vamos a hacer es desensamblar la funcion main. Pero antes de eso, ¿por que desensamblamos esa funcion y no otra?

Como hemos visto en el output de rabin2, y como en todos los crackmes hay una zona de chico bueno y chico malo, nos interesa por tanto saberen que funcion esta localizada la zona que nos da el OK, para ello usaremos el comando / Password que nos devuelve
Código: bash
Searching 8 bytes from 0x08048000 to 0x0804a020: 50 61 73 73 77 6f 72 64 
Searching 8 bytes in [0x8048000-0x804a020]
hits: 3
0x08048581 hit0_0 .kme Level 0x00Password: %s250382Inv.
0x0804859e hit0_1 .250382Invalid Password!Password OK :.
0x080485a9 hit0_2 .alid Password!Password OK :). #esta es la linea que nos interesa


Una vez sabemos la dirección donde esta la zona de 'chico bueno' le pedimos a r2 mas información sobre esa dirección con el comando axt: axt 0x080485a9
Código: bash
data 0x8048480 mov dword [esp], str.Password_OK_:__n in main; const char * format


Una vez entendido el por que de desensamblar el main, usaremos el comando pdf (print disassembled function), comando completo [email protected]

Código: bash
[0x08048360]> pdf@main
            ;-- main:
/ (fcn) main 127
|   main ();
|           ; var int local_18h @ ebp-0x18
|           ; var int local_4h @ esp+0x4
|              ; DATA XREF from 0x08048377 (entry0)
|           0x08048414      55             push ebp
|           0x08048415      89e5           mov ebp, esp
|           0x08048417      83ec28         sub esp, 0x28               ; '('
|           0x0804841a      83e4f0         and esp, 0xfffffff0
|           0x0804841d      b800000000     mov eax, 0
|           0x08048422      83c00f         add eax, 0xf
|           0x08048425      83c00f         add eax, 0xf
|           0x08048428      c1e804         shr eax, 4
|           0x0804842b      c1e004         shl eax, 4
|           0x0804842e      29c4           sub esp, eax
|           0x08048430      c70424688504.  mov dword [esp], str.IOLI_Crackme_Level_0x00_n ; [0x8048568:4]=0x494c4f49 ; "IOLI Crackme Level 0x00\n" ; const char * format
|           0x08048437      e804ffffff     call sym.imp.printf         ; int printf(const char *format)
|           0x0804843c      c70424818504.  mov dword [esp], str.Password: ; hit0_0 ; [0x8048581:4]=0x73736150 ; "Password: " ; const char * format
|           0x08048443      e8f8feffff     call sym.imp.printf         ; int printf(const char *format)
|           0x08048448      8d45e8         lea eax, dword [local_18h]
|           0x0804844b      89442404       mov dword [local_4h], eax
|           0x0804844f      c704248c8504.  mov dword [esp], 0x804858c  ; [0x804858c:4]=0x32007325 ; "%s" ; const char * format
|           0x08048456      e8d5feffff     call sym.imp.scanf          ; int scanf(const char *format)
|           0x0804845b      8d45e8         lea eax, dword [local_18h]
|           0x0804845e      c74424048f85.  mov dword [local_4h], str.250382 ; [0x804858f:4]=0x33303532 ; "250382"
|           0x08048466      890424         mov dword [esp], eax        ; const char * s2
|           0x08048469      e8e2feffff     call sym.imp.strcmp         ; int strcmp(const char *s1, const char *s2)
|           0x0804846e      85c0           test eax, eax
|       ,=< 0x08048470      740e           je 0x8048480
|       |   0x08048472      c70424968504.  mov dword [esp], str.Invalid_Password__n ; [0x8048596:4]=0x61766e49 ; "Invalid Password!\n" ; const char * format
|       |   0x08048479      e8c2feffff     call sym.imp.printf         ; int printf(const char *format)
|      ,==< 0x0804847e      eb0c           jmp 0x804848c
|      |`-> 0x08048480      c70424a98504.  mov dword [esp], str.Password_OK_:__n ; hit0_2 ; [0x80485a9:4]=0x73736150 ; "Password OK :)\n" ; const char * format
|      |    0x08048487      e8b4feffff     call sym.imp.printf         ; int printf(const char *format)
|      |       ; JMP XREF from 0x0804847e (main)
|      `--> 0x0804848c      b800000000     mov eax, 0
|           0x08048491      c9             leave
\           0x08048492      c3             ret


De nuevo nos volvemos a encontrar con la contraseña en texto plano.

Llegados a este punto ahora es cuando viene lo divertido, modificar el flujo de ejecuccion, osea, crackear el software. Al lio.

Salta a la vista que el programa compara dos registros (0x08048469, 0x0804846e) en uno esta la contraseña que valida el ejercicio y en el otro lo que nosotros hayamos introducido.
Por tanto la parte que nos interesa esta entre la direccion 0x0804846e y 0x08048480 donde se encuentra la string "Password OK :)".

A grandes rasgos lo que el software hace a partir de la direccion  0x0804846e es lo siguiente:
Compara la pwd introducida con la valida
Si son iguales salta a la direccion 0x08048480
En caso contrario llega hasta la direccion 0x0804847e que ejecuta un salto incondicional a  0x0804848c  y termina con la ejecuccion.

Lo que vamos a hacer es modificar ese "si es equivalente..." por un salto incondicional
Antes de nada, tenemos que entender el significado de la linea, mas concretamente de la segunda columna
0x08048470      740e           je 0x8048480

74 = je (jump if equal)
0e = bytes to jump (1 instruccion == 1 byte)

Por tanto el objetivo es que la linea anterior se convierta en
0x08048470      eb0e           jmp 0x8048480

Vamos a hacerlo pues.

Para ello reabrimos el archivo en modo escritura con el comando oo+

Código: bash
[0x08048360]> oo+
File crackme0x00 reopened in read-write mode


A continuacion nos movemos a la direccion donde se encuentra el salto con s 0x08048470 y imprimimos las siguientes 8 instrucciones con el comando pd 8

Si todo ha salido bien deberia aparecer lo siguiente
Código: bash

[0x08048470]> pd 8
|       ,=< 0x08048470      740e           je 0x8048480
|       |   0x08048472      c70424968504.  mov dword [esp], str.Invalid_Password__n ; [0x8048596:4]=0x61766e49 ; "Invalid Password!\n" ; const char * format
|       |   0x08048479      e8c2feffff     call sym.imp.printf         ; int printf(const char *format)
|      ,==< 0x0804847e      eb0c           jmp 0x804848c
|      |`-> 0x08048480      c70424a98504.  mov dword [esp], str.Password_OK_:__n ; hit0_2 ; [0x80485a9:4]=0x73736150 ; "Password OK :)\n" ; const char * format
|      |    0x08048487      e8b4feffff     call sym.imp.printf         ; int printf(const char *format)
|      |       ; JMP XREF from 0x0804847e (main)
|      `--> 0x0804848c      b800000000     mov eax, 0
|           0x08048491      c9             leave


Para modificar la condicion usaremos el comando wx seguido de la nueva instruccion wx 0xeb

Listamos de nuevo 8 instrucciones y observamos como el flujo de ejecuccion se ha modificado

Código: bash

[0x08048470]> pd 8
|       ,=< 0x08048470      eb0e           jmp 0x8048480
|       |   0x08048472      c70424968504.  mov dword [esp], str.Invalid_Password__n ; [0x8048596:4]=0x61766e49 ; "Invalid Password!\n" ; const char * format
|       |   0x08048479      e8c2feffff     call sym.imp.printf         ; int printf(const char *format)
|      ,==< 0x0804847e      eb0c           jmp 0x804848c
|      |`-> 0x08048480      c70424a98504.  mov dword [esp], str.Password_OK_:__n ; hit0_2 ; [0x80485a9:4]=0x73736150 ; "Password OK :)\n" ; const char * format
|      |    0x08048487      e8b4feffff     call sym.imp.printf         ; int printf(const char *format)
|      |       ; JMP XREF from 0x0804847e (main)
|      `--> 0x0804848c      b800000000     mov eax, 0
|           0x08048491      c9             leave


Mi blog: No tienes permitido ver los links. Registrarse o Entrar a mi cuenta
Si necesitas ayuda, no dudes en mandar MP