首页
社区
课程
招聘
[原创]某商业级加固 Native Loader 与 VMP 解释器逆向分析实战
发表于: 4天前 1941

[原创]某商业级加固 Native Loader 与 VMP 解释器逆向分析实战

4天前
1941

声明:本文仅用于安全技术研究与教学,所有分析均在隔离测试环境完成,不涉及任何真实业务数据。

这次把 某商业级(com.mobile.demo_target) 从加壳到加密算法完整啃了一遍。真机走 SysTrace 定制 ROM,用 unidbg 离线模拟,主要做了这几件事:

最终拿到:可直接丢进 IDA 的干净脱壳文件 payload_clean_full.so(103 函数、9185 字符串);加密链路全部跑通(#239 AES-128 key + #238 AES/CBC);VMP 解释器 16 个 opcode 语义和 W28 状态链都还原了;libDexHelper 的反篡改也绕过了。

目标 App 具备多层反调试(libantitrace.so 反跟踪、内核级检测、/proc/self/maps 扫描),常规的 "root + frida" 方案会被直接检测。

本项目全程排除 Frida,真机侧使用 SysTrace 定制 ROM(AOSP 10 深度定制,内置 Dobby 注入、SandHook/Xposed 能力、ROM 打桩与 VMP 对抗工具),配合 unidbg 做离线模拟。两条路线各司其职:

【整体环境与数据流示意】

lib/arm64-v8a/60 个 .so。按安全厂商分类:

【demo_target_libs 库矩阵(关键 so)】

结论:这是一个 某企业版加固 + 多家 SDK 混合 的典型某商业级 App——libDexHelper.so/libdexjni_target.so/libbangcle_risk.so/libsmkernel.so 均属同一套某加固体系(com.secneo.apkwrapper 即某企业版特征包名)。主加密逻辑在 libdexjni_target.solibDexHelper.so 负责反篡改,libtongdun.so 等为独立第三方风控。

首次注入 hook 后 App 异常退出。从 tombstone 分析:

【tombstone 0x79c (logcat 实测)】

关键点:sp/lr/pc 全为 0 且 pc=0x79c —— 栈完全被清零后跳转到 0x79c,是典型的扫描 maps 后主动自杀(非随机崩溃)。

问题根因

区分两种检测:① 环境检测(ro.debuggable=1、su 存在、USB 调试)触发的是 Activity.finish() 闪退(进程仍在后台);② 注入检测(maps 中发现未授权库)触发的是 0x79c 空指针自杀(进程直接崩溃)。本文 0x79c 属于后者。

应对思路:让注入的 so 在 maps 中"不可疑"。

对抗环境检测的核心工具是 setup_stealth
它集成了 5 类反检测技术

① 属性物理覆写(绕过属性 API)
加固壳会绕过 __system_property_get 直接 mmap 读取 /dev/__properties__物理内存(属性树),常规 setprop 无效。setup_stealth 反向利用这一点:

关键点:序列锁协议(写前置 serial 奇数位、写后置偶数位 + 内存屏障),保证并发读属性时不撕裂。

② Ghost Root 提权(setresuid 1337)
syscall(__NR_setresuid, 1337, 1337, 1337) 触发定制内核 kernel/sys.c 的 setresuid hook,以 UID 1337 获得内核级能力,绕过 SELinux 对属性文件写入的限制。

③ shadow 属性重定向
setresuid(1337,1337,1342) 启用内核 fs/namei.cshadow 属性重定向:把 /dev/__properties__/xxx 的读取重定向到 /dev/__properties__/xxx.realsetup_stealth 先备份原属性为 .real),实现"读真值、写假值"分离——壳读到的永远是伪装值,系统内部用真实值。

④ 完整属性伪装集(20+ 个)

精确伪装成 Pixel 1 (marlin) Android 10 的出厂指纹,覆盖 root/调试/bootloader 全部检测面。

⑤ Zygote 重启(清 Java 层属性缓存)
setprop ctl.restart zygote 重启 Zygote,让 Java 层 SystemProperties 缓存重新读取伪装后的属性——否则 Java 与 Native 层属性不一致会被壳识破。

