In Android, the Native Development Kit (NDK) is a toolset that permits developers to write C and C++ code for their Android apps.
.so, shared object libraries, in the ELF file format. If you have analyzed Linux binaries previously, it’s the same format.
/lib/<cpu>/lib<name>.so. This is the default path, but developers could also choose to include the native library in
/assets/<custom_name>if they so choose. More often, we are seeing malware developers choose to include native libraries in paths other than
/liband using different file extensions to attempt to “hide” the presence of the native library.
loadLibraryonly take takes the library short name as an argument (ie. libcalc.so = “calc” & libinit.so = “init”) and the system will correctly determine the architecture it’s currently running on and thus the correct file to use. On the other hand,
loadrequires the full path to the library. This means that the app developer has to determine the architecture and thus the correct library file to load themselves.
load) APIs are called by the Java code, the native library that is passed as an argument executes its
JNI_OnLoadif it was implemented in the native library.
System.loadin the Java code. When either of these 2 APIs is executed, the
JNI_OnLoadfunction in the native library is also executed.
nativekeyword and has no code in its implementation, because its code is actually in the compiled, native library.
RegisterNatives(doc) API in order to do the pairing between the Java-declared native method and the function in the native library. The
RegisterNativesfunction is called from the native code, not the Java code and is most often called in the
RegisterNativesmust be executed prior to calling the Java-declared native method.
JNINativeMethodstruct that is being passed to
RegisterNativesin order to determine which subroutine in the native library is executed when the Java-declared native method is called.
JNINativeMethodstruct requires a string of the Java-declared native method name and a string of the method’s signature, so we should be able to find these in our native library.
JNINativeMethodstruct requires the method signature. A method signature states the types of the arguments that the method takes and the type of what it returns. This link documents JNI Type Signatures in the “Type Signatures” section.
~/samples/Mediacode.apkin the VM. Its SHA256 hash is a496b36cda66aaf24340941da8034bd53940d1b08d83a97f17a65ae144ebf91a.
lib/directory. The native libraries for this APK are in the default CPU paths.
unzip Mediacode.APK. You will see all of the files extracted from APK, which includes the
ghidraRun. This will open Ghidra.
JNIEnvis a struct of function pointers to JNI Functions. Every JNI function in Android native libraries, takes
JNIEnv*as the first argument.
The C declarations of JNIEnv and JavaVM are different from the C++ declarations. The “jni.h” include file provides different typedefs depending on whether it’s included into C or C++. For this reason it’s a bad idea to include JNIEnv arguments in header files included by both languages. (Put another way: if your header file requires #ifdef __cplusplus, you may have to do some extra work if anything in that header refers to JNIEnv.)
blx r3. As reversers, we need to figure out what r3 is. It’s not shown in the screenshot, but at the beginning of this function,
r0was moved into
JNIEnv*. On line 0x12498 we see
r3 = [r5]. Now
r3and dereference it. This means that
r3now equals whatever function pointer is at offset 0x18 in JNIEnv. We can find out by looking at the spreadsheet.
[JNIEnv + 0x18] = Pointer to the FindClass method
JNIEnv *type and it will automatically identify the JNI Functions being called. In IDA Pro, this work out of the box. In Ghidra, you have to load the JNI types (either the jni.h file or a Ghidra Data Types archive of the jni.h file) first. For ease, we will load the JNI types from the Ghidra Data Types archive (gdt) produced by Ayrx and available here. For ease, this file is available in the VM at
jni_all.gdtfile to load. Once it’s loaded, you should see jni_all in the Data Type Manager List as shown below.