RootedCon CTF writeup nivel 9

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;
    	}
       }
}

 

2 pensamientos en “RootedCon CTF writeup nivel 9”

Deja un comentario