吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 3690|回复: 53
上一主题 下一主题
收起左侧

[Android 原创] bilibili XHS frida检测分析绕过

  [复制链接]
跳转到指定楼层
楼主
chenchenchen777 发表于 2025-3-6 16:09 回帖奖励
本帖最后由 chenchenchen777 于 2025-3-10 19:16 编辑

本文是对于了版本7.26.1,以及版本7.76.0的frida检测进行的分析研究以及绕过

bilibili 7.26.1

bilibili的旧版本frida检测

可以看到按照Spawn的方式启动的时候,直接frida就被检测了。我们按照 hook dlopen去查看可能出现的对应frida检测的so文件。

function hook_dlopen(soName = '') {
    Interceptor.attach(Module.findExportByName(null, "android_dlopen_ext"), {
        onEnter: function(args) {
            var pathptr = args[0];
            if (pathptr) {
                var path = ptr(pathptr).readCString();
                console.log("Loading: " + path);
                if (path.indexOf(soName) >= 0) {
                    console.log("Already loading: " + soName);
                    // hook_system_property_get();
                }
            }
        }
    });
}
setImmediate(hook_dlopen, "libmsaoaidsec.so");

可以看到的是libmsaoaidsec.so文件,在dlopen了之后,frida就被检测到了,所以大概率的可能是在libmsaoaidsec.so进行的frida检测。在这里之前,我们需要知道是在哪进行HOOK是最为有用的

这里我们可以通过在dlopen结束之后,去HOOK JNI_Onload函数,去判断检测函数在JNI_Onload之前还是之后,我们通过IDA可以去查看JNI_Onload的地址。这里是在JNI_Onload之前出现的frida检测。

function hook_JNI_OnLoad(){
    let module = Process.findModuleByName("libmsaoaidsec.so")
    Interceptor.attach(module.base.add(0xC6DC + 1), {
        onEnter(args){
            console.log("JNI_OnLoad")
        }
    })
}

我们通过HOOK 进程创建,来看看对于frida检测的线程是在哪里启动的。在复现过程中,原作者使用了hook _system_property_get函数,这里是 init_proc函数较早的位置,这里涉及到了安卓源码中dlopen和.init_xx函数的执行流程比较,在我的so文件执行流程的过程中有细节分析。

function hook_system_property_get() {
    var system_property_get_addr = Module.findExportByName(null, "__system_property_get");
    if (!system_property_get_addr) {
        console.log("__system_property_get not found");
        return;
    }

    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
                }
            }
        }
    });
}

由于我们知道,frida检测的是在JNI_Onload函数之前,那么我们就要在init_proc的越早的地方可以进行HOOK,我们HOOK的地方就是线程创建的位置pthread_create。

function hook_pthread_create() {
    var pthread_create = Module.findExportByName("libc.so", "pthread_create");
    var libmsaoaidsec = Process.findModuleByName("libmsaoaidsec.so");

    if (!libmsaoaidsec) {
        console.log("libmsaoaidsec.so not found");
        return;
    }

    console.log("libmsaoaidsec.so base: " + libmsaoaidsec.base);

    if (!pthread_create) {
        console.log("pthread_create not found");
        return;
    }

    Interceptor.attach(pthread_create, {
        onEnter: function(args) {
            var thread_ptr = args[2];
            if (thread_ptr.compare(libmsaoaidsec.base) < 0 || thread_ptr.compare(libmsaoaidsec.base.add(libmsaoaidsec.size)) >= 0) {
                console.log("pthread_create other thread: " + thread_ptr);
            } else {
                console.log("pthread_create libmsaoaidsec.so thread: " + thread_ptr + " offset: " + thread_ptr.sub(libmsaoaidsec.base));
            }
        },
        onLeave: function(retval) {}
    });
}

在这里我们通过去HOOK dlopen的位置,通过dlopen的位置去HOOK system_property_get 当参数是ro.build.version.sdk然后去hook pthread_create

dlopen ———>system_property_get————>pthread_create

这里去比较了对应所以由pthread_create 创建的线程,当对应的线程的地址在libmsaoaidsec.so的地址区域内的时候,打印对应的地址以及偏移。可以看到这里有两个线程出现了

