Frida

Android-Apps zur Laufzeit instrumentieren und BLE-Calls abfangen

Lernziel

Nach dieser Seite verstehst du, was dynamische Instrumentierung bedeutet und warum sie statische Analyse (JADX, Ghidra) ergänzt statt ersetzt. Du kannst Frida und frida-server einrichten, einen JavaScript-Hook in eine laufende Android-App injizieren und Laufzeitwerte — etwa Schlüssel, Argumente und Rückgabewerte — gezielt abfangen. Als konkretes Beispiel hookst du Cipher.getInstance in der LED-Brille-App, um den verwendeten Kryptoalgorithmus zur Laufzeit zu beobachten.

Was ist Frida?

Frida ist ein dynamisches Instrumentierungs-Toolkit. Es erlaubt dir, in laufende Prozesse auf Android, iOS, Windows, macOS und Linux einzugreifen und Funktionen durch JavaScript-Hooks zu überwachen oder zu modifizieren — ohne die Original-App neu zu kompilieren oder zu patchen.

Wo statische Analyse (JADX, Ghidra) den Quellcode liest, beobachtet Frida den Code beim Ausführen. Das ist besonders dann wertvoll, wenn:

  • Schlüssel erst zur Laufzeit berechnet werden (z. B. aus Geräte-ID und Seriennummer abgeleitet)
  • Code durch Obfuskation oder Packer schwer lesbar ist
  • du prüfen möchtest, ob ein bestimmter Code-Pfad tatsächlich durchlaufen wird
  • eine Funktion Argumente oder Rückgabewerte hat, die du im statischen Code nicht sehen kannst

Frida ist kein Ersatz für statische Analyse

Dynamische und statische Analyse ergänzen sich. JADX zeigt die Struktur — Frida zeigt die tatsächlichen Werte zur Laufzeit. In der Praxis stellt statische Analyse die Hypothese auf ("hier wird AES verwendet"), und Frida verifiziert sie ("der Schlüssel ist 0x41424344...").

Installation

frida-tools auf dem Rechner

# Virtuelle Umgebung (empfohlen)
python3 -m venv frida-env
source frida-env/bin/activate

# Frida installieren
pip install frida-tools

# Version prüfen
frida --version
# Ausgabe: 16.x.x

frida-server auf Android

frida-server ist ein kleiner Daemon, der auf dem Android-Gerät läuft und mit dem frida-Client auf deinem Rechner kommuniziert. Er muss zur Frida-Version auf dem Rechner passen.

# Aktuelle Release-Version herausfinden
frida --version  # z. B. 16.3.3

# frida-server herunterladen (ARM64 für die meisten Android-Geräte)
# https://github.com/frida/frida/releases
# Datei: frida-server-16.3.3-android-arm64.xz

xz -d frida-server-16.3.3-android-arm64.xz

# Auf das Gerät übertragen und ausführbar machen
adb push frida-server-16.3.3-android-arm64 /data/local/tmp/frida-server
adb shell chmod 755 /data/local/tmp/frida-server

# frida-server starten (Root erforderlich)
adb shell su -c "/data/local/tmp/frida-server &"

# Verbindung prüfen
frida-ps -U
# Listet alle laufenden Prozesse auf dem Gerät

Root-Zugriff erforderlich

frida-server auf Android benötigt Root-Rechte. Das bedeutet entweder ein gerootetes Gerät (z. B. mit Magisk) oder ein Android-Emulator mit Root (z. B. Genymotion, Android Studio AVD mit userdebug-Image). Für echte Geräte ohne Root gibt es den frida-gadget-Ansatz, der eine modifizierte APK erfordert.

BLE-Beispiel: Cipher.getInstance in der LED-Brille hooken

Die LED-Brille-App ruft Cipher.getInstance("AES/ECB/NoPadding") auf — das hat die statische Analyse in JADX gezeigt. Mit Frida kannst du zur Laufzeit prüfen: Welcher Algorithmus wird tatsächlich angefordert? Und welche Schlüssel werden übergeben?

Schritt 1: App identifizieren

# Paketname der laufenden App finden
frida-ps -Ua
# Beispiel-Ausgabe:
# PID   Name                   Identifier
# 4521  LED Brille             com.pinkysinyeeho.funkyglassesplus

