SReverseby Simpa Labs

React Native

Reading a React Native app's Hermes bytecode

A release React Native build rarely contains readable JavaScript. Metro bundles and minifies it, and when Hermes is enabled the bundler compiles the result to bytecode, so the file you extract is a binary program with a string table attached.

Where the bundle sits inside the APK

React Native keeps its JavaScript in the assets folder of the APK. In a debug build that file is readable source. In a release build Metro bundles and minifies it, and when Hermes is enabled the bundler compiles the result to bytecode before packaging, so what you extract is an instruction stream rather than text.

The usual name is index.android.bundle under assets. Apps that ship as an Android App Bundle can place the same file under a per-architecture or per-feature asset directory, so list the archive contents rather than assuming the default path. A bundle that begins with a Hermes header is bytecode. A bundle that begins with readable JavaScript was built with the interpreter path left in place, and a bundle that begins with a compression or encryption header has been transformed again by something outside the bundler.

Why bytecode is worth reading

Hermes is a bytecode virtual machine with its own instruction set, a serialised string table and a table of function headers. Property names used on objects, string literals and module references survive compilation because the runtime needs them. Endpoint paths, header names and JSON field names are exactly that kind of string, which is why a Hermes bundle still answers the questions an API project asks, and the string table gathers those literals in one place.

Confirm the format before disassembling

Inspect the first bytes of the extracted file before choosing a tool. If the file is compressed, decompress it and inspect the result. If it is bytecode, load it with a Hermes disassembler such as hermes-dec or hbctool and read the header first, because the header lists the bytecode version, the function count and the size of the string table.

The header settles two questions. It tells you which format version your tooling has to accept, and it tells you whether the file is complete. A bundle that was truncated during extraction fails to parse, which is easy to mistake for a protected file.

Read the string table first

The string table is the fastest route to the API surface, and you can read it without understanding a single instruction.

  • Base URLs and path templates appear as literals, so filtering for a scheme or a leading slash lists most of the surface in one pass.
  • Header names and authentication prefixes survive as strings, which shows which headers the client sets beyond the defaults of its HTTP library.
  • GraphQL operation names and persisted query identifiers sit close to the transport call that sends them.
  • Serialiser field names recovered here become the field names in the request models you write later.

String literals also reveal local behaviour that never leaves the device, such as storage keys for cached tokens and the event names a state manager relies on. Those tell you how the app rebuilds state between launches, which matters whenever a flow depends on a value written during an earlier session.

Reconstruct a call site

Hermes disassembly is register based. A typical request begins when a string constant is loaded into a register, followed by other values and then a call through a property such as fetch, a generated client method or a small wrapper the app defines. Reading the instructions between the string load and the call shows which parameters are attached to which string.

The practical loop is mechanical. Find an interesting string, list the functions that reference it, open the enclosing function, and read the calls it makes. Wrapper functions are usually shallow, so a helper that assembles headers and appends a signature is often only a few dozen instructions long. Once you identify that helper, its cross references give you every endpoint that flows through it, and the helper becomes the place where shared request logic lives.

Module boundaries help as well. Metro assigns each module an identifier, and cross-module calls reference those identifiers, so you can follow a request from a screen component down to the transport layer even when local variable names are minified.

Encrypted, split and remotely updated bundles

Not every Hermes bundle arrives as a plain file. Some apps decrypt the bundle at startup with a key held in native code, so the asset on disk is ciphertext and the disassembler rejects it. The decryption routine is a small function in a native library or a Java class, and the reliable way to obtain the plaintext is to let the app decrypt it and read the result from memory immediately before the runtime loads it.

Bundle splitting raises a different problem. A feature can ship its own module that loads on demand, and the strings for that feature appear only after the module downloads. If a screen is missing from the main bundle, exercise that screen once and capture the additional bundle before concluding that the code is unavailable.

From bytecode to a callable API

The bundle gives you names, and the running app gives you behaviour. Signing keys, session establishment and request ordering are visible only while the app executes, so the two sources have to be combined. Recovered strings tell you what to call, and a capture or an instrumentation hook tells you what has to be true when you call it.

That division of labour is what makes a React Native target tractable. The bytecode ends the guessing about endpoints and payload fields, and the runtime work narrows to the small number of values the app derives per request. SReverse recovers the API from React Native bundles and delivers a client that reproduces those derived values. The same approach applies to turning an APK into a callable API when an app mixes React Native screens with native modules.

Reviewed 28 September 2026 · SReverse research desk

Start your full APK reconstruction

Send the full APK

Projects start at $120. Choose WhatsApp or email, then attach the APK in the app that opens. We reply within one hour with the next step and send the fixed quote after review.

Want us to contact you?