Tutorials

Android Hacking Kurs: Teil 2 – Apps manipulieren

This post is also available in: English

Im zweiten Teil der Android-Hacking-Reihe schauen wir uns an einem Beispiel an, wie wir den Source Code einer App beliebig manipulieren können, um so am Ende eine Passwortabfrage komplett zu umgehen.

Im ersten Teil der Android-Hacking-Reihe haben wir uns bereits den Unterschied zwischen Decompiling und Disassembling angeschaut. Außerdem haben wir gelernt, wie wir den Binärcode einer App wieder in den Java-Quellcode umwandeln können. Dadurch war es uns möglich, das Passwort der Tresor-App auszulesen und so Zugang zu erlangen. Wenn du dir den ersten Teil noch nicht durchgelesen oder das Tutorial dazu gesehen hast, solltest du das vielleicht jetzt nachholen. Wir werden nämlich auf zahlreichen Information aufbauen, die wir darin besprochen haben.

Android Hacking – Teil 1: Dekompilieren & Source Code

Anhand der Tresoranwendung (Download) aus dem ersten Teil schauen wir uns in diesem Teil an, wie wir das Verhalten einer Android-App beliebig manipulieren können. Dadurch können wir in der Vault-App beispielsweise dann unser eigenes Passwort festlegen oder die Passwortabfrage sogar komplett entfernen. Als Bonus zeig ich dir am Ende noch ein extrem hilfreiches Werkzeug, was alle der Schritte, die wir in diesem und im vorherigen Teil besprochen haben, in einem einzigen GUI-Tool bündelt. Vorher ist es aber wichtig, dass wir zuerst alle einzelnen Schritte im Detail verstanden haben.

Smali Code: Vom Byte zum Text zum Byte


Wie wir im ersten Teil gesehen haben, können wir durch Dekompilieren den Source Code einer App im besten Fall fast detailgetreu wiederherstellen. Dadurch können wir leicht die Logik einer Anwendung verstehen lernen oder sogar einzelne statische Parameterwerte auslesen.

Dekompilieren ist aus technischer Sicht alles andere als eine triviale Aufgabe. Der Compiler optimiert den Quellcode so, dass dieser anschließend möglichst performant in der Dalvik-VM läuft. Die Umwandlung des Binärcodes zurück in den Quellcode ist niemals vorgesehen gewesen. Deswegen gibt es dabei immer kleinere Fehler und einzelne Stellen können nicht 100 % korrekt interpretiert und einem Java-Äquivalent zugewiesen werden. Die Anwendung zu dekompilieren, den Java-Code zu manipulieren und anschließend alles wieder zu kompilieren ist also quasi nicht möglich. Deshalb brauchen wir einen Mittelweg, der sich immer noch sehr nahe am Maschinencode orientiert, das Ganze aber für uns in eine lesbare Form bringt – und genau das bietet uns SMALI CODE. Wie genau Smali Code aussieht, schauen wir uns gleich an einem Beispiel im Detail an. Vorher müssen wir den Bytecode unserer App aber zuerst einmal umwandeln.

Disassembling der App


Um eine Android-App zu disassemblen, können wir Apktool einsetzen. In diesem Tool werden mehrere Hilfsmittel zusammengefasst, die unsre Arbeit mit Android-Apps deutlich erleichtern. Beispielsweise wird unter der Haube der Disassembler Baksmali eingesetzt, um letztendlich den Smali Code zu erzeugen. Außerdem können wir mit Apktool unsre Änderungen am Quellcode am Ende auch wieder zu einer funktionierenden App zusammenbauen.

Mit dem Aufrufparameter d (decode) wird die angegebene App dekodiert, d.h. es werden zum einen die Ressourcen und Konfigurationsdateien der Anwendung in eine leserliche Form gebracht sowie der Bytecode in Smali Code umgewandelt. Sämtliche Dateien werden dann im gleichnamigen Ordner ausgegeben. Alternativ kann mit -o ein anderer Ausgabepfad angegeben werden.

apktool d <app.apk>

Wo ist der Unterschied: Java vs. Smali


Unser Ziel in dieser Übung ist es, die App so zu manipulieren, dass wir die Passwortabfrage mit jedem beliebigen Passwort umgehen können. Am Ende soll es also keine Rolle spielen, ob wir das richtige oder falsche Passwort eingegeben haben – der Tresor öffnet sich so oder so.

Eine solche Manipulation wäre beispielsweise denkbar, wenn die App das Passwort nicht mit einem lokal gespeicherten Wert abgleicht, sondern mit einer externen Datenbank, auf die wir keinen Zugriff haben.

Mit den Kenntnissen, die wir über das Dekompilieren aus dem ersten Teil erlangt haben, wissen wir, dass sich die Logik für den Zugang zum Tresor in der Klasse VaultDataSource.smali befindet.

Ein Blick in die Datei zeigt auf Anhieb, dass der Smali Code deutlich komplexer zu verstehen ist, als der Java Code. Es ist also zu empfehlen, eine Anwendung immer zu dekompilieren und zu disassemblen und anschließend beide Ergebnisse zusammen auszuwerten.

