Archive for abril, 2011

RootedCon CTF writeup nivel 9

Viernes, abril 29th, 2011

Reto 9

En el reto anterior bajamos un fichero APK que parece ser una aplicación de Android así que vamos a abrirla con el emulador:

Firmamos con un certificado aleatorio:

Pasamos el Zipalign:

Y ya podemos instalar en el emulador (necesitamos el Android SDK):

Si metemos cualquier contraseña nos tira del programa. Así que vamos a decompilar el Java a ver que trae.

Tras instalarlo, nos conectamos con una shell a ver dónde lo instaló, para poderlo descargar:

Ahora descargamos el paquete ya instalado:

Renombramos el fichero como un ZIP:

Y lo descomprimimos:

Decompilamos con dex2jar el fichero classes.dex:

Y el JAR generado lo abrimos con el Java Decompiler:

Pinchamos en Save -> All Sources y guardamos los fuentes, creándonos el fichero: classes.dex.dex2jar.src.zip

Lo descomprimimos y obtenemos los fuentes:

Y ahora toca estudiarse bien el código :)

Veamos que hace el programa. Para ello, abrimos level.java:

Aquí podemos ver 3 cosas:

  • str1 toma el valor introducido por la pantalla, lo que nosotros escribimos como solución
  • arrayOfByte1 tiene un valor fijo, que es: iloverootedcon
  • arrayOfByte2 se trata de una especie de hash, aún por determinar: c135559de69f3f57b9d6d70729912f7060cdc4e27358b22bcc1eb54b4c941266

Tras eso vemos una serie de chequeos (concretamente 3) que son los que provocan que la aplicación, al ejecutarla, se cierre tras meter cualquier contraseña:

Mirando por curiosidad el valor de esa especie de hash, 6d656361676f20656e746f646f6f6f6f6f6f6f6f6f6f6f, vemos que se trata de la palabra: ‘mecago entodooooooooooo‘ … jeje muy oportuna a estas alturas :)

Si seguimos viendo el código, ahora llega la parte caliente:

A groso modo:

  • arrayOfByte6 toma un valor que recoge de la función readRes().
  • arrayOfByte7 coge el valor que teníamos en arrayOfByte1, es decir, iloverootedcon.
  • str2 es el resultado de lo que hace la función x(), pasando como parámetro arrayOfByte6 y arrayOfByte7.
  • arrayOfByte8 es la cadena que introducimos al ejecutar el programa.
  • arrayOfByte9 es el valor en bytes de str2.

Luego hace un encrypt() usando como parámetros, nuestra cadena y str2, que deducimos que debe ser la key de (des)encriptado.

Si analizamos las funciones de utils.java, podemos ver que se trata de una encriptación AES. Por tanto, necesitamos una clave de cifrado para poder encriptar o desencriptar.

Volviendo a la imagen anterior, podemos deducir lo siguiente:

  • Tenemos el hash resultante:

c135559de69f3f57b9d6d70729912f7060cdc4e27358b22bcc1eb54b4c941266

  • Utiliza las funciones readRes() y x() para obtener la key, o parte de ella (ya que luego va concatenando ceros).
  • Realiza un bucle en el que va añadiendo ceros (48 en decimal) a la derecha de la key y va probando a encriptar nuestra cadena y comparándola con el hash que tenemos como resultado.
  • En utils.java podemos ver que existe una función decrypt() que no se utiliza en el programa.

Pues la cosa parece clara. Tratamos de usar ese mismo código, quitando las 3 funciones detect() y sustituyendo el encrypt() por un decrypt().

Aquí vino el lío. Jeje el problema es que no es lo mismo que esté claro, a que sea fácil hacerlo :)

Yo no soy muy adepto a Java así que, en primer lugar, traté de quitar todas las referencias a Android y compilar el programa en el PC, tal cual estaba. No hubo forma.

Luego probé a recompilar tocando lo mínimo, volver a empaquetar y subir el emulador. Tampoco hubo suerte.

Otra opción era modificar los ficheros SMALI. Imposible, porque eran demasiados cambios los que teníamos que hacer y el código está muy ofuscado.

Lo siguiente que se me ocurrió fue recompilar la aplicación usando Eclipse. Esto funcionó y estuve un rato jugando con el emulador, pero tampoco logré resolver nada (supongo que por mi inexperiencia en Java).

