首页
社区
课程
招聘
[原创]对某学习APP的frida检测绕过
发表于: 2天前 2320

[原创]对某学习APP的frida检测绕过

2天前
2320

这个App的frida检测机制 集中在libDexHelper.solibmsaoaidsec.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了openwrite这些函数,这是很多安全/游戏厂商的常用手段

我们直接拿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_432774isHooked → 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                }

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

上传的附件:
收藏
免费 61
打赏
分享
最新回复 (24)
雪    币: 112
活跃值: (9145)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
kkb
2天前
0
雪    币: 51
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
3
tql
2天前
0
雪    币: 411
活跃值: (666)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
4
66
2天前
0
雪    币: 0
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
5
6666
2天前
0
雪    币: 6
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
6
感谢分享
2天前
0
雪    币: 158
活跃值: (5406)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
7
感谢分享 
1天前
0
雪    币: 4600
活跃值: (7802)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
8
感谢分享 
1天前
0
雪    币: 235
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
9
感谢大佬分享
1天前
0
雪    币: 158
活跃值: (2576)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
10
感谢分享
1天前
0
雪    币: 1560
活跃值: (5553)
能力值: ( LV4,RANK:40 )
在线值:
发帖
回帖
粉丝
11
666
1天前
0
雪    币: 219
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
12
666
1天前
0
雪    币: 0
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
13
必须点赞
1天前
0
雪    币: 203
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
14
666
1天前
0
雪    币: 588
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
15
先学习
23小时前
0
雪    币: 3055
活跃值: (2945)
能力值: (RANK:140 )
在线值:
发帖
回帖
粉丝
16
写的很详细,很不错。感谢分享。
22小时前
0
雪    币: 0
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
17
666
21小时前
0
雪    币: 19
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
18
666
21小时前
0
雪    币: 9134
活跃值: (5928)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
19
看看
18小时前
0
雪    币: 128
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
20
看的不是太懂个,不过还是支持一下
12小时前
0
雪    币: 564
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
21
看看大佬的
9小时前
0
雪    币: 1596
活跃值: (1274)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
22
感谢分享
2小时前
0
雪    币: 38
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
23
2小时前
0
雪    币: 600
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
24
1
1小时前
0
雪    币: 43
活跃值: (2249)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
25
感谢分享
1小时前
0
游客
登录 | 注册 方可回帖
返回