我们可以去通过IDA看看



这些位置的像出现的 strcmp        openat        strstr.......很多的frida的检测,我们交叉引用一下看看pthread_create,然后直接实现NOP就可以了


总体代码

function hook_dlopen(soName = '') {
    Interceptor.attach(Module.findExportByName(null, "android_dlopen_ext"), {
        onEnter: function(args) {
            var pathptr = args[0];
            if (pathptr) {
                var path = ptr(pathptr).readCString();
                console.log("Loading: " + path);
                if (path.indexOf(soName) >= 0) {
                    console.log("Already loading: " + soName);
                    hook_system_property_get();
                }
            }
        }
    });
}

function hook_system_property_get() {
    var system_property_get_addr = Module.findExportByName(null, "__system_property_get");
    if (!system_property_get_addr) {
        console.log("__system_property_get not found");
        return;
    }

    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()
                }
            }
        }
    });
}

function hook_pthread_create() {
    var pthread_create = Module.findExportByName("libc.so", "pthread_create");
    var libmsaoaidsec = Process.findModuleByName("libmsaoaidsec.so");

    if (!libmsaoaidsec) {
        console.log("libmsaoaidsec.so not found");
        return;
    }

    console.log("libmsaoaidsec.so base: " + libmsaoaidsec.base);

    if (!pthread_create) {
        console.log("pthread_create not found");
        return;
    }

    Interceptor.attach(pthread_create, {
        onEnter: function(args) {
            var thread_ptr = args[2];
            if (thread_ptr.compare(libmsaoaidsec.base) < 0 || thread_ptr.compare(libmsaoaidsec.base.add(libmsaoaidsec.size)) >= 0) {
                console.log("pthread_create other thread: " + thread_ptr);
            } else {
                console.log("pthread_create libmsaoaidsec.so thread: " + thread_ptr + " offset: " + thread_ptr.sub(libmsaoaidsec.base));
            }
        },
        onLeave: function(retval) {}
    });
}
function nop_code(addr)
{
    Memory.patchCode(ptr(addr),4,code => {
        const cw =new ThumbWriter(code,{pc:ptr(addr)});
        cw.putNop();
        cw.putNop();
        cw.flush();
    })
}

function bypass()
{
    let module = Process.findModuleByName("libmsaoaidsec.so")
    nop_code(module.base.add(0x010AE4))
    nop_code(module.base.add(0x113F8))
}
setImmediate(hook_dlopen, "libmsaoaidsec.so");

这里就绕过frida了,或者通过直接在IDA中去patch掉上面两个位置的pthread_create,然后把补丁之后的so再放到APK中也可以。

参考文章:[原创]绕过bilibili frida反调试-Android安全-看雪-安全社区|安全招聘|kanxue.com

bilibili7.76.0

7.26.1:

在高一点的版本上面,pthread_create函数是被隐藏了的

但是其实我们通过之前的方法也能看到对应的pthead_create创建线程的位置

function hook_dlopen(soName = '') {
    Interceptor.attach(Module.findExportByName(null, "android_dlopen_ext"), {
        onEnter: function(args) {
            var pathptr = args[0];
            if (pathptr) {
                var path = ptr(pathptr).readCString();
                console.log("Loading: " + path);
                if (path.indexOf(soName) >= 0) {
                    console.log("Already loading: " + soName);
                    hook_system_property_get();
                }
            }
        }
    });
}

function hook_system_property_get() {
    var system_property_get_addr = Module.findExportByName(null, "__system_property_get");
    if (!system_property_get_addr) {
        console.log("__system_property_get not found");
        return;
    }

    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()
                }
            }
        }
    });
}

