date
solved in time of CTF
Reverse category score

Fichier(s)

Nécessaires

  • Ghidra
  • Python3
  • Jdax
  • Apktool

Flag

HL{J4v4.nativ3.d0.n0t.c4r3}

Solution détaillée

La première chose est de décompiler l’apk pour en retrouver le code Java . J’ai utilisé Apktool pour extraire le code smali et avoir tous les fichiers natifs :

apktool d CrackMe.apk

Puis j’ai passé l’apk dans Jadx très utilise pour le reverse de code Java :

Alt text

J’ai installé l’apk sur mon appareil après avoir vérifié l’absence de virus et voici la page d’accueil :

Alt text

Cherchons dans le code la fonction de vérification de mot passe : On retrouve à 2 endroits une fonction getCode

public int[] getCode(String str) {
    byte[] bytes = str.getBytes();
    int[] iArr = new int[str.length()];
    for (int i = 0; i < str.length(); i++) {
        iArr[i] = bytes[i] ^ x0[i];
    }
    return iArr;
}

Ainsi qu’un appel à la fonction :

int[] checkPw = checkPw(getCode(str));
if (checkPw.length > 0) {
    this.loginResult.setValue(new LoginResult(new LoggedInUser(getStringFromCode(checkPw), "Well done you did it.")));
    return;
}

Un autre chose remarquable est ceci :

public void loginDataChanged(String str) {
        if (!isPasswordValid(str)) {
            this.loginFormState.setValue(new LoginFormState((Integer) null, Integer.valueOf(R.string.invalid_password)));
        } else if (str.indexOf("HL") < 0) {
            this.loginFormState.setValue(new LoginFormState((Integer) null, Integer.valueOf(R.string.must_contain_HL)));
        } else if (checkHooking()) {
            this.loginFormState.setValue(new LoginFormState((Integer) null, Integer.valueOf(R.string.must_not_hook)));
        } else if (str.indexOf(123) < 0) {
            this.loginFormState.setValue(new LoginFormState((Integer) null, Integer.valueOf(R.string.is_of_format)));
        } else if (str.indexOf(125) < 0) {
            this.loginFormState.setValue(new LoginFormState((Integer) null, Integer.valueOf(R.string.is_of_format)));
        } else {
            this.loginFormState.setValue(new LoginFormState(true));
        }
    }

Cette fonction prend en entrée supposément le mot de passe et vérifie si il contient :

  • HL , { , }

On retrouve aussi une fonction qui vérifie si le téléphone est rooté et une autre fonction qui vérifie qu’il n’y a pas de processus de hooking sur l’application . Nous ne nous en servirons pas dans ce challenge .

Regardons de plus prêt la fonction : Getode()

Premièrement , elle prend en entrée une chaine de charactères ; on peut deviner que c’est notre mot de passe entrée . Puis elle XoR chaque Bytes de l’input avec un élément d’une liste prédéfinit en variable globale :

protected static int[] x0 = {121, 134, 239, 213, 16, 28, 184, 101, 150, 60, 170, 49, 159, 189, 241, 146, 141, 22, 205, 223, 218, 210, 99, 219, 34, 84, 156, 237, 26, 94, 178, 230, 27, 180, 72, 32, 102, 192, 178, 234, 228, 38, 37, 142, 242, 142, 133, 159, 142, 33};

Pour être sur de bien comprendre ce que fait getBytes(); , j’ai écris ce code en java . Je voulais m’assurer que la fonction : ord en python donné le même résultat

import java.util.Arrays;
class Decode {

    protected static int[] x0 = {121, 134, 239, 213, 16, 28, 184, 101, 150, 60, 170, 49, 159, 189, 241, 146, 141, 22, 205, 223, 218, 210, 99, 219, 34, 84, 156, 237, 26, 94, 178, 230, 27, 180, 72, 32, 102, 192, 178, 234, 228, 38, 37, 142, 242, 142, 133, 159, 142, 33};

    public static void main(String[] args) {
        String str = "HL{";

        byte[] bytes = str.getBytes();
        int[] iArr = new int[str.length()];
        for (int i = 0; i < str.length(); i++) {
            iArr[i] = bytes[i] ^ x0[i];
        }
        System.out.println(Arrays.toString(iArr));
    }    
}

Quelque chose qui m’a perturbé et cette appel : int[] checkPw = checkPw(getCode(str)); On sait que GetCode renvoie une liste de charactères Xorés mais que fais CheckPw ???

Je n’ai d’abord pas trouvé de fonction de ce nom puis quelque chose m’a frappé :

static {
        System.loadLibrary("native-lib");
    }

Il y a une importation d’une librairie ! C’est là que apktool nous sert . Dans les fichiers de l’apk , il y avait une librairy en C du nom de : libnative-lib.so

Je l’ai donc ouvert avec Ghidra et voici ce que j’y ai trouvé !

Alt Text

On voit la fonction checkHooking évoqué plus haut et surtout notre fonction CheckPW ! Nous sommes sauvés


void Java_org_bfe_crackmenative_ui_LoginViewModel_checkPw
               (int *param_1,undefined4 param_2,undefined4 param_3)

