You’re almost there — sign up to start building in Notion today.
Sign up or login
Hooking NDK Libraries

Hooking NDK Libraries

Everything we learned was related to hooking methods of Dalvik Virtual Machine in Java. The Android NDK (Native Development Kit) is a toolset provided by Google that allows developers to write native code in C/C++ for Android applications.

Why Developers Use NDK

Some applications require high performance or low-level access to system resources, which may be difficult to achieve using Java alone. In such cases, the NDK can be used to write native code that can be compiled into machine code and run directly on the device's CPU
Using the NDK can be beneficial for applications that require high performance, such as games or media applications, or for applications that need to access low-level system resources. However, it's important to note that using the NDK can also introduce additional complexity and may require more time and effort to develop and maintain compared to using Java alone.

How To Identify Native Functions

In android it’s really easy to find them. The first sign you find is
System.loadLibrary("native-lib")
. This load the library in memory. Then you seen function like this:
public native String encryptString(String secretMessage)
Example native code:
#include<jni.h> #include<string> extern "C" JNIEXPORT jstring JNICALL Java_com_apphacking_ndkfrida_MainActivity_encryptString( JNIEnv* env, jobject, jstring secretMessage){ return env->NewStringUTF("hello".c_str()); }
env → This is an pointer to all important functions like
NewStringUTF
which developers require. Usually used for type casting.
jobject → This is an pointer to java object instance.

Enumerate Exported Function of Native Library

Module.enumerateExports("libnativesecret.so")

Example:

let exportedFunctions = Module.enumerateExports("libnativesecret.so") exportedFunctions.forEach(func => { if(func.name.indexOf("Java_") != -1){ send(func) } });

Enumerate Imported Function of Native Library

Module.enumerateImports("libnativesecret.so")

How To Hook NDK Functions

To hook NDK functions we use
Interceptor.attach
which has to callback.
onEnter
and
onLeave
. The
onEnter
is before the function codes execute and
onLeave
callback is when the function wants to return.
💡 Callout icon
Most of the times you didn’t need to hook native functions. Just hook Java function prototype. In upper example you can hook
encryptString
method in Java instead.
setTimeout(() => { let targetFunctionAddress = null let exportedFunctions = Module.enumerateExports("libnativesecret.so") exportedFunctions.forEach(func => { if(func.name.indexOf("Java_lab_seczone64_nativesecret_MainActivity_encryptDecrypt") != -1){ send(func) targetFunctionAddress = func.address } }); // this code find the address target function which here is encryptDecrypt Interceptor.attach( targetFunctionAddress, { onEnter: (args) => { let inputString = Java.cast(ptr(args[2]), Java.use("java.lang.String")) // We need to cast jstring to string console.log("[+] Arguments parameters: " + inputString) }, onLeave: (ret) => { var returnString = Java.cast(ptr(ret), Java.use("java.lang.String")) console.log("[+] The return is: " + returnString) } }) }, 100);

Hooking
System.loadLibrary

It’s tricky:
Java.perform(function() { const System = Java.use('java.lang.System'); const Runtime = Java.use('java.lang.Runtime'); const VMStack = Java.use('dalvik.system.VMStack'); System.loadLibrary.implementation = function(library) { try { console.log('System.loadLibrary("' + library + '")'); Runtime.getRuntime().loadLibrary0(VMStack.getCallingClassLoader(), library); if(library == "nativesecret"){ Interceptor.attach( Module.findExportByName("libnativesecret.so", "Java_lab_seczone64_nativesecret_MainActivity_encryptDecrypt"), { onEnter: (args) => { let inputString = Java.cast(ptr(args[2]), Java.use("java.lang.String")) console.log("[+] Arguments parameters: " + inputString) }, onLeave: (ret) => { var returnString = Java.cast(ptr(ret), Java.use("java.lang.String")) console.log("[+] The return is: " + returnString) } }) } } catch(ex) { console.log(ex); } }; })
VMStack.getCallingClassLoader
: This method returns the 
ClassLoader
 associated with the caller of the method. It can be useful for obtaining information about the class loading context during runtime.
The 
java.lang.Runtime
 class is a class in the Java SE API that provides access to the Java runtime environment. It serves as an interface between the Java application and the underlying operating system environment.

Hooking
strcmp
Function

Interceptor.attach( Module.findExportByName("libc.so", "strcmp"), { onEnter: (args) => { let firstArgStr = Memory.readUtf8String(args[0]) let secondArgStr = Memory.readUtf8String(args[1]) console.log("[+] Arguments are: " + firstArgStr + "\t:\t" + secondArgStr) }, onLeave: (ret) => { console.log("[+] Return value is: " + ret.toInt32()) } } )
💡 Callout icon
Please consider sometimes
readUtf8String
function didn’t work. Because the string on the memory may not be an
utf8
. Therefore we use
readCString
function.
Interceptor.attach( Module.findExportByName("libc.so", "strcmp"), { onEnter: (args) => { let firstArgStr = Memory.readUtf8String(args[0]) let secondArgStr = Memory.readCString(args[1]) console.log("[+] Arguments are: " + firstArgStr + "\t:\t" + secondArgStr) }, onLeave: (ret) => { console.log("[+] Return value is: " + ret.toInt32()) } } )
💡 Callout icon
Also you can change this args. But their not Java. To change them simply use this code:
Memory.writeUtf8String(args[0],"Hello")