function hook_pthread_create() {
    var pthread_create = Module.findExportByName("libc.so", "pthread_create");
    var libmsaoaidsec = Process.findModuleByName("libmsaoaidsec.so");

    if (!libmsaoaidsec) {
        console.log("libmsaoaidsec.so not found");
        return;
    }

    console.log("libmsaoaidsec.so base: " + libmsaoaidsec.base);

    if (!pthread_create) {
        console.log("pthread_create not found");
        return;
    }

    Interceptor.attach(pthread_create, {
        onEnter: function(args) {
            var thread_ptr = args[2];
            if (thread_ptr.compare(libmsaoaidsec.base) < 0 || thread_ptr.compare(libmsaoaidsec.base.add(libmsaoaidsec.size)) >= 0) {
                console.log("pthread_create other thread: " + thread_ptr);
            } else {
                console.log("pthread_create libmsaoaidsec.so thread: " + thread_ptr + " offset: " + thread_ptr.sub(libmsaoaidsec.base));
            }
        },
        onLeave: function(retval) {}
    });
}
setImmediate(hook_dlopen, "libmsaoaidsec.so");

这里可以看到在libmsaoaidsec.so中创建了三个新的线程,这里多半也就是进行frida检测的位置的了

pthread_create libmsaoaidsec.so thread: 0x76bcce0544 offset: 0x1c544
pthread_create libmsaoaidsec.so thread: 0x76bccdf8d4 offset: 0x1b8d4
pthread_create libmsaoaidsec.so thread: 0x76bcceae5c offset: 0x26e5c

同时这里我们进行HOOK的检测frida线程在哪的时候,也是在system_property_get的函数的位置进行HOOK的,但是实际上这里的位置也已经被混淆了,但是这个函数没有被混淆,可以直接在导入表里面找到的,那么我们就去交叉引用看看在哪引用的



其实是能够发现还是在init_proc的sub_123F0函数的位置的

但是这里去判断参数的为"ro.build.version.sdk"已经被混淆了,但是我们既然能通过这个代码找到对应的线程,说明实际上还是去执行了 _system_property_get("ro.build.version.sdk")这个函数的,而且既然代码可以直接检测得到frida检测的线程的地址,那么说明检测点还是再_system_property_get("ro.build.version.sdk")之前的。

我们同样按照交叉引用看看pthread_create 被混淆成了什么




其实通过参数的形式,我们就可以判断大概率就是被混淆了的pthead_create函数

正面对抗

按照之前旧版本的frida检测,我们的反检测是通过NOP掉对应的frida检测线程,在7.26.1中frida检测是有两个线程的,而在7.76.0中,这里的frida检测有了三个线程,那么我们也可以对于这里的三个线程进行NOP

这里按照最原始的方法,不去NOP掉pthead_create函数,而是去patch掉对应的frida函数。以及其中的NOP的实际,我选择的位置是在判断到进入libmsaoaidsec.so的时候,并且开始进行frida检测线程创建的pthead_create函数时期

function hook_pthread_create() {
    var pthread_create = Module.findExportByName("libc.so", "pthread_create");
    var libmsaoaidsec = Process.findModuleByName("libmsaoaidsec.so");

    if (!libmsaoaidsec) {
        console.log("libmsaoaidsec.so not found");
        return;
    }

    console.log("libmsaoaidsec.so base: " + libmsaoaidsec.base);

    if (!pthread_create) {
        console.log("pthread_create not found");
        return;
    }

    Interceptor.attach(pthread_create, {
        onEnter: function(args) {
            var thread_ptr = args[2];
            if (thread_ptr.compare(libmsaoaidsec.base) < 0 || thread_ptr.compare(libmsaoaidsec.base.add(libmsaoaidsec.size)) >= 0) {
                console.log("pthread_create other thread: " + thread_ptr);
            } else {
                console.log("pthread_create libmsaoaidsec.so thread: " + thread_ptr + " offset: " + thread_ptr.sub(libmsaoaidsec.base));
                Interceptor.replace(libmsaoaidsec.base.add(0x1c544),new NativeCallback(function(){
                    console.log("Interceptor.replace: 0x1c544")
                },"void",[]))
                Interceptor.replace(libmsaoaidsec.base.add(0x1b8d4),new NativeCallback(function(){
                    console.log("Interceptor.replace: 0x1c544")
                },"void",[]))
                Interceptor.replace(libmsaoaidsec.base.add(0x26e5c),new NativeCallback(function(){
                    console.log("Interceptor.replace: 0x1c544")
                },"void",[]))
            }
        },
        onLeave: function(retval) {}
    });
}

可以看到是直接绕过了frida检测的位置的。并且我写的一个frida打印函数也是成功执行了

