miércoles, 13 de noviembre de 2013

NcN PreQuals 2013: Reto 3 (b)

Hace unos días, tras la solución del Reto 1 y el Reto 2 de las quals del CTF de la NcN de este año, os comentaba una posible solución al Reto 3, y os dejaba pendiente una segunda solución "tirando de debugger", que  es la que veremos hoy.

Yo voy a utilizar radare2 para hacerlo, pero podríais utilizar gdb, IDA, o vuestro debugger favorito.
Para los que se pierdan como yo mirando el puro ensamblador, radare2 tiene una función con la que podemos pintar un poco los bloques de las funciones, lo cual nos ayudará a visualizar un poco mejor lo que pasa:

$ r2 -d level.elf
[0x0040101f]> af@main
[0x0040101f]> ag > foo.dot
foo.dot created

Hemos abierto el binario y nos hemos colocado en el main, y desde ahí generamos la gráfica que comentábamos anteriormente. Ahora solo tenemos que convertirla a otro formato más visible, por ejemplo PNG:

 $ dot -Tpng foo.dot > foo.png


No voy a subir el PNG a alta resolución (podéis generarlo vosotros mismos), pero creo que con esta captura será suficiente para que veáis el proceso.

¿Os acordáis que nos habíamos dado cuenta que la aplicación comparaba carácter a carácter? Quizá buscar bucles sea un buen punto de partida. En este caso, se puede ver fácilmente en la gráfica que existen únicamente DOS bucles (los identificamos porque hay flechas que vuelven a bloques anteriores). Si miramos ambos detenidamente veremos que el que nos interesa es el de la izquierda de los que tenéis marcados. Las dos flechas señalan donde están los dos puntos donde voy a hacer zoom a continuación, para que no os perdáis.

Pegando un vistazo al bucle, observamos una bifurcación curiosa:


Como podéis ver, llega un momento en el que se realiza una comparación y el flujo de la ejecución se va a una rama que llama a la función game_over(), o a otra rama que llama a las funciones success() y después a no_me_jodas_manolo() (vaya nombre xDDD). Está claro que lo que nos interesa es forzar a que la ejecución del programa vaya a esta última rama, pero nos interesa hacerlo desde algún sitio en el que el contenido cifrado ya haya sido descifrado, o sino seguramente no vamos a conseguir nada ¿Donde podemos estar seguros que estará el contenido cifrado pero todavía no hemos tenido que introducir caracteres? Pues al comienzo del bucle que estábamos mirando.


Donde tenéis la fecha roja es el bloque a donde vuelve el bucle, con lo que... ¿qué pasará si sobre escribimos el contenido y metemos un salto al bloque que nos da la solución? ¿funcionará?

Os he marcado también con un recuadro rojo la que supuestamente es la comparación entre lo que introduce el usuario por teclado y lo que hay en memoria. Poniendo un breakpoint aquí y yendo a la zona de memoria a la que referencia seguramente también seríamos capaces de obtener la clave, pero de momento nosotros vamos a intentar hacer el bypass directo que comentábamos antes, a ver si funciona.

$ cp level.elf foolevel.elf
$ r2 -w foolevel.elf
[0x00400690]> s 0x0040114e
[0x0040114e]> wa jmp dword 0x0040117b
Written 5 bytes ( jmp dword 0x0040117b)=wx e928000000
[0x0040114e]> pd
      ,=< 0x0040114e     e928000000       jmp dword 0x40117b

Hemos abierto una copia de level.elf a la que hemos llamado foolevel.elf y la hemos abierto en modo de escritura. Nos hemos ido a la posición 0x0040114e, que es la del inicio del bucle, y hemos sobre escrito un salto a la posición 0x0040117b, que es la del bloque que suponemos que nos mostrará el flag. Ahora solo nos queda salir de radare y ejecutar el binario a ver que pasa:

$ ./foolevel.elf
|  >  Type to win, only what I want to read...
|  >  |
|  -> Congratulations! The key is:
|  9e0d399e83e7c50c615361506a294eca22dc49bfddd90eb7a831e90e9e1bf2fb

