Archivo

Artículos etiquetados y‘Reverse’

Linux Cracking Series (LCS): lincrackme3

cracking

Find the keys

¡Buenas! Este lunes festivo he aprovechado y os traigo la nueva entrega de la LCS (Linux Cracking Series) con lincrackme3. El anterior ha recibido ya varias soluciones en crackmes.de, pero aún dejaré unos días antes de publicar la mía (con el código fuente y las explicaciones). También comentaré las más interesantes de las que se han publicado allí.

Los que no consiguierais solucionar el anterior, no os preocupéis, intentad este, y en unos días trataré de tener la explicación del anterior, que os servirá como base para abordar el nuevo. También podéis preguntarme vuestras dudas directamente.

Lincrackme3 es algo más difícil que lincrackme2, obviamente. Incluye técnicas anti depuración, anti desensamblado y también algún truco para que el análisis del flujo de código no sea tan sencillo. Aún así, creo que no es demasiado complicado, y que está al alcance de casi todo el que le dedique un rato.

Lincrackme 3

El objetivo de este crackme es encontrar las técnicas de anti depuración y anti desensamblado, entenderlas y averiguar cómo sortearlas. Una vez conseguido, tendréis que comprender cómo se comprueban las claves para encontrar varias claves válidas (idealmente, programad un sencillo keygen y me haréis feliz). El .tgz incluye una versión para 32 bits y una versión para 64 bits. La versión de 32 debería funcionar en ambos sistemas, pero por si acaso, he decidido crear las dos.

Objetivo: Obtener las claves válidas (escribir un keygen para sobresaliente). Encontrar las protecciones y sortearlas.
Nivel: 3 (Getting harder) según clasificación de crackmes.de
Plataforma: GNU/Linux 32 y 64 bits
Reglas: Parchear el binario no está permitido, salvo para saltarse alguna protección anti depuración o desensamblado. El objetivo no es imprimir el mensaje de clave correcta, si no obtener las claves válidas. Las herramientas a utilizar, así como la plataforma son completamente libres.

Podéis descargar el crackme aquí y también en mi página de crackmes.de (en cuanto lo aprueben, yo siempre os lo traigo en primicia ;) . Como siempre, os animo a que lo intentéis, y si os quedáis atascados en algún punto podéis contactarme en los comentarios (si no da pistas al resto) o bien por email (lo tenéis en About). Prometo ayudaros y daros las pistas necesarias para que disfrutéis del reto :) Eso sí, para que todos puedan disfrutarlo, no pongáis las claves en los comentarios, por favor.

Pasado un tiempo prudencial publicaré mi solución a este reto (código, imágenes…) y comentaré las más interesantes de entre las que reciba, así que ya sabéis, ¡espero vuestras soluciones!

Categorías:cracking, Linux, Reverse Etiquetas: , ,

Linux Cracking Series: Lincrackme2

10/10/2010 1 Comentario

Aquí estamos otra vez, para tratar de traer algo interesante. La verdad es que he dejado un par de entradas a medias que ya terminaré, pero ese es otro asunto. En esta nueva serie que iniciamos hoy, pretendo mostrar algo más sobre reversing/cracking en Linux. Puede parecer algo poco útil, pero la verdad es que existen muchas aplicaciones cuyo código fuente no está disponible, aparte  de los CTF u otros concursos donde podréis aplicar estos conocimientos.

La filosofía del “curso” va a ser la de aprender a través de ejemplos, concretamente de crackmes/keygenmes. Se irá de más fácil a más difícil, y se dejará un tiempo entre uno y otro para que pueda recibir soluciones de la gente que esté interesada. Publicaré mi propia solución detallada -incluyendo código fuente-, explicando paso a paso todos los problemas y cómo solventarlos para que nadie se pierda, y si alguna solución es especialmente buena o ingeniosa y el autor está de acuerdo, también se publicará en el blog. Los crackmes se publicarán aquí y, si sus moderadores lo aprueban (ya ha sido aprobado), también en http://crackmes.de/users/adrianbn. Podréis enviar las soluciones a través de esa web o directamente a mí a adrianbn[_en_]gmail[_punto_]com.

Si nunca habéis utilizado ningún debugger, ni tenéis unos conocimientos básicos de ensamblador, entonces esta serie -de momento- no es para vosotros. Si habéis seguido el blog, deberíais ser capaces de resolver los crackmes con mayor o menor esfuerzo. Si con los resultados a la vista, resulta ser demasiado complicado, ampliaré la serie hacia ambos sentidos; hacía cosas más fáciles y hacia cosas más difíciles. Todo este feedback me lo podéis dar bien a través de los comentarios, por correo electrónico directamente a mí, o en los comentarios de crackmes.de.

Lincrackme2

El objetivo de este crackme es identificar y sortear la(s) proteccion(es) anti depuración que tiene implementadas. El crackme también lleva alguna protección anti desensamblado, para tratar de que os centréis en conseguir depurar el programa. No obstante, si a alguien le resulta más fácil pelear contra las técnicas de anti desensamblado, puede hacerlo, aunque le recomiendo que se fije en las protecciones anti depuración, que es en lo que se centrarán los primeros crackmes.