Beispielhafter Auszug aus VaultDataSource.smali
Beispielhafter Auszug aus VaultDataSource.smali

Um uns besser zurechtzufinden, können wir uns innerhalb einer Methode an den .line-Nummern orientieren.

line-Nummern als Orientierung im Smali Code

In Java ist die Zuweisung der vaultCombination beispielsweise lediglich eine Zeile. Es wird automatisch eine neue String-Variable deklariert und ihr der Wert Subscr1be! zugewiesen:

Zuweisung einer String-Variable in Java

In Smali sieht es etwas komplizierter aus. Zuerst wird der Private String vaultCombination deklariert [1]. Anschließend wird innerhalb des Konstruktors der Klasse [2] der konstante Wert Subscr1be! der temporären Variable v0 zugewiesen [3] und dieser letztendlich in die Variable vaultCombination der Klasse VaultDataSource kopiert [4].

VaultDataSource.smali

...
    # instance fields
[1] .field private vaultCombination:Ljava/lang/String;

    # direct methods
[2] .method public constructor <init>()V
        .locals 1

        .line 8
        invoke-direct {p0}, Ljava/lang/Object;-><init>()V

        .line 10
[3]     const-string v0, "Subscr1be!"

[4]     iput-object v0, p0, Ldigital/basto/vault/data/VaultDataSource;->vaultCombination:Ljava/lang/String;

        return-void
    .end method

Manipulation des Quellcodes


Da die Vault-App das Passwort mit einem statischen Wert abgleicht, der in der Anwendung selbst hinterlegt ist, könnten wir auch einfach unser eigenes Passwort festlegen. Was wir wollen, ist aber die if-Abfrage, die das eingegebene Passwort mit der vaultCombination abgleicht, komplett zu entfernen. Dadurch wird der Tresor beim Klick auf unlock immer entsperren, egal welches Passwort tatsächlich eingegeben wurde.

In unsrem Smali Code suchen wir dazu zuerst die Methode unlock.

Im Block für .line 15 sehen wir den Aufruf :try_start_0, der den try-Block einleitet. Weiter unten finden wir auch den zugehörigen Aufruf :try_end_0, der quasi } im Java Code darstellt und den Block beendet.

VaultDataSource.smali

.method public unlock(Ljava/lang/String;)Ldigital/basto/vault/data/Result;
    .locals 4
    .param p1, "password"    # Ljava/lang/String;
    .annotation system Ldalvik/annotation/Signature;
        value = {
            "(",
            "Ljava/lang/String;",
            ")",
            "Ldigital/basto/vault/data/Result<",
            "Ldigital/basto/vault/data/model/VaultData;",
            ">;"
        }
    .end annotation

    .line 15
    :try_start_0
[1] iget-object v0, p0, Ldigital/basto/vault/data/VaultDataSource;->vaultCombination:Ljava/lang/String;

[2] invoke-virtual {v0, p1}, Ljava/lang/String;->equals(Ljava/lang/Object;)Z

[3] move-result v0

[4] if-eqz v0, :cond_0

    .line 16
    new-instance v0, Ldigital/basto/vault/data/model/VaultData;

    const v1, 0x44a72d71

    invoke-direct {v0, v1}, Ldigital/basto/vault/data/model/VaultData;-><init>(F)V

    .line 19
    .local v0, "unlockData":Ldigital/basto/vault/data/model/VaultData;
    new-instance v1, Ldigital/basto/vault/data/Result$Success;

    invoke-direct {v1, v0}, Ldigital/basto/vault/data/Result$Success;-><init>(Ljava/lang/Object;)V

    return-object v1

    .line 21
    .end local v0    # "unlockData":Ldigital/basto/vault/data/model/VaultData;
    :cond_0
    new-instance v0, Ldigital/basto/vault/data/Result$Error;

    new-instance v1, Ljava/security/AccessControlException;

    const-string v2, "Wrong password!"

    invoke-direct {v1, v2}, Ljava/security/AccessControlException;-><init>(Ljava/lang/String;)V

    invoke-direct {v0, v1}, Ldigital/basto/vault/data/Result$Error;-><init>(Ljava/lang/Exception;)V
    :try_end_0

[1] Zuerst wird der String-Wert aus vaultCombination ausgelesen und in die temporäre Variable v0 gespeichert. [2] Anschließend wird die Methode equals mit den Parametern v0, also der korrekten vaultCombination, und p1, d. h. mit dem eingegebenen Passwort, aufgerufen und beide Werte verglichen. [3] Mit move-result wird das Ergebnis der vorher aufgerufenen equals-Methode in der Variable v0 gespeichert.

In Smali entspricht 0 dem Boolean-Wert false und 1 dem Boolean-Wert true

[4] if-eqz steht hier für If Equals Zero. Es wird also geprüft, ob v0 gleich 0 ist, d. h. ob der vorherige Vergleich [2] als Ergebnis false geliefert hat. Falls das zutrifft, wird im Programmcode zu :cond_0 gesprungen. Andernfalls läuft der Code einfach mit der Anweisung darunter weiter und entsperrt den Tresor.

