I’m analyzing an Android application that uses native methods via a shared library libnative-lib.so. Here’s the relevant Java code:
static {
System.loadLibrary("native-lib");
}
public final native byte[] f(Context context, String m5);
public final native String m(Context context, int p02, int p12);
After decompiling libnative-lib.so using Ghidra, I found only one exported symbol: JNI_OnLoad(). No symbols for f() or m() were present, which made me suspect that the native methods are registered dynamically via RegisterNatives.
Here’s a snippet of the JNI_OnLoad implementation in Ghidra:
undefined4 JNI_OnLoad(long *param_1) {
...
iVar2 = (**(code **)(*local_40 + 0x6b8))(local_40, lVar3, &DAT_001cc108, 5);
...
}
I copied a Frida script to hook RegisterNatives and extract the names and addresses of the registered native methods:
let addrRegisterNatives = null;
Process.enumerateModules().forEach(function (m) {
Module.enumerateSymbolsSync(m.name).forEach(function (s) {
if (s.name.includes("RegisterNatives") && (!s.name.includes("CheckJNI"))) {
console.log(m.name, s.name);
addrRegisterNatives = s.address;
}
});
});
setTimeout(() => {
Interceptor.attach(addrRegisterNatives, {
onEnter: function (args) {
var nMethods = parseInt(args[3]);
console.log("n[+] env->RegisterNatives()");
console.log("tNumber of methods:", nMethods);
var class_name = Java.vm.tryGetEnv().getClassName(args[1]);
console.log("tClass name:", class_name);
var methods_ptr = ptr(args[2]);
for (var i = 0; i < nMethods; i++) {
var base = methods_ptr.add(i * Process.pointerSize * 3);
var methodName = Memory.readCString(Memory.readPointer(base));
var sig = Memory.readCString(Memory.readPointer(base.add(Process.pointerSize)));
var fnPtr = Memory.readPointer(base.add(Process.pointerSize * 2));
console.log(`ttMethod: ${methodName}, Signature: ${sig}, Address: ${fnPtr}`);
}
}
});
}, 2000);
This successfully logs the method names f() and m(), their signatures, and function pointers.
The problem is now that I have the function pointer for m(), I tried hooking it using the following script:
setTimeout(() => {
Java.perform(function () {
const NATIVE_OFFSET = 0x14610c; // offset from base
const libName = "libnative-lib.so";
const libBase = Module.findBaseAddress(libName);
if (libBase) {
const nativeFuncPtr = libBase.add(NATIVE_OFFSET);
Interceptor.attach(nativeFuncPtr, {
onEnter: function (args) {
console.log("[+] Native m() called!");
console.log(`arg1 (this): ${args[0]}`);
console.log(`arg2 (context): ${args[1]}`);
},
onLeave: function (retval) {
console.log(`[+] Native m() returned: ${retval}`);
}
});
} else {
console.error(`[-] Could not find ${libName}`);
}
});
}, 5000);
But no output at all is printed — not even the initial log statement. I’m sure the method is being called at runtime.