Objetivo: Obtener el único código válido. Identificar las protecciones y sortearlas.
Nivel: 2 (Needs a little brain, or luck), de acuerdo a la clasificación de crackmes.de.
Plataforma: GNU/Linux 32 y 64 bits.
Reglas: Parchear el binario no está permitido salvo como último recurso (ultimísimo, ¿eh?). No pongáis la clave en los comentarios, por favor, así todos podrán intentarlo. Podéis utilizar cualquier herramienta en cualquier plataforma.

Podéis descargar el crackme aquí y en mi página de crackmes.de (en cuanto sea aprobado).

Animaos a intentarlo, yo prometo ayudar y/o dar pistas si os quedáis atascados en algún punto. Tenéis mi email para contactarme si los detalles dan muchas pistas a otros usuarios, y los comentarios para cosas más generales. ¡Espero vuestras soluciones!

 

Categorías:cracking, Linux, Reverse Etiquetas: , , , ,

Solución al Gipuzkoa Encounter 2010 Hack-It! (ELF II)

En esta entrada voy a mostrar otra forma de solucionar el reto del ELF de la Gipuzkoa Encounter. Si no habéis leído la entrada previa sobre el reto, es momento de hacerlo ahora, ya que consideraré muchas cosas como sabidas. De nuevo copio el warning:

AVISO: Voy a poner la solución al reto paso a paso, tratando de que sea un solucionario completo para que tanto la gente con pocos conocimientos del tema como los más expertos puedan seguirlo. No obstante, lo ideal es que trates de resolverlo por tu cuenta, y que si te atascas continues leyendo. Dicho queda.

Binary patching

Ya dije en la otra entrada que una solución podría pasar por parchear el binario para saltarnos la protección de depuración y así utilizar gdb para seguir el programa paso a paso y localizar la contraseña que nos piden. Si el objetivo fuera la ejecución de alguna función o conseguir que el programa mostrara algún mensaje concreto podríamos tratar de parchearlo para que el flujo del programa nos llevara a ese punto, sin embargo aquí lo que nos han pedido es que les demos la contraseña válida, así que allá vamos.

(gdb) disass main
Dump of assembler code for function main:
0x080482bb <main+0>:    lea ecx,[esp+0x4] 
0x080482bf <main+4>:    and    esp,0xfffffff0 
0x080482c2 <main+7>:    push   DWORD PTR [ecx-0x4] 
0x080482c5 <main+10>:    push   ebp 
0x080482c6 <main+11>:    mov    ebp,esp 
0x080482c8 <main+13>:    push   ecx 
0x080482c9 <main+14>:    sub    esp,0x14 
0x080482cc <main+17>:    mov    DWORD PTR [esp],0x80a4f08 
0x080482d3 <main+24>:    call   0x8048f70 <puts> 
0x080482d8 <main+29>:    mov    DWORD PTR [esp],0x80a4f2f 
0x080482df <main+36>:    call   0x8048d80 <printf> 
0x080482e4 <main+41>:    call   0x8048273 <check> 
0x080482e9 <main+46>:    mov    DWORD PTR [esp],0x80c3840 
0x080482f0 <main+53>:    call   0x8048db0 <gets> 
0x080482f5 <main+58>:    mov    DWORD PTR [esp],0xa 
0x080482fc <main+65>:    call   0x8049110 <putchar> 
0x08048301 <main+70>:    mov    DWORD PTR [esp],0x80c3840 
0x08048308 <main+77>:    call   0x8048210 <balioztau> 
0x0804830d <main+82>:    test   eax,eax 
0x0804830f <main+84>:    je     0x804831f <main+100> 
0x08048311 <main+86>:    mov    DWORD PTR [esp],0x80a4f3c 
0x08048318 <main+93>:    call   0x8048f70 <puts> 
0x0804831d <main+98>:    jmp    0x804832b <main+112> 
0x0804831f <main+100>:    mov    DWORD PTR [esp],0x80a4f46 
0x08048326 <main+107>:    call   0x8048f70 <puts> 
0x0804832b <main+112>:    add    esp,0x14 
0x0804832e <main+115>:    pop    ecx 
0x0804832f <main+116>:    pop    ebp 
0x08048330 <main+117>:    lea    esp,[ecx-0x4] 
0x08048333 <main+120>:    ret 
End of assembler dump. 

Ahí tenemos el desensamblado del main. Recordad que la función check es la que comprobaba si estábamos depurando el binario y en ese caso nos mostraba un mensaje de error y finalizaba la ejecución. Una forma sencilla de evitar que pase es simplemente eliminando la llamada a check del binario. Para ello tendremos que encontrar dentro del binario ese fragmento de código y sustituirlo por otro. Podemos utilizar objdump para localizar ese fragmento de código y el hexadecimal al que ensambla:

adrian@Andromeda:~/Escritorio/gipuzkoa encounter$ objdump -M intel -d tolosa2010_unix -z | grep -i "<main>:" -A 30
080482bb <main>:
 80482bb:    8d 4c 24 04              lea    ecx,[esp+0x4]
 80482bf:    83 e4 f0                 and    esp,0xfffffff0
 80482c2:    ff 71 fc                 push   DWORD PTR [ecx-0x4]
 80482c5:    55                       push   ebp
 80482c6:    89 e5                    mov    ebp,esp
 80482c8:    51                       push   ecx
 80482c9:    83 ec 14                 sub    esp,0x14
 80482cc:    c7 04 24 08 4f 0a 08     mov    DWORD PTR [esp],0x80a4f08
 80482d3:    e8 98 0c 00 00           call   8048f70 <_IO_puts>
 80482d8:    c7 04 24 2f 4f 0a 08     mov    DWORD PTR [esp],0x80a4f2f
 80482df:    e8 9c 0a 00 00           call   8048d80 <_IO_printf>
 80482e4:    e8 8a ff ff ff           call   8048273 <check>
 80482e9:    c7 04 24 40 38 0c 08     mov    DWORD PTR [esp],0x80c3840
 80482f0:    e8 bb 0a 00 00           call   8048db0 <_IO_gets>
 80482f5:    c7 04 24 0a 00 00 00     mov    DWORD PTR [esp],0xa
 80482fc:    e8 0f 0e 00 00           call   8049110 <putchar>
 8048301:    c7 04 24 40 38 0c 08     mov    DWORD PTR [esp],0x80c3840
 8048308:    e8 03 ff ff ff           call   8048210 <balioztau>
 804830d:    85 c0                    test   eax,eax
 804830f:    74 0e                    je     804831f <main+0x64>
 8048311:    c7 04 24 3c 4f 0a 08     mov    DWORD PTR [esp],0x80a4f3c
 8048318:    e8 53 0c 00 00           call   8048f70 <_IO_puts>
 804831d:    eb 0c                    jmp    804832b <main+0x70>
 804831f:    c7 04 24 46 4f 0a 08     mov    DWORD PTR [esp],0x80a4f46
 8048326:    e8 45 0c 00 00           call   8048f70 <_IO_puts>
 804832b:    83 c4 14                 add    esp,0x14
 804832e:    59                       pop    ecx
 804832f:    5d                       pop    ebp
 8048330:    8d 61 fc                 lea    esp,[ecx-0x4]
 8048333:    c3                       ret
adrian@Andromeda:~/Escritorio/gipuzkoa encounter$ objdump -M intel -d tolosa2010_unix -z | grep -i 80482e4
 80482e4:    e8 8a ff ff ff           call   8048273 <check>

Como se puede observar nuestro objetivo es localizar la secuencia e88affffff y sustituirla. No podemos simplemente eliminarla del binario, ya que romperíamos las instrucciones que utilizan offsets y podríamos causar problemas de alineación o derivados. Sin embargo, podemos colocar en su lugar NOPs (0×90), un total de cinco, para cubrir el espacio de esa instrucción. Para esta operación podemos utilizar nuestro editor hexadecimal favorito, ya sea hexedit, ghex u otro.

GHex

Buscamos el patrón del call

GHex sustitution

Sustituimos el patron del call por nops

Una vez sustituido el patrón de la llamada a check por NOPs guardamos el nuevo binario y deberíamos ser capaces de ejecutarlo y depurarlo con gdb sin las molestas interrupciones de la rutina antidebuging. En este punto tocaría investigar un poco la rutina balioztau (comentada en la entrada previa). Basta con localizar el lugar donde compara la contraseña con nuestro valor, aunque no se comprenda el funcionamiento de la rutina esto es evidente:

0x08048250 <balioztau+64>: cmp dl,al

Con un poco de la magia de gdb podemos poner un breakpoint en ese momento y que se nos muestre el valor de los registros cada vez que se detenga ahí. Veremos qué pasa:

(gdb) display/c $eax
(gdb) display/c $edx
(gdb) br *0x08048250
Punto de interrupción 1 at 0x8048250
(gdb) run
Starting program: /home/adrian/Escritorio/gipuzkoa encounter/tu

 [Gipuzkoa Encounter 2010 - Hack It!]

 Passw0rd: passworderroneo

Breakpoint 1, 0x08048250 in balioztau ()
2: /c $edx = 116 't'
1: /c $eax = 112 'p'
(gdb) c
Continuando.
 #EPIC FAIL

Program exited with code 014.

El programa se ha detenido en la comparación, y ha probado si la “p” que hemos introducido es igual a “t”. Esa “t” es, por lo tanto, la primera letra de la contraseña que buscamos. Si le damos a continuar el programa finaliza, ya que son diferentes y no sigue comprobando el resto de la cadena. Podemos repetir el proceso, introduciendo un password que comience con t, y descubriremos la segunda letra de la contraseña. Si repetimos esto el número suficiente de veces tendremos la contraseña correcta. Sin embargo eso es un poco tedioso. Podemos volver a parchear el binario para que nos sea más fácil encontrar lo que buscamos. La instrucción culpable de que el programa finalice cuando encuentra la primera letra incorrecta es este salto condicional situado justo tras el cmp anterior:

adrian@Andromeda:~/Escritorio/gipuzkoa encounter$ objdump -M intel -z -d tolosa2010_unix | grep -i 8048252
 8048252:    74 09         je     804825d <balioztau+0x4d>

¿Cómo cambiar un je (salto si igual) por un jmp (salto incondicional) y mantener el offset correcto? Conociendo un poco las instrucciones de salto, el primer byte (0×74 en este caso) indica qué instrucción es (qué tipo de salto) y el segundo byte y siguientes (0×09 en este caso) indica el offset del salto. Por lo tanto podemos buscar con ghex nuestra secuencia de salto (7409) y sustituirla por un salto incondicional con offset 0×09. En el siguiente código de prueba podemos ver que el salto incondicional ensambla al siguiente hexadecimal:

adrian@Andromeda:~/Escritorio/gipuzkoa encounter$ cat jmp.S
BITS 32

jmp short aqui
nop
nop
nop
nop
nop
nop
nop
nop
nop
aqui:
nop
nop
adrian@Andromeda:~/Escritorio/gipuzkoa encounter$ ndisasm jmp -u
00000000  EB09              jmp short 0xb
00000002  90                nop
00000003  90                nop
00000004  90                nop
00000005  90                nop
00000006  90                nop
00000007  90                nop
00000008  90                nop
00000009  90                nop
0000000A  90                nop
0000000B  90                nop
0000000C  90                nop

Así que volvemos a nuestro editor hexadecimal, buscamos la secuencia 7409 y nos damos cuenta de que tenemos varias apariciones. Por lo tanto lo mejor es que busquemos nuestro salto y la instrucción anterior o siguiente:

adrian@Andromeda:~/Escritorio/gipuzkoa encounter$ objdump -M intel -z -d tolosa2010_unix | grep -i 8048252 -B 1
8048250:    38 c2                    cmp    dl,al
8048252:    74 09                    je     804825d <balioztau+0x4d>

Ahora sí, si buscamos 38C27409 encontramos una única aparición que se corresponde a nuestro salto, y puesto que el salto incondicional ensambla a EB (con offset 09), sustituiremos únicamente el 74 (je) por nuestro EB (jmp) y guardaremos. Ahora, tanto si la contraseña va coincidiendo como si no, seguirá comprobando toda la cadena (y podremos ver la contraseña completa):

adrian@Andromeda:~/Escritorio/gipuzkoa encounter$ gdb -q tu2
Leyendo símbolos desde /home/adrian/Escritorio/gipuzkoa encounter/tolosa2010_unix...hecho.
(gdb) br *0x8048250
Punto de interrupción 1 at 0x8048250
(gdb) display/c $eax
(gdb) display/c $edx
(gdb) run
Starting program: /home/adrian/Escritorio/gipuzkoa encounter/tolosa2010_unix

 [Gipuzkoa Encounter 2010 - Hack It!]

 Passw0rd: estapasswordnoes

Breakpoint 1, 0x08048250 in balioztau ()
2: /c $edx = 116 't'
1: /c $eax = 101 'e'
(gdb) c
Continuando.

Breakpoint 1, 0x08048250 in balioztau ()
2: /c $edx = 48 '0'
1: /c $eax = 115 's'
(gdb) c
Continuando.

Breakpoint 1, 0x08048250 in balioztau ()
2: /c $edx = 108 'l'
1: /c $eax = 116 't'
(gdb) c
Continuando.

Breakpoint 1, 0x08048250 in balioztau ()
2: /c $edx = 48 '0'
1: /c $eax = 97 'a'
(gdb) c
Continuando.

Breakpoint 1, 0x08048250 in balioztau ()
2: /c $edx = 115 's'
1: /c $eax = 112 'p'
(gdb) c
Continuando.

Breakpoint 1, 0x08048250 in balioztau ()
2: /c $edx = 64 '@'
1: /c $eax = 97 'a'
(gdb) c
Continuando.

Breakpoint 1, 0x08048250 in balioztau ()
2: /c $edx = 104 'h'
1: /c $eax = 115 's'
(gdb) c
Continuando.

Breakpoint 1, 0x08048250 in balioztau ()
2: /c $edx = 52 '4'
1: /c $eax = 115 's'
(gdb) c
Continuando.

Breakpoint 1, 0x08048250 in balioztau ()
2: /c $edx = 99 'c'
1: /c $eax = 119 'w'
(gdb) c
Continuando.

Breakpoint 1, 0x08048250 in balioztau ()
2: /c $edx = 107 'k'
1: /c $eax = 111 'o'
(gdb) c
Continuando.

Breakpoint 1, 0x08048250 in balioztau ()
2: /c $edx = 0 '\000'
1: /c $eax = 114 'r'

