Android and Java Native Interface
Java Native Interface (JNI)
JNI is a native programming interface supported by Java and is part of the Java SDK. With JNI, we can write code in other languages like C/C++ and use it in Java. We can also call C/C++ functions using Java code and vice versa - call Java functions with C/C++ code.
First, we're going to cover the first part: calling C/C++ functions with Java code, which is more widespread and is probably the reason why you're looking at this tutorial.
Calling C/C++ Functions from Java
We can use JNI whenever we like to code some functionality of the program in a different language, for one reason or another. Maybe we do it because we like writing in other languages but the project has to be written in Java. This isn't the main reason we would want to do that.
The main reason lies in the fact that Java is not the fastest programming language around. Usually, we need to sacrifice usability on account of security. The Java programming language is quite secure, since it makes a lot of checks while the program is executing to protect the program even if the programmer made an obvious buffer overflow scenario, like copying a big amount of data into a smaller container.
In faster languages like C/C++, this would result in a buffer overflow scenario, where the attacker would be able to overflow the buffer and possibly take control of the whole application and then even the entire system. But this isn't the case with Java, which checks the length of the buffer and copies only that much data to the buffer, thus preventing any overflow even if a large amount of data must be written. The downside of this is that Java needs to make a lot of checks during the execution of the program, which essentially makes it slower, but more secure.
Another reason for wanting to use C/C++ code within Java is that the functionality we want just isn't implemented yet in Java, but is already implemented in C/C++. This enables us to just use C/C++ implementation and doesn't require us to write our own code for something that has already been done.
Yet another reason for wanting to use C/C++ in Java is that we just can't do everything in Java; if we want to write a kernel driver or something very low level, we can decide that it's just better to write it in C/C++ and then use JNI to make it available in Java programs.
Let's mention one more reason why would we want to write a JNI library: we can write native code and share it among different platforms, so we can use the same code on Android as well as on IOS.
Let's write the basic Java code now, which we'll use as the basis for calling the C/C++ function later.
[java]
public class JavaJNI {
public static void main(String[] args) {
System.loadLibrary("javajni");
/* Java */
long javaStartTime = System.currentTimeMillis();
app.zerojava(100000000, 50);
long javaEndTime = System.currentTimeMillis();
long javaTime = javaEndTime - javaStartTime;
/* C++ */
long cppStartTime = System.currentTimeMillis();
app.zerocpp(100000000, 50);
long cppEndTime = System.currentTimeMillis();
long cppTime = cppEndTime - cppStartTime;
System.out.println("cpp zero: "+cppTime);
private void zerojava(long x, long y) {
while(y>0) {
long z = x;
while(z>0)
z--;
y--;
}
}
}
Compile the code with javac to obtain the Java bytecode, which can be directly executed:
[bash]
# javac JavaJNI.java
If we try to execute the JavaJNI class now, we'll receive an Exception that the specified libjavajni library cannot be found as we can see below:
[plain]
# java JavaJNI
Exception in thread "main" java.lang.UnsatisfiedLinkError: no libjavajni in java.library.path
at java.lang.ClassLoader.loadLibrary(ClassLoader.java:1738)
at java.lang.Runtime.loadLibrary0(Runtime.java:823)
at java.lang.System.loadLibrary(System.java:1028)
at JavaJNI.main(JavaJNI.java:5)
This is why we need to create the library now, so we'll be able to execute Java bytecode compiled above. The first step in doing that is creating the C/C++ header file, which defines the prototypes of the native functions we'll be using. We can write the header file ourselves, but why should we bother with that if there's a better way of doing this. We can use the javah program to do that for us. To create the header file that we require and that defines all the prototypes for native functions, we need to execute the command below:
[bash]
# javah JavaJNI
This will create a new file JavaJNI.h, which is our header file, and should look like this:
[cpp]
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
#ifndef _Included_JavaJNI
#define _Included_JavaJNI
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: JavaJNI
* Method: zerocpp
* Signature: (JJ)V
*/
JNIEXPORT void JNICALL Java_JavaJNI_zerocpp
#ifdef __cplusplus
}
#endif
#endif
We can see that the header files use the extern "C" code that is used to denote that the listed functions are defined in an external code and will be provided at runtime. There's also a prototype of the function named Java_JavaJNI_zerocpp function that we need to implement. The function takes three parameters: the first parameter is a pointer to JNIEnv, and the second and third parameters are our long integers that we will pass to the function when calling it.
The JNIEXPORT and JNICALL are provided to declare the function as an export function. Now it's the time to actually implement the function using C/C++ code. We implemented the same function as we did in Java and saved it into the JavaJNI.cpp file, which is presented below:
[cpp]
#include "JavaJNI.h"
JNIEXPORT void JNICALL Java_JavaJNI_zerocpp(JNIEnv *env, jobject obj, jlong x, jlong y) {
while(y>0) {
long z = x;
while(z>0)
z--;
y--;
}
int main() {
return 0;
[/cpp]
After that, we need to compile the C/C++ code to create the shared library. When compiling, we will get the error that jni.h cannot be found, which means that jni.h probably isn't in the path. Let's search for the missing header file on the system:
[bash]
# find /usr -name jni.h
/usr/lib/jvm/java-6-oracle/include/jni.h
/usr/lib/jvm/java-7-oracle/include/jni.h
/usr/lib/jvm/java-6-openjdk-amd64/include/jni.h
We found three occurrences of the jni.h header file, and we're using Java-6 version, which can be verified by the command below:
[bash]
# javac -version
javac 1.6.0_37
Besides the jni.h header file missing, there's also the jni_ header file missing, so we need to pass two directories where those header files are located within the -I option when compiling the source file on Linux using gcc. The actual command we used to compile the C/C++ source code is as follows:
[bash]
# gcc -I/usr/lib/jvm/java-6-oracle/include/ -I/usr/lib/jvm/java-6-oracle/include/linux -c -fpic JavaJNI.cpp
After that the JavaJNI.o object file will be available, which we need to change into a shared library with the command below:
[bash]
# gcc -shared -o JavaJNI.so JavaJNI.o
After that our JavaJNI.so library will be available. Since we specified the "javajni" in the loadLibrary() function call with lowercase letters, we need to change the name of the library with lowercase letters. We can do that simply by changing the name as follows:
[bash]
# mv JavaJni.so libjavajni.so
What's the lib string being appended to the library name, you ask? It's just the way how Linux looks for libraries; they should be prefixed with the static string lib followed by the actual name.
The last step is to actually call the Java program, which should first be invoked by the zerojava function and afterwards, the cppzero function, which is implemented by the native C/C++ code. To do that we must also tell the Java program to look for the created native C/C++ library in the current directory, which we can do with the -Djava.library.path directive. The whole command that we need to run can be seen below:
[bash]
# java -Djava.library.path=. JavaJNI
The dot (character '.') specifies the current directory, so we need to run that command from the directory that holds both the JavaJNI application as well as libjavajni.so library file.
If we programmed everything successfully, something like this should be outputted by the program:
[bash]
# java -Djava.library.path=. JavaJNI
Java zero: 4215
cpp zero: 21862
We can see that some values are being displayed. The first value is the time it took to execute the zerojava function and the second value is the time it took to execute the zerocpp function, which are supposed to be the same. In fact, they are the same, except that the first one is implemented in Java and the second one is implemented in C++. But isn't C++ supposed to be faster than Java? If so, the second value should be lower than the first value and not considerably bigger as it is in our case. What's going on? I'm not really sure but maybe I should have enabled various compilation flags when compiling the program or maybe because it needs more time to actually initialize the call stack. If you have any idea about this, please let me and other readers know.
Android and JNI
We showed an example of how to call a C/C++ function within Java, but the truth is that implementing that in Android isn't much different that we described above. The only thing that we need to do in our Android Java code is load a library like this in our class:
[java]
static {
System.loadLibrary("javajni");
}
We also have to provide AndroidManifest.xml, which will describe all the activities and XML resource files that are used to draw the Android UI representation of the activity. Of course, we mustn't forget about the code for our actual activity that needs to start our two functions when calling the onCreate() function.
There's also an option to enable JNI debugging in JVM. We can execute the command below to start debugging the JNI code on an emulator or real phone:
[bash]
# adb shell setprop debug.checkjni 1
Conclusion
We won't go into any more details about using JNI in Android, since it's the same in Java. But we will mention that the true potential to knowing how JNI is implemented in Android is the fact that we can reverse engineer the application that uses JNI.
A few guidelines for figuring out that the Android application is using the JNI code is the presence of native functions. Another giveaway is the presence of function calls, but no function implementations. It's often the case that Android application calls some functions, but there is no implementation of those functions. This might seem odd if we just started reversing the Android code, but it becomes clear a few moments later when we realize that it's JNI. Usually, it's the System.loadLibrary() call that gives it away.
![FREE role-guided training plans](/contentassets/3abce3002c72469aa59d74edbd1ecc0d/is-ad-22-06-cybersecurity-talent-development-playbook.png)
FREE role-guided training plans
Nevertheless, when we figure out that the application is using JNI, we can take the .so library from the APK file by simply unzipping the APK file and start reversing it. There are a number of tools that we can use to reverse the .so library, but we won't describe that here.