{
  int iVar1;
  uint *puVar2;
  uint *puVar3;
  bool bVar4;
  int iVar5;
  FILE *__stream;
  char *pcVar6;
  uint *puVar7;
  int iVar8;
  uint uVar9;
  char acStack4140 [4096];
  int iStack44;

  iStack44 = __stack_chk_guard;
  __android_log_write(4,"Native Check","Checking password ...");
  (**(code **)(*param_1 + 0x2cc))(param_1,0);
  iVar5 = (**(code **)(*param_1 + 0x2ac))(param_1,param_3);
  if (iVar5 == 0x1b) {
    __stream = fopen("/proc/self/maps","r");
    do {
      pcVar6 = fgets(acStack4140,0x1000,__stream);
      if (pcVar6 == (char *)0x0) {
        bVar4 = false;
        goto LAB_00010a32;
      }
      pcVar6 = strstr(acStack4140,"Xposed");
    } while ((pcVar6 == (char *)0x0) &&
            (pcVar6 = strstr(acStack4140,"frida"), pcVar6 == (char *)0x0));
    bVar4 = true;
LAB_00010a32:
    if (__stream != (FILE *)0x0) {
      fclose(__stream);
    }
    if (!bVar4) {
      iVar8 = 0;
      iVar5 = (**(code **)(*param_1 + 0x2ec))(param_1,param_3,0);
      puVar7 = &DAT_00012a60;
      do {
        if (iVar8 == 0x1b) break;
        iVar1 = iVar8 * 4;
        puVar2 = &DAT_000128f8 + iVar8;
        uVar9 = *puVar7;
        puVar3 = &DAT_00012a94 + iVar8;
        iVar8 = iVar8 + 1;
        puVar7 = puVar7 + -1;
      } while ((*(uint *)(iVar5 + iVar1) ^ *puVar2 ^ uVar9) == *puVar3);
    }
  }
  if (__stack_chk_guard - iStack44 == 0) {
    return;
  }
                    /* WARNING: Subroutine does not return */
  __stack_chk_fail(__stack_chk_guard - iStack44);
}

Voici à quoi ressemble la fonction. On y voit premièrement une partie inutile, la partie supérieur de la fonction est en fait un Hook par Frida , un Framework pour modifier des application Android .

On initialise une liste : puVar7 = &DAT_00012a60;

La Boucle while final est intéressante :

Alt Text

  • On identifie directement un conteur :
iVar8 = 0;
. . .
iVar8 = iVar8 + 1;
  • Nous voyons qu’il y a un break; On voit que celui-ci se déclenche quand iVar8 est égale à 0x1b soit 27 Parfait, on a déjà la taille de notre mot de passe !

  • Finalement, on observe 2 XoR successifs de puVar7 (défini plus haut) avec 2 autres listes : DAT_000128f8 et DAT_00012a94

On peut en déduire que le mot de passe secret est puVar7 et que le XoR avec ces 2 listes et comparé à la liste généré avec le code Java .

On peut donc utiliser les propriétés du XoR pour retrouver le flag original . Rappel :

B ⊕ ( A ⊕ B ) = B ⊕ ( B ⊕ A ) (associative)
B ⊕ ( A ⊕ B ) = (B ⊕ B) ⊕ A   (self-inverse)
B ⊕ ( A ⊕ B ) = 0 ⊕ A         (identity element)
B ⊕ ( A ⊕ B ) = A

Voici le code python de ma solution : On Xor les derniers 27 charactères de DAT_000128f8 avec DAT_00012a94 et DAT_00012a94 puis enfin avec la liste obtenue dans le java :

all_ = [121, 134, 239, 213, 16, 28, 184, 101, 150, 60, 170, 49, 159, 189, 241, 146, 141, 22, 205, 223, 218, 210, 99, 219, 34, 84, 156, 237, 26, 94, 178, 230, 27, 180, 72, 32, 102, 192, 178, 234, 228, 38, 37, 142, 242, 142, 133, 159, 142, 33]

DAT_000128f8 = [0xd0,0x45,0x28,0x76,0x6f,0xf3,0x5a,0xf4,0xc7,0xce,0xfb,0xc3,0x7f,0x48,0xce,0x3c,0x3a,0x0b,0xf1,0x53,0xb1,0x4b,0xb9,0x5e,0xa2,0x65,0x77,0xa5,0x81,0x95,0xca,0x31,0x18,0x88,0xee,0xdd,0x38,0x5d,0xd5,0xa9,0x4a,0x7c,0x9d,0xc9,0xb8,0xe8,0xfb,0x9f,0x64,0x57,0xe0,0xd6,0x42,0x95,0x29,0x8f,0xd4,0x35,0x2d,0x0a,0x54,0xed,0xc4,0x48,0x4c,0x7b,0x73,0x6f,0x72,0x72,0x79,0x2e,0x74,0x68,0x69,0x73,0x2e,0x69,0x73,0x2e,0x4e,0x4f,0x54,0x2e,0x74,0x68,0x65,0x2e,0x66,0x6c,0x61]

DAT_00012a94 = [0x80,0xe3,0xda,0xc7,0x2e,0xf1,0xa2,0x91,0x6b,0xdc,0x6b,0xb5,0xe5,0xaf,0x3f,0xb9,0xee,0x5b,0x26,0x92,0x66,0xc5,0xcb,0xde,0x81,0x79,0xda]

DAT_00012a94_Reversed = DAT_000128f8[::-1][:27+1]


final = []
for i in range(27):
	final.append(chr(DAT_000128f8[i] ^ DAT_00012a94[i] ^ DAT_00012a94_Reversed[i] ^ all_[i]))

print(''.join(final))

Output:

root@DESKTOP-HNQJECB: /c/AndroidReverse
➜   python3 solve.py
HL{J4v4.nativ3.d0.n0t.c4r3}