Bingo! Ya tenemos el flag de nuevo, pero esta vez no hemos necesitado sacar la clave. En otras situaciones no habríamos podido hacer esto (o hubiera resultado mucho más complicado), porque depende mucho de como esté implementado, pero en esta ocasión ha funcionado a las mil maravillas. Por supuesto, existen bastantes maneras de solucionar este mismo reto. Si lo habéis probado de otra manera y queréis dejar un comentario... será bien recibido :)

lunes, 11 de noviembre de 2013

NcN PreQuals 2013: Reto 3 (a)

En el último par de días habíamos hablado de la solución al Reto 1 y Reto 2 de las Quals del CTF de la NcN 2013, así que hoy nos toca ver la solución al Reto 3, el último en el que consistian las Quals. El reto consistía en un binario del que a priori no se proporcionaba más información.

En estos casos, lo mejor es empezar y saber ante que tipo de binario nos encontramos. Si no tiene la cabecera corrupta ni nada similar, debería bastarnos con usar el comando "file":

$ file level.elf 
level.elf: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.24, BuildID[sha1]=0xb589d432799bf15343387fea63d4bdc00faa177c, not stripped

Ya sabemos algo sobre el binario, es un ELF de 64 bits, así que deberíamos buscarnos un Linux de 64 bits en el que correr este binario ¿No tienes ninguno a mano? En ese caso te pasó igual que a mi al resolver este reto :) Por suerte Linux 64 bits es algo que podemos descargar facilmente de Internet.

Una vez que ya tenemos un Linux de 64 bits y podemos ejecutar el binario, a mi siempre me gusta ejecutarlo una vez a ver lo que hace antes de meterte "a saco" a desensamblar. El funcionamiento del binario te puede dar pistas sobre lo que tienes que buscar en el código.

$ ./level.elf
|  >  Type to win, only what I want to read...
|  > f
|
|  -> I DON'T THINK SO

Cuando llamas al binario, se queda esperando a que pulses alguna tecla y, si es incorrecta, inmediatamente cierra la ejecución. Es de suponer que al introducir la clave correcta nos dará el deseado flag.

Antes de seguir, solo por si fuera así de sencillo, deberíamos hacerle un "strings" al binario, no sea que la contraseña aparezca directamente:

$ strings level.elf
/lib64/ld-linux-x86-64.so.2
libc.so.6
fflush
puts
putchar
[..]
795ef9462825a6640f9a9d7e85a01f5bb311c5849f8fc0e41bf4030f43e583f3
50a89ea2b500750f9baa163adee2057881b418e47bce9ddced4941556614e499
be0978a13b5875b112bcc84b4b688d68ec2d11010543189540bffa3641f2623d
911106de1b05db8d1b0fb2fe118345b4db6ddb930cf61290fd0b8336ffb394fd
a750b2d4129207fbbe6527ddd396f24327f4302c0b8496e154f139612bfc3312
a3f0e19a20d10cb055fbaaf4bbe82859074e50f4f8c2cde2b907c0947941ec98
[...]

No os pongo toda la salida, por no hacer grande el post, pero no vemos nada que pueda parecer la clave ni el flag, pero sí que vemos algo que tiene pinta de estar cifrado. Es muy común en este tipo de retos que haya información cifrada que el propio binario se encargue descifrar, para evitar que sea tan fácil sacar la clave.

Esta vez no hubo suerte con el strings, así que vamos a tener que ejecutar el binario para que haga su trabajo. Otra de las cosas que suelo hacer es usar el comando "ltrace" (o "strace", o "ptrace", según la ocasión). El comando "ltrace" nos mostrará las llamadas a librerías, con lo que si se ejecuta un strcmp() o similar debería aparecernos:

