CVE漏洞中文网

0DayBank一个专门收集整理全球互联网漏洞的公开发布网站
  1. 首页
  2. 安全资讯
  3. 正文

移动端安全 结合android so加载机制绕过libmsaoaidsec

2024年11月12日 172点热度 0人点赞 0条评论

本文将对结合android so加载机制绕过libsaoaidsec进行一些探索思考

使用到工具及环境

pixel 2 android 10
frida 16.1.4
bilibili 7.76.0 apk

确定存在检测机制的库

可以看到小破站app存在frida的检测机制,frida的进程过了一会就被kill了

下面就想办法绕过这个检测机制,最直接的思路就是找到检测的函数,然后hook掉它
通常app对frida的检测函数都被写在native层,要想找到检测函数,首先找到它所处的so库。那么该如何找到它所属的库呢?

我们可以从android的so加载机制入手。每加载一个自定义so库,并在加载前和加载后分别打印相关信息,如果加载到某个库后frida进程被kill,那么就说明检测机制存在于这个库

Java有两种方式加载库文件,一种是System.load(),另一种是System.loadLibrary()。android采用第二种。


追溯loadLibrary0,这个函数前面都是对参数的一些判断逻辑,包括文件名是否规范及库文件是否存在(findLibrary(libraryName)),加载库的函数是nativeLoad

追溯nativeLoad,发现它是一个jni函数


根据jni函数特定的命名规则,在源码中搜索Runtime_nativeLoad(类名_函数名)


追踪JVM_NativeLoad

查看LoadNativeLibrary

这里使用OpenNativeLibrary加载native库

追踪OpenNativeLibrary。如果caller_location不为nullptr,则查找导出的命名空间,并使android_dlopen_ext加载库,如果没有找到命名空间或caller_location为nullptr,则直接使用dlopen加载库

caller_location通常不会是nullptr,指向调用该方法的类的dex文件位置。所以继续跟踪android_dlopen_ext

跟进__loader_android_dlopen_ext


继续跟进


最后来到do_dlopen

调用链大概如下

我们可以hook其中的android_dlopen_ext,而且它的第一个参数就是库的路径
代码如下:

function hook_android_dlopen_ext() {
    Interceptor.attach(Module.findExportByName(null, "android_dlopen_ext"),
        {
            onEnter: function (args) {
                this.fileName = args[0].readCString()
                console.log(`dlopen onEnter: ${this.fileName}`)
            }, onLeave: function(retval){
                console.log(`dlopen onLeave fileName: ${this.fileName}`)
            }
        }
    );
}

运行后可以看到在libmsaoaidsec库这儿终止了,说明检测机制在libmsaoaidsec中

注入时机

那么该如何定位检测函数呢?先来列举几个检测机制中可能会存在的函数:

ptrace 用于跟踪进程,PTRACE_TRACEME 可以被用来检测当前进程是否被跟踪
pthread_create 创建新线程的函数,可以用来检测新线程的创建,有时用于检测Frida注入的线程
openat和open 这些函数可以被用来检测特定的文件路径,例如:/data/local/tmp/下的Frida特征文件
strstr和strcmp 字符串处理函数,可以被用来检测特定的字符串模式,如 "frida"、"gum-js-loop" 等
readlinkat 用于读取符号链接指向的文件路径,可能被用来检测Frida相关的文件
__system_property_get 用于获取系统属性,被用来检测Frida相关的属性
pthread_startPv和start_thread 这些是与线程启动相关的函数,被用来检测线程的启动行为
dlsym 用于动态链接库中符号的解析,被用来检测Frida的符号

其中pthread_create接收一个函数指针(图中a3),它会创建a3函数的线程

所以libmsaoaidsec如果有pthread_create,我们可以尝试hook住它,它接收的函数就有可能是检测函数。那么接下来就要解决如何hook libmsaoaidsec库中的pthread_create。
但是hook的时机很重要。我们要赶在检测函数被调用前注入,否则frida进程就被kill了