¡Ya la tenemos! Cogemos las letras de edx, las ponemos en orden y obtenemos la password: “t0l0s@h4ck“. Comprobamos que es correcta, aunque ya lo sepamos :P .


adrian@Andromeda:~/Escritorio/gipuzkoa encounter$ ./tolosa2010_unix

[Gipuzkoa Encounter 2010 - Hack It!]

Passw0rd: t0l0s@h4ck

Success!

¡No os olvidéis de comprobarlo contra el binario original! Si lo probáis contra el segundo binario dará igual qué password introduzcais ya que siempre dirá “Success!”. Espero que esta solución haya ilustrado otra manera de afrontar el problema y que por el camino se haya aprendido algo acerca del ensamblado de los saltos y de binary patching. Seguro que hay más soluciones posibles, así que si alguien tiene alguna idea más, o alguna duda, puede dejar un comentario :)

Categorías:Reverse, wargame Etiquetas: ,

Solución al Gipuzkoa Encounter 2010 Hack-It! (ELF)

25/03/2010 7 comentarios

El pasado fin de semana, coincidiendo con la RootedCon, se celebraba en Guipúzcoa este encuentro anual que, entre los eventos que presenta incluye un pequeño CTF. Parte de ese CTF consiste en obtener el password para autenticarse en dos binarios, un ELF y un PE. Estos binarios fueron creados por la gente de morenops.com, y podéis descargarlos desde aquí. No os recomiendo que leais los comentarios porque dan muchas pistas sobre la solución.

AVISO: Voy a poner la solución al reto paso a paso, tratando de que sea un solucionario completo para que tanto la gente con pocos conocimientos del tema como los más expertos puedan seguirlo. No obstante, lo ideal es que trates de resolverlo por tu cuenta, y que si te atascas continues leyendo. Dicho queda.

Método para el ELF

En primer lugar decir que esta es sólo una posible solución, existen otras formas de solucionar este reto, y estaré encantado de que las dejéis en los comentarios para dar con la mejor solución. Tras descargar el binario y darle permisos de ejecución, podemos tratar de obtener algo de información acerca del mismo con la orden file.

adrian@Andromeda:~$ file tolosa2010_unix
tolosa2010_unix: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), statically linked, for GNU/Linux 2.6.8, not stripped

Con esto ya sabemos que, puesto que está enlazado de manera estática, un ltrace del programa no nos dará ninguna información. Sin embargo, no está stripeado, por lo que es posible que encontremos símbolos de depuración en el programa. El siguiente paso es darle permisos de ejecución y ejecutarlo un par de veces para hacernos una idea inicial de cómo funciona.

binario en funcionamiento

Probando lo básico

Bueno, está claro que no vale cualquier clave, ni una clave en blanco, ni una clave demasiado larga. Así que nos va a tocar currar un poco más. Lo siguiente que a mí se me ocurre es utilizar strace para averiguar qué llamadas al sistema realiza y qué señales está recibiendo el programa.


adrian@Andromeda:~/pre$ strace ./tolosa2010_unix
execve("./tolosa2010_unix", ["./tolosa2010_unix"], [/* 35 vars */]) = 0
[ Process PID=5364 runs in 32 bit mode. ]
old_mmap(0xc40000, 4096, PROT_READ|PROT_WRITE|PROT_EXEC, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0xc40000) = 0xc40000
readlink("/proc/self/exe", "/home/adrian/pre/tolosa2010_unix", 4096) = 32
old_mmap(0x8048000, 493385, PROT_READ|PROT_WRITE|PROT_EXEC, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x8048000
mprotect(0x8048000, 493382, PROT_READ|PROT_EXEC) = 0
old_mmap(0x80c1000, 3843, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0x78000) = 0x80c1000
mprotect(0x80c1000, 3840, PROT_READ|PROT_WRITE) = 0
old_mmap(0x80c2000, 6764, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x80c2000
brk(0x80c4000)                          = 0x9f2a000
munmap(0xc01000, 262144)                = 0
uname({sys="Linux", node="Andromeda", ...}) = 0
brk(0)                                  = 0x9f2a000
brk(0x9f2acb0)                          = 0x9f2acb0
set_thread_area(0xffac203c)             = 0
brk(0x9f4bcb0)                          = 0x9f4bcb0
brk(0x9f4c000)                          = 0x9f4c000
fstat64(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 0), ...}) = 0
mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xfffffffff7775000
write(1, "\n", 1
)                       = 1
write(1, "\t[Gipuzkoa Encounter 2010 - Hack"..., 38    [Gipuzkoa Encounter 2010 - Hack It!]
) = 38
write(1, "\n", 1
)                       = 1
ptrace(PTRACE_TRACEME, 0, 0x1, 0)       = -1 EPERM (Operation not permitted)
write(1, "\tPassw0rd: \tomgz0r debugger? bye"..., 40    Passw0rd:     omgz0r debugger? bye bye...
) = 40
exit_group(0)                           = ?

adrian@Andromeda:~/pre$