Wenn wir jetzt den Aufruf if-eqz v0, :cond_0 komplett entfernen, wird das Ergebnis des Vergleichs niemals ausgewertet. Stattdessen laufen wir immer in den Code-Abschnitt, der den Tresor entsperrt, egal ob das Passwort falsch oder richtig war.

Zusammenbauen der manipulierten App


Mit apktool bauen wir den modifizierten Smali Code jetzt wieder zu einer funktionierenden APK-Datei zusammen. Über den Parameter -o geben wir den Namen der neuen App an.

apktool b <app_folder> -o <out.apk>

Signieren der APK-Datei


Apps müssen signiert werden, bevor diese auf dem Gerät installiert werden können. Über die Signatur lässt sich der Autor einer App eindeutig bestimmen. Im Nachhinein können wir keine Änderungen mehr am Quellcode machen, ohne dabei auch die Signatur der App zu verändern. Dadurch wird verhindert, dass jemand die App manipuliert und anschließend wieder als offizielle Anwendung, beispielsweise von Google, ausgeben kann.

In Android werden die Schlüssel für die Signatur über den Keystore abgebildet. Der Keystore selbst ist mit einem privaten Schlüssel, den nur ich kenne, geschützt. Darin können wir dann beliebige Public/Private-Keys sicher speichern, die anschließend über ein selbst definiertes Alias referenzierbar sind. Die Private Keys im Keystore sind mit einem weiteren Passwort geschützt.

Visualisierung Keystore

Mit folgendem Aufruf können wir nun unsren eigenen Keystore erzeugen:

keytool -genkey -v -keystore my.keystore -alias cert -keyalg RSA -keysize 2048 -validity 10000

Der Parameter -alias wird verwendet, um den Namen für unser eigenes Public/Private-Key-Paar festzulegen und dieses automatisch dem Keystore hinzuzufügen. Die Gültigkeitsdauer der Schlüssel wird über den Parameter -validity in Tagen angegeben.

Wenn wir einen neuen Keystore erstellen, müssen wir auch die Informationen des Autors hinterlegen. Hier können entweder die eigenen Informationen oder einfach nur leere Felder eingetragen werden.

Mit keytool -list -v -keystore <name.keystore> können wir anschließend prüfen, ob die Erstellung des Keystores und das Hinzufügen des Alias korrekt geklappt hat. Im folgenden Ausschnitt sehen wir, dass im Keystore genau ein Key-Paar mit dem Alias cert existiert.

Keystore auflisten

Mit jarsigner können wir jetzt unsre modifizierte APK-Datei signieren. Über den Parameter -keystore geben wir dabei den Pfad zu unsrem gerade erstellten Keystore an.

jarsigner -verbose -sigalg SHA1withRSA -digestalg SHA1 -keystore <pfad_keystore> <pfad_apk> cert

Wenn wir unsre modifizierte und signierte App jetzt auf unsrem Gerät installieren und die Anwendung starten, sehen wir, dass wir jedes beliebige Passwort eingeben können und sich der Tresor immer erfolgreich entsperrt.

Das Allzweckwerkzeug APK Studio


Decompiling, Disassembling, Java und Smali Code auswerten, Code manipulieren, anschließend die App wieder bauen, signieren und auf dem Gerät installieren sind alles Schritte, die wir immer wieder durchführen müssen. Auf Dauer kann das aber auch recht nervig werden. Wie am Anfang versprochen, will ich zum Abschluss noch ein Werkzeug vorstellen, was all die kleinen Aufgaben in einem Tool bündelt.

APK Studio ist ein frei verfügbares Open-Source-Tool, das sowohl auf Windows, Linux oder MacOS läuft. Anfangs müssen nur ein paar Pfade zu den einzelnen Tools, die wir verwenden, in den Einstellungen angegeben werden.

APK Studio Settings: Pfade

Solltest du noch nicht alle Zusatztools heruntergeladen haben, findest du die Download-Links auch direkt in der Anwendung („Get it here!“).

Zum Signieren der App müssen wir dann noch die Daten des Keystores hinterlegen.

APK Studio Settings: Keystore

Anschließend können wir eine APK-Datei auswählen und diese automatisch dekompilieren und disassemblen lassen.

Außerdem ist in der Anwendung eine Entwicklungsumgebung (IDE) direkt mit eingebaut. Wir können also Änderung am Smali Code direkt über APK Studio machen, wie zum Beispiel das Passwort auf den Wert 12345 zu ändern.

APK Studio: Code manipulieren

Wenn wir fertig sind, können wir die App wieder zusammenbauen und signieren lassen.

Extrem hilfreich ist auch, dass wir die App direkt installieren lassen können, wenn wir einen Emulator laufen lassen oder unser Smartphone per ADB mit dem Rechner verbunden haben.


Für mehr Tutorials kannst du gerne einen Blick auf meinen YouTube-Kanal werfen. Lass gerne ein Abo da, um bei neuen Tutorials direkt benachrichtigt zu werden. In den nächsten Teilen schauen wir uns dann etwas komplexere Themen wie Debugging von Smali Code, Netzwerkverkehr und Obfuscation an.

Frohes Hacken!