这个App的frida检测机制 集中在libDexHelper.so和libmsaoaidsec.so两个so里面,主要思路都是一步步dump,trace到相关检测函数入口位置,再交给ai静态分析关键逻辑,写出绕过脚本
哎,AI真王朝了,力大砖飞,我们的逆向究竟会变成什么样子...
首先Hook了so的加载发现到DexHelper就闪退了,所以需要看看是什么问题,使用hook_init.js去hook拿到输出,分析出大致流程如下
而在dlopen到又一次call_constructors的调用都指向一个offset:
更完整一点的调用栈就是:
毋庸质疑so肯定是有smc的,需要去dump,我们直接在android_dlopen_ext返回之后dump发现我们hook到之后,没dump就被kill了。猜测一下很有可能是自Hook了open,write这些函数,这是很多安全/游戏厂商的常用手段
我们直接拿syscall去dump即可,部分没有权限的内存空间也要dump下来防止遗漏,用syscall_dump.js成功dump。其实也想在JNI_OnLoad加载之后去dump(syscall_Load_dump.js),但是发现frida就是死在JNI_OnLoad里面
改一下脚本在JNI_OnLoad之前dump,这应该就是我们能dump的最晚时机了,解密应该会更完全一点
现在大致流程就是这样,我们将dump下来的so拿到bn里面分析
dump下来的so使用sofixer修复之后大部分地方都可以反编译了
我们之前通过svc在JNI_OnLoad结束之后进行了dump,我们直接复用之前的so,然后拿之前的so加载Hook脚本,在加几个函数来个大满贯hook
我们使用带了init_array调用监控的脚本去hook,发现崩溃不仅在init_array第一个调用结束之后崩溃,而且指向匿名内存,并且第二个调用还未开始,很有可能是在init_array第一个函数中创建线程去kill的,Claude看过linker64了,说脚本肯定没问题
这里app版本更新到6.7.8了,重新dump并且hook了一遍,结果不变,fix一下到bn里面看看
奇怪,找不到线程创建,而且kill发生在dlopen返回之后(其实写到这里突然想到还有JNI_OnLoad了......)
但是通过字符串找到两个比较可疑的函数
不出所料,调用来自JNI_OnLoad
来到这里发现这是一个jump,这里应该是一个动态跳转,但是我们结合打印出来的调用栈就可以轻松定位

我们可以考虑借助Frida Stalker进行Trace,从JNI_OnLoad调用开始Trace,而且似乎会在sub_432774中有非常大量的循环,看了一下,这里面是一个对Java层API的批量入口点检查,避免Java函数被Hook
这里是最上层的入口,里面内部进行大量的循环检测
对应调用就是这里
我们随便二分法找个靠后的offset开始trace,看看哪些会被触发
慢慢跟着offset向后追,如果发现进了循环就找顶层的trace向后设置trace(BN反编译代码有点抽象,汇编和伪C一团乱麻......)
如果卡住了就多等一会在trace,一路trace跟踪发现进入sub_436bb8之后被kill掉了,在0x3596c下Hook无法去Trace到,所以我们需要继续进入到里面去Trace

到0x385ec的时候很明显发现只有2000多条trace,但是结尾也不是类似kill指令那种
这里很明显还动了pthread_create函数,这里看起来像是将pthread_create拿到之后进行了调用,然后后面也做了一些fd之类的检测

sub_452944内部也很明显发现了疑似maps的扫描和sleep相关的调用,这里大致定位之后就可以交给claude了,重点入口函数就是sub_436bb8

