Frida0D - hook JNIEnv 相关函数

NewStringUTF

看一个例子:

    private external fun stringFromJNI(): String

    private external fun registerNativeStringFromJNI(): String
JNIEXPORT jstring JNICALL registerNativeStringFromJNI(
        JNIEnv *env,
        jobject clazz) {
    std::string hello = "Hello from C++ registerNativeStringFromJNI";
    return env->NewStringUTF(hello.c_str());
}

extern "C" JNIEXPORT jstring JNICALL
Java_com_aprz_myapplication_MainActivity_stringFromJNI(
        JNIEnv *env,
        jobject clazz) {
    std::string hello = "Hello from C++";
    return env->NewStringUTF(hello.c_str());
}

目标

使用 frida hook NewStringUTF 函数,拿到参数与返回值,并尝试修改参数与返回结果。

查找符号

编译后,查看 so 函数符号表:

 objdump -tT libhookjnienv.so > s.txt
 
 -----
  -t, --syms               Display the contents of the symbol table(s)
  -T, --dynamic-syms       Display the contents of the dynamic symbol table

搜索NewStringUTF发现了两个结果,是同一个符号:

000000000001e408  w    F .text 0000000000000034              _ZN7_JNIEnv12NewStringUTFEPKc
000000000001e408  w   DF .text 0000000000000034  Base        _ZN7_JNIEnv12NewStringUTFEPKc

如果发现有多个符号,可以去https://demangler.com/ 网站看看这个符号对应的具体方法是什么,比如上面的符号解出来就是:

_JNIEnv::NewStringUTF(char const*)

namespace 是对的上的,说明是我们需要hook的符号。

编写脚本

export function hook_jni_env() {
    var hook_jni_env = Process.getModuleByName("libhookjnienv.so");
    var new_string_utf = hook_jni_env.base.add(0x1e408);
    Interceptor.attach(new_string_utf, {
        onEnter: function (args) {
            console.log("jni env = ", args[0]);
            console.log("char * arg = ", args[1]);
        }, onLeave: function (retval) {
            console.log("jstring = ", retval);
        }
    });
}

查看输出

[Pixel::com.aprz.myapplication ]-> jni env =  0x76851126c0
char * arg =  0x7ff3d2c6a1
jstring =  0x71

使用Frida提供的JNIEnv来打印参数和返回结果

我们可以使用 frida提供的 JJNIEnv来获取 jstirng与 char * 类型的值。

Java.vm.getEnv().getStringUtfChars()
export function hook_jni_env() {
    var hook_jni_env = Process.getModuleByName("libhookjnienv.so");
    var new_string_utf = hook_jni_env.base.add(0x1e408);
    Interceptor.attach(new_string_utf, {
        onEnter: function (args) {
            console.log("jni env = ", args[0]);
            console.log("char * arg = ", args[1].readCString());
        }, onLeave: function (retval) {
            console.log("jstring = ", Java.vm.getEnv().getStringUtfChars(retval, false).readCString());
            retval.replace(Java.vm.getEnv().newStringUtf("nihao"));
        }
    });
}

查看输出

[Pixel::com.aprz.myapplication ]-> jni env =  0x76851126c0
char * arg =  Hello from C++
jstring =  Hello from C++

注意,我们 hook 了 JNIEnv 的 newStringUTF 函数,一般情况下需要注意死循环,比如inlinehook,但是这里使用 frida 提供的不需要担心这个问题,有点神奇。

打印堆栈

我们可以直接使用frida提供的方法:

export function hook_jni_env() {
    var hook_jni_env = Process.getModuleByName("libhookjnienv.so");
    var new_string_utf = hook_jni_env.base.add(0x1e408);
    Interceptor.attach(new_string_utf, {
        onEnter: function (args) {
            console.log("jni env = ", args[0]);
            console.log("char * arg = ", args[1].readCString());
            console.log('CCCryptorCreate called from:\n' +
                Thread.backtrace(this.context, Backtracer.ACCURATE)
                    .map(DebugSymbol.fromAddress).join('\n') + '\n');
        }, onLeave: function (retval) {
            console.log("jstring = ", Java.vm.getEnv().getStringUtfChars(retval, false).readCString());
            retval.replace(Java.vm.getEnv().newStringUtf("nihao"));
        }
    });
}

