7

我正在尝试了解一个包含foo类中命名的本机方法的 Android 应用程序com.app.Bar

在类内部Bar有一个静态子句加载一个共享对象System.loadLibrary("libfoo.so"),我假设它是使用-fvisibility=hidden构建的,因为唯一的导出是JNI_OnLoad,不JNIEXPORT void JNICALL Java_com_app_Bar_foo,这意味着public native int foo不遵循命名约定

  1. 调用时的流程foo是什么?
  2. 如何提取地址foo?我对弗里达很熟悉
  3. 有没有办法全部输出JNINativeMethod[] methods

到目前为止我尝试了什么?

  1. JNIAnalyzer输出 13K 行,看起来像JNI_OnLoad@@Base+0x712e

  2. Frida 脚本尝试查找foo地址(无效)

function intercept(address) {
    try {
        Interceptor.attach(address, {
            onEnter: function(args) {
                console.log("onEnter", address);
            },
            onLeave: function(ignored) {}
        });
    } catch (e) {
        console.error(e);
    }
}
function Main() {
    var dlopen = new NativeFunction(Module.findExportByName(null, 'dlopen'), 'pointer', ['pointer', 'int']);
    var dlsym = new NativeFunction(Module.findExportByName(null, 'dlsym'), 'pointer', ['pointer', 'pointer']);
    Process.enumerateModulesSync().forEach(function(m) {
        if (m.name === "libfoo.so") {
            console.log("Module", JSON.stringify(m));
            var handle = dlopen(Memory.allocUtf8String(m.path), 1);
            var symb = Memory.allocUtf8String("foo");
            var exports = Module.enumerateExportsSync(m.name);
            console.log(JSON.stringify({
                handle: handle,
                symb: symb,
                dlsym: dlsym(handle, symb),
                exports: exports.map(function(ex){ return ex.address + ": " + ex.name })
            }, null, 2));
            // intercept all exports
            exports.forEach(function(ex){
                intercept(ex.address);
            });
            // explicit intercept foo by known offset
            intercept(m.base.add(0x22334)); // this outputs "Error: unable to intercept function at 0x86c96328; please file a bug"
        }
    });

    console.log("sleep..");
    Thread.sleep(1.5);
    console.log("invoke", Java.use('com.clazz.foo').signToken("A".repeat(32)));
}
Java.perform(Main);

4

2 回答 2

8

我假设是使用 -fvisibility=hidden 构建的,因为唯一的导出是 JNI_OnLoad,没有 JNIEXPORT void JNICALL Java_com_app_Bar_foo 这意味着 public native int foo 不遵循命名约定。

并不总是正确的,因为您也可以使用RegisterNativesinsideJNI_OnLoad来声明您的 JNI 方法不遵循典型的命名约定。

/*
 * 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;
}

调用 foo 的过程是什么?

也许你可以看看这篇文章https://arophix.com/2017/12/17/andoid-jni-summary/

如何提取 foo 的地址?(我对弗里达很熟悉)

我不确定Frida,对于Cydia Substrate,你可以用它dlsym来查找指定符号的地址.so,例如

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;
    }
}

这是使用 Cydia Substrate 的完整教程 https://arophix.com/2017/11/30/android-substrate-hooking/

- - - - 更新 - - - -

编辑#1

对于弗里达来说,也许是类似的。这是一个参考链接,可能对您的案例有所帮助。https://www.notsosecure.com/instrumenting-native-android-functions-using-frida/

编辑#2

有关基于 Frida 的 hooking 的完整 Github 教程项目,请参阅使用 Apktool 和 Frida 对 Android Apk 进行逆向工程。.

于 2018-08-15T02:39:14.317 回答
8

我已经解决了Frida

挂钩art::JNI::RegisterNativeMethods(_JNIEnv*, _jclass*, JNINativeMethod const*, int, bool)和加载模块art::JNI::FindClass后。libart.so

代码

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);
于 2018-08-22T18:16:52.947 回答