Así que no quedó otra que reconstruir las funciones y generar un único fichero, para poderlo usar desde el PC. En realidad sólo terminaremos usando 4 funciones:

  • hexStringToByteArray()
  • readRes()
  • x()
  • decrypt()

La cosa no fue tan dura como pintaba, aunque sí que dieron guerra algunas cosas:

En esta función vemos context.getString(2130968578) y resulta que es una función de Android, por lo que había que averiguar qué hace exactamente.

Buscando en Google llegué a esta página:

http://developer.android.com/reference/android/content/Context.html que dice:

getString(int resId)

Return a localized string from the application’s package’s default string table.

Buscando dónde coge ese valor llegué hasta R.java, en donde aparece:

Así que pensé que era ese valor: 2130968578. Nada más lejos de la realidad. Tras perder bastante tiempo, resultó que xxx toma su valor en el fichero strings.xml, que podemos encontrar dentro de /res/values/:

Otra función a retocar era x(), que quedó así:

Aquí lo que me dio guerra fue ese str1 = c

Yo pensé que copiaba en el String el valor del Char, es decir:

str1 = String.valueOf(c)

Esto provocaba que la función siempre devolviera un byte[1].

Tras mucho pelear, resultó faltarme un puñetero más (+), es decir:

str1 += String.valueOf(c)

Lo que debe hacer es concatenar los caracteres que va sacando y no quedarse con el último, que es lo que me estaba ocurriendo a mi

Tras ejecutar el programa obtenemos la ansiada contraseña, en decimal:

Que en ASCII resulta ser: IwantToWinTheCTF

El código completo del Java que me dio la solución es este:

import java.io.IOException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.SecretKeySpec;
import java.util.Arrays;

public class reto9 {
	public static void main(String[] args) {
		scan();
}

	public static void scan()  {
		byte[] arrayOfByte1 = new byte[14];
    	arrayOfByte1[0] = 105;
    	arrayOfByte1[1] = 108;
    	arrayOfByte1[2] = 111;
    	arrayOfByte1[3] = 118;
    	arrayOfByte1[4] = 101;
    	arrayOfByte1[5] = 114;
    	arrayOfByte1[6] = 111;
    	arrayOfByte1[7] = 111;
    	arrayOfByte1[8] = 116;
    	arrayOfByte1[9] = 101;
    	arrayOfByte1[10] = 100;
    	arrayOfByte1[11] = 99;
    	arrayOfByte1[12] = 111;
    	arrayOfByte1[13] = 110;
    	Object localObject = "";
	 	byte[] arrayOfByte11 = hexStringToByteArray("c135559de69f3f57b9d6d70729912f7060cdc4e27358b22bcc1eb54b4c941266");

		int cont = 0;
      byte[] arrayOfByte6 = readRes();
      byte[] arrayOfByte7 = arrayOfByte1;
      String str2 = x(arrayOfByte6, arrayOfByte7);

      localObject = str2;
      byte[] arrayOfByte77 = str2.getBytes();

		while (cont<100) {
    		try  {
          	byte[] arrayOfByte9 = ((String)localObject).getBytes();
//          byte[] arrayOfByte10 = encrypt(arrayOfByte8, arrayOfByte9);
          	byte[] arrayOfByte10 = decrypt(arrayOfByte11, arrayOfByte9);

   			int i = 0;

				while (i < arrayOfByte10.length) {
					System.out.print(arrayOfByte10[i]+" ");
					i += 1;
				}

				System.out.println();
				return;
			}
      	catch (Exception localException) {
				System.out.println(localException.getMessage());
      	}

      	String str3 = String.valueOf(localObject);
      	str2 = str3 + "0";
      	localObject = str2;
      	cont += 1;
		}
	}

	public static byte[] readRes() {
//    	String str1 = this.context.getString(2130968578);
    	String str1 = "1B03000200160C001A170B00041D48";
    	byte[] arrayOfByte = new byte[str1.length() / 2];
    	int i = 0;

    	while (true) {
      	int j = arrayOfByte.length;

      	if (i >= j) {
        		String str2 = new String(arrayOfByte);
        		return arrayOfByte;
      	}

      	int k = i * 2;
      	int m = i * 2 + 2;
      	int n = (byte)Integer.parseInt(str1.substring(k, m), 16);
      	arrayOfByte[i] = (byte)n;
      	i += 1;
    	}
  	}

