Dans ce crackme on avait un fichier apk qui nous était présenté. Personnellement je ne le savais pas mais c'est le format des archives Dalvik pour Android. Dans tous les cas, file nous indiquait qu'il s'agissait d'un format zip et le manifest nous renseignait sur un format Android.
Comme la plupart des outils travaillent sur du Java classique, j'ai en premier lieu cherché à transformer ce apk en jar. Une recherche google nous rend l'utilitaire dex2jar qui fait très bien le travail. Il nous trouve 4 classes : ndh.prequals.rce.ReverseMe, ndh.prequals.rce.a, ndh.prequals.rce.b et ndh.prequals.rce.c. A partir de là, il faut décompiler ces classes dans le format qui nous plait. Personnellement, j'utilise Soot pour transformer les class en Jimple (SSA) et Jad pour tenter des décompilations classiques.
Après avoir décompilé (avec Jad pour le moment), on consulte donc ReverseMe.java qui semble être le point d'entrée de l'application. On voit qu'on a une classe de type Android.Activity qui a deux méthodes, onCreate et onActivity. On consulte rapidement onCreate pour voir les particularités d'initilialisation. On se rend compte que notre application semble avoir deux composants : un bouton et un champ de texte. Le petit boubt de code à la fin semble cependant bizarre :
if(getPackageManager().queryIntentActivities(new Intent(c.d()), 0).size() != 0 && !c.b().equals(Build.PRODUCT)) button.setOnClickListener(new b(this)); a = new a();
On tombe donc sur l'utilisation de méthodes des classes a, b et c du package. On se penche sur les méthodes de ndh.prequals.rce.c :
private static String a(byte abyte0[]) { byte abyte1[] = new byte[abyte0.length]; for (int i=0;i<abyte0.length;i++) abyte1[i] = (byte)(0x3c ^ abyte0[i]); return new String(abyte1); } public static String b() { return a(b); }
Toutes les méthodes de c n'ayant pas d'argument sont en fait des wrappers de la méthode a(byte[]). Cette méthode prend l'un des tableaux de byte statiques existants et le xor par 0x3c. Pour avoir une meilleure idée des variables contenues dans ces tableaux de byte on les unxor manuellement et on obtient les variables suivantes :
La fonction ne fait donc que vérifier a priori que la reconnaissance vocale est activée et que le produit n'est pas buildé avec google_sdk. Ensuite, il définit comme listener du bouton de l'application un objet de type ndh.prequals.rce.b, on cherche donc sa méthode onClick. Rien de bien intéressant sinon un prompt vocal qui demande le mot de passe et qui démarre l'activité sur l'objet ReverseMe de l'application. On retourne donc dans ndh.prequals.rce.ReverseMe pour regarder la méthode onActivityResult, vraisemblablement la callback de la reconnaissance vocale dans notre cas :
ArrayList arraylist = intent.getStringArrayListExtra("android.speech.extra.RESULTS"); if(!arraylist.isEmpty() && ndh.prequals.rce.a.b((String)arraylist.get(0))) b.setText(ndh.prequals.rce.a.a((String)arraylist.get(0)));
A priori, on prend simplement les résultats de la reconnaissance vocale et on passe le premier mot à ndh.prequals.rce.a.b, puis on mets le retour à ndh.prequals.rce.a.a avec ce premier mot en paramètre, si la première méthode ne retourne pas faux. En consultant la méthode b(), je me rends compte que Jad ne s'en est pas très bien sorti, donc je retourne à mon Jimple Soot (java -jar soot-2.5.0.jar -cp . -allow-phantom-refs -f j ndh.prequals.rce.a). L'essentiel de la méthode se trouve ici :
$r1 = ndh.prequals.rce.c.c(); r2 = java.security.MessageDigest.getInstance($r1); $r8 = r0.getBytes(); r2.update($r8); r3 = r2.digest(); $r9 = new java.lang.StringBuffer; specialinvoke $r9.<init>(); r4 = $r9; $r10 = ndh.prequals.rce.c.b(); $r11 = android.os.Build.PRODUCT; $z1 = $r10.equals($r11); if $z1 == 0 goto label7; r4.append(65); goto label7; label1: $i1 = lengthof r3; if i0 < $i1 goto label2; $r13 = r4.toString(); $r14 = ndh.prequals.rce.c.a(); z0 = $r13.equals($r14);
Bon ok, c'est moins lisible mais au moins c'est exact. On se rend compte que le but de cette fonction est de hasher le paramètre par la fonction de hashage c.c() (donc MD5). Ensuite, elle est comparée à c.a() (donc f9dd11ff6857af73ac9a944dfc52f41b) et renvoie vrai ou faux selon le résultat. Bon on s'en doutait pas mal depuis qu'on avait unxoré les chaînes, mais le mot de passe est donc f9dd11ff6857af73ac9a944dfc52f41b en md5. En regardant la fonction a.a() censée a priori ressortir le flag de validation, on voit qu'elle ne fait que ressortir le hash md5 de la chaîne et on n'a donc rien de plus à faire. Bon le fait de ne pas avoir d'android ou d'émulateur m'a empêché de crier s***** chez moi pour tester :]