首先我们要清楚Android链接器(linker)在加载.so库时会按顺序执行.init、.init_array(init_array是ELF文件格式中的一部分,它包含了一个函数指针数组)和JNI_OnLoad。检测函数
大概率会在这两个时期中的某一个启动。

.init、.init_array

.init、.init_array是在do_dlopen中执行的,我们回到先前分析的调用链的最后:do_dlopen

soinfo* si = find_library(ns, translated_name, flags, extinfo, caller);

find_library函数来查找指定的库。这个函数根据命名空间ns、转换后的库名translated_name、加载标志flags、扩展信息extinfo和调用者caller来查找库。如果找到了库,si将指向库的soinfo结构;如果没有找到,si将为nullptr

si->call_constructors();

call_constructors负责调用库中的.init段和.init_array段中的函数

JNI_OnLoad

JNI_OnLoad函数的执行是在LoadNativeLibrary函数中
可以回到调用链的LoadNativeLibrary,可以看到JNI_Onload的相关调用代码,而且这些代码是在OpenNativeLibrary之后的

所以我们可以在原先的hook脚本中假如对jni_onload是否调用的判断,来确定检测机制调用的时机

function hook_android_dlopen_ext() {
    Interceptor.attach(Module.findExportByName(null, "android_dlopen_ext"),
        {
            onEnter: function (args) {
                this.fileName = args[0].readCString()
                console.log(`dlopen onEnter: ${this.fileName}`)
            }, onLeave: function(retval){
                console.log(`dlopen onLeave fileName: ${this.fileName}`)
                if(this.fileName != null && this.fileName.indexOf("libmsaoaidsec.so") >= 0){
                    let JNI_OnLoad = Module.getExportByName(this.fileName, 'JNI_OnLoad')
                    console.log(`dlopen onLeave JNI_OnLoad: ${JNI_OnLoad}`)
                }
            }
        }
    );
}

结果发现在JNI_Onload没被打印了。所以检测机制在JNI_OnLoad之前,即.init,.init_array执行中。因此我们可以尝试hook call_constructors,在onEnter中注入代码

先找到call_constructors的offset
使用adb把设备的linker64拉下来
/apex/com.android.runtime/bin/linker64

先找到call_constructors的offset

于是可以编写hook到call_constructors

let linker64_base_addr = Module.getBaseAddress('linker64')
let offset = 0x50cf8    // __dl__ZN6soinfo17call_constructorsEv
let call_constructors = linker64_base_addr.add(offset)

时机把握好了之后,我们就可以尝试hook pthread_create
在android系统中,pthread_create位于libc.so,hook代码如下:

function hook_pthred_create(){
    console.log("libmsaoaidsec.so --- " + Process.findModuleByName("libmsaoaidsec.so").base)
    Interceptor.attach(Module.findExportByName('libc.so','pthread_create'),{
        onEnter(args){
            let func_addr = args[2]
            console.log(`The thread Called function address is: ${func_addr}`)
        }
    })
}

整体代码如下:

确定检测函数

执行脚本

在输出中,我们看到了两个不同的函数地址被多次提及:
0x7ba4cbcaa8被多次提及。
0x7ae60228d4和0x7ae602de5c也被多次提及。
而根据输出,libmsaoaidsec.so的基地址是0x7ae6007000。我们可以通过计算这些函数地址与基地址的差值与libmsaoaidsec.so的大小的比较来判断它们是否属于同一个库。

先来看看库的大小

对于0x7ae60228d4和0x7ae602de5c:
0x7ae60228d4-0x7ae6007000=0x1b8d4
0x7ae602de5c-0x7ae6007000=0x26e5c
这两个地址与基地址的差值都在合理的范围内,表明它们很可能是libmsaoaidsec.so中的函数。

对于 0x7ba4cbcaa8:
0x7ba4cbcaa8-0x7ae6007000=0xbecb5aa8
这个差值远远超出了libmsaoaidsec.so的大小699ff0,因此它不可能是libmsaoaidsec.so中的函数。这个地址很可能属于另一个库或应用程序的其他部分