	public static String x(byte[] paramArrayOfByte1, byte[] paramArrayOfByte2) {
   	String str1 = "";
    	int i = 0;

    	while (true) {
      	int j = paramArrayOfByte1.length;

      	if (i >= j)
        		return str1;

      	String str2 = String.valueOf(str1);
      	StringBuilder localStringBuilder = new StringBuilder(str2);
      	int k = paramArrayOfByte1[i];
      	int m = paramArrayOfByte2.length;
      	int n = i % m;
      	int i1 = paramArrayOfByte2[n];
      	char c = (char)(byte)(k ^ i1);
			StringBuffer sb = new StringBuffer(40);
//      	str1 = c;
      	str1 += String.valueOf(c);
      	i += 1;
    	}
  	}

  	public static byte[] encrypt(byte[] paramArrayOfByte1, byte[] paramArrayOfByte2)
   	throws NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException, NoSuchAlgorithmException {
   	Cipher localCipher = Cipher.getInstance("AES");
    	SecretKeySpec localSecretKeySpec = new SecretKeySpec(paramArrayOfByte2, "AES");
    	localCipher.init(1, localSecretKeySpec);
    	return localCipher.doFinal(paramArrayOfByte1);
  	}
	public static byte[] decrypt(byte[] paramArrayOfByte1, byte[] paramArrayOfByte2)
   	throws NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException, NoSuchAlgorithmException {
    	Cipher localCipher = Cipher.getInstance("AES");
    	SecretKeySpec localSecretKeySpec = new SecretKeySpec(paramArrayOfByte2, "AES");
    	localCipher.init(2, localSecretKeySpec);
    	return localCipher.doFinal(paramArrayOfByte1);
  	}

  	public static byte[] hexStringToByteArray(String paramString) {
	   int i = paramString.length();
    	byte[] arrayOfByte = new byte[i / 2];
    	int j = 0;

    	while (true) {
      	if (j >= i)
        		return arrayOfByte;

      	int k = j / 2;
      	int m = Character.digit(paramString.charAt(j), 16) << 4;
      	int n = j + 1;
      	int i1 = Character.digit(paramString.charAt(n), 16);
      	int i2 = (byte)(m + i1);
      	arrayOfByte[k] = (byte)i2;
      	j += 2;
    	}
       }
}

 

RootedCon CTF writeup niveles 7 y 8

Jueves, abril 14th, 2011

Reto 7

Este reto era de ingeniería social y trataba de localizar a alguien. Ahora la prueba está deshabilitada y se pasa de forma rápida:

——————–

La prueba 7 se ha eliminado ya que es necesario estar en la rooted para completarla, es de ingeniería social… y bueno… no es necesario llamar a ese teléfono,… así que para saltarte la prueba este es el resultado de la prueba 7:

——————–

La imagen que había inicialmente era esta:

Así que ahora en lugar de ingeniería social tenemos una imagen que no sabemos si es la que han subido para el reto o es que el enlace está mal. Vamos a http://imageupload.org y cargamos cualquier número aleatorio, con lo que obtenemos la misma imagen. La descargamos y hacemos un diff con la que tenemos y vemos que son iguales. Por tanto, lo que está mal es el enlace a la foto, que supongo será parte del reto.

Bueno, tras un rato probando cosas resultó que el enlace estaba mal porque imageupload no funciona bien y esto no era parte del reto. Así que tras avisar a Pablo, subió bien la imagen, que era un código de barras:

Así que lo leemos desde Linux con el Zbarimg:

La solución para pasar el reto es: 2887188576

Reto 8

El número obtenido en el reto anterior resulta ser una dirección IP, en decimal: 172.23.0.96, que se puede obtener en esta web:

http://www.kloth.net/services/iplocate.php

Hacemos un escaneo a esa web para ver los puertos abiertos:

Nos conectamos como anónimo por FTP a ver qué hay:

El fichero proftpd-howto.conf trae lo siguiente:

La verdad es que me recuerda mucho al reto de Security by Default :)

Tenemos información del bug disponible aquí:

http://www.exploit-db.com/exploits/8037/

Consigo acceder con:

user: USER%’) and 1=2 union select 1,1,uid,gid,homedir,shell from ftpuser #