$ ltrace ./level.elf 
__libc_start_main(0x40101f, 1, 0x7fff991818e8, 0x4011c0, 0x401250
fputc('\n', 0x7f279f827260
)                                                                      = 10
fwrite("|  >  ", 1, 6, 0x7f279f827260)                                                           = 6
fflush(0|  >  )                                                                                        = 0
fwrite("Type to win, only what I want to"..., 1, 41, 0x7f279f827260)                             = 41
fflush(0Type to win, only what I want to read... )                                                                                        = 0
fputc('\n', 0x7f279f827260
)                                                                      = 10
fwrite("|  >  ", 1, 6, 0x7f279f827260)                                                           = 6
fflush(0|  >  )                                                                                        = 0
tcgetattr(0, 0x00603400)                                                                         = 0
tcsetattr(0, 0, 0x00603440)                                                                      = 0
getchar(0, 21505, 51729, -1, 0x603440)                                                           = 120
tcsetattr(0, 0, 0x00603400)                                                                      = 0
fputc('\n', 0x7f279f827260
[...]

Tampoco aparece nada, así que una de dos, o el programador está comparando carácter a carácter (algo coherente con el funcionamiento que hemos visto antes) o se ha implementado su propia versión de comparador de cadenas. Vamos a suponer lo primero y vamos a hacer una pequeña prueba: Vamos a hacer un pequeño script en python que pruebe todos los posibles valores ascii para el primer elemento, y vamos a pegar un vistazo a ojo a ver si hay algún cambio en la salida:

$ cat test.py 
#!/usr/bin/python
import os
for c in xrange(1,255):
cmd = 'echo '+str(c)+' ; echo "'+str(chr(c))+'" | ./level.elf'
os.system(cmd)

$ python test.py
[...]
31
|  >  Type to win, only what I want to read... 
|  >  
|
|  -> I DON'T THINK SO
32
|  >  Type to win, only what I want to read... 
|  >  *
|
|  -> I DON'T THINK SO
[...]

Vaya! Parece que sí que hay algo que cambia! Cuando probamos con el carácter 32 (0x20, vamos, el espacio) la salida nos marca con un asterisco, como señalando que hemos marcado un carácter bien, aunque nos sigue diciendo "I DON'T THINK SO", que nos hace ver que aún nos quedan caracteres por meter. Asumiendo que cada vez que acertemos otro carácter tendremos un asterisco más, y que esa frase de "I DON'T THINK SO" desaparecerá una vez obtengamos la solución, nos hacemos otro script en python para que nos automatice un poco la tarea:

$ cat bruteforce.py
#!/usr/bin/python
import subprocess
password = ""
for l in xrange(1,20):
        for c in xrange(1,255):
                if c == 34:
                        continue
                temp_pass = password + chr(c)
                result = subprocess.check_output('echo \"'+temp_pass+'\" | ./level.elf', shell=True)
                if "I DON'T THINK SO" not in result:
                        print temp_pass
                        print result
                        exit(0)
                n = result.count('*')
                if n == l:
                        print temp_pass
                        password = password + chr(c)
                        break

Hacemos un bucle de longitudes de 1 a 20 (podríamos poner más), y para cada posición probamos todos los posibles valores ascii y analizamos el resultado. Si la cantidad de caracteres asterisco es igual a la longitud de la clave que habíamos probado, es que es que hemos acertado, y sino tenemos que probar con el siguiente. Además, si la frase "I DON'T THINK SO" desaparece del resultado, probablemente hemos encontrado la contraseña correcta y tendremos la solución. Vamos a ver si funciona:

$ time ./bruteforce.py
 S
 SU
 SUR
 SURP
 SURPR
 SURPRI
 SURPRIS
 SURPRISE
 SURPRISE!
|  >  Type to win, only what I want to read...
|  >  **********
|
|  -> Congratulations! The key is:
|  9e0d399e83e7c50c615361506a294eca22dc49bfddd90eb7a831e90e9e1bf2fb

real    0m7.179s
user    0m0.772s
sys     0m4.316s

Bingo! Parece que ya tenemos el tercer flag :)
Este reto había muchas maneras de resolverlo. En este caso, como no soy persona que se dedique a hacer reversing a diario, me resultaba más cómoda esta aproximación, pero quise probar también a ver como lo podría haber resuelto arrancando el debugger y viéndole las tripas al binario, por eso he llamado a este post "a", porque falta la solución "b", con debugger.

jueves, 7 de noviembre de 2013

NcN PreQuals 2013: Reto 2

Ayer veíamos la solución al reto 1 de las prequals del CTF de la NocONName de 2013, así que hoy vamos a ir con la solución del reto 2. Este reto consistía en una aplicación para Android (APK) de la que no se nos daba mucha más información.

Lo primero que intenté fue instalarla en el emulador, pero esta se cerraba nada más intentar arrancarla. Intenté lo mismo con un par de dispositivos físicos pero obtuve el mismo resultado. Tras intentar hacer cambios en el código a nivel smali (eso para otro post) para intentar arreglar los errores, pensando que era parte del reto, al final pensé en pegarle un vistazo al código antes de seguir por ese camino, no fuera que sea un código no ofuscado y sencillo de leer y no esté haciendo más que perder tiempo intentando ejecutarlo. Así que vamos a ello, a desempaquetar:

$ apktool d level.apk levelapk_apktool

Este comando, si tenemos instaladas las apktools, nos creará un directorio "levelapk_apktool" con todo el contenido del APK. Vamos a pegarle un vistazo al AndroidManifest.xml a ver cual es la MainActivity para ver por donde tenemos que empezar a buscar:

$ cat levelapk_apktool/AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest android:versionCode="1" android:versionName="1.0" package="com.facebook_ctf.challenge"
  xmlns:android="http://schemas.android.com/apk/res/android">
    <uses-sdk android:minSdkVersion="8" android:targetSdkVersion="15" />
    <application android:theme="@style/AppTheme" android:label="@string/app_name" android:icon="@drawable/ic_launcher">
        <activity android:label="@string/title_activity_main" android:name="com.facebook_ctf.challenge.MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity android:label="@string/title_activity_main" android:name="com.facebook_ctf.challenge.MainActivity" />
    </application>
</manifest>

Ya sabemos que el inicio de todo se encuentra en "com.facebook_ctf.challenge.MainActivity". Ahora podríamos irnos al directorio "smali" y bucear hasta esa función, pero a mi me resulta más sencillo extraer el dex y decompilarlo con alguna herramienta como jd-gui. Para ello tendremos que extraer el APK de otra forma, simplemente haciendo un unzip:

$ mkdir levelapk_unzip
$ cp level.apk levelapk_unzip/
$ cd levelapk_unzip
$ unzip level.apk

Tras hacer el unzip veremos que tenemos un fichero classes.dex . Este fichero no puede ser abierto directamente con jd-gui, pero podemos transformarlo en un JAR de forma sencilla con la herramienta dex2jar:

$ d2j-dex2jar.sh classes.dex 
dex2jar classes.dex -> classes-dex2jar.jar
$ mv classes-dex2jar.jar classes.jar

Ahora ya podemos irnos a JD-GUI y buscar "com.facebook_ctf.challenge.MainActivity" y sus funciones, particularmente el constructor y onCreate(), que es donde generalmente se inician las acciones:



Como podemos ver, el onCreate() realiza una serie de acciones y acaba llamando a una función de su propia clase llamada yaaaay(), que vamos a ver que pinta tiene:




En la función yaaaay(), se comienza instanciando un montón de Bitmaps que se sacan de los recursos de la aplicación (todavía no sabemos que son). Luego se elige uno de ellos de forma aleatoria y se muestra. Por en medio de todo eso, aparece una función makeMeHappyAgain() a la que se le están pasando el resultado de otras funciones makeMeHappy() a las que a su vez se les pasa los Bitmaps que comentamos anteriormente. La llamada sería una cosa así:

makeMeHappyAgain(
        makeMeHappy( localBitmap1, localBitmap2, localBitmap3, localBitmap4 ),
        makeMeHappy( localBitmap5, localBitmap6, localBitmap7, localBitmap8 ),
        makeMeHappy( localBitmap9, localBitmap10, localBitmap11, localBitmap12 ),
        makeMeHappy( localBitmap13, localBitmap14, localBitmap15, localBitmap16 )
);

Vamos a ver entonces que hacen estas funciones que nos van a hacer tan felices :)


