1、环境:
手机:Google Pixel 6,Aosp android13
cpu架构:Arm64
Frida:16.1.10
爱奇艺:15.2.5
2、问题
这段时间在研究爱奇艺的一些功能,当用Frida调试时发现有反Frida检测,现象是执行frida -l index.js -U -f com.qiyi.video时进程重启,下面是对Frida检测的一些分析,并通过hook绕过Frida检测。
3、分析
用Frida hook do_dlopen函数看加载哪个so时崩溃的,hook之前先获取do_dlopen在linker64中的相对偏移

hook 脚本如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | function hookDlopen() { let linker64_base_addr = Module.getBaseAddress('linker64') let offset = 0x3ba00 // __dl__Z9do_dlopenPKciPK17android_dlextinfoPKv let android_dlopen_ext = linker64_base_addr.add(offset) Interceptor.attach(android_dlopen_ext, { onEnter: function(args){ this.name = args[0].readCString() Log.log(`dlopen onEnter ${this.name}`) }, onLeave: function(retval){ Log.log(`dlopen onLeave name: ${this.name}`) if (this.name != null && this.name.indexOf('libmsaoaidsec.so') >= 0) { let JNI_OnLoad = Module.getExportByName(this.name, 'JNI_OnLoad') Log.log(`dlopen onLeave JNI_OnLoad: ${JNI_OnLoad}`) } } }) } |
执行frida -l index.js -U -f com.qiyi.video

从结果可以看出加载的最后一个so是:libmsaoaidsec.so,并且没有调用onLeave,由此可断定崩溃点在libmsaoaidsec.so中,并且在JNI_OnLoad之前检测的,检测点应该是在init_array初始化函数中。
<br/>
既然挂上Frida后进程就退出,那么我们就来分析是调用哪个系统调用退出的,可以通过strace查看系统调用,但在执行strace时需要在dlopen加载libmsaoaidsec.so之前让线程sleep 10秒,以便留出strace执行时机。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | function hookDlopen() { // 获取libc中的sleep函数 let sleep: Function = new NativeFunction(Module.getExportByName('libc.so', 'sleep'), 'uint', ['uint']) let linker64_base_addr = Module.getBaseAddress('linker64') let offset = 0x3ba00 // __dl__Z9do_dlopenPKciPK17android_dlextinfoPKv let android_dlopen_ext = linker64_base_addr.add(offset) Interceptor.attach(android_dlopen_ext, { onEnter: function(args){ this.name = args[0].readCString() Log.log(`dlopen onEnter ${this.name}`) if (this.name != null && this.name.indexOf('libmsaoaidsec.so') >= 0) { sleep(10) // sleep10秒留出strace执行时机 } }, onLeave: function(retval){ Log.log(`dlopen onLeave name: ${this.name}`) } }) } |
下面重新执行frida -l index.js -U -f com.qiyi.video

然后得到pid立即执行strace -e trace=process -i -f -p 15534 等待10秒后结果:

我们看这一行[pid 15576] [000000751926c008] exit_group(0 <unfinished ...>,显示15576线程是在0x751926c008地址处调用exit_group退出的,通过proc/15534/maps查看libc.so的地址范围是0x7509b9c000 - 0x7509c70000,很明显0x751926c008不是libc.so的地址,由此可以断定exit_group的代码是动态释放的。
<br/>
动态释放代码一定是要操作内存的,接下来我们用前面相同的逻辑,用strace查看调用了哪些和内存相关的系统调用
<br/>strace -e trace=process,memory -i -f -p 25947

注意这行:[pid 25990] [0000007509c48ab8] mmap(NULL, 28, PROT_READ|PROT_WRITE|PROT_EXEC, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x751926c000
<br/>
上面这行mmap申请的内存返回的地址是:0x751926c000,正好匹配后面exit_group前面的地址:000000751926c008
<br/>
由mmap的地址可以看出mmap是libc.so中的函数,于是我们可以hook mmap打印一下调用栈
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | function hook_mmap() { const mmap = Module.getExportByName("libc.so", "mmap"); Interceptor.attach(mmap, { onEnter: function (args) { let length = args[1].toString(16) if (parseInt(length, 16) == 28) { Log.log('backtrace:\n' + Thread.backtrace(this.context, Backtracer.ACCURATE) .map(DebugSymbol.fromAddress).join('\n') + '\n'); } } })}function hookDlopen() { let sleep: Function = new NativeFunction(Module.getExportByName('libc.so', 'sleep'), 'uint', ['uint']) let linker64_base_addr = Module.getBaseAddress('linker64') let offset = 0x3ba00 // __dl__Z9do_dlopenPKciPK17android_dlextinfoPKv let android_dlopen_ext = linker64_base_addr.add(offset) Interceptor.attach(android_dlopen_ext, { onEnter: function(args){ this.name = args[0].readCString() Log.log(`dlopen onEnter ${this.name}`) if (this.name != null && this.name.indexOf('libmsaoaidsec.so') >= 0) { // sleep(10) hook_mmap() } }, onLeave: function(retval) { Log.log(`dlopen onLeave name: ${this.name}`) } }) } |
调用栈如下:

下面用IDA打开libmsaoaidsec.so,看0x2358c地址处对应的函数

下面看C语言伪码,的确是在释放代码并且执行:

经过IDA分析0x1bdcc地址对应的函数:void sub_1B924()是检测逻辑所在,具体检测逻辑就不在这赘述了,大家有兴趣可以自行研究

4、解决方案
解决方案就是replace掉这个无返回值的函数 void sub_1B924()
<br/>
由于这个检测逻辑在.init_函数中,所以得先找到hook时机,查看Aosp代码发现linker执行init_array类的函数是call_constructors:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | void soinfo::call_constructors() { if (constructors_called) { return; } constructors_called = true; if (!is_main_executable() && preinit_array_ != nullptr) { // The GNU dynamic linker silently ignores these, but we warn the developer. PRINT("\"%s\": ignoring %zd-entry DT_PREINIT_ARRAY in shared library!", get_realpath(), preinit_array_count_); } get_children().for_each([] (soinfo* si) { si->call_constructors(); }); TRACE("\"%s\": calling constructors", get_realpath()); // DT_INIT should be called before DT_INIT_ARRAY if both are present. call_function("DT_INIT", init_func_); call_array("DT_INIT_ARRAY", init_array_, init_array_count_, false);} |
所以直接hook call_constructors函数,在onEnter中replace掉sub_1b924
<br/>
查看call_constructors在linker64中的偏移

5、最终代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 | function hookDlopen() { let linker64_base_addr = Module.getBaseAddress('linker64') let offset = 0x3ba00 // __dl__Z9do_dlopenPKciPK17android_dlextinfoPKv let android_dlopen_ext = linker64_base_addr.add(offset) if (android_dlopen_ext != null) { Interceptor.attach(android_dlopen_ext, { onEnter: function(args){ this.name = args[0].readCString() if (this.name != null && this.name.indexOf('libmsaoaidsec.so') >= 0) { hook_linker_call_constructors() } }, onLeave: function(retval){ Log.log(`dlopen onLeave name: ${this.name}`) if (this.name != null && this.name.indexOf('libmsaoaidsec.so') >= 0) { let JNI_OnLoad = Module.getExportByName(this.name, 'JNI_OnLoad') Log.log(`dlopen onLeave JNI_OnLoad: ${JNI_OnLoad}`) } } }) }}function hook_linker_call_constructors() { let linker64_base_addr = Module.getBaseAddress('linker64') let offset = 0x521f0 // __dl__ZN6soinfo17call_constructorsEv let call_constructors = linker64_base_addr.add(offset) let listener = Interceptor.attach(call_constructors, { onEnter: function (args) { Log.log('hook_linker_call_constructors onEnter') let secmodule = Process.findModuleByName("libmsaoaidsec.so") if (secmodule != null) { hook_sub_1b924(secmodule) listener.detach() } } })}function hook_sub_1b924(secmodule) { Interceptor.replace(secmodule.base.add(0x1b924), new NativeCallback(function () { Log.log(`hook_sub_1b924 >>>>>>>>>>>>>>>>> replace`) }, 'void', []));} |