目标:com.--------.mobile 6.7.8(---)
加固壳:libDexHelper.so(爱加密 / SecNeo 系)
平台:Android 14(API 34),arm64
镜像基址:0x400000(Binary Ninja 中「地址 = 文件 offset + 0x400000」)
状态:libDexHelper.so 反调试已绕过,App 可继续启动;下一关为 libmsaoaidsec.so
libDexHelper.so 在 JNI_OnLoad 阶段执行一整套反调试/反 Hook 逻辑,入口为 sub_436bb8。它的防护是多线程、多手段、分散触发的:
最终以 6 组 Hook 通过该库全部检测(见第 6 节)。
检测 code_addr 处的代码开头是否被 inline hook(trampoline 跳转特征等)。返回 &1 表示检测到 Hook。被 4 处复用:
各调用者在 sub_432774 返回 0 时均走「环境干净」分支,不读输出 buffer → 恒返回 0 即可安全绕过所有 hook 检测。
备注:绕过后日志里 boot*.oat 的 access-violation 是 ART 正常的隐式 SIGSEGV(null-check / GC read-barrier),由 ART 自身 handler 恢复,非崩溃。异常处理器已改为仅关注 libDexHelper 内异常。
关键取舍:
运行:
日志出现以下业务库加载即表示 libDexHelper 关卡通过:
绕过脚本exp(需要更换本机linker64的定位 call_constructors 内部调用 .init/.init_array 的位点地址):
从此开始我换了个安卓16的设备,下面脚本都是frida17版本
把之前的脚本注入,全部按预期绕过,卡在load SO: libc.so后就Process terminated,我们放开异常捕获,发现
但是没打出 [NEUTER]或者KILL,大概率是在.init_array 之类的早期构造函数里同步执行检测+kill了,想用Stalkerfollow一下syscall,结果直接崩
看到上面ai表现这么好,这个so也比较经典了,那么直接写提示词交给ai,感觉ai王朝了啊...
不出所料是类似SMC出的exit,检测时间在.init_proc
目标:libmsaoaidsec.so(Android arm64,ELF64 AArch64,base=0,所有偏移即文件偏移)
分析方式:IDA Pro 9.0 + ida-pro-mcp 直连反编译,全部结论有地址/字符串交叉引用依据
库身份:JNI_OnLoad 日志标签 NagaLinker v8.83(娜迦/Naga 加固体系的加载期反调试库)
杀进程的"真凶"不是 kill/tgkill/exit 符号,而是运行时解密出的 28 字节内联 shellcode:
由 sub_234E0 / sub_26334 / sub_269AC / sub_260B0 四个执行器 mmap RWX 后直接执行,
完全不经过 libc 符号。
用 ELF 动态段解析(非猜测)得到:
所以 .init_proc 就是"同步检测 + 线程调度者"。
实际只观察到 3 个线程(0x1c544/0x1b8d4/0x26e5c),后两个被配置表门控
(203/204/167/218/248/249/777 等常量)在部分设备上不创建。
库的导入表没有 pthread_create。三个派生函数都是同一套路:运行时在栈上拼密文、
用 3 字节密钥(99 A7 EC)解密出 "libc.so" 与 "pthread_create",然后
dlopen("libc.so",2) + dlsym(...),以 (attr, 0, 入口, arg) 调用。
另有自实现 ELF 解析器(sub_18240 / sub_1806C,解析 PHDR/SHDR/.got/.dynstr/.rel.*
及 DT_ANDROID_* 标签)和自建符号注册表(qword_49248,sub_8784 构建、sub_8734 查询;
JNI_OnLoad 通过它转发真正的 JNI_OnLoad)。
以上特征串在 sub_1C544 内用密钥 99 A7 A9 解密(0x49030~0x4909F),已逐一验证。
sub_234E0 @0x234E0(同模板:sub_26334 @0x26334、sub_269AC @0x269AC、sub_260B0 @0x260B0)
全部执行同一流程:
精确复刻解密算法后反汇编:
即 exit_group(0) —— 零 libc 调用、无 kill/tgkill/exit/syscall 符号痕迹,
这就是符号级 hook 全部扑空的原因。
调用点:
sub_1B380 @0x1B380:
配合线程 2 的判定:
那么依旧利用已有的 pthread_create,一次性把 P0(4 个 exit_group 执行器 + 统一杀点)全部 patch 掉,再patchP1直接调用libcexit(0)的点,p2暂时不管;
同时把所有的调用栈打印之类的全部去掉,这种trace开销很大会把agent卡死, 这个神秘问题卡了我好久呜呜
这样就过了检测了,
带调用栈打印的完整脚本(可能会有trace过多造成的环境问题,自行删除即可):
| bit |
名称 |
bit |
名称 |
bit |
名称 |
| 0x1 |
root |
0x100 |
frida |
0x8000 |
bl |
| 0x2 |
usb? |
0x200 |
hook |
0x10000 |
developer |
| 0x4 |
emu |
0x400 |
integrity |
0x20000 |
unsource |
| 0x8 |
appmon? |
0x800 |
signature |
0x40000 |
location |
| 0x10 |
proxy |
0x1000 |
debug |
|
|
| 0x20 |
polling |
0x2000 |
rom |
|
|
| 0x40 |
inject |
0x4000 |
display |
|
|
| 0x80 |
xposed |
|
|
|
|
| 调用者 |
检测对象 |
isHooked(0x4326e0) |
Java 方法的 ArtMethod entry_point |
sub_433028(0x43336c) |
批量 Java 入口点 / 内部数据 |
sub_452944(0x452a74) |
native 库导出符号 |
sub_4612cc(0x463634/670/718) |
其它检测 |
| 阶段 |
现象 |
根因 |
处理 |
| 1 |
崩 libDexHelper.so+0x49f5c |
sub_448f14 写 perfetto g_signal_pipe_fds |
FIX_PERFETTO 重定向符号到合法内存 |
| 2 |
sub_431bc4(0x100 frida) 触发 |
/proc/self/task 扫到 Frida 线程 |
replace sub_431bc4 → void |
| 3 |
崩 boot-framework.oat / 静默退出 |
isHooked/批量检测经 sub_432774 读 entry 越界 |
replace sub_432774、isHooked → 0 |
| 4 |
App 继续加载业务 SO |
libDexHelper 全部检测通过 ✔ |
—— |
| 符号 |
地址 |
offset |
作用 |
sub_436bb8 |
0x436bb8 |
0x36bb8 |
反调试总入口(JNI_OnLoad 调用) |
sub_432774 |
0x432774 |
0x32774 |
核心 hook 检测原语 |
isHooked |
0x4326e0 |
0x326e0 |
ArtMethod entry hook 检测 |
sub_431bc4 |
0x431bc4 |
0x31bc4 |
kill / 上报原语 |
sub_430aac |
0x430aac |
0x30aac |
写 envc.push 上报 |
sub_452944 |
0x452944 |
0x52944 |
inline-hook 检测(process_vm_readv) |
sub_448f14 |
0x448f14 |
0x48f14 |
IO-hook 框架 + 反 heap-dump |
sub_441bc4 |
0x441bc4 |
0x41bc4 |
自实现 ELF 符号解析器 |
sub_433028 |
0x433028 |
0x33028 |
批量 Java 入口点检测 |
| 全局配置指针 |
0x502de0 |
0x102de0 |
*(*(0x502de0)+0x164) = 检测位图 |
| 开关 |
目标 |
手段 |
说明 |
BYPASS_SUB432774 |
sub_432774 |
replace→0 |
核心,让所有 hook 检测判定「干净」 |
BYPASS_ISHOOKED |
isHooked |
replace→0 |
双保险,Java 层入口 |
BYPASS_KILL |
sub_431bc4 |
replace→void |
屏蔽 kill/上报,防跳非法地址 |
BYPASS_INLINE_CHK |
sub_452944 |
onLeave→0 |
屏蔽 inline-hook 检测 |
FIX_PERFETTO |
sub_441bc4 |
重定向 g_signal_pipe_fds |
修 0x449f5c 写崩溃 |
WATCH_KILL/BLOCK_SELF_KILL/BLOCK_EXIT |
exit/kill/tgkill/pthread_kill |
attach/replace |
拦截并观察退出 |
| 异常处理器 |
libDexHelper 内 SIGSEGV |
仅观测,其余放行 |
避免拦截 ART 隐式异常 |
HOOK_TOP/HOOK_JAVASCAN |
sub_436bb8/sub_432774 观测 |
默认关 |
侵入式 attach 易与壳冲突 |
| 项目 |
地址 |
内容 |
| DT_INIT |
0x14400 |
.init_proc(0x14400–0x148A0),控制流平坦化状态机 |
| DT_INIT_ARRAY |
0x46F80(0x30 字节) |
5 个有效指针 + 1 个 0 终止 |
| DT_FINI_ARRAY |
0x46FB0 |
0x83F0(start)+ 0 |
| 函数 |
地址 |
角色 |
| sub_83FC |
0x83FC |
注册两个 atexit(nullsub) |
| sub_8448 |
0x8448 |
清零 0x4D370 起的一组全局 |
| sub_8460 |
0x8460 |
pthread_key_create(&dword_5D3A8, sub_28DD8) + atexit(sub_28DBC),TLS 键 |
| sub_84B4 |
0x84B4 |
把 0x5D3C8~0x5D420 一组 qword 置 1(once_flag 表) |
| sub_85A8 |
0x85A8 |
同上,0x5D428~0x5D480 一组 |
| 线程入口 |
作用 |
派生者 |
sub_1C544 @0x1C544 |
Frida 看门狗:扫 task/status 线程名、/proc/self/fd、/proc/self/maps+ELF 指纹 |
sub_1CEF8(被 sub_1B924 调) |
sub_1B8D4 @0x1B8D4 |
TracerPid / PPid / T 态看门狗 |
sub_1B924 直接调 |
sub_26E5C @0x26E5C |
CRC32 模块特征扫描 |
sub_2701C(被 sub_13728 调) |
sub_8CAC @0x8CAC |
ART 内部结构完整性校验(版本门控) |
sub_9150(被 init_proc 调) |
sub_19E0C @0x19E0C |
ADB 检测(sys.usb.config 含 "adb") |
sub_1B924,需 sub_CA28()==167 |
| 函数 |
exit(0) 位置 |
检测内容 |
| sub_1BFAC |
0x1C150 |
遍历 /proc/self/task/%s/status,第一行命中 gum-js-loop(0x4904A)或 gmain(0x49056) |
| sub_1C158 |
0x1C264 |
遍历 /proc/self/fd 符号链接,命中 linjector(0x4906A) |
| sub_1C26C |
0x1C53C |
解析 /proc/self/maps,收集 /data/local/tmp 下 r-x 映射,mmap 后 ELF 解析,命中 frida-agent(0x4909F)或 _AGENT_1.0(0x49094) |
| 优先级 |
偏移(函数) |
作用 |
建议 hook |
签名(NativeCallback) |
|
|
|
|
|
| P0 |
0x234E0 sub_234E0 |
内联 exit_group(0) 执行器 |
Interceptor.replace 为 no-op |
void(uint32_t) |
| P0 |
0x26334 sub_26334 |
线程1 自杀倒计时 |
replace no-op |
void(int64_t) |
| P0 |
0x269AC sub_269AC |
线程3/ART 线程 exit_group |
replace no-op |
void(int64_t) |
| P0 |
0x260B0 sub_260B0 |
ART 线程 exit_group |
replace no-op |
void(int64_t) |
| P0 |
0x11FA4 sub_11FA4 |
TracerPid/T态/ADB 的统一杀点 |
replace no-op |
void(void) |
| P1 |
0x1AE48 sub_1AE48 |
查 TracerPid |
replace 恒返回 0 |
int(void) |
| P1 |
0x1AB54 sub_1AB54 |
查 tracer 的 PPid |
replace 恒返回 1 |
int(uint32_t) |
| P1 |
0x1B730 sub_1B730 |
T 态任务扫描 |
replace 恒返回 0 |
int(void) |
| P1 |
0x1BFAC sub_1BFAC |
task/status 线程名扫描 |
replace 恒返回 NULL |
void*(void) |
| P1 |
0x1C158 sub_1C158 |
fd 符号链接扫描 |
replace 恒返回 NULL |
void*(void) |
| P1 |
0x1C26C sub_1C26C |
maps+/data/local/tmp ELF 指纹 |
replace 恒返回 0 |
int(void) |
| P1 |
0x1678C sub_1678C |
模块 CRC32 特征扫描(线程3) |
replace 恒返回 0 |
int(void) |
| P2 |
0x19A58 sub_19A58 |
ADB 线程判定 |
replace 恒返回 0 |
uint32_t(void*) |
| P2 |
0x8CAC sub_8CAC |
ART 结构校验线程 |
replace no-op |
int(void) |
| P2 |
0x19E0C sub_19E0C |
ADB 检测线程 |
replace no-op |
void(void) |
| P2 |
0x1B380 sub_1B380 |
fork+ptrace 反调试 |
replace no-op |
int(void*, void*) |
so加载 -> init -> 多次call_constructors -> android_dlopen_ext结束 -> dlopen("libc.so", RTLD_NOW) -> Process terminated
0x70f3c57544 libDexHelper.so + 0x4a544
libDexHelper.so + 0x4a544
libDexHelper.so + 0x4a544
libDexHelper.so + 0x37a40
libDexHelper.so + 0x3596c
libart.so + 0x46ae64
libopenjdkjvm.so + 0x5360
boot.oat + 0x9c940
[SoDump] output: /data/local/tmp/libDexHelper.so_0x7171aa3000_memdump.so
Error: Permission denied
at dumpModule (E:\Test\Work\--------\6.7.7_anti_frida\dump_so.js:79)
at onLeave (E:\Test\Work\--------\6.7.7_anti_frida\dump_so.js:166)
Process terminated
[2312DRAABC::com.--------.mobile ]->
[JniDump] ========================================
[JniDump] JNI_OnLoad enter
[JniDump] addr: 0x70f6c4a018
[JniDump] offset: libDexHelper.so + 0x33018
[JniDump] vm: 0xb400007188a22e00
[JniDump] reserved: 0x0
[JniDump] caller: 0x7185185e64
[JniDump] caller offset: libart.so + 0x46ae64
[JniDump] ========================================
Process terminated
[JniDump] ========================================
[JniDump] dump reason: JNI_OnLoad_enter
[JniDump] module: libDexHelper.so
[JniDump] base: 0x70f2e98000
[JniDump] size: 0x129000
[JniDump] path: /data/app/~~rQzi3tAFqHBlWrFpOm7m5A==/com.--------.mobile-exvsiXgJRbXfwGfAbBJQwQ==/lib/arm64/libDexHelper.so
[JniDump] out : /data/data/com.--------.mobile/files/libDexHelper.so_0x70f2e98000_after_JNI_OnLoad_memdump.so
[JniDump] fd: 90
[JniDump] mprotect whole module readable
[JniDump] mprotect pages total=297 ok=297 fail=0
[JniDump] dump finished
[JniDump] saved: /data/data/com.--------.mobile/files/libDexHelper.so_0x70f2e98000_after_JNI_OnLoad_memdump.so
[JniDump] unreadable before retry: 0
[JniDump] zero filled pages: 0
[JniDump] ========================================
Process terminated
libDexHelper.so 加载完成
-> JNI_OnLoad 进入
-> 入口处 dump 成功
-> JNI_OnLoad 内部继续执行
-> 进程被 kill / terminate
[android_dlopen_ext] path : /data/app/~~rQzi3tAFqHBlWrFpOm7m5A==/com.--------.mobile-exvsiXgJRbXfwGfAbBJQwQ==/lib/arm64/libDexHelper.so
[android_dlopen_ext] flags: 0x2
[android_dlopen_ext] extinfo: 0x7fd78bdf20
>>> [#1] CALL init_array @ 0x779cc8d650 (libDexHelper.so + 0x2f650) for 'libDexHelper.so'
<<< [#1] DONE init_array @ 0x779cc8d650 (libDexHelper.so + 0x2f650) for 'libDexHelper.so'
[android_dlopen_ext] handle: 0xad0715354bd48a2b
==============================
Process crashed: Bad access due to invalid address
......
lr 00000078e6b0610c sp 0000007fd78bdee0 pc 00000078e6b06130 pst 0000000080001000
1 total frames
backtrace:
#00 pc 0000000000000130 <anonymous:78e6b06000>
***
[2312DRAABC::com.--------.mobile ]->
[android_dlopen_ext] path : /data/app/~~h0YzYCcRX4xFSmKUejHKAA==/com.--------.mobile-0382fGhoiO1SDD5DI9qaPQ==/lib/arm64/libDexHelper.so
[android_dlopen_ext] flags: 0x2
[android_dlopen_ext] extinfo: 0x7fe6e77c00
>>> [#1] CALL DT_INIT @ 0x7aa7729098 (libDexHelper.so + 0x128098) for 'libDexHelper.so'
<<< [#1] DONE DT_INIT @ 0x7aa7729098 (libDexHelper.so + 0x128098) for 'libDexHelper.so'
>>> [#2] CALL DT_INIT_ARRAY @ 0x7aa7630650 (libDexHelper.so + 0x2f650) for 'libDexHelper.so'
<<< [#2] DONE DT_INIT_ARRAY @ 0x7aa7630650 (libDexHelper.so + 0x2f650) for 'libDexHelper.so'
[android_dlopen_ext] handle: 0xf5ec6bbf7a0cd67d
==============================
const TARGET_FUNCS = [
{
name: 'sub_431bc4',
offset: 0x31bc4,
retType: 'void',
},
{
name: 'sub_457c58',
offset: 0x57c58,
retType: 'int64',
},
];
========== ENTER sub_431bc4 ==========
addr = 0x7aa48c2bc4 (libDexHelper.so + 0x31bc4)
arg1 = 256
arg2 = -1230861953
arg3 = 0xfff
---- registers ----
pc = 0x7aa48c2bc4
lr = 0x7aa48ca560
sp = 0x7fe6e76430
Backtrace:
#0 0x7aa48ca560 0x7aa48ca560 libDexHelper.so!0x39560
#1 0x7aa48ca560 0x7aa48ca560 libDexHelper.so!0x39560
#2 0x7aa48c696c 0x7aa48c696c libDexHelper.so!JNI_OnLoad+0x2954
libDexHelper.so+0x33364 0x7a1a450364 add x2, sp, #0xa8
libDexHelper.so+0x33368 0x7a1a450368 mov x0, xzr
libDexHelper.so+0x3336c 0x7a1a45036c bl #0x7a1a44f774
libDexHelper.so+0x32774 0x7a1a44f774 stp x28, x27, [sp, #-0x60]!
libDexHelper.so+0x32778 0x7a1a44f778 stp x26, x25, [sp, #0x10]
0043337c if (sub_432774(nullptr, 0x503e12, &var_c78) & 1
0043337c && (uint32_t)var_a67 != 0x77)
0043337c {
00433380 int64_t x0_20 = var_c78;
⚠️0043338c int64_t var_c70;
0043338c 0x42cfc0(x0_20, var_c70 - x0_20, 3);
0043337c }
传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!!