配套关系setup_stealth 是集成执行器;其属性覆写核心逻辑在 resetprop_ult.c(独立版单工具)中复用;fake_build_prop.h 定义内核态 /proc/fake_build_prop 的伪装内容(配合内核 namei.c 的 build.prop 重定向)。

执行依赖(设备侧):/data/local/tmp/setup_stealth(属性伪装二进制)、/data/local/tmp/send_cmd(HWBP 控制)、内核侧 hwbp_ghost.ko(已加载)。

涉及文件:setup_stealth.cresetprop_ult.cfake_build_prop.hhide_root.c

壳检测 /system/xbin/su 存在即自杀退出(com.secneo.apkwrapper.H)。

su 的隐藏用的是 nsenter 进 Zygote 挂载命名空间,拿空 tmpfs 盖住 /system/xbin,App 就看不到 su 了。

涉及文件:setup_stealth.chide_root.c

注入靠 ptrace_inject32.c 做 ARM64 ptrace 注入。App 第一次启动基本都会因为内部逻辑/网络初始化闪退Bad file descriptor),采用双击启动策略

【真机注入 + App 稳定运行(logcat 实测)】

关键点:Displayed targetMainActivity: +641ms —— 改名 libanalytics.so 后 0x79c 不再触发,App 稳定进入主界面。

涉及文件:ptrace_inject32.cstart_demo_target.shghost_cold_sniper.c

libDexHelper.so 是某企业版反篡改核心(arm64 版 1MB、32 函数)。IDA 实证其与 libdexjni_target.so同一壳框架(相同 273/70/261/21 状态机混淆、相同 RC4 引擎、相同 libc 跳板表):

四重检测面(IDA 交叉确认):

对抗演进

对抗原理

结论:libDexHelper 的静态 patch 成本极高(多层状态机混淆 + 自校验 + 自解密),对抗重点在运行时注入规避而非修改其代码——通过"属性一致性 + 名称规避 + 匿名执行"组合拳完整绕过。

用户态反调试绕不过去,干脆搞了内核硬件断点(HWBP)驱动

【内核模块加载日志(GhostHWBP,dmesg 实测)】

关键点:驱动经 /proc/hwbp_cmd 控制,对 App 所有线程统一布防;reg_offset=0x145404libart.soRegisterNatives 符号偏移(由 hwbp_resolver 用 readelf 解析符号表得到)。

涉及文件:hwbp_ghost.chwbp_resolver.chwbp_ghost.ko

某企业版壳把真实 DEX 解密后加载到匿名内存。脱壳 = 找到解密后的 DEX 段并 dump。核心思路是 Zero-Break 读取:不 attach、不 ptrace、不动调试寄存器,仅通过 /proc/<PID>/mem + pread 带外读取,避免触发壳的调试检测。

关键技术(dex_dumper 演进)

难点与关键点

解密时机:Bangcle DEX 解密需 8~12 秒(libDexHelper 加载后),必须在解密完成瞬间 dump,过早抓不到、过晚内存被复用。配套 launch_and_dump.sh 等待 libDexHelper 加载完成(grep libDexHelper /proc/PID/maps)+ 12s 后立即 dump。

双击启动:App 首次启动必然闪退(壳初始化检测),第二次启动才正常解密。launch_and_dump.sh 实现 LAUNCH#1(预期崩溃)→ 等 PID 重启 → LAUNCH#2(正常解密)→ 12s 后 dump。

SELinux:dump 前必须 setenforce 0,否则 /proc/PID/mem 读取被拒。

magic 被抹:Bangcle 可能抹掉 DEX magic 字节,v3 用 header 签名(header_size=0x70 + endian_tag=0x12345678)识别并恢复 magic

多段:DEX 可能分散在多个匿名段(含 ashmem/memfd),v2 三层策略确保全捕获,再合并。

checksum/签名修复:dump 出的 DEX 头部被壳篡改,fix_dex_checksum_full.py 重算后才能被 jadx 识别。修复偏移(DEX 头布局):

核心工具:

【dumped_dex 产物(真机 dump + checksum 修复后)】

反编译产出:payload_2_full_fixed.dex2070 类(主业务);payload_6_fixed.dex198 类(加密/HTTP 核心)。

完整还原了 App 的加密体系:

注:mic(MAC) 消息认证码的生成/校验算法本次未深入——它属请求完整性保护,独立于 #239/#238 的加密链路。MAC 缺失不影响 AES 加解密的算法复现,但构造真实请求时需另行还原(常见 HMAC-SHA256,本文未覆盖)。

注:RSA 用于加密 AES Key 传给服务端(非对称密钥交换)。本文未深入 RSA 公钥的来源与存放——若公钥硬编码在 Java 层则易被替换,若由 Native 白盒保护则更安全;此边界本次未评估。

EncryptWithECB 是里面唯一纯 Java 不走 JNI 的方法(AES/ECB/PKCS5Padding),可以直接 Hook;其余核心加密全在 JNI 派发里(#232~#247)。

服务端信息:

libdexjni_target.so 98% 内容为加密数据:

【sub_13F8 / sub_1184 / sub_CD0 关键逻辑(IDA 反编译摘要)】

标准 RC4(256 字节 S-box + 16 字节 key + PRGA XOR 写回),每次调用解密 ≤0x40 字节分块执行。关键参数:

调用链:

RC4 key 是顺着 xrefs 追 sub_AC0(pread)的 offset 参数找到的,key 就在文件偏移 0x2E1E00

【0x2E1E00 处 key hexdump(原始 so 磁盘实测)】

pread(fd, &qword_13050, 0x14, 0x2E1E00) 读出的前 16 字节即 key,与 qword_13068 完全一致。

key = e0670dd6...

sub_13F8 中发现壳实现了独立的 ELF 重定位解析器:

解密产物:libdexjni_decrypted_dump.so(3.1MB,内存 dump 版)含 80,708 个明文字符串 与全套重定位数据;
与 7.7 的 payload_clean_full.so(9185 个 IDA 已识别字符串)是不同文件,统计口径不同。

最初尝试在 unidbg 中手动执行:

问题:sub_CD0 运行后自动注册的 Natives 字典: {}(空)、FULL_HEAD_HEX 全 0——JNI_OnLoad 从未真正执行

原因:手动拆分调用链绕过了壳的完整流程。正确做法是让 sub_13F8 完整跑完,再触发 JNI_OnLoad。

运行 sub_13F8 时遇到未处理系统调用:

这是壳的自定义 mmap2 系统调用(反模拟检查)。 测试代码中的 SecneoUnpacker.java,注册该 svc 即可让 sub_13F8 继续执行:

注册后 sub_13F8 完整执行并返回!mmap 分配出解密目标段。

【svc 63505 注册 + sub_13F8 执行日志(unidbg 实测)】

关键点:sub_13F8 内部经自定义 mmap2(svc 63505)分配 seg0/seg1 两个 RWX 段,解密产物写在这里(不是 module.base+0x13090)——这是此前静态 dump 基址错误的根因。

payload 每次运行 mmap 地址都是随机的(0x12963000/0x12a47000 这种),硬编码不行,只能从 memoryMap 里动态找:

"静态分析 dump"基址错误的根因

payload 使用定制 JNIEnv 布局,与 unidbg 标准表不同:

【JNIEnv 表 patch 代码(DemotargetUnpack_final.java 实测)】

配套:svcMap.put(63505, ...) 注册自定义 mmap2(见 7.2)、0x101A8 改分支跳主状态机、0x90F8 canary NOP。

payload 的 SDK 版本检查使用 ADRP 跳转表 dispatch,但 ADRP 重定位缺失(sub_13F8 只重定位 GOT 数据,没修 ADRP 页偏移),导致跳转表全 0 → BR 到错误地址。

状态机靠改两条指令绕过去:

同时修复状态机跳转表基址(X23 = seg0 + 0x9120)。

经过上述修正,JNI_OnLoad 完整执行,10 个 JNI 方法全部注册

【RegisterNatives 全部注册日志(unidbg 实测)】

10 个方法全部注册成功,cL 入口在 RWX@0x12973690

DemotargetDumpFull.java 从正确基址连续 dump,产出 payload_clean_full.so

IDA 打开验证:103 函数、9185 字符串、entry 0x2D80,cL 函数体可反编译。

【payload_clean_full.so 全貌(IDA survey 实测)】

IDA 中 cL 函数体可正常反编译(103 个已识别函数之一),证明脱壳彻底、无残留加密。

注册成功后,构造 Object[] 参数调用 cL

【cL(#239) 执行 JNI 日志(unidbg 实测关键行)】

完整链:239 → KeyGenerator.getInstance("AES") + init(128) + generateKey().getEncoded(),即 AES-128

unidbg 完整记录了 cL 内部执行序列:

结论:generateAESKey(#239) = 标准 KeyGenerator.getInstance("AES") + init(128) + generateKey().getEncoded(),即 AES-128 随机密钥。

运行结果:

注:真实环境为 SecureRandom 随机值。unidbg 中通过 AbstractJni mock 了 KeyGenerator/String.intern/Proxy.isProxyClass 等 unidbg 不支持的方法。

cL 函数体调用 loc_10950——一个 VMP 数据混淆型字节码解释器

【VMP 16-opcode 跳转表反汇编(payload_clean_full.so @0x10950)】

关键点:16 个 opcode 统一模式 byte[X25+addr] ^= key,addr/key 由 W28 混淆算术派生——VMP 保护的是 cL 参数解析与 methodID 分发(Instruction Stealing),非 AES 算法本身。

关键结论:VMP 保护的是 cL 的参数解析与 methodID 分发(Instruction Stealing 型),非 AES 算法本身。算法 = 标准 KeyGenerator。

16 opcode 语义表(IDA 实测反汇编归纳):

W28 状态链符号化(4 类更新原语)ADD/SUB(仿射,op2/3/7/11/12/14)、EOR(仿射,op5/6/7/12)、LSL/AND(位混淆,op4/8/6)、MUL/MADD(非线性,op0/4/13)。addr/key 均 = f(W28,W19,const)

opcode 10 详解(全解释器核心,JNI 方法调用指令):

语义:opcode 10 = methodID 查表(qword_2E2580) → JNIEnv 槽位调用 → 按返回类型(12 case)写 VM 寄存器文件。cL(#239) 的 KeyGenerator.getInstance("AES") 等 JNI 调用完成。

AESUtil.aesEncry(byte[] bArr, String str, String str2)JniLibtarget.cL(AESUtil.class, bArr, str, str2, 238) 进入 Native。算法链已由四路证据交叉锁定:

结论aesEncry(#238) = Cipher.getInstance("AES/CBC/PKCS5Padding") + SecretKeySpec(key) + IvParameterSpec(str2) + doFinal(明文),返回 Base64。与 #237 aesDncode 构成加解密配对;#239 generateAESKey 提供 16 字节 key。

环境 作用
SysTrace 定制 ROM(真机) 反检测对抗、真机 hook、DEX 内存 dump
unidbg(宿主机) Native loader 模拟执行、全自动脱壳、算法复现
加固/安全库 作用
libDexHelper.so 某企业版 反篡改核心(扫描 maps + checksum)
libdexjni_target.so 某企业版 加密 loader(本文主角)
libsmkernel.so / libbangcle_risk.so / libnllvm1623735669.so 加固内核 / 风险检测 / LLVM 混淆(同属某体系)
libantitrace.so 反跟踪(ptrace 检测等)
libtongdun.so 第三方风控
libHKESipCryptor.so / libSwipeLockCryptor.so / libSipCryptor.so / libcfcaMLog.so 某商业级专用加密/CFCA 国密
# 检测机制 证据 后果
1 环境检测 ro.debuggable=1、su 存在、USB 调试(Bangbang/Secneo wrapper) finish() 闪退
2 maps 扫描 WeexJSBridgeThr 线程例行扫 /proc/self/maps 找未授权库 0x79c 空指针自杀
3 可执行段 checksum 对 libc.so 等可执行映射做完整性校验 篡改即 0x79c
4 第三方风控 SDK 检测 环境/注入检测
关键方法 JNI ID
AESUtil generateAESKey() #239
AESUtil Encrypt/Decrypt/aesDncode/aesEncry #234/#232/#237/#238
EncryptUtil Decrypts/EncryptUtil/Encrypts #243~#246
EncryptUtil EncryptsGM(国密) #247
RsaUtil RSA/ECB/PKCS1Padding 常量
函数 作用
sub_13F8 主加载器(自定义 Dynamic Linker)
sub_1184 RC4 解密引擎
sub_CD0 JNI_OnLoad 查找与调用
问题 表现 修复
[0x30] 入口检查 payload 期望返回 0 的成功检查 code hook 在 0x2E00 强制 W0=0
[0x480] GetStaticFieldID SDK_INT 查询失败 反射改 impl 表返回 23
[0x4B0] GetStaticIntField SDK 值获取失败 同上返回 23
opcode 地址 操作模式 next 含svc
0 0x109EC 纯状态更新(无 X25 读写) 0xC
1 0x109D0 1 次 X25 字节 XOR 0xC
2 0x10A30 7 次 X25 字节 XOR 0xD
3 0x10AC8 4 次 X25 字节 XOR 0xB
4 0x10B44 3 次 X25 字节 XOR 重分发
5 0x10BAC 条件分支(W28 更新) 1
6 0x10C0C 纯状态更新 1
7 0x10C2C 4 次 XOR + svc 外部调用 6
8 0x10CE0 5 次 X25 字节 XOR 0x95?
9 0x10D90 操作 X29 栈区(寄存器文件) ?
10 0x10DC8 JNI 方法调用指令(160 条,见下) 9
11 0x11048 5 次 X25 字节 XOR 0xE
12 0x110E8 X25 字节 XOR 7
13 0x1116C 3 次 XOR 3
14 0x111D0 6 次 X25 字节 XOR(addr/key 均为 W28 仿射函数) 8
15 0x1127C 结束指令:哨兵校验 → 弹栈 RET(失败走 sub_2C80)

冰与火的战歌:Windows内核攻防实战高级班!从零到实战,融合AI与Windows内核攻防全技术栈,打造具备自动化能力的内核开发高手。

最后于 3天前 被kudos5566编辑 ,原因:
收藏
免费 81
打赏
分享
最新回复 (41)
雪    币: 109
活跃值: (565)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
666
4天前
0
雪    币: 0
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
3
xuexiba
4天前
0
雪    币: 0
活跃值: (2096)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
4
666
4天前
0
雪    币: 200
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
5
666
4天前
0
雪    币: 0
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
6
6666
4天前
0
雪    币: 1041
活跃值: (1485)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
7
1
3天前
0
雪    币: 2
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
8
666
3天前
0
雪    币: 28
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
9
666
3天前
0
雪    币: 227
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
10
666666
3天前
0
雪    币: 200
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
11
666
3天前
0
雪    币: 7119
活跃值: (7215)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
12
66
3天前
0
雪    币: 231
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
13
wqeqweq
3天前
0
雪    币: 723
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
14
666
3天前
0
雪    币: 9110
活跃值: (5888)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
15
看看看
3天前
0
雪    币: 158
活跃值: (5331)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
16
学习
3天前
0
雪    币: 5
活跃值: (5350)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
17
上rom
3天前
0
雪    币: 729
活跃值: (2100)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
18
6666
3天前
0
雪    币: 213
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
19
强强强
3天前
0
雪    币: 4
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
20
666
3天前
0
雪    币: 3584
活跃值: (7225)
能力值: ( LV11,RANK:185 )
在线值:
发帖
回帖
粉丝
21
1
3天前
0
雪    币: 411
活跃值: (611)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
22
666
3天前
0
雪    币: 2897
活跃值: (5782)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
23
111
3天前
0
雪    币: 568
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
24
学习信息
3天前
0
雪    币: 6
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
25
感谢分享
3天前
0
游客
登录 | 注册 方可回帖
返回