0x01 init init_array JNI_OnLoad是什么
在共享库被加和卸载时自动执行的函数,主要用于做一些初始化操作。
init 函数优先于 init_array 函数执行。
JNI_OnLoad:加载库文件的时候,如果代码中有此函数,就会自动执行。执行的时机晚于 init 和 init_array
0x02 使用方式
这里我们在 zzcracker.cc 中增加 init 和 init_array jni_load
1. init 函数
注意:在 IDA 中反编译出来看到的 init 函数名字为 .init_proc
extern "C" void _init() {
LOGD("SO INIT METHOD");
}
2. init_array
这里定义 3 个 init_array 函数
__attribute__ ((constructor, visibility("hidden"))) void initArrayTest1() {
LOGD("initArrayTest1");
}
__attribute__ ((constructor, visibility("hidden"))) void initArrayTest2() {
LOGD("initArrayTest2");
}
__attribute__ ((constructor, visibility("hidden"))) void initArrayTest3() {
LOGD("initArrayTest3");
}
3. 定义 JNI_OnLoad
JNIEXPORT jint JNI_OnLoad(JavaVM *vm, void *reserved) { JNIEnv *env; vm->GetEnv((void **) &env, JNI_VERSION_1_6); LOGD("JNI_Onload JNIEnv %p", env); return JNI_VERSION_1_6; }
上面的代码定义了 4 个函数,我们从输出的日志中可以查看函数执行的顺序
IDA 中查看 init_array
ctrl+s 查看短信息,可以看到 .init_array的信息
点击跳转就可以看到我们定义的单个 init_array 定义的位置。
分别点击每个函数
按 F5 转换成 伪 C 代码,很明显就是我们写的 init_array 函数啦
IDA 查看 init
在 IDA 左边的符号表搜索 init 。搜索结果里面的 .init_proc 就是我们的目标函数。
点击跳转进入汇编指令然后 F5 进位伪 C 代码。奈斯!就是我们的 init 函数啦
IDA 查看 JNI_OnLoad
在 IDA 的导出表页面搜索 JNI
点击 JNI_OnLoad 跳转
可以看到我们定义的 LOGD 日志输出(其他的代码可以忽略,不是本文的重点)
分别 hook 以上三个函数
so 是通过系统的 dlopen进行加载的,在 android 7.0 之后使用的是 android_dlopen_ext 。这个函数是在 libdl.so 中。因为 init 和 ini_array 执行的时机很早,且执行过一次之后就不再执行。因为我们需要在 android_dlopen_ext 运行过程中hook
第一步: Hook android_dlopen_ext
hook 到这个函数之后,要对 so的名字进行过滤,只 hook 需要的 so 。
第二步:hook callConstructorAdd
通过android源码分析 init 和 init_array 是在 callConstructorAdd 中被调用的。所以我们需要先找到这个函数并 hook 。
callConstructorAdd 在android编译之后, 是放在手机 system/bin/linker64 动态库中。把 linker64 用 IDA 打开,并在符号表中搜索 constructor。得到导出的符号:__dl__ZN6soinfo17call_constructorsEv
然后通过 frida 遍历 so 的符号表,找到我们的目标函数 __dl__ZN6soinfo17call_constructorsEv 并hook上。
第三步: HOOK init 和 init_array 函数啦
下面 hook init 和init_array 都是使用了 Interceptor.replace 对原函数进行替换。模拟了我们在 init 或者 init_array 中有反调试检测。把这个函数直接替换掉,让其无法检测,就达到了 bypass 的目的。
1. hook init
2.hook init_array
第四步: hook JNI_OnLoad
JNI_Onload 执行的时机比较晚,在 dlopen 离开的时候再进行 hook就可以了
这里我们就直接 Interceptor.attach 进行 hook 做一个简单的打印日志,不替换原函数了。
hook前的android 日志输出
hook 后的日志输出
先看 frida 的hook 日志
在 so 加载之前 hook 上 dlopen 函数。
然后点击加载 so ,可以看到我们正常 hook 到 init init_array 和 JNI_OnLoad 啦。
android 的日志没有 init 和 init_array 的日志输出啦。说明我们 hook 成功。
以上,就是 hook init init_array JNI_OnLoad 的原理以及实现啦。多谢各位看官的时间!如果有什么问题可以再评论区提出。有写的不好的地方,希望大家包含咯!