adb pull下libmsaoaidsec.so,用IDA打开libmsaoaidsec.so,查找偏移
查看0x1b8d4地址处对应的函数

这个函数是一个死循环,它会不停的调用三个函数 sub_1AE48、sub_1AB54、sub_1B730

sub_1AE48
从/proc/pid/status文件中提取并返回TracerPid的值,并根据该值判断进程是否正在被调试


sub_1AB54
打开sub_1AE48中返回的TracerPid进程的/proc/pid/status文件


sub_1B730
遍历线程的stat文件,如果状态为t则返回777

sub_11FA4
调用了sub_234E0,

sub_234E0,这个函数进行了动态解密,执行代码退出进程的操作

于是大致可以看出sub_1b8d4存在检测逻辑。
ctrl+X 使用交叉引用向上查找,这里的v26应该就是pthred_create

而调用v26的则是sub_1B924

接下来我们可以替换创建检测进程的函数sub_1B924来绕过frida检测
最终代码:

function hook_dlopen() {
    Interceptor.attach(Module.findExportByName(null, "android_dlopen_ext"),
        {
            onEnter: function (args) {
                this.fileName = args[0].readCString()
                console.log(`dlopen onEnter: ${this.fileName}`)
                if (this.fileName !== undefined && this.fileName.indexOf("libmsaoaidsec.so") >= 0) {
                    hook_linker_call_constructors()
                }
            }, onLeave: function (retval) {
                console.log(`dlopen onLeave fileName: ${this.fileName}`)
                if (this.fileName != null && this.fileName.indexOf("libmsaoaidsec.so") >= 0) {
                    let JNI_OnLoad = Module.getExportByName(this.fileName, 'JNI_OnLoad')
                    console.log(`dlopen onLeave JNI_OnLoad: ${JNI_OnLoad}`)
                }
            }
        }
    );
}

function hook_linker_call_constructors() {
    let linker64_base_addr = Module.getBaseAddress('linker64')
    let offset = 0x50cf8    // __dl__ZN6soinfo17call_constructorsEv
    let call_constructors = linker64_base_addr.add(offset)
    let listener = Interceptor.attach(call_constructors,{
        onEnter:function(args){
            console.log('hook_linker_call_constructors onEnter')
            let secmodule = Process.findModuleByName("libmsaoaidsec.so")
            if (secmodule != null){
                // hook_pthred_create()
                hook_sub_1b924()
                listener.detach()
            }
        }
    })
}
function hook_pthred_create(){
    console.log("libmsaoaidsec.so --- " + Process.findModuleByName("libmsaoaidsec.so").base)
    Interceptor.attach(Module.findExportByName('libc.so','pthread_create'),{
        onEnter(args){
            let func_addr = args[2]
            console.log(`The thread Called function address is: ${func_addr}`)
        }
    })
}

function hook_sub_1b924() {
    let secmodule = Process.findModuleByName("libmsaoaidsec.so")
    Interceptor.replace(secmodule.base.add(0x1B924), new NativeCallback(function () {
      console.log(`hook_sub_1b924 >>>>>>>>>>>>>>>>> replace`)
    }, 'void', []));
  }

setImmediate(hook_dlopen)

运行,发现成功绕过检测

参考文章

https://xiaoeeyu.github.io/2024/08/09/%E7%BB%95%E8%BF%87%E7%88%B1%E5%A5%87%E8%89%BAlibmsaoaidsec-so%E7%9A%84Frida%E6%A3%80%E6%B5%8B/
http://www.yxfzedu.com/article/8993

点击收藏 | 0关注 | 1打赏
标签: 暂无
最后更新:2024年11月12日

小助手

这个人很懒,什么都没留下

点赞
< 上一篇
下一篇 >

文章评论

您需要 登录 之后才可以评论

COPYRIGHT © 2024 www.pdr.cn CVE漏洞中文网. ALL RIGHTS RESERVED.

鲁ICP备2022031030号

联系邮箱:wpbgssyubnmsxxxkkk@proton.me