Schritt 2: Hook-Skript schreiben

Erstelle eine Datei hook_cipher.js:

Java.perform(function () {
  // javax.crypto.Cipher hooken
  var Cipher = Java.use("javax.crypto.Cipher");

  // getInstance interceptieren
  Cipher.getInstance.overload("java.lang.String").implementation = function (transformation) {
    console.log("[*] Cipher.getInstance aufgerufen");
    console.log("    Transformation: " + transformation);
    console.log("    Stack:\n" + Java.use("android.util.Log")
      .getStackTraceString(Java.use("java.lang.Exception").$new()));

    // Original-Funktion aufrufen (App weiterhin funktionsfähig)
    return this.getInstance(transformation);
  };

  // SecretKeySpec hooken — zeigt den tatsächlichen Schlüssel
  var SecretKeySpec = Java.use("javax.crypto.spec.SecretKeySpec");
  SecretKeySpec.$init.overload("[B", "java.lang.String").implementation = function (keyBytes, algorithm) {
    var hex = Array.from(keyBytes)
      .map(function (b) { return ("0" + (b & 0xff).toString(16)).slice(-2); })
      .join("");
    console.log("[*] SecretKeySpec erstellt");
    console.log("    Algorithmus: " + algorithm);
    console.log("    Schlüssel:   " + hex);
    return this.$init(keyBytes, algorithm);
  };
});

Schritt 3: Hook injizieren

# An laufende App anhängen (attach)
frida -U -n "LED Brille" -l hook_cipher.js

# Oder App beim Start hooken (spawn) — empfohlen, um frühe Initialisierung zu erfassen
frida -U -f com.pinkysinyeeho.funkyglassesplus -l hook_cipher.js --no-pause

Jetzt die App normal benutzen — die Konsole zeigt jeden Cipher.getInstance-Aufruf mit Algorithmusname und, sobald eine Verbindung hergestellt wird, den tatsächlichen Schlüssel.

Spawn vs. Attach

ModusBefehlWann verwenden
Attachfrida -U -n "App Name" -l script.jsApp läuft bereits, spätere Funktionsaufrufe interessieren
Spawnfrida -U -f com.example.app -l script.js --no-pauseApp vom Start hooken, frühe Initialisierung beobachten

Für BLE-Analysen ist Spawn in der Regel besser: Verbindungsaufbau und Schlüsselaustausch geschehen häufig beim App-Start, bevor du attach hättest.

Erweiterte Techniken

SSL Pinning umgehen

Apps mit SSL Pinning verwerfen TLS-Verbindungen, wenn das Zertifikat nicht exakt dem erwarteten entspricht. Frida kann die Prüfung zur Laufzeit deaktivieren.

Java.perform(function () {
  // TrustManager mit leerem Validator ersetzen
  var TrustManager = Java.registerClass({
    name: "com.frida.TrustManager",
    implements: [Java.use("javax.net.ssl.X509TrustManager")],
    methods: {
      checkClientTrusted: function (chain, authType) {},
      checkServerTrusted: function (chain, authType) {},
      getAcceptedIssuers: function () { return []; },
    },
  });

  var SSLContext = Java.use("javax.net.ssl.SSLContext");
  var ctx = SSLContext.getInstance("TLS");
  ctx.init(null, [TrustManager.$new()], null);
  SSLContext.getDefault.implementation = function () { return ctx; };

  console.log("[+] SSL Pinning deaktiviert");
});

objection: Frida-basiertes Pentest-Framework

objection baut auf Frida auf und bietet vorgefertigte Hooks für häufige Aufgaben: SSL-Pinning-Bypass, Root-Detection-Bypass, Dateisystem-Exploration. Statt Skripte von Hand zu schreiben, verwendest du objection -g com.example.app explore. Installieren mit: pip install objection.

Native JNI-Funktionen hooken

Wenn kritische Logik in einer .so-Bibliothek liegt (wie libAES.so der LED-Brille), hookst du auf Native-Ebene statt auf Java-Ebene.

// Native Funktion nach Export-Name hooken
var keyExpansion = Module.findExportByName(
  "libAES.so",
  "Java_csh_tiro_cc_aes_keyExpansionDefault"
);

