Find manually registered (obfuscated) native function address
which I assume is build with -fvisibility=hidden because the only export is JNI_OnLoad, no JNIEXPORT void JNICALL Java_com_app_Bar_foo which means public native int foo does not follow the naming convention.
This is not always true, because you can also use RegisterNatives
inside JNI_OnLoad
to declare your JNI methods not following the typical naming convention.
/*
* Register several native methods for one class.
*/
static int registerNativeMethods(JNIEnv* env, const char* className,
JNINativeMethod* gMethods, int numMethods)
{
jclass clazz;
clazz = env->FindClass(className);
if (clazz == NULL) {
ALOGE("Native registration unable to find class '%s'", className);
return JNI_FALSE;
}
if (env->RegisterNatives(clazz, gMethods, numMethods) < 0) {
ALOGE("RegisterNatives failed for '%s'", className);
return JNI_FALSE;
}
return JNI_TRUE;
}
What is the process when foo is invoked ?
Maybe you can take a look at this article https://arophix.com/2017/12/17/andoid-jni-summary/
How can I extract the address of foo ? ( I'm familiar with Frida )
I am not sure about Frida, For Cydia Substrate, you can use dlsym
to find the address of specified symbol of .so
, e.g.
void* find_symbol(const char* libraryname, const char* symbolname)
{
void *imagehandle = dlopen(libraryname, RTLD_GLOBAL | RTLD_NOW);
if (imagehandle != NULL) {
void * sym = dlsym(imagehandle, symbolname);
if (sym != NULL) {
LOGE("symbol (%s) is found at address %p (%p) in lib %s", symbolname, sym, &sym, libraryname);
return sym;
} else {
LOGE("find_symbol() can't find symblo (%s).", symbolname);
return NULL;
}
} else {
LOGE("dlopen error: %s, when opening lib %s",dlerror(), libraryname);
return NULL;
}
}
Here is the full tutorial for using Cydia Substrate https://arophix.com/2017/11/30/android-substrate-hooking/ ,
--------Updates--------
Edit #1
For Frida, maybe it is similar. Here is a reference link which may help with your case. https://www.notsosecure.com/instrumenting-native-android-functions-using-frida/
Edit #2
For a complete Github tutorial project about Frida based hooking, please refer to Using Apktool and Frida to do reverse engineering on Android Apk..
I've solved it using Frida
Hooking art::JNI::RegisterNativeMethods(_JNIEnv*, _jclass*, JNINativeMethod const*, int, bool)
and art::JNI::FindClass
after libart.so
module is loaded.
Code
var RevealNativeMethods = function() {
var pSize = Process.pointerSize;
var env = Java.vm.getEnv();
var RegisterNatives = 215, FindClassIndex = 6; // search "215" @ https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/functions.html
var jclassAddress2NameMap = {};
function getNativeAddress(idx) {
return env.handle.readPointer().add(idx * pSize).readPointer();
}
// intercepting FindClass to populate Map<address, jclass>
Interceptor.attach(getNativeAddress(FindClassIndex), {
onEnter: function(args) {
jclassAddress2NameMap[args[0]] = args[1].readCString();
}
});
// RegisterNative(jClass*, .., JNINativeMethod *methods[nMethods], uint nMethods) // https://android.googlesource.com/platform/libnativehelper/+/master/include_jni/jni.h#977
Interceptor.attach(getNativeAddress(RegisterNatives), {
onEnter: function(args) {
for (var i = 0, nMethods = parseInt(args[3]); i < nMethods; i++) {
/*
https://android.googlesource.com/platform/libnativehelper/+/master/include_jni/jni.h#129
typedef struct {
const char* name;
const char* signature;
void* fnPtr;
} JNINativeMethod;
*/
var structSize = pSize * 3; // = sizeof(JNINativeMethod)
var methodsPtr = ptr(args[2]);
var signature = methodsPtr.add(i * structSize + pSize).readPointer();
var fnPtr = methodsPtr.add(i * structSize + (pSize * 2)).readPointer(); // void* fnPtr
var jClass = jclassAddress2NameMap[args[0]].split('/');
console.log('\x1b[3' + '6;01' + 'm', JSON.stringify({
module: DebugSymbol.fromAddress(fnPtr)['moduleName'], // https://www.frida.re/docs/javascript-api/#debugsymbol
package: jClass.slice(0, -1).join('.'),
class: jClass[jClass.length - 1],
method: methodsPtr.readPointer().readCString(), // char* name
signature: signature.readCString(), // char* signature TODO Java bytecode signature parser { Z: 'boolean', B: 'byte', C: 'char', S: 'short', I: 'int', J: 'long', F: 'float', D: 'double', L: 'fully-qualified-class;', '[': 'array' } https://github.com/skylot/jadx/blob/master/jadx-core/src/main/java/jadx/core/dex/nodes/parser/SignatureParser.java
address: fnPtr
}), '\x1b[39;49;00m');
}
}
});
}
Java.perform(RevealNativeMethods);