安卓的最新版B站APP(7.76.0)有反FRIDA机制,本文介绍一下如何绕过它
问题说明
截止到2024年5月1日,B站最新版的安卓APP(7.76.0)有反Frida机制,不管是spawn还是attach,都无法注入frida,如下图所示。本文介绍一下如何绕过它

方法
定位检测点
检测Frida的机制一般在Native层实现,通常会创建几个线程轮询检测。首先要知道检测机制是由哪个so实现的,通过hook android_dlopen_ext函数,观察加载到哪个so的时候,触发反调试进程终止即可。Frida脚本代码与输出如下,最终可以定位到检测点在libmsaoaidsec.so中。
1 2 3 4 5 6 7 8 9 10 11 | var interceptor = Interceptor.attach(Module.findExportByName(null, "android_dlopen_ext"), { onEnter: function (args) { var pathptr = args[0]; if (pathptr !== undefined && pathptr != null) { var path = ptr(pathptr).readCString(); console.log("[LOAD]", path) } }, }) |

绕过检测点
对抗
使用IDA载入libmsaoaidsec.so,发现没有导入pthread_create符号

使用Frida脚本尝试HOOK pthread_create 函数,也没有找到来自libmsaoaidsec.so的调用
1 2 3 4 5 6 7 8 9 10 11 12 13 | var interceptor = Interceptor.attach(Module.findExportByName(null, "pthread_create"), { onEnter: function (args) { var module = Process.findModuleByAddress(ptr(this.returnAddress)) if (module != null) { console.log("[pthread_create] called from", module.name) } else { console.log("[pthread_create] called from", ptr(this.returnAddress)) } }, }) |

分析
尽管表现有点奇怪,但它一定会调用pthread_create创建检测线程。所以尝试hook dlsym函数,在加载libmsaoaidsec.so之前挂钩dlsym函数,代码如下
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 | function hook_dlsym() { var count = 0 console.log("=== HOOKING dlsym ===") var interceptor = Interceptor.attach(Module.findExportByName(null, "dlsym"), { onEnter: function (args) { const name = ptr(args[1]).readCString() // const module = Process.findModuleByAddress(ptr(this.returnAddress)) console.log("[dlsym]", name) if (name == "pthread_create") { count++ } } } ) return Interceptor}function hook_dlopen() { var interceptor = Interceptor.attach(Module.findExportByName(null, "android_dlopen_ext"), { onEnter: function (args) { var pathptr = args[0]; if (pathptr !== undefined && pathptr != null) { var path = ptr(pathptr).readCString(); console.log("[LOAD]", path) if (path.indexOf("libmsaoaidsec.so") > -1) { hook_dlsym() } } }, } ) return interceptor}var dlopen_interceptor = hook_dlopen() |
输出如下,在加载libmsaoaidsec.so后,调用了2次dlsym获取pthread_create函数,然后进程就终止了。证明它确实会调用pthread_create,只是调用方式不是直接调用,可能采取了一些对抗手段。

绕过
它应该是使用了一些反hook的手段,没有必要和它正面对抗。简单描述一下我的绕过策略,创建一个fake_pthread_create函数,它只有一条ret汇编指令,然后hook来自libmsaoaidsec.so的前2次对dlsym的调用,返回fake_pthread_create函数的地址,这样就达成了欺骗它调用fake_pthread_create函数的目的。最终代码如下
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 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 | function create_fake_pthread_create() { const fake_pthread_create = Memory.alloc(4096) Memory.protect(fake_pthread_create, 4096, "rwx") Memory.patchCode(fake_pthread_create, 4096, code => { const cw = new Arm64Writer(code, { pc: ptr(fake_pthread_create) }) cw.putRet() }) return fake_pthread_create}function hook_dlsym() { var count = 0 console.log("=== HOOKING dlsym ===") var interceptor = Interceptor.attach(Module.findExportByName(null, "dlsym"), { onEnter: function (args) { const name = ptr(args[1]).readCString() console.log("[dlsym]", name) if (name == "pthread_create") { count++ } }, onLeave: function(retval) { if (count == 1) { retval.replace(fake_pthread_create) } else if (count == 2) { retval.replace(fake_pthread_create) // 完成2次替换, 停止hook dlsym interceptor.detach() } } } ) return Interceptor}function hook_dlopen() { var interceptor = Interceptor.attach(Module.findExportByName(null, "android_dlopen_ext"), { onEnter: function (args) { var pathptr = args[0]; if (pathptr !== undefined && pathptr != null) { var path = ptr(pathptr).readCString(); console.log("[LOAD]", path) if (path.indexOf("libmsaoaidsec.so") > -1) { hook_dlsym() } } } } ) return interceptor}// 创建虚假pthread_createvar fake_pthread_create = create_fake_pthread_create()var dlopen_interceptor = hook_dlopen() |
执行frida -U -f tv.danmaku.bili -l bypass.js后即可绕过反frida机制,如下图所示

代码也可从github下载,见引用[1]。
