Überwachung des Systemprotokolls
Tomaso Vasella
So lassen sich Mobile Apps untersuchen
Viele Apps verwenden sensitive, oft personenbezogene Daten oder haben Zugriff darauf und kommunizieren mit diversen Diensten im Internet. Vor diesem Hintergrund ist es nützlich und interessant, die Funktionsweise und die Datenkommunikation von Apps aus Sicherheitssicht zu analysieren. Dieser Beitrag zeigt eine pragmatische Einführung in die Möglichkeiten zur Analyse von Android Apps.
Reverse Engineering (auch: rekonstruieren, Nachkonstruktion) bezeichnet generell die technische Untersuchung einer fertigen Anwendung oder eines Systems mit dem Ziel, möglichst detaillierte und genaue Informationen über die Funktionsweise zu erhalten, häufig mit dem Ziel, den Quellcode oder eine Repräsentation davon rückzugewinnen. Je nach Absicht kann auch nur das Verhalten oder die Kommunikation einer Anwendung im Fokus stehen (vgl. auch die Fachartikel Einstieg in die Welt des Disassemblierens und Dekompilierens und Reverse Engineering). Für das Reverse Engineering existiert eine grosse Anzahl nützlicher Werkzeuge. Die Verwendung einiger davon im Hinblick auf die Analyse von Android Apps wird nachfolgend aufgezeigt.
Für die folgenden Betrachtungen wird eine absichtlich für Testzwecke erstellte, unsichere Android App des MSTG Hacking Playground verwendet. Android Apps liegen als APK-Dateien vor (Android Package Kit), welches normale ZIP-Archive sind:
$ file MSTG-Android-Java.apk MSTG-Android-Java.apk: Zip archive data, at least v0.0 to extract, compression method=deflate $ unzip -q MSTG-Android-Java.apk -d MSTG-Android-Java $ ls MSTG-Android-Java AndroidManifest.xml assets classes.dex lib META-INF res resources.arsc
Die üblichen Inhalte einer APK-Datei, wie auch im obigen Beispiel ersichtlich, sind:
classes.dex
: Datei, welche den kompilierten Dalvik Bytecode der App im Dex Dateiformat beinhaltet. Dieser Bytecode wird von der Android Runtime oder dem Vorgänger Dalvik ausgeführt.resources.arsc
: enthält einige vorkompilierte Ressourcen wie Strings, Colors oder Styles.res/
: beinhaltet die meisten XML-Ressourcen wie Layouts oder Bilddateien im Binary XML Format.AndroidManifest.xml
: Enthält Metainformationen zur App wie Name, Version, Permissions, usw. Diese Datei liegt ebenfalls im Binary XML Format vor.lib/
: enthält kompilierte, native Bibliotheken spezifisch für alle von der App unterstützten CPU-Architekturen (*.so Dateien), beispielweise arm64-v8a oder x86_64.assets/
: Ein Verzeichnis mit Assets der App, beispielsweise Bilddateien, Fonts oder Java Script.META-INF/
: Verzeichnis mit APK Metadaten, beinhaltet eine Liste aller Dateien des APK und ihre Signatur (Hash).In den folgenden Abschnitten wird auf die statische Analyse von Android Apps eingegangen.
Für das Entpacken und Analysieren von APK-Dateien gibt es diverse Werkzeuge mit unterschiedlichem Funktionsumfang. Das Android Studio enthält ein APK-Analysewerkzeug und ist damit eines der komfortabelsten Tools, wenn auch recht ressourcenhungrig. Der APK-Analyzer kann über das Menu Build / Analyze APK
gestartet werden.
Die Inhalte des APK werden entpackt und analysiert und können nun weiter untersucht werden. Die im APK als Binärdateien vorliegenden Ressourcen werden ebenfalls entpackt, so dass z.B. das AndroidManifest.xml
im Klartext betrachtet werden kann.
Bei der Analyse über das Android Studio wird der Dalvik Bytecode aus der classes.dex
Datei automatisch decodiert und kann direkt betrachtet werden.
Will man den Code genauer betrachten, lässt sich durch Rechtsklick der Bytecode direkt anzeigen:
Die Darstellung erfolgt als smali, eine für Menschen lesbare Repräsentation (Disassembler Syntax) des Bytecodes. Es ist auch möglich, den Dalvik Bytecode in Java-Bytecode umzuwandeln. Dies funktioniert häufig auch dann, wenn die Android App ursprünglich in Kotlin geschrieben wurde. Anschliessend kann der Java Bytecode mit einem Java Decompiler analysiert werden, was eine noch komfortablere Betrachtung ermöglicht.
Alternativ zur Verwendung de Android Studio bietet sich das Kommandozeilen-Tool Apktool an, welches vor allem bei komplexeren Apps manchmal bessere Resultate erzielt:
$ java -jar apktool_2.6.1.jar d MSTG-Android-Java.apk I: Using Apktool 2.6.1 on MSTG-Android-Java.apk I: Loading resource table... I: Decoding AndroidManifest.xml with resources... I: Loading resource table from file: ~/.local/share/apktool/framework/1.apk I: Regular manifest package... I: Decoding file-resources... I: Decoding values */* XMLs... I: Baksmaling classes.dex... I: Copying assets and libs... I: Copying unknown files... I: Copying original files... $ ls MSTG-Android-Java AndroidManifest.xml apktool.yml assets lib original res smali
Dieses Tool konvertiert den Dalvik Bytecode direkt zu smali:
$ head -20 smali/sg/vp/owasp_mobile/OMTG_Android/MyActivity.smali .class public Lsg/vp/owasp_mobile/OMTG_Android/MyActivity; .super Landroidx/appcompat/app/AppCompatActivity; .source "MyActivity.java" # static fields .field public static final EXTRA_MESSAGE:Ljava/lang/String; = "com.mycompany.myfirstapp.MESSAGE" # direct methods .method public constructor <init>()V .locals 0 .line 12 invoke-direct {p0}, Landroidx/appcompat/app/AppCompatActivity;-><init>()V return-void .end method
Ein grosser Vorteil des Apktool besteht darin, dass die decodierten Dateien (XML usw.) aber auch der decodierte smali Code editiert werden können und anschliessend kann mit den editierten Dateien wieder ein APK erzeugt werden.
Ein weiteres nützliches Tool im Analyse-Arsenal ist dex2jar, welches die in APKs enthaltenen Dex Dateien direkt zu Java Klassen umwandelt. Anschliessend kann der Java-Code einfach dekompiliert werden.
$ dex-tools-2.1/d2j-dex2jar.sh MSTG-Android-Java.apk dex2jar MSTG-Android-Java.apk -> ./MSTG-Android-Java-dex2jar.jar
Die so erzeugte jar-Datei kann anschliessend mit einem Java-Decompiler analysiert werden, im folgenden Beispiel wird dazu das JD-GUI verwendet.
Analoges kann erreicht werden mit dem Tool jadx, welches ebenfalls Dex Bytecode nach Java umwandelt.
Als Beispiel eines möglichen Vorgehens für statische Analysen verwenden wir einen der Testfälle in der oben erwähnten Test-App. Diese App enthält eine Funktion zur Erstellung einer verschlüsselten SQLite Datenbank.
Der entsprechende Code ist in der dekompilierten Version einfach auffindbar:
Betrachtet man die Methode SQLiteEnc()
wird ersichtlich, dass das zur Verschlüsselung verwendete Secret offenbar von der Methode stringFromJNI()
ausgegeben werden muss. Interessant ist die Methode System.loadLibrary()
, welche eine native, für die jeweilige CPU kompilierte Library lädt. Die Methode stringFromJNI()
ist mit dem Schlüsselwort native
versehen, dies bedeutet, dass diese Methode in nativem Code mittels JNI (Java Native Interface) implementiert ist. Ein Blick ins Verzeichnis lib/x86
zeigt folgende Dateien:
$ ls MSTG-Android-Java/lib/x86 libnative-lib.so libsqlcipher.so $ file libnative-lib.so libnative-lib.so: ELF 32-bit LSB shared object, Intel 80386, version 1 (SYSV), dynamically linked, BuildID[sha1]=5feb6239bf0911b46bbdf5ff1bccd29570bbb00f, stripped
Das gesuchte Secret muss also durch eine Funktion in der Datei libnative-lib.so
erzeugt werden. Zur statischen Analyse dieser Library gibt es diverse mögliche Ansätze. Zur Illustration wird hier eine kurze Analyse mit dem Werkzeug Ghidra gezeigt. Als erstes muss ein neues Projekt erstellt werden über das Menu File / New Project
, anschliessend kann die Library über Drag and Drop geladen werden, wobei die Defaults hier bereits richtig sind:
Doppelklick auf die Importierte Datei öffnet den Code Browser, welcher eine Analyse der Datei vorschlägt. In diesem Beispiel werden alle vorgeschlagenen Defaults akzeptiert.
Anschliessend suchen wir nach dem Namen der Funktion, den wir im Java Code gefunden haben: Search / Program Text
:
Als Ergebnis werden die gefundenen Lokationen angezeigt:
Durch Klick darauf wird zur entsprechenden Stelle im Code navigiert:
Bewegt man den Mauszeiger über die Hex-Werte der Variablen puVar1
wird ersichtlich, dass hier ein String zusammengesetzt wird aus dwords. Diese sind 32 Bits lang, deshalb EAX + 0×4, EAX + 0×8, etc.:
Durch Rechtsklick kann man die Hex-Werte konvertieren mit folgendem Ergebnis:
Die Sequenz \0
lässt einen mit einem Null-Byte terminierten String erahnen, man darf also annehmen, dass der String rückwärts gelesen werden muss, womit sich das gesuchte Secret ergibt: S3cr3tString!!!
Dieses Beispiel ist zwar interessant und illustrativ, aber man hätte auch einfach nach vorhandenen Strings im Binary suchen können:
$ strings /tmp/MSTG-Android-Java/lib/x86/libnative-lib.so ... [^_] [^_] S3cr3tString!!! cannot allocate __cxa_eh_globals std::__libcpp_tls_set failure in __cxa_get_globals() execute once failure in __cxa_get_globals_fast() ...
Vergleichbare Funktionalität steht auch in Ghidra zur Verfügung über Window / Defined Strings
Nachdem wir nun einige Methoden zur statischen Analyse von APKs und ihren Inhalten gesehen haben, ist es nützlich, auch einen Blick auf die Möglichkeiten zur dynamischen Analyse zu werfen, d.h. die Analyse des Verhaltens von Android Apps zu ihrer Laufzeit. Praktischerweise steht mit den Android Emulator als Bestandteil des Android Studio eine umfassende Möglichkeit zur Emulation von Android-Geräten zur Verfügung, einschliesslich der Möglichkeit, Apps vom Google Play Store zu installieren.
Ein neues virtuelles Gerät lässt sich über Tools / Device Manager / Create Device
einrichten, wobei man auch die gewünschte Android-Version wählen kann, ebenso, ob eine Version mit oder ohne Playstore verwendet werden soll. Hinweis: Möchte man später auf einfache Weise root-Rechte auf dem virtuellen Gerät erlangen können, so muss man ein Android Image ohne Playstore wählen.
Das so erstellte virtuelle Device befindet sich nun unter ~/.android/avd/<device-name>.avd/
und kann über das Android Studio oder über die Kommandozeile gestartet werden. Sobald das Gerät gestartet ist, kann es via Android Debug Bridge erreicht werden:
> ~/Android/Sdk/platform-tools/adb devices List of devices attached emulator-5554 device
Zur dynamischen Analyse von Android Apps steht eine Vielzahl unterschiedlicher Methoden und Werkzeuge zur Verfügung, eines der bekanntesten und leistungsfähigsten dürfte Frida sein.
Nachfolgend wird auf eine bequeme Methode zur Analyse des Kommunikationsverhaltens von Android Apps eingegangen, ein praktisch bei jeder Analyse erforderlicher Schritt. Ein naheliegender Gedanke dazu besteht darin, die Kommunikation von Apps oder des gesamten Geräts über einen Proxy zu leiten und so die Kommunikation mitverfolgen zu können. Mit dem Emulator kann sehr einfach ein Proxy gesetzt werden über das GUI:
Bei älteren Android-Versionen hat dies recht gut funktioniert, nachdem das Zertifikat des Proxy im Trust Store des Geräts installiert ist und solange die Apps kein Certificate Pinning verwenden. Bei Android-Versionen ab Android Nougat wird man aber rasch feststellen, dass die meisten Apps so nicht mehr kommunizieren. Dies liegt unter anderem daran, dass in solchen Versionen die Apps nicht mehr den im Benutzer Trust Store installierten Zertifikaten vertrauen. Man könnte Apps Dekompilieren, die darin enthaltene Datei network_security_policy.xml
entsprechend verändern und die App neu kompilieren.
Eine einfachere Methode besteht darin, die eigenen Zertifikate in den System Trust Store von Android zu installieren, der sich unter /system/etc/security/cacerts
befindet. Allerdings erfordert dies root-Rechte auf dem Gerät, was aber mit dem Android-Emulator und einem System-Image ohne Playstore sehr einfach möglich ist. Praktischerweise gibt es dafür das relativ neue Werkzeug HTTP Toolkit, das dieses ganze Prozedere enorm einfach macht. Nachdem das Tool entpackt und gestartet ist, kann man sich via “Android device via ADB” direkt mit einem gestarteten Emulator verbinden.
Solange das verwendete System-Image root-Rechte erlaubt, sind die nachfolgenden Schritte vollständig automatisiert.
Das Tool beinhaltet einen Request-Browser, mit dem die Kommunikation einfach betrachtet werden kann. In diesem Beispiel wurde wiederum die MSTG Test App verwendet.
Für die Analyse von Android Apps sind heute viele, teilweise sehr leistungsfähige Werkzeuge verfügbar. Einfache statische und dynamische Analysen sind mit relativ geringem Aufwand und ohne allzu komplexe Umgebungen realisierbar. Obwohl in der Praxis selbstverständlich auch viel schwierigere Fälle regelmässig auftreten, sei dies aufgrund von Code-Verschleierung oder aus anderen Gründen, kommt man oft mit einfachen Mitteln ziemlich weit. Selbst wenn man nur beschränkte Zeit damit verbringt, unter die Haube von mobilen Anwendungen zu schauen und ihr grundlegendes Verhalten zu betrachten, ist es oft möglich, Schwachstellen zu finden, insbesondere da unsichere Praktiken wie die Verwendung von im Code hinterlegten API-Schlüsseln oder Anmeldeinformationen in mobilen Anwendungen leider immer noch recht häufig vorkommen.
Unsere Spezialisten kontaktieren Sie gern!
Tomaso Vasella
Tomaso Vasella
Tomaso Vasella
Tomaso Vasella
Unsere Spezialisten kontaktieren Sie gern!