这篇文章上次修改于 1006 天前,可能其部分内容已经发生变化,如有疑问可询问作者。
前言
jnitrace想必大家都不陌生,但是使用起来肯定有些不顺手,在此对其进行优化并增强信息打印
优化jnitrace
你可能遇到过这样的情况
- 使用jnitrace的时候APP总是卡在启动页、手机一直处于黑屏状态
这个问题我也遇到过了,不过根据经验,确实有些手机没有这样的情况,具体这是怎么回事我也没搞清楚
不过在分析了一波jnitrace的源代码之后,我发现这个地方可能存在问题
const dlopen = new NativeFunction(dlopenRef, "pointer", ["pointer", "int"]);
Interceptor.replace(dlopen, new NativeCallback((filename: NativePointer, mode: number): NativeReturnValue => {
const path = filename.readCString();
const retval = dlopen(filename, mode);
if (path !== null) {
if (checkLibrary(path)) {
// eslint-disable-next-line @typescript-eslint/no-base-to-string
trackedLibs.set(retval.toString(), true);
} else {
// eslint-disable-next-line @typescript-eslint/no-base-to-string
libBlacklist.set(retval.toString(), true);
}
}
return retval;
}, "pointer", ["pointer", "int"]));
Interceptor.replace
的目标函数是dlopen
,但是在其回调函数内又调用了dlopen
,这是否会出现死循环呢?
我尝试在NativeCallback
中加入打印日志,发现并没有出现死循环,然后我尝试将其注释掉,发现这下APP能进到启动页了
后来检查logcat,发现在APP卡退时,日志中出现了Waiting for a blocking GC ProfileSaver
搜了下这个异常好像和内存有关,然后再次测试,发现jnitrace启动后APP在卡退之前,CPU居然占到了100%
所以是为什么呢,把上面那部分代码注释掉就正常,但是根据日志结果又没有无限循环
最后我的推测是:ROM某些编译设定引发的bug
如果有人知道为什么请告诉我
为了不影响原有功能,用Interceptor.attach
实现上面的逻辑,这样不会引起卡死
Interceptor.attach(dlopenRef, {
onEnter:function(args){
this.path = args[0].readCString();
},onLeave:function(retval){
if (this.path !== null) {
if (checkLibrary(this.path)) {
trackedLibs.set(retval.toString(), true);
}
else {
libBlacklist.set(retval.toString(), true);
}
}
}
});
增强信息打印
jnitrace会贴心地把jstring内容打印出来,无论是传入参数还是返回结果
但还有一种情况下迫切需要知道内容是什么,即参数是Ljava/lang/String;
类型
在分析了jnitrace源码后,以CallObjectMethodV
为例,对于返回结果是Ljava/lang/String;
的,可以在这两处添加代码,实现内容打印
if (name == "CallObjectMethodV") {
// args => JNIEnv*, jobject, jmethodID, va_list
const MID_ARG_INDEX = 2;
const mid_key = data.args[MID_ARG_INDEX].toString();
const sig = this.jmethodIDs.get(mid_key);
if (sig && sig.endsWith("Ljava/lang/String;")) {
let utf = "getStringUtfChars error";
try {
utf = Java.vm.getEnv().getStringUtfChars(data.ret, null).readUtf8String();
} catch (error) {
console.log(`errorerror => ${error}`);
}
outputRet.push(
new DataJSONContainer(
data.ret,
utf
)
);
return null;
}
}
if (typeof data === "string"){
this.metadata = data as string;
}
修改后的效果
另外有一个需要注意的点,win用户运行的时候记得设置PYTHONIOENCODING
,不然有时候会出现终端输出会出现编码异常
chcp 65001
set PYTHONIOENCODING=utf-8
一旦出现了中文,那么保存为json文件的时候也要注意编码,这里需要修改两处
argparse.FileType("w", encoding='utf-8')
json.dump(formatter.get_output(), args.output, ensure_ascii=False, indent=4)
同理对于其他jni调用,如果入参是Ljava/lang/String;
,则可以在addJNIEnvArgs
中操作
jnitrace原本有关字符串获取是通过从DataTransport
的jstrings
缓存取值实现的,但是这样只能打印部分字符串
上面通过Java.vm.getEnv().getStringUtfChars(data.ret, null).readUtf8String();
获取的字符串,日志中会多出ReleaseStringUTFChars
对应字符串的信息,不过这倒无伤大雅了
奇怪的技巧
在测试过程中,我修改替换了dlopen部分的代码,但是还是有hook不上的情况
但是我发现,如果我点击启动界面的跳过按钮,则hook顺利...
(当然,如果不修改dlopen部分的代码,根本见不到启动界面
演示视频如下
- 演示视频
- 第一次直接执行命令,很快就崩掉了
- 第二次点击了屏幕任意位置,相对第一次打印的信息多一些
- 第三次点击了跳过按钮,顺利hook且不会崩溃
已有 2 条评论
@(大佬)
大佬修改后的代码??