OAIDSEC模块调试的坑
OAIDSEC模块调试的坑
OAIDSEC模块网上资料很多,这里就不细讲了,检测点貌似就两种:
1、exit、_exit正常系统调用是一种
2、mmap 申请内存shellcode退出是一种
通过交叉引用就可以迅速找到检测关键点,OLLVM CFF混淆在现在的AI面前也不值一提。
这里记录点需要记录的
关于ART中的PrettyMethod检查
检测逻辑大概是这样的:
1 | |
这是在检测Frida对PrettyMethod方法的hook,使用了Java.perform就会触发,Frida要hook这个方法是为了修复ART的一个BUG。
一、ART 的 bug 是什么
art::Thread::QuickDeliverException() 是 ART 投递 Java 异常的入口,它有个隐含前提:抛异常时当前线程栈上至少有一个托管(Java)栈帧——因为异常通常是从 Java 代码里 throw 出来的。
但 native 线程也能抛 Java 异常。最典型的就是 JNIEnv->FindClass() 找不到类时,ART 内部会 ThrowNoClassDefFoundError。此刻线程栈上全是 native 帧,没有任何 Java 帧,QuickDeliverException() 仍按”有 Java 帧”去取当前方法,拿到 nullptr,程序就会炸。
art::Thread::QuickDeliverException() 是 ART 投递 Java 异常的入口,它有个隐含前提:抛异常时当前线程栈上至少有一个托管(Java)栈帧——因为异常通常是从 Java 代码里 throw 出来的。但 native 线程也能抛 Java 异常。最典型的就是 JNIEnv->FindClass() 找不到类时,ART 内部会 ThrowNoClassDefFoundError。此刻线程栈上全是 native 帧,没有任何 Java 帧,QuickDeliverException() 仍按”有 Java 帧”去取当前方法,拿到 nullptr,后面拿它去查处理器、拼错误信息,就炸了。Frida的脚本往往在任意线程(JS 线程、RPC 线程)里按名字 FindClass,是主动高频触发者。
Frida 的注释就是这么说的:
1 | |
二、Frida 怎么”修”的
它改不了系统库,所以选择在ART 打印方法名的那一刻把空指针换掉(lib/android.js:1990):
1 | |
逻辑很朴素:
- 每次
PrettyMethod被调用时,记下这个ArtMethod*; - 一旦 ART 拿
nullptr来调PrettyMethod,就把第一个参数替换成上一次记录到的合法 ArtMethod。
于是 ART 内部后续访问 method->... 时不再是空指针,进程活着。这是个启发式补丁,语义上未必对(打印出来的方法名可能是错的),但能避免崩溃。
三、为什么非得挂 PrettyMethod
因为它是异常投递路径上”描述这个方法”的唯一汇聚点——ART 在崩之前一定会走到这里,把方法名/签名渲染出来。劫持它,等于在最靠近故障点的位置拦一刀。
和Frida的冲突
如果你找到了OAIDSEC模块的所有检测点,尝试使用Frida来Patch所有的检测点,还是有可能会遇到程序崩溃引起的死锁。
1、问题的关键在于Frida存在一个mmap机制,js脚本尝试执行寻找导出函数类似的代码时,例如执行**findExportByName** ,需要拿到模块的句柄
gum 不使用公共 dlopen,而是直接调用 linker 内部的 do_dlopen/dlopen。要拿到这两个函数地址,gum_linker_api_try_init 会在 linker 的符号表里找:
1 | |
enumerate_symbols 先枚举 **.symtab**:
1 | |
而内存里的 linker 镜像只有 PT_LOAD 段:section header table 在 e_shoff 处,如果不在任何 PT_LOAD 段内, ONLINE 模式就拿不到 shdr:
于是在 ONLINE 模式下改为打开磁盘文件,而“读整个文件”的实现就是 g_mapped_file_new(:589):mmap(fd, 0, size, PROT_READ, MAP_PRIVATE),即frida将整份 linker64进行了一份只读映射。
2、而OAIDSEC模块则刚好有一段CRC32校验检测代码,其中要用到linker64来定位模块,它的选择是遍历/proc/self/maps,选择第一个遇到的linker64作为基址,这将会有很大概率选错linker64,选中的是Frida的那份文件映射内存。最终导致程序崩溃。
这种情况就属于是,OAIDSEC和Frida这两个吊毛代码,单个看起来都问题,组合起来就会出问题。
而崩溃和死锁往往是有关联的,特殊的地方的崩溃,是有很高概率引起死锁的。。。最终看起来表现就是应用卡住了。OAIDSEC无意中又写出了一个anti frida的检测机制。