Las funciones, como podéis ver, son casi iguales, ambas crean un nuevo bitmap y van pegando en él los otros bitmaps uno a continuación del otro. La única diferencia entre ambas funciones es que una lo hace en horizontal y la otra en vertical. Si volvemos a pensar en la llamada original, veremos que lo que está haciendo es coger todos los Bitmaps que había cargarlo y juntarlos todos en un solo Bitmap 4x4. Ese bitmap resultado... no va a ningún sitio!! La aplicación no hace nada con él, pero llegados a este punto seguro que la solución del reto iba por ahí, así que... vamos a ver, viendo que recurso estaba cargado en cada variable, como quedaría ese bitmap 4x4:


Pero... ¿cuales son estos recursos? Para saberlo tenemos que irnos a los ficheros de recursos de la aplicación a ver si encontramos estos mismos números, a ver que son:

$ cat levelapk-apktool/res/values/public.xml
[...]
    <public type="raw" name="a" id="0x7f040000" />
    <public type="raw" name="b" id="0x7f040001" />
    <public type="raw" name="c" id="0x7f040002" />
    <public type="raw" name="d" id="0x7f040003" />
    <public type="raw" name="e" id="0x7f040004" />
    <public type="raw" name="f" id="0x7f040005" />
    <public type="raw" name="g" id="0x7f040006" />
    <public type="raw" name="h" id="0x7f040007" />
    <public type="raw" name="i" id="0x7f040008" />
    <public type="raw" name="ic_secret" id="0x7f040009" />
    <public type="raw" name="j" id="0x7f04000a" />
    <public type="raw" name="k" id="0x7f04000b" />
    <public type="raw" name="l" id="0x7f04000c" />
    <public type="raw" name="m" id="0x7f04000d" />
    <public type="raw" name="n" id="0x7f04000e" />
    <public type="raw" name="o" id="0x7f04000f" />
    <public type="raw" name="p" id="0x7f040010" />