Sin embargo parece que por aquí tampoco llegamos a ninguna parte. Vemos que realiza alguna reserva de memoria, que llama a uname y a write para mostrar el mensaje de bienvenida. Después hace una llamada a ptrace y nos escribe un mensaje: “Passw0rd:     omgz0r debugger? bye bye..”. Al tratar de tracear el programa y ya estar siendo traceado por nosotros, ptrace devuelve un error y el programa finaliza. Vamos a tratar de utilizar gdb para seguir un poco el funcionamiento interno del programa, con la idea de averiguar qué está pasando y de tratar de localizar la función que comprueba si una contraseña es válida o no.


adrian@Andromeda:~/pre$ gdb -q tolosa2010_unix
Leyendo símbolos desde /home/adrian/pre/tolosa2010_unix...(no debugging symbols found)...hecho.
(gdb) list
No hay tabla de símbolos cargada. Use la orden "file".
(gdb) br main
No hay tabla de símbolos cargada. Use la orden "file".
Make breakpoint pending on future shared library load? (y o [n]) y
Punto de interrupción 1 (main) pendiente.
(gdb) run
Starting program: /home/adrian/pre/tolosa2010_unix

 [Gipuzkoa Encounter 2010 - Hack It!]

 Passw0rd:     omgz0r debugger? bye bye...

Program exited normally.
(gdb) disass main
No symbol table is loaded.  Use the "file" command.

Otra cosa que falla. Está claro que al binario le pasa algo, ya que aparte de evitar la depuración, no nos encuentra el punto de entrada del binario en main. Llegados a este punto (los lectores avezados lo habrán visto antes) cabe pensar que el binario tiene algún tipo de cifrado o empaquetado. Volcaremos el binario, ya sea en hexadecimal con objdump o buscando solo las cadenas de texto con strings en busca de alguna pista como la que se ve a continuación:


adrian@Andromeda:~/pre$ hexdump -C tolosa2010_unix | head
00000000  7f 45 4c 46 01 01 01 03  00 00 00 00 00 00 00 00  |.ELF............|
00000010  02 00 03 00 01 00 00 00  60 f1 c3 00 34 00 00 00  |........`...4...|
00000020  00 00 00 00 00 00 00 00  34 00 20 00 02 00 28 00  |........4. ...(.|
00000030  00 00 00 00 01 00 00 00  00 00 00 00 00 10 c0 00  |................|
00000040  00 10 c0 00 68 e9 03 00  68 e9 03 00 05 00 00 00  |....h...h.......|
00000050  00 10 00 00 01 00 00 00  6c 0a 00 00 6c 3a 0c 08  |........l...l:..|
00000060  6c 3a 0c 08 00 00 00 00  00 00 00 00 06 00 00 00  |l:..............|
00000070  00 10 00 00 4d a8 73 e7  55 50 58 21 08 08 0d 0c  |....M.s.UPX!....|
00000080  00 00 00 00 5a a9 08 00  5a a9 08 00 d4 00 00 00  |....Z...Z.......|
00000090  79 00 00 00 08 00 00 00  77 1f a4 f9 7f 45 4c 46  |y.......w....ELF|
adrian@Andromeda:~/pre$ strings tolosa2010_unix | head
UPX!
[]g;;
PTRh
=]<r
mnfM
F>wp
<    wD
1]>ng
FzFM
{0c;]_V

Si nos fijamos bien, encontraremos la cadena UPX!, lo que nos indica que el binario ha sido empaquetado con esta utilidad. Esto ha sido fácil, en otras ocasiones habría sido necesario detectar en el binario el código que realiza el empaquetado y tratar de deshacerlo o identificar el empaquetador. Por lo tanto instalaremos el packer, bien desde su web, bien desde el sistema de paquetes de la distribución que utilicemos. Una vez instalado procederemos a desempaquetar el binario.


adrian@Andromeda:~/pre$ upx -d tolosa2010_unix
 Ultimate Packer for eXecutables
 Copyright (C) 1996,1997,1998,1999,2000,2001,2002,2003,2004,2005,2006,2007
UPX 3.01        Markus Oberhumer, Laszlo Molnar & John Reiser   Jul 31st 2007

 File size         Ratio      Format      Name
 --------------------   ------   -----------   -----------
 567642 <-    256396   45.17%  linux/elf386   tolosa2010_unix

Unpacked 1 file.

Ahora que hemos desempaquetado el binario podemos retomar la idea de depurarlo y tratar de localizar el punto en el que verifica la clave. Vamos a ver qué sucede:


drian@Andromeda:~/pre$ gdb -q tolosa2010_unix
Leyendo símbolos desde /home/adrian/pre/tolosa2010_unix...hecho.
(gdb) br main
Punto de interrupción 1 at 0x80482c9
(gdb) run
Starting program: /home/adrian/pre/tolosa2010_unix
Breakpoint 1, 0x080482c9 in main ()
Idioma actual:  auto
The current source language is "auto; currently asm".
(gdb) list
1    /tmp/cc1PTP7g.s: No existe el fichero ó directorio.
 in /tmp/cc1PTP7g.s
(gdb) n
Single stepping until exit from function main,
which has no line number information.

 [Gipuzkoa Encounter 2010 - Hack It!]

 Passw0rd:     omgz0r debugger? bye bye...

Era de esperar que sucediera esto, pero ahora encuentra los símbolos, lo cual está bien, ya que nos va a permitir hacer esto:


(gdb) disass main
Dump of assembler code for function main:
0x080482bb <main+0>:    lea    ecx,[esp+0x4]
0x080482bf <main+4>:    and    esp,0xfffffff0
0x080482c2 <main+7>:    push   DWORD PTR [ecx-0x4]
0x080482c5 <main+10>:    push   ebp
0x080482c6 <main+11>:    mov    ebp,esp
0x080482c8 <main+13>:    push   ecx
0x080482c9 <main+14>:    sub    esp,0x14
0x080482cc <main+17>:    mov    DWORD PTR [esp],0x80a4f08
0x080482d3 <main+24>:    call   0x8048f70 <puts>
0x080482d8 <main+29>:    mov    DWORD PTR [esp],0x80a4f2f
0x080482df <main+36>:    call   0x8048d80 <printf>
0x080482e4 <main+41>:    call   0x8048273 <check>
0x080482e9 <main+46>:    mov    DWORD PTR [esp],0x80c3840
0x080482f0 <main+53>:    call   0x8048db0 <gets>
0x080482f5 <main+58>:    mov    DWORD PTR [esp],0xa
0x080482fc <main+65>:    call   0x8049110 <putchar>
0x08048301 <main+70>:    mov    DWORD PTR [esp],0x80c3840
0x08048308 <main+77>:    call   0x8048210 <balioztau>
0x0804830d <main+82>:    test   eax,eax
0x0804830f <main+84>:    je     0x804831f <main+100>
0x08048311 <main+86>:    mov    DWORD PTR [esp],0x80a4f3c
0x08048318 <main+93>:    call   0x8048f70 <puts>
0x0804831d <main+98>:    jmp    0x804832b <main+112>
0x0804831f <main+100>:    mov    DWORD PTR [esp],0x80a4f46
0x08048326 <main+107>:    call   0x8048f70 <puts>
0x0804832b <main+112>:    add    esp,0x14
0x0804832e <main+115>:    pop    ecx
0x0804832f <main+116>:    pop    ebp
0x08048330 <main+117>:    lea    esp,[ecx-0x4]
0x08048333 <main+120>:    ret
End of assembler dump.

Al principio hace unas llamadas a printf y puts para mostrar el mensaje inicial en la terminal, y despúes (main+41) realiza una llamada a la función check. Tras esto, lee lo que le pasamos como clave (gets), imprime un salto de línea (putchar 0xa) y llama a una función “balioztau” con un argumento, que es donde ha almacenado lo que hemos tecleado. En función del resultado de esa función (main+84) saltará a nuestro FAIL o al mensaje de éxito. Todos los valores de memoria que aparecen contienen las cadenas que maneja el programa, podemos verlo rápidamente:

(gdb) x/s 0x80a4f08
0x80a4f08:     "\n\t[Gipuzkoa Encounter 2010 - Hack It!]"
(gdb) x/s 0x80a4f2f
0x80a4f2f:     "\n\tPassw0rd: "
(gdb) x/s 0x80c3840
0x80c3840 <pasahitza>:     ""
(gdb) x/s 0x80a4f3c
0x80a4f3c:     "\tSuccess!"
(gdb) x/s 0x80a4f46
0x80a4f46:     "\t#EPIC FAIL"

Tal y como yo lo veo, una vez aquí tenemos dos opciones: tratar de saltarnos la protección antidepuración para poder examinar la memoria en ejecución y localizar el password válido, o desensamblar la función balioztau y comprender de manera estática qué comprobaciones hace sobre el password. Ambos métodos son muy interesantes, pero de momento expondremos el segundo, explicando además una técnica utilizada para la realización de keygens y para la ingeniería inversa en general. Veamos el código de la función balioztau:


(gdb) disass balioztau
Dump of assembler code for function balioztau:
0x08048210 <balioztau+0>:    push   ebp
0x08048211 <balioztau+1>:    mov    ebp,esp
0x08048213 <balioztau+3>:    sub    esp,0x14
0x08048216 <balioztau+6>:    mov    DWORD PTR [ebp-0x4],0x0
0x0804821d <balioztau+13>:    mov    BYTE PTR ds:0x80c17de,0x0
0x08048224 <balioztau+20>:    mov    DWORD PTR [ebp-0x8],0x0
0x0804822b <balioztau+27>:    jmp    0x8048261 <balioztau+81>
0x0804822d <balioztau+29>:    mov    edx,DWORD PTR [ebp-0x8]
0x08048230 <balioztau+32>:    mov    eax,edx
0x08048232 <balioztau+34>:    shr    eax,0x1f
0x08048235 <balioztau+37>:    add    eax,edx
0x08048237 <balioztau+39>:    sar    eax,1
0x08048239 <balioztau+41>:    mov    DWORD PTR [ebp-0x4],eax
0x0804823c <balioztau+44>:    mov    eax,DWORD PTR [ebp-0x8]
0x0804823f <balioztau+47>:    movzx  edx,BYTE PTR [eax+0x80c17c8]
0x08048246 <balioztau+54>:    mov    eax,DWORD PTR [ebp-0x4]
0x08048249 <balioztau+57>:    movzx  eax,BYTE PTR [eax+0x80c3840]
0x08048250 <balioztau+64>:    cmp    dl,al
0x08048252 <balioztau+66>:    je     0x804825d <balioztau+77>
0x08048254 <balioztau+68>:    mov    DWORD PTR [ebp-0x14],0x0
0x0804825b <balioztau+75>:    jmp    0x804826e <balioztau+94>
0x0804825d <balioztau+77>:    add    DWORD PTR [ebp-0x8],0x2
0x08048261 <balioztau+81>:    cmp    DWORD PTR [ebp-0x8],0x16
0x08048265 <balioztau+85>:    jle    0x804822d <balioztau+29>
0x08048267 <balioztau+87>:    mov    DWORD PTR [ebp-0x14],0x1
0x0804826e <balioztau+94>:    mov    eax,DWORD PTR [ebp-0x14]
0x08048271 <balioztau+97>:    leave
0x08048272 <balioztau+98>:    ret
End of assembler dump.

Con el código delante el proceso consiste en leerlo e ir anotándolo con comentarios hasta que se haya comprendido su funcionamiento. Lo primero será averiguar qué hay en la memoria en 0x80c17c8, puesto que en 0x80c3840 ya sabemos que se encuentra lo que hemos introducido en la terminal. Os recomiendo que tratéis de hacer vosotros la anotación antes de leer la mía. Tras un par de pasadas anotando deberíais tener unas anotaciones similares a estas:

balioztau anotada

anotaciones de balioztau

Os las subo también como fichero adjunto, porque ponerlas en forma de código quedaba descolocado debido al tamaño de la caja de texto. Con esto debería ser evidente qué está realizando la función y cual es el password que debemos introducir. Aún así, cuando se trata de conseguir un keygen o de hacer más claro el problema (esta función no es especialmente complicada) se procede a traducir el código a C. En primer lugar una traducción directa del ensamblador, y finalmente se refactoriza el código para obtener algo más parecido a lo que escribiría un programador. La traducción directa a C desde ese código podría ser algo como esto (la función main ha sido añadida por comodidad y para que el programa sea usable):


#include <stdio.h>
#include <stdlib.h>

char pass_valido[20] = "t_0/l=0(s?@ah:40c7k#";

int balioztau(char* pass_usuario){

unsigned int var1,var2,ret;
unsigned char *pp,*pu;
var1=0;
var2=0;
ret = 0;

pp = pass_valido;
pu = pass_usuario;

do{
var1 = var1 >>> 31; // var1 = 0;
var1 = var2 + var1;
var1 = var2/2;

if (*(pu+var1) == *(pp+var2)){
 var2 = var2 +2;
}else{
 ret =0;
 return ret;
}

} while (var2 <= 20);

ret = 1;
return ret;
}

int main(int argc, char* argv[]){
 if (argc != 2){
 printf("Uso: %s <password a comprobar>\n",argv[0]);
 exit(0);
 }
 if (balioztau(argv[1])){
 printf("WIN\n");
 }else{
 printf("FAIL\n");
 }
return 0;
}

Aquí se ve mejor que el funcionamiento consiste en recorrer la cadena en memoria (pass_valido) de dos en dos e ir comparándola con lo que ha introducido el usuario (recorrido de uno en uno). Por lo tanto, el caracter en la posición 0 del usuario se validará contra el 0 de la password, el primero del usuario contra el segundo de la password, y así hasta el final. Por tanto la password para el programa es “t0l0s@h4ck“, como se muestra a continuación:


adrian@Andromeda:~/pre$ ./tolosa2010_unix

 [Gipuzkoa Encounter 2010 - Hack It!]

 Passw0rd: t0l0s@h4ck

 Success!
adrian@Andromeda:~/pre$ ./a.out
Uso: ./a.out <password a comprobar>
adrian@Andromeda:~/pre$ ./a.out hola
FAIL
adrian@Andromeda:~/pre$ ./a.out t0l0s@h4ck
WIN

Queda como tarea vuestra pasar el código C a algo más habitual, convertir el while en un for, eliminar las variables sobrantes, los desplazamientos extraños, etc. En otra entrada veremos cómo solucionar el binario de windows, y quizá cómo solucionar este mismo parcheando el binario. Si tenéis cualquier duda o aportación ya sabéis ;)

Categorías:Reverse, wargame Etiquetas: ,
Seguir

Get every new post delivered to your Inbox.