Heap Introspection

Aperi'CTF 2019 - Mobile (200 pts).

Aperi’CTF 2019 - Heap Introspection

Challenge details

Event Challenge Category Points Solves
Aperi’CTF 2019 Heap Introspection Mobile 200 1

Votre collègue, développeur Android, a la fâcheuse habitude d’utiliser les mêmes identifiants en production et en phase de développement.

Depuis peu, sa marotte est d’optimiser la vitesse de ses applications. Il a donc réalisé des tests.

Vous avez trouvé deux fichiers sur le serveur de versioning interne à votre entreprise : app-debug.apk et dump.hprof.

Essayez de lui prouver que sa fâcheuse tendance est une mauvaise pratique.

Fichiers :
- app-debug.apk - md5sum: cd4f3bbd17e3938040a4847d86b15287
- dump.hprof - md5sum: 401c1d65d64025396b438aa23e92046d

Methodologie

Ici on a accès à deux fichiers, une application et un dump de mémoire au format hprof.

Grâce à Jadx l’application se décompile plutôt facilement, l’activité principale contient le code suivant :

public class MainActivity extends AppCompatActivity {
    private EditText loginForm;
    private MessageDigest md;
    private EditText passwordForm;

    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView((int) R.layout.activity_main);
        this.loginForm = (EditText) findViewById(R.id.loginForm);
        this.passwordForm = (EditText) findViewById(R.id.passwordForm);
        ((Button) findViewById(R.id.submit)).setOnClickListener(new OnClickListener() {
            public void onClick(View v) {
                try {
                    MainActivity.this.onLogin();
                    MainActivity.this.loginForm = null;
                    MainActivity.this.passwordForm = null;
                    System.gc();
                    Debug.dumpHprofData(new File(MainActivity.this.getApplicationInfo().dataDir, "dump.hprof").getAbsolutePath());
                } catch (Exception e) {
                }
            }
        });
    }

    private void onLogin() throws Exception {
        StringBuilder stringBuilder = new StringBuilder();
        stringBuilder.append(this.loginForm.getText().toString());
        stringBuilder.append(":");
        stringBuilder.append(this.passwordForm.getText().toString());
        byte[] loginToken = Base64.encode(stringBuilder.toString().getBytes(), 0);
        this.loginForm.setText(BuildConfig.FLAVOR);
        this.passwordForm.setText(BuildConfig.FLAVOR);
        this.md = MessageDigest.getInstance("MD5");
        this.md.update(loginToken);
        try {
            sendCredentials(this.md.digest());
        } catch (Exception e) {
        }
        for (int i = 0; i < loginToken.length; i++) {
            loginToken[i] = (byte) 0;
        }
    }

    private void sendCredentials(byte[] md5Token) throws Exception {
        throw new Exception("TODO : not implemented yet (btw you lost)");
    }
}

Aucun flag n’est présent dans le code, mais on peut voir que le code récupère les identifiants puis les envoie au serveur.

Pour ce faire il les encode en Base64 puis les hash à la manière d’une Basic Auth un peu particulière. Cependant, la fonction sendCredentials n’est pas encore implémentée.

On peut se douter, grâce à l’énoncé, que l’application a été testée et que les identifiants ont été utilisés.

On remarque aussi qu’après l’utilisation de la fonction onLogin, le Garbage Collector est appelé afin de ramasser les miettes.

Analyse du dump mémoire

Le but va donc être de récupérer les identifiants dans le dump. À première vue, ils peuvent rester dans la mémoire de 3 objets :

  • Base64$Encoder
  • MessageDigest
  • EditText

MessageDigest étant une fonction de cryptographie, celle-ci implémente une gestion de la mémoire “propre”, donc on peut la supprimer par déduction. Il nous reste donc deux objets.

JHAT Time

On va donc vouloir lancer Jhat (Java Heap Analyzer Tool), mais en le lançant on obtient une erreur :

Reading from dump.hprof...
java.io.IOException: Version string not recognized at byte 17
	at com.sun.tools.hat.internal.parser.HprofReader.readVersionHeader(HprofReader.java:390)
	at com.sun.tools.hat.internal.parser.HprofReader.read(HprofReader.java:175)
	at com.sun.tools.hat.internal.parser.Reader.readFile(Reader.java:92)
	at com.sun.tools.hat.Main.main(Main.java:159)

Aprés quelques recherche Google, le dump mémoire android doit être converti avant de pouvoir être utilisé par Jhat.

hprof-conv dump.hprof dump-converted.hprof

En regardant tous les tableaux de bytes associés à Base64$Encode, impossible de retrouver des informations sur les identifiants.

On va donc regarder les EditText. En allant voir le code source d’Android, aucune info ne nous est donnée (https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/widget/EditText.java).

On va donc regarder les EditText, deux objets EditText ont été créés, on regarde leurs attributs et on voit un attribut mText qui contient un objet du type SpannableStringBuilder

Cet objet ne contient rien, mais on pouvait s’en douter this.loginForm.setText(BuildConfig.FLAVOR); l’a remplacé. Mais l’instance précédente n’a peut-être pas été supprimée de la mémoire ?

En regardant les autres instances on en trouve deux contenants Do_not_trust_ et your_garbage_collector

Flag

APRK{Do_not_trust_your_garbage_collector}

Areizen