查看输出

可以自己比较一下Backtracer.FUZZY 和 Backtracer.ACCURATE 的区别。

[Pixel::com.aprz.myapplication ]-> jni env =  0x76851126c0
char * arg =  Hello from C++
CCCryptorCreate called from:
0x7591e3a330 libhookjnienv.so!Java_com_aprz_myapplication_MainActivity_stringFromJNI+0x50
0x75ffd52354 libart.so!art_quick_generic_jni_trampoline+0x94
0x75ffd49338 libart.so!art_quick_invoke_stub+0x228
0x75ffd58068 libart.so!_ZN3art9ArtMethod6InvokeEPNS_6ThreadEPjjPNS_6JValueEPKc+0xf8
0x75ffef6bc4 libart.so!_ZN3art11interpreter34ArtInterpreterToCompiledCodeBridgeEPNS_6ThreadEPNS_9ArtMethodEPNS_11ShadowFrameEtPNS_6JValueE+0x184
0x75ffef1abc libart.so!_ZN3art11interpreter6DoCallILb0ELb0EEEbPNS_9ArtMethodEPNS_6ThreadERNS_11ShadowFrameEPKNS_11InstructionEtPNS_6JValueE+0x3a4
0x76001b7620 libart.so!MterpInvokeDirect+0x194
0x75ffd43918 libart.so!mterp_op_invoke_direct+0x18
0x76001b8158 libart.so!MterpInvokeStatic+0x48c
0x75ffd43998 libart.so!mterp_op_invoke_static+0x18
0x76001b8158 libart.so!MterpInvokeStatic+0x48c
0x75ffd43998 libart.so!mterp_op_invoke_static+0x18
0x76001b6d88 libart.so!MterpInvokeInterface+0x6e8
0x75ffd43a18 libart.so!mterp_op_invoke_interface+0x18
0x76001b5568 libart.so!MterpInvokeVirtual+0x5b4
0x75ffd43818 libart.so!mterp_op_invoke_virtual+0x18

jstring =  Hello from C++

可以看到还是比较准确的。

想到 frida 可以注入 so,所以也可以考虑编译一个打印堆栈的 so,然后调用方法。

RegisterNatives

对这个函数的 hook,前一篇文章已经贴了一段代码了,这里简单分析一下就ok。

  function getNativeAddress(idx) {
    return env.handle.readPointer().add(idx * pSize).readPointer();
  }

上面,我们采用的是查询符号的地址,然后hook的方式。但是这里更加巧妙,它利用了 JNIENV 的源码相关知识。

JNIEnv 是一个结构体,整个结构体看成一个表,第 215 项对应的就是  RegisterNatives ,所以我们可以直接用指针计算偏移。

getNativeAddress(215)

因为,JNIEnv 结构是不会变化的,所以这个相当实用。

同样的,在处理参数的时候,RegisterNatives 接收一个JNINativeMethod 结构体指针,我们也可以利用指针来获取整个结构体的字段信息。

console.log("[RegisterNatives]method counts :", args[3]);
var env = args[0];
var jclass = args[1];
var class_name = Java.vm.tryGetEnv().getClassName(jclass);
var methods_ptr = ptr(args[2]);
var method_count = parseInt(args[3]);
for (var i = 0; i < method_count; i++) {
    var name_ptr = Memory.readPointer(methods_ptr.add(i * Process.pointerSize * 3));
    var sig_ptr = Memory.readPointer(methods_ptr.add(i * Process.pointerSize * 3 + Process.pointerSize));
    var fnPtr_ptr = Memory.readPointer(methods_ptr.add(i * Process.pointerSize * 3 + Process.pointerSize * 2));
    var name = Memory.readCString(name_ptr);
    var sig = Memory.readCString(sig_ptr);
    var find_module = Process.findModuleByAddress(fnPtr_ptr);
    console.log("[RegisterNatives] java_class:", class_name, "name:", name, "sig:", sig, "fnPtr:", fnPtr_ptr, "module_name:", find_module.name, "module_base:", find_module.base, "offset:", ptr(fnPtr_ptr).sub(find_module.base));
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值