- 作者:老汪软件技巧
- 发表时间:2024-09-12 17:01
- 浏览量:
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
如果发现有多个符号,可以去网站看看这个符号对应的具体方法是什么,比如上面的符号解出来就是:
_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));
}