Un blog de Seguridad Informática, desde un punto de vista tecnológico

Archivo para octubre, 2013

Write-up del 2º Reto del CTF de la No cON Name 2013

Buenas a todos!

Vamos a cerrar el fin de semana y ésta cadena de posts exponiendo la solución al segundo reto del CTF de las NoConName 2013 que se celebró el pasado fin de semana.

El juego consistía en una aplicación Android (.APK) que debía ser descargada de la pagina oficial del CTF.

Una vez instalada en el emulador o dispositivo, a nuestro pesar observamos que ésta no se ejecutaba correctamente, dando lugar a un error desconocido de entrada.

Rascando los ficheros de log del dispositivo mediante la aplicación CatLog, pudimos observar que algo no funcionaba correctamente en la aplicación, concretamente se mostraba el mensaje de error que se muestra a continuación:

screenshot-1380333703143

Efectivamente, parecía que había algún tipo de error producido por un fichero Manifest.xml mal generado.

Para obtener el fichero Manifest.xml primero hay que decompilar el código fuente de la aplicación. Para ello se utilizó la herramienta APKTOOL:

Una vez instalada la herramienta en el sistema, obtener el decompilado de la aplicación , y consecuentemente el fichero Manifest.xml, ejecutando lo siguiente:

root@bt:~/Desktop# apktool d level.apk
I: Baksmaling...
I: Loading resource table...
I: Loaded.
I: Decoding AndroidManifest.xml with resources...
I: Loading resource table from file: /root/apktool/framework/1.apk
I: Loaded.
I: Regular manifest package...
I: Decoding file-resources...
I: Decoding values */* XMLs...
I: Done.
I: Copying assets and libs...
root@bt:~/Desktop#

A continuación se muestra el fichero Manifest.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">
 <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>

Cómo se puede observar, hay dos MainActivity definidas, lo cual ocasiona un conflicto que evita que la aplicación pueda ser iniciada correctamente. Eliminando una de las dos líneas que definen el MainActivity será suficiente para que la aplicación sea ejecutada correctamente.

Una vez modificado el fichero Manifest.xml, hay que volver a recompilar la aplicación. Para ello volvímos a utilizar la herramienta APKTOOL pero ahora además es necesario el empleo de JARSIGNER para generar la firma:

root@bt:~/Desktop# apktool b level-smali level-modified.apk
I: Checking whether sources has changed...
I: Checking whether resources has changed...
I: Building apk file...
root@bt:~/Desktop# java -jar signapk.jar testkey.x509.pem testkey.pk8 ../level-modified.apk ../level-modified-signed.apk

Al instalar de nuevo la aplicación, ya pudimos ver de que se trataba:

screenshot-1380334377050

La aplicación básicamente funciona de la siguiente forma: Dispone de un botón con la etiqueta “Gimme Gimme!” mediante el cual, cuando se hace TAP sobre él, se muestra una imágen en la aplicación. Concretamente en la aplicación original se muestra de forma aleatória una de las imágenes almacenadas en la carpeta resource de la aplicación:

Screen Shot 2013-10-06 at 8.34.46 PMNo hace falta mucha ingeniería inversa para poder comprobar que las imágenes que se muestran son fragmentos de un código QR que, tal como ya os habréis imaginado, habrá que reconstruir. Así que, Let’s go!

Mediante el proceso de decompilado anteriormente mencionado, también se obtiene el código Dalvik de la aplicación que procederemos a comentar a continuación. No obstante, con la finalidad de poder comprender mejor el funcionamiento de la aplicación, antes de meternos en detalle en el código Smali, primero se procedió a pasar el código almacenado en el fichero classes.dex a Java para sacar un poco de luz al asunto.

Para esta tarea se hizo uso de la herramienta DEX2JAR:

  • DEX2JAR – http://code.google.com/p/dex2jar/

El uso de la herramienta es bastante simple. Solamente es necesario descomprimir el fichero APK (p.ej. con el comando unzip) y posteriormente ejecutar lo siguiente con el fichero classes.dex obtenido:

/dex2jar-0.0.9.15/d2j-dex2jar.sh classes.dex

Mediante la ejecución de dicha herramienta, en conjunto con el uso de JD-GUI, fue posible obtener el código fuente en JAVA, donde se pudo observar cómo en la clase MainActivity se pinta la imágen escogida aleatoriamente. Concretamente se realiza en la función “yaaaay()” mediante la variable “localBitmap17”:

int i = new Random().nextInt(localArrayList.size());
   makeMeHappyAgain(makeMeHappy(localBitmap1, localBitmap2, localBitmap3, localBitmap4), makeMeHappy(localBitmap5, localBitmap6, localBitmap7, localBitmap8), makeMeHappy(localBitmap9, localBitmap10, localBitmap11, localBitmap12), makeMeHappy(localBitmap13, localBitmap14, localBitmap15, localBitmap16));
   Bitmap localBitmap17 = (Bitmap)localArrayList.get(i);
   this.secret.setImageBitmap(localBitmap17);

La función “makeMeHappyAgain”, analizando el código se observó que era la responsable de maquetar el código QR correctamente (bueno casi, pero eso lo veremos más adelante :-P), por lo que por el momento, únicamente había que modificar el código Smali para que en lugar de pintar por pantalla la imagen aleatória almacenada en “localBitmap17”, se pintara el resultado de la llamada a la función “makeMeHappyAgain”:

Screen Shot 2013-09-28 at 4.20.42 AM

En la imágen anterior, se puede apreciar cómo se modificó el código Smali original para que en la última llamada al método virtual “setImageBitmap”, en lugar de utilizar el valor “v1”,  se utilizara el valor “v4” que es la variable resultado de la llamada a la función “makeMeHappyAgain”, definido por la  instrucción “move-result-object v4”.

Aunque a simple vista, una vez recompilado el código y ejecutado de nuevo parece ser que el código QR se genera correctamente, realmente no fue así 😦

Hay  dos imágenes que no están correctamente posicionadas, lo cual no nos permitía escanear correctamente el QR y obtener la clave del reto. Para ello hay que hacer otra modificación del código Smali para hacer swap de las imágenes “c.png” (0x7f040002) y “g.png” (0x7f040006). Para obtener la relación entre las imágenes y sus correspondientes  identificadores Hex hay que consultar el fichero “public.xml” almacenado en la carpeta  de los resources (res) de la aplicación.

A continuación se muestra el código resultante de dicha modificación:

Screen Shot 2013-09-28 at 4.22.51 AM

Finalmente, una vez recompilada, refirmada y reinstalada la aplicación se puede observar el código QR generado correctamente:

welldone

Y posteriormente, una vez escaneado, obtuvimos la SECRET KEY del Reto:

screenshot-1380333471692

Solo nos queda felicitar a los 12 equipos que se han clasificado en la final que se celebrará el día 1 de Noviembre en el congreso NoConName 2013:

Screen Shot 2013-10-06 at 8.16.14 PM

……y decir que logramos clasificarnos en 7º posición 😉

Así que nos vemos en la final!

Anuncios

Reto 1 del CTF de las No cON Name 2013

Hola a todos, hola a tothom!

Mi nombre es Ignasi M. (ny4nyi) y he tenido el placer de ser invitado a participar en este blog. Es todo un honor. Espero que todo lo que pueda aportar os sea útil y disfrutéis!

Sobretodo, dar las gracias a Julian y a todo el equipo por darme esta oportunidad de poder participar con gente tan grande.

Ahora, a por materia.

El primer reto del CTF NoConName 2013 consistía en una página web que solicita una clave secreta para superar el reto.

Paso 1

La primera aproximación fue probar un número aleatorio para estudiar el comportamiento normal de la página. Obviamente, se produce un error de “Clave incorrecta” y se sigue en la misma página inicial (ya hubiera sido la ostia acertar a la primera ).

Una vez se conoce el comportamiento estándar de la página, se realizó un estudio del código fuente de la misma, y se encuentra lo siguiente:

   <head>

    <title>NcN 2013 Registration Quals</title>

    <link rel=”stylesheet” href=”../res/main.css” type=”text/css” media=”screen”/>

    <link href=’../res/UbuntuMono.css’ rel=’stylesheet’ type=’text/css’>

    <meta content=”Javier Marcos @javutin” name=”author” />

    <script type=”text/javascript” src=”crypto.js”></script>

  </head>

La página carga un “js” de nombre “crypto.js” que es el responsable de realizar la validación. Resaltar que al tratarse de un “js” se ejecuta en el navegador del cliente, no en servidor. Por lo tanto, el siguiente paso a seguir es realizar un estudio del código del “js” y entender su lógica/funcionamiento para identificar la posible manera de evadir o hallar la clave secreta de forma exitosa.

Se accede al archivo “cryto.js” para obtener su código fuente. Es algo tan bonito como esto:

var_0x52ae=[“\x66\x20\x6F\x28\x38\x29\x7B\x63\x20\x69\x2C\x6A\x3D\x30\x3B\x6B\x28\x69\x3D\x30\x3B\x69\x3C\x38\x2E\x6C\x3B\x69\x2B\x2B\x29\x7B\x6A\x2B\x3D\x28\x38\x5B\x69\x5D\x2E\x73\x28\x29\x2A\x28\x69\x2B\x31\x29\x29\x7D\x67\x20\x74\x2E\x75\x28\x6A\x29\x25\x76\x7D\x66\x20\x70\x28\x68\x29\x7B\x68\x3D\x68\x2E\x71\x28\x30\x29\x3B\x63\x20\x69\x3B\x6B\x28\x69\x3D\x30\x3B\x69\x3C\x77\x3B\x2B\x2B\x69\x29\x7B\x63\x20\x35\x3D\x69\x2E\x78\x28\x79\x29\x3B\x6D\x28\x35\x2E\x6C\x3D\x3D\x31\x29\x35\x3D\x22\x30\x22\x2B\x35\x3B\x35\x3D\x22\x25\x22\x2B\x35\x3B\x35\x3D\x7A\x28\x35\x29\x3B\x6D\x28\x35\x3D\x3D\x68\x29\x41\x7D\x67\x20\x69\x7D\x66\x20\x6E\x28\x38\x29\x7B\x63\x20\x69\x2C\x61\x3D\x30\x2C\x62\x3B\x6B\x28\x69\x3D\x30\x3B\x69\x3C\x38\x2E\x6C\x3B\x2B\x2B\x69\x29\x7B\x62\x3D\x70\x28\x38\x2E\x71\x28\x69\x29\x29\x3B\x61\x2B\x3D\x62\x2A\x28\x69\x2B\x31\x29\x7D\x67\x20\x61\x7D\x66\x20\x42\x28\x39\x29\x7B\x63\x20\x32\x3B\x32\x3D\x6E\x28\x39\x2E\x64\x2E\x65\x29\x3B\x32\x3D\x32\x2A\x28\x33\x2B\x31\x2B\x33\x2B\x33\x2B\x37\x29\x3B\x32\x3D\x32\x3E\x3E\x3E\x36\x3B\x32\x3D\x32\x2F\x34\x3B\x32\x3D\x32\x5E\x43\x3B\x6D\x28\x32\x21\x3D\x30\x29\x7B\x72\x28\x27\x44\x20\x64\x21\x27\x29\x7D\x45\x7B\x72\x28\x27\x46\x20\x64\x20\x3A\x29\x27\x29\x7D\x39\x2E\x47\x2E\x65\x3D\x6E\x28\x39\x2E\x64\x2E\x65\x29\x3B\x39\x2E\x48\x2E\x65\x3D\x22\x49\x22\x2B\x6F\x28\x39\x2E\x64\x2E\x65\x29\x3B\x67\x20\x4A\x7D”,”\x7C”,”\x73\x70\x6C\x69\x74″,”\x7C\x7C\x72\x65\x73\x7C\x7C\x7C\x68\x65\x78\x5F\x69\x7C\x7C\x7C\x73\x74\x72\x7C\x66\x6F\x72\x6D\x7C\x7C\x7C\x76\x61\x72\x7C\x70\x61\x73\x73\x77\x6F\x72\x64\x7C\x76\x61\x6C\x75\x65\x7C\x66\x75\x6E\x63\x74\x69\x6F\x6E\x7C\x72\x65\x74\x75\x72\x6E\x7C\x66\x6F\x6F\x7C\x7C\x68\x61\x73\x68\x7C\x66\x6F\x72\x7C\x6C\x65\x6E\x67\x74\x68\x7C\x69\x66\x7C\x6E\x75\x6D\x65\x72\x69\x63\x61\x6C\x5F\x76\x61\x6C\x75\x65\x7C\x73\x69\x6D\x70\x6C\x65\x48\x61\x73\x68\x7C\x61\x73\x63\x69\x69\x5F\x6F\x6E\x65\x7C\x63\x68\x61\x72\x41\x74\x7C\x61\x6C\x65\x72\x74\x7C\x63\x68\x61\x72\x43\x6F\x64\x65\x41\x74\x7C\x4D\x61\x74\x68\x7C\x61\x62\x73\x7C\x33\x31\x33\x33\x37\x7C\x32\x35\x36\x7C\x74\x6F\x53\x74\x72\x69\x6E\x67\x7C\x31\x36\x7C\x75\x6E\x65\x73\x63\x61\x70\x65\x7C\x62\x72\x65\x61\x6B\x7C\x65\x6E\x63\x72\x79\x70\x74\x7C\x34\x31\x35\x33\x7C\x49\x6E\x76\x61\x6C\x69\x64\x7C\x65\x6C\x73\x65\x7C\x43\x6F\x72\x72\x65\x63\x74\x7C\x6B\x65\x79\x7C\x76\x65\x72\x69\x66\x69\x63\x61\x74\x69\x6F\x6E\x7C\x79\x65\x73\x7C\x74\x72\x75\x65″,””,”\x66\x72\x6F\x6D\x43\x68\x61\x72\x43\x6F\x64\x65″,”\x72\x65\x70\x6C\x61\x63\x65″,”\x5C\x77\x2B”,”\x5C\x62″,”\x67″];eval(function (_0x7038x1,_0x7038x2,_0x7038x3,_0x7038x4,_0x7038x5,_0x7038x6){_0x7038x5=function (_0x7038x3){return (_0x7038x3<_0x7038x2?_0x52ae[4]:_0x7038x5(parseInt(_0x7038x3/_0x7038x2)))+((_0x7038x3=_0x7038x3%_0x7038x2)>35?String[_0x52ae[5]](_0x7038x3+29):_0x7038x3.toString(36));};if(!_0x52ae[4][_0x52ae[6]](/^/,String)){while(_0x7038x3-)_0x7038x6[_0x7038x5(_0x7038x3)]=_0x7038x4[_0x7038x3]||_0x7038x5(_0x7038x3);} ;_0x7038x4=[function (_0x7038x5){return _0x7038x6[_0x7038x5];} ];_0x7038x5=function (){return _0x52ae[7];} ;_0x7038x3=1;} ;while(_0x7038x3–){if(_0x7038x4[_0x7038x3]){_0x7038x1=_0x7038x1[_0x52ae[6]]( new RegExp(_0x52ae[8]+_0x7038x5(_0x7038x3)+_0x52ae[8],_0x52ae[9]),_0x7038x4[_0x7038x3]);} ;} ;return _0x7038x1;} (_0x52ae[0],46,46,_0x52ae[3][_0x52ae[2]](_0x52ae[1]),0,{}));

Como se puede ver, el código del “js” esta ofuscado (lastima, habrá que currar más). Ha de quedar claro que esta ofuscado, NO cifrado.

Si se disecciona el código, a grandes rasgos se puede detectar lo siguiente:

  • Una variable de nombre “var_0x52ae” : Parece ser un vector donde el valor de cada posición está representado en código hexadecimal. Recordar que si un valor se escribe como “\x” significa que se representa en sistema hexa.
  • Una función eval: Definición purista, “Si el argumento es una expresión, eval () evalúa la expresión. Si el argumento es una o más sentencias JavaScript, eval () ejecuta las sentencias.”
  • La definición de funciones: Por ejemplo, “function (_0x7038x1,…)”
  • La llamada de funciones: Por ejemplo, “(_0x52ae[0],46,46,_0x52ae[3])”

Aclarar que en las definiciones de las funciones se ven nombres de parámetros tal que “_0x_7038x1” pero se desconoce su valor. Esto es porque en una declaración de función se puede dar cualquier nombre al parámetro, que luego éste recibirá el valor del parámetro usado en la llamada. En resumen, cuando se llame a la función, el valor del parámetro será el siguiente:

_0x7038x1=_0x52AE[0]=”\x66…..\7D”.

Paso 2

Para desofuscar el código js se pueden utilizar diferentes recursos online en la red, o simplemente, modificar la función “eval” por una función “alert” que se encargara de imprimir en una ventana la representación del código sin ofuscar, ya que interpretara todos los valores hexadecimales posibles como caracteres.

El resultado es el siguiente:

Imagen

El código sin ofuscar es:

Function simpleHash(str) {

var i, hash = 0;

for (i = 0; i < str.length; i++) {

hash += (str[i].charCodeAt() * (i + 1))

}

return Math.abs(hash) % 31337

}

 function ascii_one(foo) {

foo = foo.charAt(0);

var i;                              

for (i = 0; i < 256; ++i) {

var hex_i = i.toString(16);

if (hex_i.length == 1) hex_i = “0” + hex_i;

hex_i = “%” + hex_i;

hex_i = unescape(hex_i);

if (hex_i == foo) break

}

return i

}

 function numerical_value(str) {

var i, a = 0,

b;

for (i = 0; i < str.length; ++i) {

b = ascii_one(str.charAt(i));

a += b * (i + 1)

}

return a

}

function encrypt(form) {

var res;

res = numerical_value(form.password.value);

res = res * (3 + 1 + 3 + 3 + 7);

res = res >>> 6;

res = res / 4;

res = res ^ 4153;

if (res != 0) {

alert(‘Invalid password!’)

} else {

alert(‘Correct password :)’)

}

form.key.value = numerical_value(form.password.value);

form.verification.value = “yes” + simpleHash(form.password.value);

return true}

Paso 3

La lógica general de este código es la siguiente:

  1. La función “numerical_value” recibe el valor o cadena “X” que introduce el usuario en la página web y retorna un valor resultado que es la suma de cada carácter de la cadena X en su correspondiente valor decimal ASCII multiplicado por el resultado de la suma del valor de la posición del carácter en la cadena X +1
  2. La función “encryp” se encarga primero de aplicar varias operaciones sobre el valor retornado por la función “numerical_value” y después compara este valor “final” con 0. Si la comparación es cierta, el password (cadena “X”) introducida por el usuario es correcta.
  3. Estudiando con más detalle la función “encrypt” se determina que el valor “final” ha de estar entre el siguiente rango de valores :62540-62554.

Ya con esta información clara el último paso es introducir, mediante técnicas de “brute forcing”, una cadena de valores/caracteres que después de ser tratada por la función “numerical_value” de un valor que pertenezca al rango deseado.

En este caso se encontró la siguiente cadena:

ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZNR

Se introdujo esta cadena en la página web y tachan!:

Congrats! you passed the level! Here is the key:

23f8d1cea8d60c5816700892284809a94bd00fe7347645b96a99559749c7b7b8

Twitter: @Ny4nyi