pas: 1

pero veo los mismos datos que antes:

Así que vamos a añadir un LIMIT 1,1 para ver si accedemos con otro usuario:

Por tanto, la clave para el siguiente reto es: I have to drink some beers


RootedCon CTF writeup niveles 5 y 6

Lunes, abril 4th, 2011

Esta vez voy a subir la solución de dos retos en lugar de uno sólo, ya que el nivel 5 es fácil fácil (el más fácil de todo el CTF) … como dijo Pablo Catalina, este venía de regalo xDD

Reto 5

Para este reto tenemos que continuar por donde dejamos el anterior, como viene siendo habitual. Así que vamos a ver lo que trae esa imagen de disco:

Tras abrir el CAP con el wireshark vemos que tiene toda la pinta de ser una captura de una red wifi con encriptación WEP:

Por tanto, vamos a lanzar el aircrack-ng a ver si sacamos la clave:

Como se puede ver, la clave es: 01:01:01:01:01:01:01:01:01:01:01:01:01

Y la contraseña para pasar el reto: 01010101010101010101010101

Reto 6

Ya que tenemos la clave WEP (que por cierto, era de lo más sencilla), vamos a obtener el paquete sin cifrar:

Esto nos genera un nuevo fichero, ya descifrado (captura1-01-dec.cap) con 75.325 paquetes, nada menos. Lo abrimos con Wireshark y vemos que hay muchísimos paquetes TCP con basura (SYN, ACK, RST) pero ningún PSH con información útil. Esto supongo que lo harían para generar algo de tráfico y hacer efectivo un ataque con Aircrack-ng y así poder sacar fácilmente la clave WEP, por lo que vamos a aplicar algunos filtros que nos limpien un poco la basura:

Filtro: tcp.flags.push==1

0 resultados

Como comentaba antes, todos los paquetes TCP están de relleno, así que vamos a quitarlos, junto con los ARP e ICMP:

Filtro: !arp && !icmp && !tcp

17.075 resultados

Siguen apareciendo demasiados paquetes, pero ahora vemos algunas cosas interesantes. Parece que hay capturas de VoIP, así que vamos a filtrar esas capturas:

Filtro: ip.src eq 172.23.0.9 || ip.dst eq 172.23.0.9

Bien, pues parece que tenemos un bonito Asterisk tras todas esas tramas de datos y, como Wireshark trae una herramienta muy buena para VoIP, vamos a usarla. Entramos en Telephony -> VoIP Calls:

Tras escuchar cada una de las conversaciones repetidas veces, me quedo con los siguientes detalles:

  1. Alguien llama a un buzón de voz para escuchar los mensajes. Tenemos los sonidos de dos pines que se introducen.
  2. Parece que dejan el número de teléfono que buscamos en un buzón de voz y no se escucha en las conversaciones de voz capturadas.

——————————

Nota: Yo me quedé aquí en este punto cuando el CTF llegó a su fin. El resto de retos los hice ya desde casa, relajado en una buena silla :)

Me quedé atascado porque no le encontré mucho sentido a lo que estaba pasando, es decir, el mensaje decía algo como: ‘… llámame al número de teléfono’ y se cortaba. Luego veremos que el resto del mensaje está en el buzón de voz. La verdad es que no entiendo cómo si alguien deja un mensaje en un buzón de voz, se captura una parte sólo de ese mensaje, pues debería o capturarse todo o no capturarse nada.

Esto me llevó a pensar que era un problema de ruido, así que me dediqué a descargar las voces, convertirlas a WAV y perder mucho tiempo con el Audacity.

——————————

Bien, pues continuando con el reto, parece ser que tenemos que acceder al buzón de voz del usuario para escuchar el número de teléfono que deja como mensaje. Para poder acceder al buzón de un usuario necesitamos 2 cosas. Los datos del usuario (incluida su contraseña) y su PIN. Vamos con ello:

Para extraer los datos del usuario usamos sipdump y sipcrack:

Tras pasar varios diccionarios obtuve las claves de ambos usuarios, que son:

Para el user 1001: 0000aa

Para el user 1002: aa0000

Nota: las claves para cada usuario son las mismas a pesar de tener diferente hash, lo cual nos indica que el MD5 usa algún salt.