if (keyExpansion !== null) {
  Interceptor.attach(keyExpansion, {
    onEnter: function (args) {
      console.log("[*] keyExpansionDefault aufgerufen");
      // args[0] = JNIEnv*, args[1] = jclass, args[2] = jbyteArray (Schlüssel)
      var jenv = Java.vm.tryGetEnv();
      if (jenv !== null) {
        var keyArray = jenv.getByteArrayElements(args[2], null);
        console.log("    Schlüssel-Bytes: " + keyArray);
      }
    },
    onLeave: function (retval) {
      console.log("[<] keyExpansionDefault Rückgabewert: " + retval);
    },
  });
}

BLE GATT-Aufrufe tracen

Android verwendet BluetoothGatt.writeCharacteristic für ausgehende BLE-Schreibvorgänge und BluetoothGattCallback.onCharacteristicChanged für eingehende Notifications. Beide lassen sich mit Frida hooken.

Java.perform(function () {
  var BluetoothGatt = Java.use("android.bluetooth.BluetoothGatt");

  BluetoothGatt.writeCharacteristic.overload(
    "android.bluetooth.BluetoothGattCharacteristic"
  ).implementation = function (characteristic) {
    var value = characteristic.getValue();
    var hex = Array.from(value)
      .map(function (b) { return ("0" + (b & 0xff).toString(16)).slice(-2); })
      .join(" ");
    console.log("[>] writeCharacteristic");
    console.log("    UUID:    " + characteristic.getUuid().toString());
    console.log("    Payload: " + hex);
    return this.writeCharacteristic(characteristic);
  };

  // Notifications kommen über BluetoothGattCallback, nicht BluetoothGatt
  var GattCallback = Java.use("android.bluetooth.BluetoothGattCallback");

  GattCallback.onCharacteristicChanged.overload(
    "android.bluetooth.BluetoothGatt",
    "android.bluetooth.BluetoothGattCharacteristic"
  ).implementation = function (gatt, characteristic) {
    var value = characteristic.getValue();
    var hex = Array.from(value)
      .map(function (b) { return ("0" + (b & 0xff).toString(16)).slice(-2); })
      .join(" ");
    console.log("[<] onCharacteristicChanged (Notification)");
    console.log("    UUID:    " + characteristic.getUuid().toString());
    console.log("    Payload: " + hex);
    return this.onCharacteristicChanged(gatt, characteristic);
  };
});

Damit siehst du jeden BLE-Write und jede Notification auf Android-API-Ebene. Wenn die App selbst verschlüsselt (wie die LED-Brille mit AES-ECB), hookst du zusätzlich die Cipher-Aufrufe weiter oben im Stack, um die Klartextdaten vor der Verschlüsselung abzufangen. Die Kombination beider Hooks zeigt Klartext und Chiffrat nebeneinander.

Bekannte Probleme

ProblemUrsacheLösung
Unable to attach to remote frida-serverfrida-server läuft nicht oder Version stimmt nicht übereinVersion prüfen: adb shell /data/local/tmp/frida-server --version
Failed to spawn: unable to find processApp-Paketname falsch oder App nicht installiertMit frida-ps -Uai alle installierten Apps auflisten
Architecture mismatchfrida-server für falsche CPU-Architektur heruntergeladenadb shell getprop ro.product.cpu.abi prüfen, passendes Binary wählen
App stürzt beim Hook abHook-Skript hat Fehler oder Methoden-Signatur ist falschMethoden-Overloads mit Cipher.getInstance.overloads auflisten
SELinux blockiert frida-serverEnforcing-Modus auf ungepatchten Gerätenadb shell setenforce 0 (temporär, nur für Tests)

Zusammenfassung

  • Frida instrumentiert laufende Prozesse ohne APK-Modifikation — es zeigt Laufzeitwerte, die statische Analyse nicht sehen kann.
  • Einrichtung: pip install frida-tools auf dem Rechner + passendes frida-server-Binary auf dem Gerät.
  • Der Kernbefehl für Analysen: frida -U -f <paketname> -l script.js --no-pause (Spawn-Modus).
  • Für BLE: BluetoothGatt.writeCharacteristic und BluetoothGattCallback.onCharacteristicChanged hooken, um gesendete und empfangene Payloads mitzulesen.
  • objection bietet vorgefertigte Hooks für SSL-Pinning-Bypass und weitere häufige Pentest-Aufgaben.