What the Java layer shows
An app that delegates to native code loads one or more shared objects at start and declares methods without implementations. The Java side of the request may consist of a native method that accepts a payload and returns a result, plus the code that loads the library. Decompiling the DEX file is still worth doing, because it gives you the package and the method signature, which is the address of the code you actually need.
In the APK the libraries sit under lib/, split by architecture. Work with the variant the device uses, since picking a different one costs time and produces confusing offsets.
Understand what you are reading
A shared object is a compiled program. Static reading shows instructions, data and a symbol table that a release build often strips down to the exported entries.
Two consequences follow. First, code compiled for Android has a specific instruction set and calling convention per architecture, so the disassembly view depends on the target. Second, JNI imposes its own conventions on the functions that Java can call. An exported entry point has a name the runtime resolves, or it was registered at load time and its address appears in a registration call. Finding that mapping is the first real step, because it tells you which function in the library corresponds to which Java declaration.
Locate the request code
Reading a whole library is rarely necessary. Request construction leaves traces you can search for before you understand any instruction.
- String references. Compiled code points at data in the same file, so a search for a path segment, a header name or a parameter key lands near the code that uses it. Cross references from the string to its readers do most of the navigation work.
- Imported functions. A library that builds an HTTP request links against symbols for memory allocation and string handling. A library that canonicalises and signs links against hash and cipher routines. The import list narrows the purpose of the file quickly.
- Constants. Initialisation vectors, lookup tables and fixed masks are byte sequences in the data section. Their shape identifies a cryptographic primitive even when no name is attached to it.
- Registration calls. Where the app registers its native methods at runtime, the table it passes maps names to addresses, and it is a reliable index into the binary.
Work the boundary between the layers
The interesting values usually arrive from Java and leave through the same door. Instrument the process and record what crosses.
- Hook the exported function. The arguments show what Java provided and the return value shows what native code produced.
- Compare those values with the captured request. A field in the request that does not appear among the arguments was added later, on the Java side or by the HTTP client.
- Replay with one argument altered to see which outputs move. This separates an input that feeds a signature from one that is merely copied into the payload.
Run that work only where you are authorised to analyse the application.
Plan for the parts that resist
Not every library yields to the same effort. Budget accordingly.
- Plain JSON built in native code is straightforward: recover the field names and the formatting, and the request is reproducible.
- A signed or encrypted payload costs more, because the canonical form and the key handling both matter.
- Binary protocols and WebSocket sessions cost the most, since state carries across messages and each frame has to be read in context.
Mixed cases are common. A library signs a body that Java serialised, which means your reproduction has to match the serialisation exactly before the signature check can pass.
Deliver an equivalent implementation
The output of this work is a function, expressed in a language you can run, that produces the same bytes as the compiled one for the same inputs. Verification compares your output with the app's on several inputs, then sends the result to the live service and checks that the server accepts it. A shared object is inconvenient to read, and its behaviour is fully specified by what it computes. Reproducing that behaviour is what the client needs.
Related work
Reviewed 28 September 2026 · SReverse research desk