-
-
[原创]Frida 检测 libmsaoaidsec.so 绕过学习
-
发表于: 4天前 717
-
1 2 3 | frida 16.2.1android 14IDA pro 9.1 |
b站 7.76版本——8.81版本(最新版)
SO 库的加载流程详解
Android 系统加载一个 SO 库的顺序如下:
dlopen/android_dlopen_ext:系统调用加载器,将 SO 映射到内存。.init/.init_proc:执行初始化段的代码。.init_array:执行初始化数组中的函数(C++ 全局构造函数等)。JNI_OnLoad:最后执行,通常用于注册 JNI 方法。
定位检测点
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 | function hook_dlopen() { const funcName = "android_dlopen_ext"; const libc = Module.findBaseAddress("libc.so"); var funcPtr = Module.findExportByName(null, funcName); if (funcPtr !== null && funcPtr !== undefined) { console.log(`[*] Hooking ${funcName} at libc.so!0x${(funcPtr - libc.base).toString(16)}`); Interceptor.attach(funcPtr, { onEnter: function (args) { this.pathPtr = args[0]; if (this.pathPtr !== null && this.pathPtr !== undefined) { try { // 读取加载的so名称字符串并打印 var path = this.pathPtr.readCString(); console.log("\x1b[36m[dlopen] \x1b[0m" + path); if (path.indexOf("libmsaoaidsec.so") !== -1) { this.isTarget = true; } } catch (e) { console.log("[!] Error reading path string in " + this.funcName); } } }, onLeave: function (retval) { console.log("结束"); } }); } else { console.log("[-] Warning: " + funcName + " not found in exports."); }}function main() { hook_dlopen();}setImmediate(main); |
可以看到除了libmsaoaidsec.so,都输出了结束,并且最后是打印libmsaoaidsec.so后程序退出,所以libmsaoaidsec.so大概率为检测的so文件,并且没有打印出“结束”,所以我们可以确定是检测点在so加载完成之前

可以通过在dlopen结束之后,去HOOK JNI_Onload函数,去判断检测函数在JNI_Onload之前还是之后
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 | function hook_dlopen() { const funcName = "android_dlopen_ext"; const libc = Module.findBaseAddress("libc.so"); var funcPtr = Module.findExportByName(null, funcName); if (funcPtr !== null && funcPtr !== undefined) { console.log(`[*] Hooking ${funcName} at libc.so!0x${(funcPtr - libc.base).toString(16)}`); Interceptor.attach(funcPtr, { onEnter: function (args) { this.pathPtr = args[0]; if (this.pathPtr !== null && this.pathPtr !== undefined) { try { // 读取加载的so名称字符串并打印 var path = this.pathPtr.readCString(); console.log("\x1b[36m[dlopen] \x1b[0m" + path); if (path.indexOf("libmsaoaidsec.so") !== -1) { this.isTarget = true; } } catch (e) { console.log("[!] Error reading path string in " + this.funcName); } } }, onLeave: function (retval) { if (this.isTarget) { hook_JNI_OnLoad(); } } }); } else { console.log("[-] Warning: " + funcName + " not found in exports."); }}function hook_JNI_OnLoad() { let module = Process.findModuleByName("libmsaoaidsec.so") Interceptor.attach(module.base.add(0x13A4C), { onEnter(args) { console.log("JNI_OnLoad") } })}function main() { hook_dlopen();}setImmediate(main); |
发现最后没有打印出JNI_OnLoad,所以肯定是在JNI_OnLoad之前检测的

如果在JNI_OnLoad加载后,执行结果应当如下:会先打印出JNI_OnLoad再退出

所以根据以上的结果,我们可以判断出,检测函数是在JNI_OnLoad之前就开始检测了
所以我们找一个靠前的时间点, 在so初始化的时候我们找一个锚点,在这个锚点再去进行后续的hook,首先我们先找锚点
绕过检测点
打开IDA pro,我们一般情况下都会找 **__system_property_get**作为我们的锚点,一般情况下参数为ro.build.version.sdk
我们可以直接通过IDA搜索字符串ro.build.version.sdk,双击跳转到这个函数


我们可以找到sub_123F0,发现其调用了_system_property_get,调用系统属性获取函数,获取 SDK 版本,

查看sub_123F0的交叉引用,可以看到在init_proc中,sub_123F0被调用了,我们知道init_proc是一个很靠前的时间点,所以可以确认 **__system_property_get**就是一个很好的锚点,此时 SO 已经在内存中(基址已确定),但后续的检测线程还没来得及创建

所以我们给出下面的代码先做一个测试:
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 59 60 61 62 63 64 65 66 | function hook_dlopen() { const funcName = "android_dlopen_ext"; const libc = Module.findBaseAddress("libc.so"); var funcPtr = Module.findExportByName(null, funcName); if (funcPtr !== null && funcPtr !== undefined) { console.log(`[*] Hooking ${funcName} at libc.so!0x${(funcPtr - libc.base).toString(16)}`); Interceptor.attach(funcPtr, { onEnter: function (args) { this.pathPtr = args[0]; if (this.pathPtr !== null && this.pathPtr !== undefined) { try { // 读取加载的so名称字符串并打印 var path = this.pathPtr.readCString(); console.log("\x1b[36m[dlopen] \x1b[0m" + path); if (path.indexOf("libmsaoaidsec.so") !== -1) { this.isTarget = true; hook_system_property_get(); //在libmsaoaidsec.so进行初始化的时候hook } } catch (e) { console.log("[!] Error reading path string in " + this.funcName); } } }, onLeave: function (retval) { } }); } else { console.log("[-] Warning: " + funcName + " not found in exports."); }}function hook_JNI_OnLoad() { let module = Process.findModuleByName("libmsaoaidsec.so") Interceptor.attach(module.base.add(0x13A4C), { onEnter(args) { console.log("JNI_OnLoad") } })}function hook_system_property_get() { var system_property_get_addr = Module.findExportByName(null, "__system_property_get"); if (system_property_get_addr !== null && system_property_get_addr !== undefined) { Interceptor.attach(system_property_get_addr, { onEnter: function (args) { var nameptr = args[0]; if (nameptr) { var name = ptr(nameptr).readCString(); if (name.indexOf("ro.build.version.sdk") >= 0) { console.log("Found ro.build.version.sdk, need to patch"); //这里可以开始进行HOOK } } } }) }}function main() { hook_dlopen();}setImmediate(main); |
可以看到结果打印了Found ro.build.version.sdk, need to patch,说明hook_system_property_get()在检测函数之前就执行了,说明我们的推断是合理的

接下来就是去hook检测线程了,我们在这个锚点尝试hook 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 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 | function hook_dlopen() { const funcName = "android_dlopen_ext"; const libc = Module.findBaseAddress("libc.so"); var funcPtr = Module.findExportByName(null, funcName); if (funcPtr !== null && funcPtr !== undefined) { console.log(`[*] Hooking ${funcName} at libc.so!0x${(funcPtr - libc.base).toString(16)}`); Interceptor.attach(funcPtr, { onEnter: function (args) { this.pathPtr = args[0]; if (this.pathPtr !== null && this.pathPtr !== undefined) { try { // 读取加载的so名称字符串并打印 var path = this.pathPtr.readCString(); console.log("\x1b[36m[dlopen] \x1b[0m" + path); if (path.indexOf("libmsaoaidsec.so") !== -1) { this.isTarget = true; hook_system_property_get(); } } catch (e) { console.log("[!] Error reading path string in " + this.funcName); } } }, onLeave: function (retval) { } }); } else { console.log("[-] Warning: " + funcName + " not found in exports."); }}function hook_JNI_OnLoad() { let module = Process.findModuleByName("libmsaoaidsec.so") Interceptor.attach(module.base.add(0x13A4C), { onEnter(args) { console.log("JNI_OnLoad") } })}function hook_pthread_create() { var pthread_create_addr = Module.findExportByName("libc.so", "pthread_create"); console.log("pthread_create addr: ", pthread_create_addr); Interceptor.attach(pthread_create_addr, { onEnter: function (args) { var thread_func_addr = args[2]; var module = Process.findModuleByAddress(thread_func_addr); console.log(`pthread_create thread func: ${module.name}+0x${(thread_func_addr - module.base).toString(16)}`); }, onLeave: function (retval) { } });}function hook_system_property_get() { var system_property_get_addr = Module.findExportByName(null, "__system_property_get"); if (system_property_get_addr !== null && system_property_get_addr !== undefined) { Interceptor.attach(system_property_get_addr, { onEnter: function (args) { var nameptr = args[0]; if (nameptr) { var name = ptr(nameptr).readCString(); if (name.indexOf("ro.build.version.sdk") >= 0) { console.log("Found ro.build.version.sdk, need to patch"); // hook_pthread_create(); // bypass() //这里可以开始进行HOOK hook_pthread_create(); } } } }) }}function main() { hook_dlopen();}setImmediate(main); |

我们直接尝试去nop这些线程
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 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 | function hook_dlopen() { const funcName = "android_dlopen_ext"; const libc = Module.findBaseAddress("libc.so"); var funcPtr = Module.findExportByName(null, funcName); if (funcPtr !== null && funcPtr !== undefined) { console.log(`[*] Hooking ${funcName} at libc.so!0x${(funcPtr - libc.base).toString(16)}`); Interceptor.attach(funcPtr, { onEnter: function (args) { this.pathPtr = args[0]; if (this.pathPtr !== null && this.pathPtr !== undefined) { try { // 读取加载的so名称字符串并打印 var path = this.pathPtr.readCString(); console.log("\x1b[36m[dlopen] \x1b[0m" + path); if (path.indexOf("libmsaoaidsec.so") !== -1) { this.isTarget = true; hook_system_property_get(); } } catch (e) { console.log("[!] Error reading path string in " + this.funcName); } } }, onLeave: function (retval) { } }); } else { console.log("[-] Warning: " + funcName + " not found in exports."); }}function hook_JNI_OnLoad() { let module = Process.findModuleByName("libmsaoaidsec.so") Interceptor.attach(module.base.add(0x13A4C), { onEnter(args) { console.log("JNI_OnLoad") } })}function hook_pthread_create() { var pthread_create_addr = Module.findExportByName("libc.so", "pthread_create"); console.log("pthread_create addr: ", pthread_create_addr); Interceptor.attach(pthread_create_addr, { onEnter: function (args) { var thread_func_addr = args[2]; var module = Process.findModuleByAddress(thread_func_addr); console.log(`pthread_create thread func: ${module.name}+0x${(thread_func_addr - module.base).toString(16)}`); }, onLeave: function (retval) { } });}function nopFunc(addr) { Memory.protect(addr, 4, 'rwx'); // 修改该地址的权限为可读可写 var writer = new Arm64Writer(addr); writer.putRet(); // 直接将函数首条指令设置为ret指令 writer.flush(); // 写入操作刷新到目标内存,使得写入的指令生效 writer.dispose(); // 释放 Arm64Writer 使用的资源 console.log("nop " + addr + " success");}function bypass_detect_func() { var base = Module.findBaseAddress("libmsaoaidsec.so") // jxbank nopFunc(base.add(0x1c544)); nopFunc(base.add(0x1b8d4)); nopFunc(base.add(0x26e5c));}function hook_system_property_get() { var system_property_get_addr = Module.findExportByName(null, "__system_property_get"); if (system_property_get_addr !== null && system_property_get_addr !== undefined) { Interceptor.attach(system_property_get_addr, { onEnter: function (args) { var nameptr = args[0]; if (nameptr) { var name = ptr(nameptr).readCString(); if (name.indexOf("ro.build.version.sdk") >= 0) { console.log("Found ro.build.version.sdk, need to patch"); // hook_pthread_create(); // bypass() //这里可以开始进行HOOK // hook_pthread_create(); bypass_detect_func(); } } } }) }}function main() { hook_dlopen();}setImmediate(main); |
frida已经不退出了

其他锚点
这种寻找锚点的方式,不只可以使用__system_property_get作为我们的锚点,还可以使用其他的函数,这边使用gemini找了几个可以尝试作为锚点的函数
| 锚点函数 | 推荐指数 | 触发时机 | 适用场景 |
|---|---|---|---|
| __system_property_get | ⭐⭐⭐⭐⭐ | 极早 | 几乎所有加固都会读取ro.build.version或厂商信息 |
| dlsym | ⭐⭐⭐⭐⭐ | 极早 | 壳需要隐藏 API 调用时(如隐藏ptrace等) |
| prctl | ⭐⭐⭐ | 较早 | 防止 Dump 或 允许 Ptrace 时 |
dlsym
首先我们在IDA中看dlsym是否在init_proc阶段被调用:找到dlsym,查看它的引用

定位到sub_9150,继续查看引用,可以发现在init_proc中被调用了,所以在初始化阶段确实存在dlsym

SO 加壳或做对抗时,为了隐藏导入表,往往会通过 dlopen/dlsym 动态获取系统函数地址(如 ptrace, open, pthread_create)。特征参数:第二个参数是函数名称字符串,这里编写如下的代码,测试是否会动态导入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 | function hook_dlopen() { const funcName = "android_dlopen_ext"; const libc = Module.findBaseAddress("libc.so"); var funcPtr = Module.findExportByName(null, funcName); if (funcPtr !== null && funcPtr !== undefined) { console.log(`[*] Hooking ${funcName} at libc.so!0x${(funcPtr - libc.base).toString(16)}`); Interceptor.attach(funcPtr, { onEnter: function (args) { this.pathPtr = args[0]; if (this.pathPtr !== null && this.pathPtr !== undefined) { try { // 读取加载的so名称字符串并打印 var path = this.pathPtr.readCString(); console.log("\x1b[36m[dlopen] \x1b[0m" + path); if (path.indexOf("libmsaoaidsec.so") !== -1) { this.isTarget = true; // hook_system_property_get(); // hook_prctl_anchor() hook_dlsym_anchor(); } } catch (e) { console.log("[!] Error reading path string in " + this.funcName); } } }, onLeave: function (retval) { } }); } else { console.log("[-] Warning: " + funcName + " not found in exports."); }}function hook_dlsym_anchor() { const dlsym_addr = Module.findExportByName(null, "dlsym"); if (dlsym_addr) { Interceptor.attach(dlsym_addr, { onEnter: function (args) { this.symbolName = args[1].readCString(); // 监听壳是否在动态获取pthread_create if (this.symbolName && (this.symbolName.indexOf("pthread_create") >= 0)) { console.log("[Anchor] dlsym finding: " + this.symbolName); // 触发核心 Bypass 逻辑 // bypass_detect_func(); } } }); }}function main() { hook_dlopen();}setImmediate(main); |
可以看到打印了[Anchor] dlsym finding: pthread_create

我们在这个锚点执行我们的bypass方法
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 | function hook_dlopen() { const funcName = "android_dlopen_ext"; const libc = Module.findBaseAddress("libc.so"); var funcPtr = Module.findExportByName(null, funcName); if (funcPtr !== null && funcPtr !== undefined) { console.log(`[*] Hooking ${funcName} at libc.so!0x${(funcPtr - libc.base).toString(16)}`); Interceptor.attach(funcPtr, { onEnter: function (args) { this.pathPtr = args[0]; if (this.pathPtr !== null && this.pathPtr !== undefined) { try { // 读取加载的so名称字符串并打印 var path = this.pathPtr.readCString(); console.log("\x1b[36m[dlopen] \x1b[0m" + path); if (path.indexOf("libmsaoaidsec.so") !== -1) { this.isTarget = true; // hook_system_property_get(); // hook_prctl_anchor() hook_dlsym_anchor(); } } catch (e) { console.log("[!] Error reading path string in " + this.funcName); } } }, onLeave: function (retval) { } }); } else { console.log("[-] Warning: " + funcName + " not found in exports."); }}function hook_dlsym_anchor() { const dlsym_addr = Module.findExportByName(null, "dlsym"); if (dlsym_addr) { Interceptor.attach(dlsym_addr, { onEnter: function (args) { this.symbolName = args[1].readCString(); // 监听壳是否在动态获取pthread_create if (this.symbolName && (this.symbolName.indexOf("pthread_create") >= 0)) { console.log("[Anchor] dlsym finding: " + this.symbolName); // 触发核心 Bypass 逻辑 bypass_detect_func(); } } }); }}function main() { hook_dlopen();}setImmediate(main); |
可以看到已经成功绕过,frida未退出

prctl
这里验证了**prctl**,IDA中的调用路径如下:
通过function中搜索函数,定位到函数

开始查看引用,找到sub_1B144

查看sub_1B144的引用,找到了sub_1B380:

继续查看引用,找到sub_1B924:

继续往上跟进,找到sub_1BEC4:

发现在init_proc中调用了sub_1BEC4,说明了prctl确实是在初始化阶段被调用了

具体代码如下:
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 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 | function hook_dlopen() { const funcName = "android_dlopen_ext"; const libc = Module.findBaseAddress("libc.so"); var funcPtr = Module.findExportByName(null, funcName); if (funcPtr !== null && funcPtr !== undefined) { console.log(`[*] Hooking ${funcName} at libc.so!0x${(funcPtr - libc.base).toString(16)}`); Interceptor.attach(funcPtr, { onEnter: function (args) { this.pathPtr = args[0]; if (this.pathPtr !== null && this.pathPtr !== undefined) { try { // 读取加载的so名称字符串并打印 var path = this.pathPtr.readCString(); console.log("\x1b[36m[dlopen] \x1b[0m" + path); if (path.indexOf("libmsaoaidsec.so") !== -1) { this.isTarget = true; // hook_system_property_get(); hook_prctl_anchor() } } catch (e) { console.log("[!] Error reading path string in " + this.funcName); } } }, onLeave: function (retval) { } }); } else { console.log("[-] Warning: " + funcName + " not found in exports."); }}function hook_pthread_create() { var pthread_create_addr = Module.findExportByName("libc.so", "pthread_create"); console.log("pthread_create addr: ", pthread_create_addr); Interceptor.attach(pthread_create_addr, { onEnter: function (args) { var thread_func_addr = args[2]; var module = Process.findModuleByAddress(thread_func_addr); console.log(`pthread_create thread func: ${module.name}+0x${(thread_func_addr - module.base).toString(16)}`); }, onLeave: function (retval) { } });}function nopFunc(addr) { Memory.protect(addr, 4, 'rwx'); // 修改该地址的权限为可读可写 var writer = new Arm64Writer(addr); writer.putRet(); // 直接将函数首条指令设置为ret指令 writer.flush(); // 写入操作刷新到目标内存,使得写入的指令生效 writer.dispose(); // 释放 Arm64Writer 使用的资源 console.log("nop " + addr + " success");}function bypass_detect_func() { var base = Module.findBaseAddress("libmsaoaidsec.so") // jxbank nopFunc(base.add(0x1c544)); nopFunc(base.add(0x1b8d4)); nopFunc(base.add(0x26e5c));}function hook_prctl_anchor() { const prctl_ptr = Module.findExportByName(null, "prctl"); const PR_SET_DUMPABLE = 4; if (prctl_ptr) { Interceptor.attach(prctl_ptr, { onEnter: function (args) { const option = args[0].toInt32(); // 锚点:检测到尝试禁止内存 dump if (option === 15) { console.log(`[Anchor] prctl(PR_SET_DUMPABLE) detected!`); bypass_detect_func(); } } }); }}function main() { hook_dlopen();}setImmediate(main); |
但是存在一个问题,这个锚点虽然能执行bypass_detect_func,但是实测下来无法执行hook_pthread_create()
其他绕过方法
这时候就有兄弟要问了,有没有更轮椅的方法,有的兄弟,有的
只需要去下载一个florida 就可以一键绕过检测了
下载对应的版本,直接替换原本的frida-server即可。
参考文章
绕过最新版bilibili app反frida机制
[原创]经典 Frida 检测 libmsaoaidsec.so 绕过
[原创]某加固新版frida检测绕过-trace一把嗦
[原创] bilibili frida检测分析绕过
小白第一次发帖,可能分析和描述中存在错漏,望大佬指点~