Para averiguar el PIN, primero extraemos las voces. En el Wireshark vamos a Telephony -> RTP -> Show All Streams. Luego seleccionamos el paquete y damos a Analyze. Después, Save Payload y guardamos con formato AU.

Abrimos con el Audacity y recortamos la parte que nos interesa:

Una vez que hemos recortado los 2 pines que hemos encontrado, los desciframos con Multimon (click para agrandar la imagen):

El pin de pownme es: 987321

El pin de lamde es: 123654

El siguiente paso es conseguir un software para conectar con el Asterisk. Vamos a usar para ello el Twinkle, que está en el repositorio de Debian.

Si te atascas instalándolo, aquí hay un bonito tutorial:

http://openmaniak.com/trixbox_phone.php#twinkle

Nos conectamos a ambos buzones y en el de lamde escuchamos el número de teléfono que buscábamos y que es la clave para pasar al siguiente reto: 666.699.004


RootedCon CTF writeup nivel 4

Viernes, abril 1st, 2011

Reto 4

Accedemos ahora a la shell de este usuario (pownme) con un su, o simplemente haciendo otra conexión por ssh:

Y vemos lo que tiene en su directorio y a lo que no teníamos acceso:

El fichero comprimido, que descargamos con un scp desde nuestra máquina, parece tener una imagen de un disco que ha sido cifrada con LUKS usando SHA256. Esto lo vemos haciendo un head del fichero o abriéndolo con un editor hexadecimal.

Según la Wikipedia (http://es.wikipedia.org/wiki/LUKS):

——————–

LUKS (de las siglas en inglés, Linux Unified Key Setup) es una especificación de cifrado de disco creado por Clemens Fruhwirth, originalmente destinado para Linux. Mientras la mayoría del software de cifrado de discos implementan diferentes e incompatibles formatos no documentados, LUKS especifica un formato estándar en disco, independiente de plataforma, para usar en varias herramientas. Esto no sólo facilita la compatibilidad y la interoperabilidad entre los diferentes programas, sino que también garantiza que todas ellas implementen gestión de contraseñas en un lugar seguro y de manera documentada.

La implementación de referencia funciona en Linux y se basa en una versión mejorada de cryptsetup, utilizando dm-crypt como la interfaz de cifrado de disco. En Microsoft Windows, los discos cifrados con LUKS pueden ser utilizados con FreeOTFE. Ha sido diseñado para ajustarse a la clave de configuración TKS1 de sistema seguro.

——————–

Pues vamos a tratar de montar esa imagen, de la que por cierto, no tenemos la contraseña ….

Yo nunca había encriptado una unidad por lo que tras documentarme en Google, al final hice:

Una vez tenemos los módulos de crypt, vamos a tratar de montar la imagen. Para ello usaremos loop, que asigna una imagen a un dispositivo, lo que nos permitirá tratarlo como una unidad.

Para ver el primer dispositivo loop libre:

Para usar el dispositivo (con esto asociamos /dev/loop0 con nuestra imagen):

Luego tratamos de montar la imagen (si fuera una imagen normal usaríamos el comando mount, pero al tratarse de un LUKS, debemos usar cryptsetup):

Parece que la imagen está dañada ya que no la reconoce como LUKS. Tras buscar información sobre el formato y compararla con nuestra imagen, vemos claramente que hay una serie de ceros en la cabecera (antes de la palabra LUKS) que no deberían estar, por lo que hacemos una copia de la imagen (por si las moscas) y modificamos esta quitando todos esos ceros:

Para probarlo, desasociamos el dispositivo y lo volvemos a asociar:

Bueno, pues parece que ya tenemos resuelto el primer problema. Ahora queda averiguar cuál es la contraseña. Para ello me creé un pequeño script en Perl:

Lo que hace este script es probar las palabras de un diccionario, parando si consigue montar la unidad. Para ello:

  1. Cargamos el diccionario.
  2. Ejecutamos con un system el cryptsetup usando un pipe y así no tener que meter la contraseña escribiéndola desde la línea de comandos.
  3. Miramos en /dev/mapper si se ha montado el dispositivo, tras lo cual paramos el script y mostramos la última contraseña que se probó, que deberá ser la buena.

Tras unos minutos corriendo con un diccionario de palabras comunes, obtenemos la contraseña, que es: key y que nos sirve para pasar al reto siguiente.