Phase 3: Reverse Engineering
Native Libraries mit Ghidra analysieren und Schlüssel extrahieren
Lernziel
Nach dieser Seite weißt du, wie du eine native Android-Bibliothek (.so-Datei) in Ghidra öffnest, JNI-Exportfunktionen als Einstiegspunkte nutzt, das FindCrypt-Plugin zur Erkennung von Kryptoalgorithmen einsetzt und hartcodierte Schlüssel in der .data-Section einer ARM64-ELF-Datei lokalisierst. Als konkretes Beispiel extrahierst du den AES-128-Schlüssel aus der libAES.so der Sonhomay LED Glasses.
Was ist Reverse Engineering einer nativen Bibliothek?
Viele Android-Apps lagern sicherheitskritischen Code in native Bibliotheken aus — Dateien mit der Endung .so (Shared Object). Das ist dasselbe Format wie .dll unter Windows oder .dylib unter macOS. Diese Bibliotheken sind kompilierter Maschinencode für einen bestimmten Prozessortyp (hier: ARM64) und enthalten keinen lesbaren Quelltext.
Stell dir eine verschlossene Metalldose vor, deren Inhalt du nicht direkt sehen kannst. Ghidra ist das Werkzeug, das die Dose öffnet: Es übersetzt die binären Maschinenbefehle zurück in pseudo-C-Code (Decompiler) und zeigt dir, was das Programm tatsächlich macht. Diese Übersetzung ist nicht perfekt, aber gut genug, um Schlüssel, Algorithmen und Protokolllogik zu erkennen.
Phase 1 hat gezeigt, dass die App Krypto verwendet und auf libAES.so delegiert. Phase 3 öffnet jetzt diese Bibliothek und liest heraus, welcher Schlüssel tatsächlich drin steckt.
ARM64 vs. x86
Android-Geräte nutzen ARM-Prozessoren. Ghidra unterstützt ARM64 (AArch64) nativ — du musst keinen Prozessortyp manuell auswählen, Ghidra erkennt ihn automatisch beim Import einer ELF-Datei.
Was ist JNI und warum ist es der Schlüssel zum Einstieg?
JNI steht für Java Native Interface — die Brücke zwischen Java/Kotlin-Code einer App und nativem C/C++-Code in einer .so-Datei. Wenn Java-Code eine native Funktion aufruft, muss diese Funktion in der Bibliothek einen ganz bestimmten Namen haben:
Java_<Paketname>_<Klassenname>_<Methodenname>
Punkte im Paketnamen werden durch Unterstriche ersetzt. Das ist eine verbindliche Konvention des Android-Betriebssystems, die du dir zunutze machen kannst: Du musst nicht den gesamten Binärcode durchsuchen — du suchst einfach nach Funktionen, die mit Java_ beginnen.
Beispiel aus der LED-Brille:
In Phase 1 hast du in JADX die Klasse com.pinkysinye.funkyglassesplus.model.data.Agreement mit einer nativen Methode gesehen. Die zugehörige JNI-Exportfunktion in libAES.so heißt daher:
Java_csh_tiro_cc_aes_keyExpansionDefault
Dieser Name ist der Einstiegspunkt für die gesamte AES-Implementierung.
Sonderzeichen in Paketnamen
Enthält ein Paketname Sonderzeichen oder Ziffern, werden diese nach dem JNI-Encoding-Schema mit _0 ersetzt (z. B. _1 für $). Wenn eine Suche nach dem erwarteten Namen scheitert, prüfe auf solche Kodierungen.
Ghidra einrichten für ARM64-ELF-Analyse
Systemvoraussetzungen:
- Ghidra 11.4.3 (aktuellste stabile Version)
- Java 17 oder neuer (JDK, nicht nur JRE)
- 8 GB RAM empfohlen; für große Bibliotheken mehr
Installation:
# Java-Version prüfen
java -version
# Muss: openjdk 17 oder höher
# Ghidra herunterladen: https://ghidra-sre.org/
# ZIP entpacken, kein Installer nötig
cd ~/tools/ghidra_11.4.3_PUBLIC
./ghidraRunFindCrypt-Plugin installieren:
Das FindCrypt-Plugin erkennt bekannte kryptographische Konstanten (AES-S-Box, SHA-Konstanten, u. a.) automatisch im Binärcode. Das erspart stundenlange manuelle Suche.
# Ghidra öffnen -> Script Manager (Fenster -> Script Manager)
# Oder direkt: Tools -> Run Script
# Alternative: FindCrypt als Extension installieren
# Script: FindCrypt.py aus dem Ghidra-Repository oder community builds
# Ablage: ~/ghidra_scripts/FindCrypt.py
# Im Script Manager: "FindCrypt" suchen, Skript auswählen, RunDecompiler-Timeout bei großen Funktionen
Manche AES-Implementierungen haben große, unrollte Funktionen (>500 Zeilen Assembler). Der Ghidra-Decompiler bricht standardmäßig nach 60 Sekunden ab. Erhöhe den Timeout unter Edit -> Tool Options -> Decompiler -> Analysis -> Timeout auf 300 Sekunden, bevor du mit der Analyse beginnst.
Schritt 1: libAES.so in Ghidra importieren
Du hast die .so-Datei bereits in Phase 1 aus der APK extrahiert. Sie liegt im APK-Archiv unter lib/arm64-v8a/libAES.so.
1. Ghidra öffnen -> Neues Projekt anlegen (File -> New Project)
2. File -> Import File -> libAES.so auswählen
3. Ghidra erkennt automatisch: ELF, ARM Cortex, 64-bit, little-endian
4. "OK" klicken -> "Analyze" im folgenden Dialog bestätigen
5. Analyse dauert 10–30 Sekunden (Fortschrittsbalken unten)
Nach der Analyse öffnet sich der Code Browser. Links siehst du den Symbol Tree — eine Liste aller Funktionen, Exports und Daten.
Schritt 2: JNI-Exportfunktion finden
Im Symbol Tree unter Exports findest du alle öffentlichen Funktionen der Bibliothek. JNI-Exporte beginnen mit Java_.
Symbol Tree -> Exports -> Java_csh_tiro_cc_aes_keyExpansionDefault
Adresse: 0x00101278
Doppelklick auf die Funktion öffnet den Disassembler und den Decompiler gleichzeitig. Im Decompiler-Fenster (rechts) siehst du pseudo-C-Code — dieser ist für die Analyse entscheidend.
Was sind Exports?
Eine ELF-Shared-Library "exportiert" Funktionen, die von außen aufrufbar sind — ähnlich wie eine öffentliche API. Nur exportierte Funktionen sind von Java aus via JNI aufrufbar. Private (interne) Hilfsfunktionen sind nicht exportiert.
Schritt 3: FindCrypt ausführen
Bevor du den Decompiler-Code liest, lass FindCrypt laufen. Das Skript markiert alle Stellen im Binärcode, an denen bekannte kryptographische Konstanten vorkommen.
Window -> Script Manager -> "FindCrypt" eingeben -> Run
FindCrypt zeigt nach dem Durchlauf eine Tabelle mit Treffern. Für libAES.so der LED-Brille findest du:
Adresse Algorithmus Beschreibung
0x00102000 AES AES S-Box (Forward)
0x00102100 AES AES S-Box (Inverse)
0x00102200 AES Round Constants
Diese Adressen zeigen dir, wo die AES-Implementierung ihre Tabellen gespeichert hat. Doppelklick auf einen Eintrag springt direkt dorthin.
Schritt 4: GOT-Referenzen und Schlüssel verfolgen
Jetzt gehst du zurück zur JNI-Funktion bei 0x00101278 und liest den Decompiler-Code. Du wirst zwei markante Zeilen sehen:
// Ghidra Decompiler-Ausgabe (vereinfacht):
___bss_start__ = *PTR_key_00112ff0; // Key Bytes 0–7
_DAT_00113038 = *(PTR_key_00112ff0 + 8); // Key Bytes 8–15Was bedeutet das?
PTR_key_00112ff0 ist ein Eintrag in der GOT — der Global Offset Table. Die GOT ist eine Tabelle in ELF-Binärdateien, die Adressen zu globalen Variablen und Funktionen speichert. Der Name PTR_key_ wurde von Ghidra automatisch vergeben, weil es erkennt, dass hier ein Schlüssel referenziert wird.
Der Pointer in der GOT zeigt auf die eigentlichen Schlüsseldaten in der .data-Section bei Adresse 0x00113020.
Decompiler-Artefakt: Zwei Ladeoperationen
Ghidra stellt den 128-Bit-AES-Schlüssel als zwei separate 64-Bit-Ladeoperationen dar. Das ist ein bekanntes Decompiler-Artefakt. Im echten ARM64-Assembler wird der Schlüssel durch eine einzige ldr q0, [x1]-Instruktion in ein 128-Bit-NEON-Register geladen. Die zweigeteilte Darstellung täuscht — es ist ein einziger zusammenhängender 16-Byte-Schlüssel.
Schritt 5: Schlüssel in der .data-Section lesen
Navigiere zu Adresse 0x00113020:
Navigation -> Go To Address -> 0x00113020 -> Enter
Im Listing-Fenster siehst du rohe Bytes. Der AES-128-Schlüssel der LED-Brille:
Adresse: 0x00113020 (.data-Section)
34 52 2A 5B 7A 6E 49 2C 08 09 0A 9D 8D 2A 23 F8
Das sind genau 16 Bytes (128 Bit) — die Schlüsselgröße für AES-128. Dieser Schlüssel ist statisch und hardcodiert: Er ist in allen weltweit verkauften Exemplaren der LED-Brille identisch.
Verifizierung: Der extrahierte Schlüssel kann sofort überprüft werden. Das Klartext-/Chiffretext-Paar aus dem PCAP-Capture (Phase 2) liefert den Testfall:
from Crypto.Cipher import AES
key = bytes([0x34, 0x52, 0x2A, 0x5B, 0x7A, 0x6E, 0x49, 0x2C,
0x08, 0x09, 0x0A, 0x9D, 0x8D, 0x2A, 0x23, 0xF8])
# DATS-Command aus dem PCAP-Capture
plaintext = bytes([0x07, 0x44, 0x41, 0x54, 0x53, 0x01, 0x00, 0x26,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00])
cipher = AES.new(key, AES.MODE_ECB)
result = cipher.encrypt(plaintext).hex()
# Ergebnis: e703dd68d7c0bb9ca75f0140df55e219
# Stimmt mit dem erfassten PCAP-Paket überein — Schlüssel korrekt!Häufiger Fehler: Falsche .so-Architektur
Android-Apps liefern oft mehrere .so-Versionen für verschiedene CPU-Architekturen: armeabi-v7a (32-Bit ARM), arm64-v8a (64-Bit ARM), x86, x86_64. Lade immer die passende Version für dein Analyseziel. Für moderne Android-Geräte ist arm64-v8a die richtige Wahl. Die Speicheradressen unterscheiden sich je nach Architektur.
Warum ECB-Modus ein Problem ist
Der extrahierte Schlüssel enthüllt nicht nur das Geheimnis — er zeigt auch den verwendeten AES-Modus: ECB (Electronic Codebook). ECB ist der einfachste AES-Modus, hat aber eine fundamentale Schwäche:
Stell dir vor, du verschlüsselst ein Bild. Im ECB-Modus bleibt jeder identische 16-Byte-Block im Klartext auch ein identischer Block im Chiffretext. Das Muster des Originalbildes bleibt erkennbar — selbst nach der Verschlüsselung. Für die LED-Brille bedeutet das: Identische Protokollbefehle erzeugen immer exakt denselben Chiffretext. Ein Angreifer kann Muster erkennen und Befehle replizieren, ohne den Schlüssel zu kennen.
Sichere Alternativen
Statt AES-ECB sollte AES-GCM (Galois/Counter Mode) verwendet werden. GCM bietet zusätzlich zur Verschlüsselung auch Integritätsschutz (Authenticated Encryption). Noch besser: BLE LE Secure Connections auf Link-Layer-Ebene aktivieren — das erledigt die gesamte Schlüsselaushandlung sicher.
Interaktive Checkliste: Phase 3
Phase 3: Native Library Reverse Engineering
0/12Zeitaufwand und Ergebnisse
Phase 3 dauert typisch 4–8 Stunden, abhängig von der Komplexität der Bibliothek. Größere Bibliotheken mit mehreren JNI-Funktionen und verschachtelten Hilfsfunktionen brauchen mehr Zeit.
Das Hauptergebnis dieser Phase ist der extrahierte Schlüssel — ein konkretes, verifizierbares Artefakt, das die Sicherheitslücke beweist. Dieser Schlüssel fließt direkt in Phase 5 (PoC-Entwicklung) ein.
Sekundäres Ergebnis: Du verstehst jetzt, wie die Verschlüsselung implementiert ist — welcher AES-Modus, welche Paketstruktur, welche Padding-Strategie. Diese Information ist für Phase 4 (Protokollrekonstruktion) unverzichtbar.
Zusammenfassung
- Native
.so-Bibliotheken enthalten oft hartcodierten Kryptoschlüssel in der.data-Section. - JNI-Funktionen (Präfix
Java_) sind der Einstiegspunkt in der nativen Bibliothek — sie müssen diesen Namensschema folgen. - Das FindCrypt-Plugin erkennt AES- und andere Kryptoalgorithmen automatisch anhand ihrer mathematischen Konstanten.
- GOT-Einträge (Global Offset Table) zeigen auf die eigentlichen Schlüsseldaten — verfolge den Zeiger von der JNI-Funktion bis zur
.data-Section. - Der gefundene 16-Byte-Schlüssel
34 52 2A 5B 7A 6E 49 2C 08 09 0A 9D 8D 2A 23 F8ist in allen Geräten identisch — ein globaler Schlüssel ohne gerätespezifische Variation.
Warum sind JNI-Exportfunktionen (Präfix Java_) so nützlich als Einstiegspunkt in der Ghidra-Analyse?