Working with native code

For additional details, refer to the official documentation.

Native functions

Introduction

Loading the library

System.loadLibrary("calc")
System.load("lib/armeabi/libcalc.so")

The Java to Native Code Connection

public native String doThingsInNativeLibrary(int var0);

There are 2 different ways to do this pairing, or linking:

  1. Dynamic Linking using JNI Native Method Name Resolving, or

  2. Static Linking using the RegisterNatives API call

Dynamic Linking

The developer names the method and the function according to the specs. E.g. class com.android.interesting.Stuff. The function in the native library would need to be named

Java_com_android_interesting_Stuff_doThingsInNativeLibrary

Static Linking

Using the RegisterNatives. This function is called from the native code, not the Java code and is most often called in the JNI_OnLoad function since RegisterNatives must be executed prior to calling the Java-declared native method.

Detecting external native library load

var library = "libyouwant.so";
var flag =  0;

Interceptor.attach(Module.findExportByName(null, "android_dlopen_ext"), {
    onEnter: function(args){
        var library_path = Memory.readCString(args[0])
        if (library_path.indexOf(library) >= 0) {
            console.log("Loading library: " + library_path)
            flag = 1;
        }
    },
    onLeave: function(retval){
        if (flag == 1){
            console.log("Library loaded");
            flag = 0;
        }
    }
});

The android_dlopen_ext API [🔗] is invoked every time an application attempts to load an external library.

When onEnter is called, it is checked whether the library that android_dlopen_ext is loading is the desired library. If so, it sets flag = 1.

onLeave checks whether the flag == 1. If this check is omitted, the code within onLeave will be executed each time any library is loaded.

Working with native library

var library = "libyouwant.so";
var flag =  0;

Interceptor.attach(Module.findExportByName(null, "android_dlopen_ext"), {
    onEnter: function(args){
        var library_path = Memory.readCString(args[0])
        if (library_path.indexOf(library) >= 0) {
            console.log("Loading library: " + library_path)
            flag = 1;
        }
    },
    onLeave: function(retval){
        if (flag == 1){
            console.log("Library loaded");
            
            // Create a Module object
            var module = Process.findModuleByName(library);
            
            // Print base address of the library
            console.log("[*] Base address of " + library + ": " + module.base);
            
            // Enumerate exports of the library
            console.log("[*] Enumerating imports of " + library);
            console.log(JSON.stringify(module.enumerateExports(), null, 2));
            
            flag = 0;
        }
    }
});

To work with the native library, you can create a Module object. Once you have created it you can perform various actions. Refer to https://frida.re/docs/javascript-api/#module.

Hooking a native functions

You first need to get the address of a particular function in frida.

Interceptor.attach(targetAddress, {
    onEnter: function (args) {
        console.log('Entering ' + functionName);
        // Modify or log arguments if needed
    },
    onLeave: function (retval) {
        console.log('Leaving ' + functionName);
        // Modify or log return value if needed
    }
});
Example
// In this case we want to hook the strcmp function of the libc.so.
// Since the libc.so library is interal and loaded soon, we can directly use
// Module.findExportByName() to find the absolute address of the function.
var strcmp_adr = Module.findExportByName("libc.so", "strcmp");

Interceptor.attach(strcmp_adr, {
    onEnter: function (args) {
        var arg0 = Memory.readUtf8String(args[0]); // first argument
        var flag = Memory.readUtf8String(args[1]); // second argument
        if (arg0.includes("Hello")) {

            console.log("Hookin the strcmp function");
            console.log("Input " + arg0);
            console.log("The flag is "+ flag);

        }
    },
    onLeave: function (retval) {
        // Modify or log return value if needed
    }
});

Change the return of a native function

Interceptor.attach(targetAddress, {
    onEnter: function (args) {
        console.log('Entering ' + functionName);
        // Modify or log arguments if needed
    },
    onLeave: function (retval) { 
        console.log("Original return value :" + retval);
        retval.replace(1337)  // changing the return value to 1337.
    }
});

Last updated