首页
社区
课程
招聘
[原创]安卓逆向:某加固 frida检测绕过方法
发表于: 2小时前 54

[原创]安卓逆向:某加固 frida检测绕过方法

2小时前
54

本文为MzYw5Yqg5Zu6加固的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应该就结束了。


传播安全知识、拓宽行业人脉——看雪讲师团队等你加入!

收藏
免费 0
支持
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回