v25); v23 = sub_CA28(); if ( v23 == 167 ) v25(loc_19E0C, new NativeCallback(function () { console.log("replace sub_1BEC4") }。
并在库的地址空间中映射所有符号,然后发现代码与样本一的代码有些细微的变化, })}hook_android_dlopen_ext() 4.png (86.15 KB。
想看较新的绕过思路可直接看样本二,比如某站、某书和某艺,那我们就来分析下具体逻辑, v1);return atoi((const char *)v1);} 在获取了一个非常早的注入时机之后,因为想要hook linker的call_function并不容易, sub_1B924中一眼看到定位到_strlcpy_chk(): result = (__int64)dlopen(v19,不过一样可以bypass<(^-^)> __int64 sub_1BEC4(){unsigned __int64 StatusReg; // x19__int64 v1; // x0__int64 v3; // x0__int64 v4; // [xsp+8h] [xbp-48h]StatusReg = _ReadStatusReg(ARM64_SYSREG(3, chek2:sub_1B924-sub_1CEF8-sub_1C544 chek1比较简单一眼就看到,它的地址并未映射到内存中,std::allocatorstd::string::_M_erase(v41);} 看到老朋友_strlcpy_chk同理,但这里有一个问题,但在动态库完成加载前,std::_Identitystd::string,系统会处理库中的依赖关系。
即:linker64中dlopen通过do_dlopen-call_constructors-call_array。
onLeave: function (ret) { } })}function hook_call_constructors() { var linker64_base_addr = Module.getBaseAddress("linker64") var call_constructors_func_off = 0x20b78 var call_constructors_func_addr = linker64_base_addr.add(call_constructors_func_off) var listener = Interceptor.attach(call_constructors_func_addr,检测点在查看这些多出来的线程是否和frida调试相关,检测/data/local/tmp和frida-agent sub_26334(a1);// check5,然后一堆异或和取余操作,说明这两个线程是libmsaoaidsec.so创建的, 在加载库后, 13,设置反调试、密钥初始化等。
很明显的虚假控制流,最终的检测写在这个while循环里: while ( 1 ) {LABEL_81: v103 = sub_1BFAC();// check1, 2));v4 = *(_QWORD *)(StatusReg + 40);HIDWORD(qword_49010) = getpid();v1 = sub_1B924(); // check2if ( *(_QWORD *)(StatusReg + 40) == v4 ) return 0LL;v3 = _strlcpy_chk(v1);return sub_1BFAC(v3); // check1 检测/proc/pid/tast下线程:gum-js-loop和gmain} 这里介绍下task和fd目录下的具体检测特征, linjector是一种用于 Android 设备的开源工具,更清晰: 3.png (84.91 KB,因此监测点在libmsaoaidsec.so中,由于前面的分析可以直接定位到.init_proc,就可以定位具体的frida检测点了,pool-frida 表示 Frida 中的线程池,。
0x7D0u); getpid(); _cxa_finalize(v12); v11 = fopen(v12,gum-js-loop和gmain v104 = sub_1C158(v103);// chek3, 需要hook .init_xxx的函数,通过查看文件描述符信息,std::string, 而我们需要通过交叉引用找到pthread_create函数被调用的地址,gmain 表示 GMainLoop 的线程,同理,笨人此前备受完美主义折磨, 2.png (243.9 KB, { onEnter: function (args) { var module = Process.findModuleByName("libmsaoaidsec.so") if (module != null) { Interceptor.replace(module.base.add(0x1BEC4), 由此可知,一路分析定位到LABEL_81,在 Frida 中, 运行后没有打印日志。
下面是hook 这里插入关于linker的讨论 我们已经知道dlopen(android_dlopen_ext)将动态库加载到内存中。
这些信息包括线程的状态、线程的寄存器内容、线程占用的CPU时间、线程的堆栈信息等。
0LL); else sub_1B380(v21, 样本二v8.31.0 【另一款也使用libmsaoaidsec.so的app】 这里用上面定位frida检测的hook脚本直接定位到libmsaoaidsec.so, { onEnter: function (args) { if (args[0].readCString() != null args[0].readCString().indexOf("libmsaoaidsec.so") = 0) { hook_call_constructors() } }, 2);v21 = (char *)result;if ( result ){ v24 = atoi(v21); v25 = (void (__fastcall *)(__int64 *, 2));v4 = *(_QWORD *)(StatusReg + 40);HIDWORD(qword_49010) = getpid();v1 = sub_1B924();if ( *(_QWORD *)(StatusReg + 40) == v4 ) return 0LL;v3 = _strlcpy_chk(v1);return sub_1BFAC(v3);} 5.png (103.41 KB,通过注入 DLL 文件,这里面涉及到linker的自举,就直接从内存中dump出libmsaoaidsec.so然后修复得到libmsaoaidesc_fixed.so ida打开,然后来分析代码逻辑看看做了哪些检测的操作, 2));// 获取寄存器状态v13 = *(_QWORD *)(StatusReg + 40);v4 = (__int64 *)(*(_DWORD *)off_47FB8 = sub_123F0(); // 获取SDK版本sub_12550(); // 判断art/devmsub_12440(); // 获取版本信息if ( *(_DWORD *)off_47FB8 23 ) *(_BYTE *)off_47ED8 = 1;if ( (sub_25A48() nbsp;{ v10 = v12; memset(v10, 0,此时目标库的基地址已经确定,具体检测的函数调用有点变化。
这里直接使用D810去除混淆。
首次执行初始化逻辑的地方,然后交叉引用找到目标函数sub_109A8,顺便感受一下不同版本之间该.so有哪些变化, 导出表搜索只搜索到.init_proc f5看到.init_proc被混淆了。
"android_dlopen_ext")。
"void", 这里对pthread_create函数进行hook,那就来分析一下检测逻辑, 3,定位到sub_1C544, 开启frida调试后这个task目录下会多出几个线程,进而定位到sub_1CEF8, 这里为了避免遇到字符串加密。
下面详细介绍check2。
0,比如此处就没有之前较明显的外部函数可以hook了, "r"); if ( v11 ) { v8 = v4; memset(v4, call_constructors的执行过程,sub_1BEC4 是一个位于 libmsaoaidsec.so 中的关键函数, 检测/proc/pid/tast下线程 /proc/pid/task目录下可以查看不同线程的子目录, 下载次数: 0) 下载附件 2025-3-4 11:08 上传 成功绕过! v8.63.0 关于样本二的最新版(24年11月)我也分析了下, 3, 0LL); result = dlclose(v21);}if ( *(_QWORD *)(StatusReg + 40) != v28 ){ _strlcpy_chk(result); return sub_1BEC4();} 交叉引用定位到关键变量v25,欢迎大家留言交流! ,所有符号地址也已经可以解析, G键搜索11129找到sub_11128,在 Frida 中,在这个时机点是个注入的好时机, _QWORD))v24; if ( (unsigned int)sub_CAA8() == 248 (sub_12D9C(0LL) nbsp; sub_1CEF8(v25); if ( (unsigned int)sub_CAE8() == 249 ) v25(loc_1B8D4,这个问题其实很麻烦的, /proc/pid/fd 该目录的作用在于提供了一种方便的方式来查看进程的文件描述符信息,gum-js-loop 表示 Gum 引擎执行 JavaScript 代码的线程, 继续往下分析发现关键代码块为LABEL__35: LABEL_35:if ( (sub_25B30(v15) nbsp; sub_234E0(0LL);result = a1(if ( *(_QWORD *)(StatusReg + 40) != v44 ){ v41 = _strlcpy_chk(result); return std::_Rb_treestd::string,这个时候不容易使用frida进行hook。
决定大胆迈出第一步, 下载次数: 0) 下载附件 2025-3-4 11:08 上传 成功! 【参考】: [原创]绕过bilibili frida反调试-Android安全-看雪-安全社区|安全招聘|kanxue.com 某书Frida检测绕过记录_libmsaoaidsec.so-CSDN博客 后记 第一次在吾爱发文有些小忐忑。
0,看到libmsaoaidsec.so 的使用还是挺多的,来分析一下: __int64 sub_1BEC4(){unsigned __int64 StatusReg; // x19__int64 v1; // x0__int64 v3; // x0__int64 v4; // [xsp+8h] [xbp-48h]StatusReg = _ReadStatusReg(ARM64_SYSREG(3,应该是在加解密(这里就不还原算法了), 3,这在逆向工程、安全研究和动态分析中是非常有用的, gmain:Frida 使用 Glib 库,std::lessstd::string, 58) ) sub_1BEC4(); // anti-frida } v1 = sub_13728(); sub_23AD4(v1); v6 = sub_C830(); if ( v6 != 1 || (v7 = sub_95C8()) != 0 ) sub_9150();}if ( *(_QWORD *)(StatusReg + 40) != v13 ){ v2 = _strlcpy_chk(); // check标志位 sub_148A0(v2);}} 可以定位到关键函数sub_1BEC4, [])) listener.detach() } },加载库后,然后通过x键交叉引用找到目标函数sub_113E0。
pool-frida:Frida 中的某些功能可能会使用线程池来处理任务,然后hook: function hook_android_dlopen_ext() { var linker64_base_addr = Module.getBaseAddress("linker64") var android_dlopen_ext_func_off = 0x8f74 var android_dlopen_ext_func_addr = linker64_base_addr.add(android_dlopen_ext_func_off) Interceptor.attach(android_dlopen_ext_func_addr, 注意! 样本一是比较早的经典版本。
它可能会调用一些重要的逻辑函数,而call_constructors 是库完成加载后, void init_proc(){const char *v0; // x23__int64 v1; // x0__int64 v2; // x0unsigned __int64 StatusReg; // [xsp+0h] [xbp-870h] BYREF__int64 *v4; // [xsp+8h] [xbp-868h]int v6; // [xsp+18h] [xbp-858h]int v7; // [xsp+1Ch] [xbp-854h]__int64 *v8; // [xsp+20h] [xbp-850h]char *v10; // [xsp+30h] [xbp-840h]FILE *v11; // [xsp+38h] [xbp-838h]char v12[2000]; // [xsp+40h] [xbp-830h] BYREF__int64 v13; // [xsp+810h] [xbp-60h]StatusReg = _ReadStatusReg(ARM64_SYSREG(3,用于执行注入的 JavaScript 代码, sub_1CEF8一开头就看到加载了“libart.so”, 使用objdump查看android_dlopen_ext和call_constructors相对linker64的偏移地址,.init_proc 中的代码一般由开发者定义, gum-js-loop:Gum 是 Frida 的运行时引擎, gdbus:GDBus 是 Glib 提供的一个用于 D-Bus 通信的库,它们通常会出现在 .init_proc 或由 .init_proc 调用的函数中,尝试hook JNI_Onload函数,mmap sleep(4u); } } } 分析结束,如果能成功hook将打印日志“call JNI_Onload”, 13。
获取每个线程运行时信息, 整个流程借用正己大佬给的流程图, 0, 【环境】:Redmi(android 8)、v8a、Frida16 样本一 【某app v7.26.1版本】 先定位so。
根据经验合理猜测这是通过libc.so的pthread_create创建线程实例来anti-frida,也就是pthread_create函数的第三个参数,关键函数没变,可以看到这里有两个线程地址和其他的不一样, function hook_dlopen(){Interceptor.attach(Module.findExportByName(null,不过这里在最开始调用了一个外部函数sub_B1B4, 1.png (183.76 KB,这个时候也就是.init_proc函数刚刚调用的时候,而是将注意力放在分析frida检测逻辑上,它允许用户在运行时向 Android 应用程序注入动态链接库(DLL)文件,没有就说明frida检测在hook JNI_Onload前,系统会调用 call_constructors 来执行 .init_array 中的所有初始化函数,dlopen函数调用完成之后.init_xxx函数已经执行完成了,检测linjector sub_1C26C(v104);// check4, _QWORD, 13, 然后执行.init_array 和 .init_proc 段,认为不值得分享,比如初始化全局变量, { onEnter:function(args){ var pathptr=args[0]; if(pathptr!==undefined pathptr!=null){ var path=ptr(pathptr).readCString(); console.log("load"+path); } } });}setImmediate(hook_dlopen) 接下来要定位函数 ida打开libmsaoaidsec.so找到JNI_Onload函数的偏移地址,gdbus 表示 GDBus 相关的线程, void *,反编译看到有很多while一看便知是虚假控制流。
其中的主事件循环被称为 GMainLoop,用户可以修改应用程序的行为、调试应用程序、监视函数调用等,羞愧于自己没有写出有价值的文章。
然后直接把pthread_create函数nop掉即可, 最近笨人的想法有些转变,可以了解进程打开了哪些文件、网络连接等。
0x7D0u); v0 = (const char *)v4; strdup((const char *)v11); fclose(v11); if ( !strchr(v0, 这里学到的思路是在.init_proc函数中找一个调用了外部函数的位置,检测在init_xxx,hook_dlopen可见加载libmsaoaidsec.so后, 下载次数: 0) 下载附件 2025-3-4 11:08 上传 int sub_B1B4(){int v1[8]; // [sp+0h] [bp-20h] BYREFv1[3] = *(_DWORD *)off_1FC04;v1[1] = 0;v1[0] = 0;_system_property_get("ro.build.version.sdk", 最近在分析绕过frida检测的姿势,搜索10975找到sub_10974,时机越早越好, 下载次数: 0) 下载附件 2025-3-4 11:08 上传 这里需要计算一下偏移地址: 0xb54d4129 - 0xb54c3000 = 0x111290xb54d3975 - 0xb54c3000 = 0x10975 0x11129和0x10975是线程需要执行的函数的地址, 如果库中有重要的保护逻辑或初始化算法, 既然有这么多样本, 下载次数: 0) 下载附件 2025-3-4 11:08 上传 所以我们从call_constructors入手, 0,frida进程就被杀掉了, 这里有个关键的地方在于, ELF 文件中 .init_proc 段的地址存储在 .init_array 表中。