[...]

Como veis, los números aparecen, pero en su forma hexadecimal, y aparecen referenciados a elementos de tipo "raw", que si miramos en la carpeta "raw" a ver que pinta tienen, veremos algo así:


Está claro que al final de la jugada vamos a obtener un código QR donde seguramente estará el flag que buscamos. Ahora solo nos falta rehacer nuestro cuadro anterior pero esta vez poniendo ya el nombre de las imágenes:


La composición de la imagen quedaría tal que así, y la podríamos hacer con un script, con algunas herramientas que directamente juntan imágenes, o completamente a mano. En mi caso, como son pocas imágenes y no sabía de memoria como manejar imágenes (no es algo que suela programar), me pareció más rápido a mano. Veréis que he marcado dos bitmaps en amarillo, eso es porque, al montar la imagen, veréis de repente que el bitmap que estáis poniendo no encaja con los demás, pero no resulta muy complicado colocarlo "a ojo" en su sitio adecuado. Después de juntar todo y ver que el código QR resultante parece tener sentido, tenemos algo así:


Hay herramientas de linea de comandos para leer estos código, herramientas on-line, o directamente podéis apuntar con vuestro móvil a la pantalla y leerlo si tenéis la aplicación adecuada. Yo opté por hacerlo con una herramienta on-line. La primera que probé no me funcionó, pero la segunda que probé ya me sacó el resultado esperado:


Ya tenemos el segundo :)

miércoles, 6 de noviembre de 2013

NcN PreQuals 2013: Reto 1

Como ya sabéis, esta pasado fin de semana fueron las finales del CTF de la NcN, organizadas por el equipo de seguridad de FaceBook, pero algunas semanas antes de eso los equipos tuvieron que pasar una clasificación basada en tres pruebas.

