-
-
[原创]安卓逆向某加固 frida检测绕过方法
-
发表于: 2小时前 65
-
本文为某加固的frida检测绕过方法,纯个人分享,绕过是通过frida的native hook并结合ida pro分析绕过。
hook dlopen
首先hook一下dlopen函数,检测一下该加固lib的加载:
function hook_dlopen(target_module) { var funcPtr = Process.findModuleByName("libdl.so").findExportByName("android_dlopen_ext"); if (!funcPtr) { funcPtr = Process.findModuleByName("libdl.so").findExportByName("dlopen"); } if (!funcPtr) { console.log("[-] dlopen not found"); return; } console.log("[*] Hooking dlopen at: " + funcPtr); Interceptor.attach(funcPtr, { onEnter: function (args) { try { var path = args[0].readCString(); console.log("[dlopen] " + path); if (!is_lib_attached && path.indexOf(target_module) !== -1) { is_lib_attached = true; } } catch (e) { } }, onLeave: function (retval) { if (is_lib_attached && flag) { flag = 0; var target_base = Process.getModuleByName(target_module).base; console.log("[*] " + target_module + " loaded at: " + target_base); // hook_sub_47A50(target_base); } } });}这里加入了防止重复检测的逻辑。因为在dlopen() onEnter和onLeave里进行判断,dlopen()需要调用很多次,我们需要的lib只加载一次就够了,不要每次都就检测。
还有一点需要注意,frida在17+的版本里更新了api,详情可以去查一下,这里用的是frida 17.5.2。
运行截图如下:


程序检测到了frida直接就挂掉了,我们需要检测一下在哪里导致我们的程序挂掉了。
我们使用了一个ida的插件: stalker_trace_so
直接复制到ida的plugin目录下就行了,会在ida里输出对应的用法。这个frida脚本用的是旧版本的frida api,如果用的是frida 17+版本,需要把对应的api修改一下。
我们运行之后结果如下:


sub_9740()
在调用sub_9740()的时候挂了,我们直接在ida里去搜索这个函数:


进一步查看:



这个地址直接就是0。这里通过对汇编的分析,再结合gemini,我怀疑这里是在调用got表里的东西。
如果直接到这里那肯定是个Invalid Address,但是不确定运行的时候会不会动态把这个值给填进来,所以写个frida脚本检测一下63910这个位置:
const hook_sub = (base) => { var gotEntryAddr = base.add(0x63910); console.log("[*] GOT Entry Address (X16): " + gotEntryAddr); var jumpTarget = gotEntryAddr.readPointer(); console.log("[*] Jump Target Address (X17): " + jumpTarget); console.log("[*] Target Symbol: " + DebugSymbol.fromAddress(jumpTarget));}输出如下:


并没有填值进去,就是单纯的0,那很好判断,这就是检测到了frida之后干掉程序的地方。我们直接看一下刚刚sub_9740()的交叉引用就行。
sub_3E15C()
通过交叉引用找到了sub_3E15C(),函数比较长,就不放了。我的思路就是看看程序是不是在这里挂掉的,于是直接写脚本,在onEnter里输出一下log,onLeave里log一下:
const hook_sub_3E15C = (base) => { var sub = base.add(0x3E15C); var counter = 0; Interceptor.attach(sub, { onEnter: function (args) { counter++; console.log("[*] sub_3E15C called " + counter + " times"); }, onLeave: function (retval) { console.log("[*] sub_3E15C returned: " + retval); } });}运行了50次之后,发现:


只有call,没有return。猜测这个函数其实是递归调用的,调用了50次挂了,并没有return,说明就是在这个函数里结束了程序。
通过对sub_3E15C()的分析,发现sub_9740()就只调用了两次,那这样的sub_47A50()就非常可以了,高度怀疑程序在这里崩溃了。


sub_94D0()
于是我修改了之前的代码,不断的检测sub_47A50(),果然发现程序就是崩溃在了这个函数里:


而且其返回值是有一定的规律的。
通过之前的分析,我将sub_94D0()函数名修改成了to_address_0()。ida同时自动调整了部分函数的命名,可以通过xref查看:


这样的话谁调用了to_address_0()一目了然,在sub_47A50()里果然有两处调用:


还有一个地方:


也就是说,在这个函数里,有两处逻辑,可能会导致程序挂掉。既然已经定位到了这里,我也不想关心正常的逻辑怎么走了,我直接在第50次调用这个函数的时候直接修改返回值就行了:
var counter = 0;const hook_sub_47A50 = (base) => { var sub = base.add(0x47A50); Interceptor.attach(sub, { onEnter: function (args) { counter++; console.log("[*] sub_47A50 called " + counter + " times"); if (counter == 50) { this.context.pc = this.context.lr; this.context.x0 = ptr("0x7ff09c17e0"); } }, onLeave: function (retval) { console.log("[*] sub_47A50 returned: " + retval); } });}再次运行,程序就进入到了主页面中,没有被挂掉,到这里bypass应该就结束了。
[培训]Windows内核深度攻防:从Hook技术到Rootkit实战!