Native library analysis
Use this route when lib/<abi>/*.so contains application logic, a framework payload, or a loader that DEX analysis cannot explain.
Inventory libraries and ABIs
unzip -l sample.apk 'lib/*/*.so'
Common ABI directories:
| ABI | Architecture | Notes |
|---|---|---|
arm64-v8a | AArch64 | best default for most modern physical devices |
armeabi-v7a | 32-bit ARM | common in older/universal builds |
x86_64 | 64-bit x86 | often useful for emulators |
x86 | 32-bit x86 | legacy emulator target |
Pick the ABI matching the planned device and keep all related artifacts from the same build. Addresses differ between ABIs.
Extract without flattening paths:
unzip sample.apk 'lib/arm64-v8a/*.so' -d output/native
file output/native/lib/arm64-v8a/*.so
sha256sum output/native/lib/arm64-v8a/*.so > output/native/SHA256SUMS
Perform inexpensive reconnaissance
readelf -hW libnative.so
readelf -dW libnative.so
readelf -Ws libnative.so | less
nm -D --defined-only libnative.so | c++filt | less
strings -a -n 6 libnative.so | less
Record:
- architecture, endianness, and ELF type;
- imported libraries and functions;
- exported functions and JNI symbols;
- whether symbols are stripped;
- unusual sections, packer clues, or embedded assets;
- references to class descriptors, method signatures, paths, URLs, and errors.
Strings and symbols are navigation aids, not proof that a code path is reachable.
Map static JNI names
A conventional exported JNI name encodes its Java class and method:
Java_com_example_crypto_NativeBridge_verify
Cross-reference it with a declaration such as:
package com.example.crypto;
final class NativeBridge {
static native boolean verify(byte[] input);
}
Overloaded JNI names can include encoded signatures. Underscores and special characters use JNI name escaping; consult the JNI specification when manually decoding them.
Map dynamic registration
Many libraries export only JNI_OnLoad and call RegisterNatives. A native method table has entries conceptually equivalent to:
static JNINativeMethod methods[] = {
{"verify", "([B)Z", (void *)native_verify},
};
In Ghidra or IDA:
- locate
JNI_OnLoad; - identify calls through the
JNIEnvfunction table or a recognizedRegisterNativesimport; - trace the target class passed to
FindClass; - recover the
JNINativeMethodarray; - label each function with its Java name and descriptor;
- verify against native declarations in jadx/smali.
Stripped symbols make labels more important. Keep address, original autogenerated name, new label, and evidence in notes.
Import into Ghidra
A reproducible Ghidra pass:
- create a project dedicated to the target hash;
- import the selected
.soas ELF; - accept the detected language/compiler specification only after checking architecture;
- run auto-analysis;
- start from exports, imported API calls, strings, or recovered JNI mappings;
- rename functions and data types incrementally;
- preserve project/version metadata with the report.
The C-like decompiler is an interpretation. Confirm critical details in the disassembly, especially integer widths, signed comparisons, pointer arithmetic, structure layouts, and calling conventions.
Framework-specific native payloads
- Flutter:
libapp.sois compiled Dart; snapshot-aware tooling is usually more productive than generic C recovery. - Unity IL2CPP: combine
libil2cpp.sowith its matchingglobal-metadata.datbefore navigating in Ghidra/IDA. - Rust/C++ libraries: demangling and runtime signatures can recover useful namespace/type clues even when Java only sees a small bridge.
- Packed libraries: if sections or code are decrypted only at load time, static output may be intentionally incomplete. Runtime dumping requires explicit dynamic-analysis scope.
Report address-stable evidence
ASLR changes runtime addresses. Report module-relative offsets or static image addresses with the exact hash and tool configuration:
Binary: lib/arm64-v8a/libnative.so
SHA-256: …
Tool: Ghidra … / AARCH64 little endian
Function: native_verify
Static address: 0012a430
Module offset: +0x12a430
Mapped from: NativeBridge.verify([B)Z via RegisterNatives
Continue with Dynamic analysis fallback only when static limits are established and runtime work is authorized.