取巧绕过

绕过最新版bilibili app反frida机制-Android安全-看雪-安全社区|安全招聘|kanxue.com

在这一篇文章中,作者并没有去实现正面对抗,而且取巧绕过了,通过的方式就是在HOOK dlsym函数,在进入libmsaoaidsec.so之后去HOOK dlsym 判断调用pthead_create函数的次数,在前两次进行调用pthead_create函数时,去调用fake_pthead_create函数,从而实现frida线程不启动,达成绕过。

以下是取巧绕过的代码(复制于上面的网址):

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_create
var fake_pthread_create = create_fake_pthread_create()
var dlopen_interceptor = hook_dlopen()

同样也可以实现绕过,不过,我们其实能知道,最开始的时候,其实是发现在libmsaoaidsec.so一共是开启了三个线程的,其实我觉得这里可以把count设置到三

onLeave: function(retval) {
                if (count == 1) {
                    retval.replace(fake_pthread_create)
                }
                else if (count == 2) {
                    retval.replace(fake_pthread_create)
                }
                else if (count == 3) {
                    retval.replace(fake_pthread_create)
                    // 完成3次替换, 停止hook dlsym
                    interceptor.detach()
                }
            }

[md]# XHS 8.32.0
在XHS的8.32.0中这里的代码仍然是可以使用的,我这里也是测试过的,不过对于代码里面新的修改

function hook_dlopen(soName = '') {
    Interceptor.attach(Module.findExportByName(null, "android_dlopen_ext"), {
        onEnter: function(args) {
            var pathptr = args[0];
            if (pathptr) {
                var path = ptr(pathptr).readCString();
                console.log("Loading: " + path);

                if (path.indexOf(soName) >= 0) {
                    console.log("Already loading: " + soName);
                    hook_system_property_get()  //这里是在__system_property_get之后进行的frida检测,这里可以去patch frida检测
                    // hook_JNI()
                    // setTimeout(hook_JNI,100) 这里没有出现JNI_onLoad的输出就挂掉了,说明是在之前进行frida检测
                }
            }
        }
    });
}
setImmediate(hook_dlopen,"libmsaoaidsec.so")
function hook_JNI()
{
    let module_msaoaidsec = Process.findModuleByName("libmsaoaidsec.so")
    if(!module_msaoaidsec){
        console.log("module_msaoaidsec wrong")
    }
    console.log("get module_msaoaidsec")
    Interceptor.attach(module_msaoaidsec.base.add(0x013A4C+1),{
        onEnter:function()
        {
            console.log("JNI_onLoad")
        },
        onLeave:function(){
        }
    })
}
function hook_system_property_get() {
    var system_property_get_addr = Module.findExportByName(null, "__system_property_get");
    if (!system_property_get_addr) {
        console.log("__system_property_get not found");
        return;
    }

    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");//在这里patch
                    hook_pthread_create();
                    // bypass()
                    //这里可以开始进行HOOK
                }
            }
        }
    });
}
function call_func(){
    console.log("frida call_func")
}
function hook_pthread_create() {
    var pthread_create = Module.findExportByName("libc.so", "pthread_create");
    var libmsaoaidsec = Process.findModuleByName("libmsaoaidsec.so");
    let num =0;
    if (!libmsaoaidsec) {
        console.log("libmsaoaidsec.so not found");
        return;
    }

    console.log("libmsaoaidsec.so base: " + libmsaoaidsec.base);

    if (!pthread_create) {
        console.log("pthread_create not found");
        return;
    }

    Interceptor.attach(pthread_create, {
        onEnter: function(args) {
            var thread_ptr = args[2];
            if (thread_ptr.compare(libmsaoaidsec.base) < 0 || thread_ptr.compare(libmsaoaidsec.base.add(libmsaoaidsec.size)) >= 0) {
                // console.log("pthread_create other thread: " + thread_ptr);
            } else {
                console.log("pthread_create libmsaoaidsec.so thread: " + thread_ptr + " offset: " + thread_ptr.sub(libmsaoaidsec.base));
               if(num==0){
                Interceptor.replace(libmsaoaidsec.base.add(0x1c544),new NativeCallback(function(){
                    console.log("Interceptor.replace: 0x1c544")
                },"void",[]))
                Interceptor.replace(libmsaoaidsec.base.add(0x1b8d4),new NativeCallback(function(){
                    console.log("Interceptor.replace: 0x1c544")
                },"void",[]))
                Interceptor.replace(libmsaoaidsec.base.add(0x26e5c),new NativeCallback(function(){
                    console.log("Interceptor.replace: 0x1c544")
                },"void",[]))
                num++
            }

            }
        },
        onLeave: function(retval) {}
    });
}