En la primera de ellas, nos encontrábamos ante un formulario web con un solo campo en el que supuestamente debíamos acertar con la contraseña para que nos proporcionara el Flag. En estos casos, lo primero es ver como son las conexiones a bajo nivel, para lo que normalmente lo mejor es configurar un proxy intermedio (Burp en mi caso).


Al hacerlo, nos damos cuenta que la aplicación nos devuelve un mensaje "Invalid password" antes de realizar ninguna conexión, con lo que podemos intuir que se produce algún tipo de validación en el lado cliente. Para confirmarlo, desconectamos el equipo de Internet y volvemos a probar. Es conveniente hacer esto porque puede haber plugins (flash, java, ...) que no hagan caso de la configuración del proxy del navegador, y confundirnos un poco. Una vez confirmado que hay algo en el lado cliente, solo tenemos que pegar un vistazo al código HTML:


Como podemos ver, la página carga un script llamado crypto.js, y llama a una función encrypt() al enviar el contenido del formulario. Si miramos el contenido de crypto.js veremos que tiene un javascript ofuscado que no nos deja ver muy bien la función de validación. Existen maneras de desofuscar este código, pero una rápida que suelo hacer yo es sustituir la función eval() por un alert(), de tal modo que el código javascript desofuscado que se debería ejecutar, en lugar de ser ejecutado, es mostrado en un alert, y lo podemos copiar y pegar en un fichero:


Todo lo que nos devuelve el alert lo copiamos y pegamos en un TXT, aplicamos unos cuantos saltos de linea, y podemos leer fácilmente la función encrypt() que habíamos visto antes, que tiene una pinta tal que así:


Como podemos ver, la cadena de texto que introduzcamos es convertida en un valor numérico por medio de la función numerical_value(), y a este valor se le aplican una serie de operaciones matemáticos. Si el resultado final es CERO, entonces la contraseña es correcta.

Si hacemos el proceso inverso podemos ir calculando que valor debería devolver numerical_value() para que la contraseña sea correcta:

res = res^4153      -> res debería ser 4153, para que al hacer XOR el resultado sea cero.
res = res/4             -> multiplicamos 4153 por cuatro, para hacerla operación contraria.
res = res>>>6       -> desplazamos hacia la izquierda 6 bits
res = res*(3+1+3+3+7) -> dividimos por 17.

El resultado final es 62540, aunque otros valores cercanos también pueden dar el mismo resultado, debido a las operaciones de división y desplazamiento de bits.

Ahora solo nos queda encontrar una cadena de texto que, al aplicarle numerical_value(), dé éste valor o cercano. Como podemos ver en la imagen anterior, numerical_value() coge los valores ASCII de cada uno de los caracteres y los multiplica por su posición (empezando en 1), y los suma todos para obtener el valor final.

A partir de aquí, tenemos varias opciones para obtener la cadena: hacer un análisis o probar por fuerza bruta. En este caso a mi me pareció sencillo y rápido hacerlo a mano, así que tomé esta aproximación:
  1. Modificamos el código javascript para que haga un alert() del valor devuelto por numerical_value(), así tenemos una referencia del valor de la cadena que estamos calculando (res=numerical_value(form.password.value); alert(res); ...)
  2. Vamos metiendo cadenas compuestas del caracter 'Z' y vamos aumentando la longitud hasta que el valor devuelto sea mayor que 62540 (ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ -> 63270).
  3. Ahora vamos decrementando el último caracter mientras el valor devuelto sea mayor que 62540. Cada cambio en caracteres de más a la derecha es más significativo en el resultado final, y cada cambio en caracteres de más a la izquierda proporciona un cambio más sutil.
  4. Vamos jugando con las posiciones y letras hasta que llegamos al valor deseado.
ZPZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZG -> 62547


Como podéis ver, el valor finalmente es 62547, que como decíamos no es exactamente el que habíamos calculado, pero ya sabíamos que podía pasar esto y que era perfectamente válido.

Ya tenemos uno :)