SuperAPI AI Directory

reversing-react-native-apps

Frontend & websites

View repository

Updated 10-16 02:28

About this skill

Reversing React Native Apps

React Native apps are JavaScript wearing a native shell. That is good news — the application logic ships as a bundle you can read — unless the bundle was compiled to Hermes bytecode, in which case you need a decompiler and the version compatibility problem becomes the whole task.

When to Use

  • jadx shows ReactActivity, ReactNativeHost, and little application logic
  • The APK contains assets/index.android.bundle or lib/*/libhermes.so
  • The IPA contains main.jsbundle
  • You need to find API endpoints, secrets, or business logic in an RN app
  • You need to patch or hook JavaScript-level behaviour

When NOT to Use

  • Flutter (libapp.so, libflutter.so) — use reversing-flutter-apps
  • Unity (global-metadata.dat) — use reversing-unity-il2cpp
  • Native Java/Kotlin apps — use testing-mobile-applications
  • Native modules written in C/C++ — use analyzing-binaries

Identify the Bundle Format

This one check decides whether the job takes ten minutes or a day.

unzip -j target.apk 'assets/index.android.bundle' -d ./out
file ./out/index.android.bundle
xxd ./out/index.android.bundle | head -2
First bytes Format Path
Readable JS (var __BUNDLE_START, __d(function) Plain JavaScript Beautify and read directly
Binary, Hermes magic (c6 1f bc 03 little-endian) Hermes bytecode Decompile — see below
# Plain bundle: beautify, then it reads like any minified web app
npx js-beautify index.android.bundle -o bundle.js
rg -n 'https?://|api[_-]?key|Bearer |secret|token' bundle.js | head -50

A plain bundle is a gift. Module boundaries survive as __d(function(...) registrations, source maps are occasionally shipped by mistake (index.android.bundle.map — always check), and the whole application logic is in front of you.

Hermes Bytecode

Hermes compiles JS to its own bytecode (HBC). The bytecode version is embedded in the header and changes with React Native releases, which is the single biggest practical obstacle: a tool that supports HBC 74 will refuse or misparse HBC 96.

# Read the version out of the header before choosing a tool
hbctool disasm index.android.bundle ./disasm     # HBC 59, 62, 74 ONLY
hermes-dec ...                                    # decompiler; wider version support

Upstream hbctool ships support for exactly three bytecode versions — 59, 62, and 74. Anything else fails at parse. Check the header version first rather than reading a parse error as "the bundle is protected"; on a current React Native release you will usually be above 74 and should reach for hermes-dec or a hermesc you built from the matching Hermes tag.

Tool Gives you Caveat
hbctool Disassembly and reassembly — you can patch and repack Supports a limited set of HBC versions
hermes-dec Decompiled pseudo-JavaScript Read-only; output is approximate
hasmer Disassembly/assembly, alternate version coverage Try when hbctool refuses the version

When every tool rejects the version, the reliable fallback is to build the matching hermesc from the React Native release the app used, and use its tooling. That is slower but version-correct.

What to do with the output. Even imperfect decompilation is enough for the questions that matter: string tables survive intact, so endpoints, keys, and feature flags are recoverable directly:

strings -n 6 index.android.bundle | rg -i 'https?://|api|token|secret|firebase' | sort -u

The Hermes string table is a flat, readable region of the file. Reach for it before decompiling — it often answers the question outright.

Runtime Analysis

Frida is frequently faster than static work on RN, because the interesting boundary is where JavaScript calls into native.

# Enumerate the native modules the JS side can reach
frida -U -f com.target.app -l rn-enumerate-modules.js

# Hook the bridge: every JS↔native call, with arguments
# (target the ReactNative bridge / TurboModule dispatch)
objection -g com.target.app explore

Useful hook points: fetch/XMLHttpRequest in the JS runtime, the native networking module (OkHttp on Android — hookable with standard pinning bypass), AsyncStorage reads and writes, and any NativeModules.* the app defines.

TLS interception is ordinary here — unlike Flutter, RN uses the platform HTTP stack, so the usual system-CA plus proxy setup works, and standard pinning bypasses apply. If interception fails, suspect pinning in a native module, not a separate trust store.

Where the Findings Usually Are

React Native apps concentrate the same few problems:

  • Secrets in the bundle. API keys, Firebase configs, and third-party tokens shipped in JS because "it's compiled." Hermes is not encryption.
  • Client-side authorization. Role checks and feature gates implemented in JS, trivially patched or simply ignored by calling the API directly.
  • AsyncStorage as a credential store. Unencrypted by default; tokens and PII routinely land there.
  • Over-broad native modules. Custom bridge methods that expose file system or shell access to JS, reachable from any injected script.
  • Debug artifacts. Source maps, dev-mode bundles, and console.log output left in release builds.

Patching and Repacking

hbctool disasm index.android.bundle ./work
# edit ./work/instruction.hasm and ./work/metadata.json
hbctool asm ./work index.android.bundle.patched
# replace in the APK, then re-sign
apktool b target -o patched.apk && apksigner sign --ks debug.keystore patched.apk

Patch only what you must, and only with authorization. Repacking trips integrity checks in hardened apps; if the app validates its own bundle, hook the check rather than defeating it by editing.

Rationalizations to Reject

  • "The bundle is binary, so the logic is protected." Hermes is a compiler, not a protection. The string table alone often gives up the API.
  • "jadx found nothing, so it's obfuscated." There is nothing in the dex. Look in assets/.
  • "No tool supports this HBC version, dead end." Build hermesc from the matching React Native release, or read the string table.
  • "The key is only used client-side." It is in the bundle, so it is public. Treat every bundle secret as disclosed.
  • "The app enforces the role check." The app is the client. Verify the check server-side by calling the API without it.

References

  • testing-mobile-applications — the wider assessment, storage, and platform issues
  • testing-apis — the backend, which is where the real findings are
  • reversing-flutter-apps — the other common cross-platform framework
  • hbctool, hermes-dec, hasmer, Frida/objection, jadx, apktool