这里是可以绕过XHS的frida检测的,也是这三个线程的frida检测



这样我尝试过,也能实现绕过的。
同时在其他高版本的各种frida检测中,也会去HOOK pthread_create 函数 去NOP 线程,但是同时在安全对抗升级之后,也开始去检测pthread_create是否被HOOK了,但是同样也有很多绕过方式,比如像 pthread_create 会去调用更底层的 clone函数 同样的的还会去调用Sys_clone函数。 pthread_create ——> clone ——>Sys_clone等多种方式,去绕过检测。
个人博客: b站frida反检测分析绕过 - fisherman-ovo - 博客园[/md]

本文章中所有内容仅供学习交流使用,不用于其他任何目的,擅自使用本文讲解的技术而导致的任何意外,与作者不负责

免费评分

参与人数 13吾爱币 +14 热心值 +11 收起 理由
pk8900 + 3 + 1 先收藏,有时间学习一下。
抱歉、 + 1 用心讨论,共获提升!
heartfilia + 1 + 1 我很赞同!
st0rm + 1 + 1 谢谢 @Thanks!
浮尘晓梦 + 1 谢谢@Thanks!
helian147 + 1 + 1 热心回复!
zhczf + 1 + 1 我很赞同!
0qxqy0 + 1 + 1 我很赞同!
tinyclown + 1 + 1 用心讨论,共获提升!
yan999 + 1 + 1 我很赞同!
sgf227 + 1 热心回复!
xiaokarami + 1 + 1 用心讨论,共获提升!
就往丶 + 1 + 1 我很赞同!

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

推荐
zy7dt20 发表于 2025-4-11 21:39
楼主您好,在“dlopen和.init_xx函数的执行流程比较,在我的so文件执行流程的过程中有细节分析”这里我不太明白,是dlopen先执行还是.init_xx函数先执行呢?我根据您的帖子绘制了张流程图,但是有关“进行真正的so文件加载”的部分我不太确定是放在流程图中的位置,还是放在find_library()旁边的位置。

PixPin_2025-04-11_21-36-30.jpg (99.84 KB, 下载次数: 0)

PixPin_2025-04-11_21-36-30.jpg
推荐
zy7dt20 发表于 2025-4-11 22:03
zy7dt20 发表于 2025-4-11 21:39
楼主您好,在“dlopen和.init_xx函数的执行流程比较,在我的so文件执行流程的过程中有细节分析”这里我不太 ...

我回去看了do_dlopen函数的代码,搞明白了,感谢楼主的教程!
应该是这样的吧:
do_dlopen{
    find_library();
    so.init_proc();
    so.init_array();
    so.JNI_Onload();
    return so
}
沙发
sgf227 发表于 2025-3-6 17:21
3#
洞见未来 发表于 2025-3-6 17:27
谢谢分享b站frida绕过的方法。
4#
hihopkc 发表于 2025-3-6 17:37
我早上还弄了下tantan的发现frida和r0capture,能抓到包,但是没有任何证书导出来,存储权限已经给到了,奇怪了
5#
frankwang 发表于 2025-3-6 17:45
b站frida绕过的方法很有用,需要慢慢的学习,感谢楼主分享
6#
techliu 发表于 2025-3-6 18:32
学到了,感谢楼主分享
7#
wsshh123 发表于 2025-3-6 18:33
厉害,搞 b 站
8#
Chrystal 发表于 2025-3-6 19:17
学习一下!
9#
ComPE 发表于 2025-3-6 19:47
感谢分享,干货
10#
laustar 发表于 2025-3-6 20:06
学习一下。
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2025-5-22 09:08

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表