Invoking a native function

Our app displays a message: "Hello from C++".

It does this by calling a native C++ function called stringFromJNI() from a FridaTen library. So let’s examine the library.

After we load this into Ghidra, We find a hidden function named getFlag(int).

This function wasn’t declared in the Java space and isn’t being called from anywhere in the library.

  • c code

    #include <jni.h>
    #include <string>
    #include <strings.h>
    #include <string.h>
    
    extern "C" JNIEXPORT jstring
    
    JNICALL
    Java_com_mobilehackinglab_FridaTen_MainActivity_stringFromJNI(
            JNIEnv *env,
            jobject /* this */) {
        std::string hello = "Hello from C++";
        return env->NewStringUTF(hello.c_str());
    }
    
    char* getFlag(int a){
        char *password = static_cast<char *>(malloc(100));
        strcpy(password,"NOT_THE_FLAG");
        if(a == 123){
            const char *hardcoded = "AD@w_IO^IXSJYBOXECBq";
    
            for (int i = 0; i < strlen(hardcoded) ; i++) {
    
                password[i] = (char)(hardcoded[i] ^ 12);
            }
    
            password[strlen(hardcoded)] = '\0';
    
        }
    
       return  password;
    
    }
  • What does getFlag(int) do?

    • It creates a string "NOT_THE_FLAG" by default.

    • If the input is 123, it:

      • Takes a hardcoded string.

      • Applies XOR with 12 on each character.

      • Returns the decoded result (the real flag).

So, to get the flag, we need to invoke this method. Let's use Frida to call this native function.

// Get the base address of the library and add the offset to locate getFlag
var adr = Module.findBaseAddress("libFridaTen.so").add(0x206C0);

// Create a pointer to the getFlag function
var get_flag_ptr = new NativePointer(adr);

// Create a NativeFunction wrapper: getFlag(int) -> returns pointer (char *)
const get_flag = new NativeFunction(get_flag_ptr, 'pointer', ['int']);

// Call the function with argument 123
var flag = get_flag(123);

// Read the returned C string from memory
var str = Memory.readUtf8String(flag);

// Print the flag
console.log("FLAG : " + str);
  • Line-by-line Explanation

    1. This gets the exact memory address of a function inside libFridaTen.so

      var adr = Module.findBaseAddress("libFridaTen.so").add(0x206C0);
    2. Converts the memory address into a pointer object Frida can work with.

      var get_flag_ptr = new NativePointer(adr);
    3. Turns the native function pointer into a JavaScript function you can call.

      const get_flag = new NativeFunction(get_flag_ptr, 'pointer', ['int']);
      • NativeFunction — What is it?

        • It lets you call native functions (e.g., C/C++ functions inside .so or .dll libraries) directly from your JavaScript code.

    4. Calls the native function getFlag(123)

      var flag = get_flag(123);
      • The function returns a memory address, pointing to the flag string.

      • Example: it might return something like 0x12345678.

    5. Converts the pointer into a readable text string

      var str = Memory.readUtf8String(flag);
    6. Prints the flag to the Frida console.

      console.log("FLAG : " + str);

We can’t use Module.getExportByName() to get this function because it is not exported.

So, we will get manual address resolution using offsets

  • But how do you get the offset?

    • Open the .so file using Ghidra.

    • Look for the function you want (getFlag, for example)

    • In the Symbol Tree (left side), expand "Functions".

    • Find and click the function name, e.g. getFlag.

    • Look at the function's address (top bar or in the Listing window).

    • Subtract the base address of the binary (usually 00100000 for .so files in Ghidra):

      Offset = Function Address - Base Address
      Offset = 001206c0 - 00100000 = 0x206C0
    • Use that offset in Frida like:

      var adr = Module.findBaseAddress("libFridaTen.so").add(0x206C0);

Last updated