Why no Java HTTP stack appears in the trace
An Android request normally leaves a trail in Java: an OkHttp interceptor chain, a call to the platform URL connection class, or a client library that wraps one of them. A native client skips that layer. The library opens its own connection, performs its own TLS handshake and writes its own bytes, so a hook on the Java networking classes sees nothing and an interceptor registered in the application has no effect on the request.
The Java layer still contains the entry point. A native method declared in a Java class and loaded with a library load call is where the application hands arguments into native code. Whether the transfer happens inside that library or returns to Java decides where the rest of the investigation goes.
Two ways a native client reaches the network
The first path keeps the transfer in Java. A native method builds a URL, a header list or a body and returns it, and the Java layer sends the request through its usual client. In that arrangement the Java stack is present and the native routine produces signed material for it.
The second path keeps everything in native code. The library links against a transfer library such as libcurl, includes an HTTP stack compiled from source, or opens sockets directly with the platform socket functions. A library that links a transfer or TLS library shows those names in its dynamic dependency list, and a library that compiles the stack into itself shows only the platform libraries it needs.
Read the dynamic section of the shared object first. The list of needed libraries and the imported symbols says which transfer and TLS implementations are in play, and that decides which functions to hook. A statically linked stack removes the import names but leaves the strings, which is the next thing to read.
What symbols and strings still identify the request path
- Exported Java_ symbols name the JNI methods the application calls, and they carry the Java class and method names even after the native code is stripped.
- C++ symbols in the dynamic table demangle to class and method names, so a demangler turns a mangled string into a readable signature with argument types.
- String literals hold hosts, path templates, header names, JSON keys, certificate fingerprints and user agent text, and each one cross-references to the function that uses it.
- Error messages from the transfer library name the failed step, such as a TLS verification error or a connection failure, and they point at the function that handles it.
In a disassembler the strings window and the cross-reference list carry most of the value. Follow a host name to its function, then follow that function callers upward to reach the code that assembles the request. A string that names a header is usually written by the same routine that writes the body. When the transfer library was compiled into the object, its export names are gone but its error strings remain, and those strings identify the write and verification routines by the messages they emit.
Observing a request that never passes through OkHttp
- Hook the platform connect function to learn every destination the process opens, including hosts that appear in no Java string.
- Hook the TLS write function to read plaintext before encryption, which avoids the certificate problem a proxy would raise.
- Watch the library load call to confirm which shared object belongs to the application, and check the architecture directory it came from.
- Search the dynamic symbol table for the load entry point and for registration calls, which reveal dynamically registered methods that have no Java_ name.
A capture taken at a proxy shows only the encrypted stream, so a hook inside the process or a transparent redirect on the device is what produces readable bytes. When the destination is the only unknown, the connect hook answers it before any TLS work begins.
The signing step usually sits in the same library as the transport. A native client that signs its own requests holds the key material and the algorithm in the shared object, so an interceptor hook cannot reach it. The signing function is a caller of the transfer function, and the strings around it name the header it writes. A statically linked TLS library keeps its verification callback in the same object, which is where a pinning check becomes visible. Native Android reverse engineering and APK to API both cover work of this kind.
Related work
Reviewed 28 September 2026 · SReverse research desk