金铠甲加固 Android APK/AAB 原生加固
TECHNICAL CAPABILITY

加固技术实现与能力边界

本页从指令变换、数据格式与运行时实现三个层面说明加固实际做了什么, 并明确列出不支持的 opcode、被跳过的方法类型,以及尚未提供的能力与原因。 定位为面向中小企业的增强级(L3)加固,非金融支付等强监管场景。

最后更新:2026 年 9 月 17 日

POSITIONING

产品定位

适合

需要提高逆向成本、但无需金融级对抗强度的 Android 应用

  • 工具、效率、内容类 App 的核心业务方法
  • 企业内部应用:业务规则、接口端点、鉴权流程
  • 游戏侧逻辑:数值校验、资源校验、反作弊外围
  • 智能硬件配套:配网、通信协议、控制指令

不适合

需要白盒强度或合规资质的场景,不应作为唯一手段

  • 支付、银行、证券等核心交易链路
  • 要求白盒加密、密钥远程下发、RASP 的产品
  • 需要安全键盘、防截屏、设备指纹等完整终端安全套件
加固提高的是攻击成本,不是绝对防护。攻击者完全控制设备时,任何客户端保护最终都可能被绕过; 上述场景应以服务端风控与合规体系为主。
HOW TO READ

等级说明

"常见等级"指该能力在行业内通常所属档位,不对应任何具体厂商的产品划分。

L1 基础

字符串/资源/标识符混淆、Manifest 处理、基础防二次打包。

L2 标准

DEX 整体加壳加密、完整性校验、签名校验、反调试。

L3 增强

方法级代码虚拟化(dex2c)、opcode 随机映射、反注入与 Hook 检测、运行时主动防御。

L4 专业级

控制流平坦化、白盒密钥、RASP、远程密钥下发、实时风控。

支持的平台与版本

以下为当前预编译运行时与打包链路实际支持的组合。

项目支持情况说明
最低 Android 版本 Android 5.0(API 21) 由注入运行时的 NDK API level 决定;构建脚本默认值即为 21。低于该版本的系统将无法加载运行时
minSdk 处理 随包自适应 加固时解析 AndroidManifestminSdkVersion(AAB 从 protobuf 读取,AAR 无 manifest 时按 21 处理),并据此调整代码生成
minSdk < 23 的差异 有额外处理 低于 23 时需要考虑与父类 direct 方法的冲突,代码生成会按该情形调整
目标版本 无硬性上限 不改写 targetSdkVersion,由你的构建配置决定
CPU 架构 4 种可选 armeabi-v7a(32 位)、arm64-v8a(64 位)、x86x86_64;按所选 ABI 注入对应运行时
APK 签名 v1 + v2 使用 apksigner 同时启用 v1(JAR 签名)与 v2(APK 签名方案 v2);v2 在 Android 7.0 以下不生效,此时回落到 v1
AAB 签名 仅 v1 AAB 使用 jarsigner 做 JAR 签名,且商店会重新签名与重新打包
包格式 APK / AAB / AAR AAR 只处理 classes.jar,不含应用级签名校验
最低版本受注入运行时的 NDK API level 约束,而不是受加固服务本身约束。 若你的应用 minSdk 低于 21,需要以更低的 API level 重新构建运行时才能使用; 当前预编译产物按 21 构建。

一、加固产物的技术构成

加固不是简单加壳,而是对 DEX 方法体做变换,并注入一个原生运行时来解释执行这些方法。

注入产物

lib/arm64-v8a/libzorvault.so     ← 解释器 + 校验逻辑
lib/armeabi-v7a/libzorvault.so
lib/x86/libzorvault.so
lib/x86_64/libzorvault.so
assets/zorvault_data.bin         ← 加密后的方法数据

运行时按所选 ABI 注入;数据文件为加密容器,随包交付。

DEX 侧变化

原方法体  ──▶  exec shell(调用原生层)
  const-string   ──▶  stringFromPool(I)
  其余指令        ──▶  搬入 zorvault_data.bin
classesN.dex     ──▶  重写并重新编号

方法体不再以 DEX 字节码形式留在包内,由原生解释器按数据解释执行。

加密容器(zorvault_data.bin)

NMS2\x01 | nonce(12B) | AES-256-GCM ciphertext+tag
  AAD: "ZORVAULT-ANDROID-STRING-POOL-V2"

任务密钥来自 64 字节随机 seed,经 SHA-256(带域分隔)派生为 32 字节密钥; seed 写入该任务 SO 副本的 .zorvault.key 槽位。明文数据会被拒绝加载。

明文数据布局(解密后)

ZRVT\x02 | num_dex(u32) | decoder[256]
  per-DEX: name_len(u16) | name
           string pool | type pool
           field/method pool
           方法数据(指令原样保留)

decoder[256] 为该任务随机生成的 opcode 映射表,产物中的指令以映射后的 opcode 存储,运行时查表还原为标准 opcode。

二、DEX 代码变换

决定反编译后还能看到多少原始逻辑,是加固的核心环节。

能力实现要点等级状态技术边界
方法体抽取 解析 code_item,指令字节原样搬入方法数据(不做翻译);随后把原方法替换为调用原生层的 exec shell L3 具备 仅处理规则接受的方法,非全量
池索引重映射 string / type / field / method 四类索引从原 DEX 重映射到数据内的引用池;随指令一并修正 L3 具备 volatile 字段指令(0xf5–0xf8)的字段索引亦纳入重映射
opcode 随机映射 每任务生成 256 字节 decoder 表;产物指令用映射后的 opcode,运行时查表还原 L3 具备 映射不跨应用复用,同一包重复加固也不同
exec shell 生成 原方法改为 execX(data_idx, Object[]);基本类型参数装箱(Integer.valueOf 等),对象直存;返回值拆箱并补 check-cast 以通过校验器 L3 具备 data_idx = dex_idx << 16 | method_idx,保证跨 DEX 唯一
控制结构保真 try/catch 的 uleb128 重算、handler 重映射;switch payload 原样搬运;分支偏移按替换后长度重算 L3 具备 goto 偏移溢出时升级为 goto/16
字符串加密 const-string 改写为 invoke-static stringFromPool(I) + move-result-object;字符串进数据池 L2 具备 仅覆盖被保护方法内命中的站点;替换会改变指令长度,故需重算分支与 try 范围
资源混淆 改写 resources.arsc 的资源项名与路径串,并同步改名包内 res/ 文件 L2 具备 AAPT2 标准布局的路径由 (类型, 项名, 配置) 推导而非存储,故必须同步改文件名;.9.png 后缀保留
方法名混淆 把壳 DEX 与 VM 数据中的方法名改写为无意义短名(a、b、c…),壳 DEX 与运行时引用池同步改名,不影响执行 L1–L2 具备(默认开启) 仅改名安全的方法:private、非构造、非 native、非生命周期,且继承链上无对外名字契约;构建异常时自动放弃混淆回退原名
字段名混淆 私有字段名改写为短名,并同步壳 DEX 的 field_ids 与 VM 字段池 L1–L2 具备(默认开启) 跳过 synthetic、序列化契约名,以及所在类含 native 方法或实现 Serializable 的字段;构建异常时自动回退原名
类名混淆 把壳 DEX 与 VM 数据中的类描述符改写为短名 L1–L2 可选(默认关闭) 仅 APK;AAB/AAR 的 manifest 为 protobuf 格式,无法提取组件名单,自动跳过。已内置保守过滤(manifest 组件、Serializable 闭包、异常类、含 native 的类、注解/枚举、反射字符串命中的类、资源绑定类),但类名的反射/序列化用法多样,建议充分测试后再启用
控制流平坦化 打散基本块并以状态机调度 L4 未提供 明确不做:需重写大量基本块,兼容性风险与体积/性能代价不成比例

三、数据与密钥

能力实现要点等级状态技术边界
任务级加密 每任务 64 字节随机 seed → SHA-256(domain || seed) 派生 32 字节密钥 → AES-256-GCM;nonce 12 字节 L3 具备 不跨应用复用固定密钥
密钥绑定 seed 写入该任务 SO 副本的 .zorvault.key 槽位(64 字节),数据与运行时一对一绑定 L3 具备 槽位被替换后即失效,已 patch 的 SO 不能再次用于加固
篡改认证 GCM 认证加密 + 固定 AAD;密文被改则解密失败 L3 具备 不接受未加密的明文数据,避免绕过解密层
白盒加密 / 远程下发 密钥不以可还原形式存在于包内,或运行时由服务端下发 L4 未提供 密钥材料随包分发,具备静态分析能力的攻击者原则上可还原 —— 这是与 L4 的主要差距

四、运行时实现

能力实现要点等级状态技术边界
启动与注册 JNI_OnLoad 取 Env、设置隐藏 API 豁免、读取并解密数据;随后用 RegisterNatives 把 8 个入口动态绑定到随机类名的壳类 L3 具备 壳类名随机化后静态 JNI 符号名不再匹配,必须走动态注册
解释执行 switch 派发;寄存器文件 + fp_flags 标记对象寄存器;引用池按索引解析并缓存 L3 具备 方法/字段解析结果带缓存,避免每条指令重复 FindClass/GetMethodID
引用生命周期 写入对象寄存器前先释放该寄存器原有局部引用;异常路径处理完即释放 L3 具备 JNI 局部引用表有上限(通常 512),循环内频繁抛异常曾导致累积,当前已修正
数组访问语义 下标按无符号比较判定越界,负索引与超大索引均触发 ArrayIndexOutOfBoundsException L3 具备 早期按有符号比较,负索引会绕过检查并越界读写,属于会静默损坏数据的缺陷,已修正
反调试 ptrace(TRACEME) 自检;被占用时回退读 /proc/self/statusTracerPid L2 具备 自检会占用追踪槽位,可能与外部调试器或崩溃采集组件竞争
反注入 / 反 Hook 扫描 GOT 表改写、inline hook 及已知注入特征 L3 部分具备 特征库为静态:改名或换路径的注入框架可能不匹配
Frida 主动检测 扫描 Frida 默认监听端口(27042 / 27043,同时查 /proc/net/tcptcp6)、遍历 /proc 查找 frida-server / gdbserver 进程,并匹配 frida-agent / frida-gadget / gum-js-loop 等注入特征 L3 具备(默认开启) 端口检测可覆盖"改名 / 改路径"的 Frida;改用自定义端口或自编译 Frida 仍可能绕过
Root 环境检测 检查常见 su 二进制落点(/system/xbin/su/system/bin/su/magisk/.core/bin/su 等)与 Root 管理包目录(Magisk / SuperSU 等) L2–L3 具备(默认开启) Root 管理包目录在 Android 10+ 受权限限制读不到,主要依据 /system/sbin 下的 su;检测路径与特征均混淆存储
多开环境检测 /proc/self/maps 与私有数据目录中匹配多开容器特征(平行空间、双开助手、VirtualApp 等)及 /virtual/:work/clone/ 等路径片段 L2–L3 具备(默认开启) 只用高置信特征,避免把正常 SDK 误判为多开;改名 / 换壳的多开框架可能不匹配
模拟器检测 检查 qemu 设备路径(/dev/qemu_pipe 等)与 /proc/cpuinfo 中的 goldfish / qemu 特征 L2–L3 具备(默认开启) 可用 disable_emulator_check 关闭(手游渠道包等确有模拟器用户的场景);AAR 无 security section,不生效
Java 层包名校验 在壳类的 <clinit> 中校验包名,位于加载原生库之前 L2 具备 即使原生库被剥离,该校验仍会执行,可作为兜底
违规处置 检测到违规后可选择:抛异常(默认)、静默返回零值 / 空引用、终止进程 L2 具备 默认抛异常,便于宿主与开发者发现排查;未知取值也回退为抛异常。静默策略能隐藏检测点,但可能掩盖问题,需按业务显式配置

五、完整性与打包交付

能力实现要点等级状态技术边界
DEX 完整性 自实现 ZIP 解析(尾部定位 EOCD → 中央目录 → 条目),对 DEX 计算 SHA-256 并与加固时记录值比对 L2 具备 加固产物的 DEX 为未压缩(STORED)存储;被重新压缩会判为篡改而非跳过校验
签名校验 校验签名证书指纹,识别重签名 L2 具备 AAB 不适用:商店会重新签名,绑定必然失效
重打包与对齐 清理 META-INF → 重签(v1/v2)→ 校验签名 → 对齐(so 16KB、dex 4B、arsc 16KB)→ 多 DEX 重新编号 L2 具备 自定义签名材料不持久化
方法级降级 方法含不支持指令时只跳过该方法,同 DEX 内其余方法照常处理 L3 具备 早期为整 DEX 降级,会造成大片代码未保护;已收敛为方法级
结果可核查 输出方法覆盖率、未保护类清单、字符串加密站点数 L3 具备 覆盖率分母为"规则接受的方法数",规则主动排除的不计为未保护
构建一致性校验 比对加固服务与运行时的数据格式版本指纹,不一致则拒绝产出 L3 具备 防止"代码已改、运行时未同步"导致可安装但会崩的产物

六、技术边界:哪些代码不会被保护

这些是由实现机制决定的硬边界,加固后请据任务报告中的覆盖率判断是否符合预期。

含以下 opcode 的方法会被跳过

这些指令解释器尚未支持,遇到即整方法不保护(不会回退整 DEX,但该方法保持原样):

iget-*-quick iput-*-quick invoke-virtual-quick invoke-super-quick

实际影响:这些 quickened 指令只会出现在被优化过的 odex 中,正常构建的 APK 不会命中。 invoke-custom / invoke-polymorphic / const-method-handle / const-method-type 等 Java 8+ 指令自 codegen 版本 2 起已完整支持,含这些指令的方法同样会被保护。

以下方法类型不参与保护

出于类初始化安全与语义正确性考虑,这些方法不会被抽取:

<init> <clinit> abstract native bridge synthetic

此外,生命周期方法中对父类的 invoke-super 调用也会跳过,避免类初始化阶段崩溃。

性能与体积代价

被保护方法改由原生解释器执行,热点路径会慢于原 DEX(无 JIT 参与); 每选一个 ABI 都会注入对应运行时。以当前版本为例, libzorvault.so 单 ABI 约 0.46–0.82 MB(armeabi-v7a ≈ 0.46 MB,arm64-v8a ≈ 0.66 MB, x86_64 ≈ 0.75 MB,x86 ≈ 0.82 MB),四个 ABI 全选约 2.7 MB, 另有随方法数增长的数据文件。 建议按规则只保护核心逻辑,而非全量保护。

七、当前未提供的能力及原因

控制流平坦化、指令级混淆 L4

原因:需要重写大量基本块与指令序列,兼容性风险显著(校验失败、机型相关异常), 且体积与运行时性能代价大。对"提高逆向成本"这一中小企业诉求而言收益风险不成比例,明确不纳入路线。

白盒加密与远程密钥下发 L4

原因:未提供。密钥材料随包分发,具备足够能力的攻击者可静态还原。 非强监管场景下通常不是首要风险,但不应期待其达到白盒强度。

AAB 的签名与 DEX 完整性绑定 L2

原因:分发机制限制而非实现缺失。Google Play 会重新签名并重新打包 AAB, 加固时绑定的签名必然失效,因此 AAB 产物只写入包名与开关。

READY TO PROTECT

先加固一个包,看实际覆盖率

新账户含 5 次永久有效的免费额度。任务报告会给出方法覆盖率、未保护类清单与字符串加密站点数。

打开加固控制台
FAQ

常见问题

加固支持哪些 Android 版本和 ABI?

Android 5.0(API 21)及以上;armeabi-v7a、arm64-v8a、x86、x86_64 四种 ABI 可选。

哪些代码不会被保护?

<init>、<clinit>、abstract、native、bridge、synthetic 方法会被跳过,保持原样执行;正常 APK 不会出现的 quickened 指令(iget/iput-*-quick、invoke-virtual/super-quick)所在的方法也会跳过。invoke-custom / invoke-polymorphic 等 Java 8+ 指令已支持保护。

加固会影响应用性能吗?

受保护方法无 JIT 参与,热点路径慢于原始 DEX;建议仅保护核心业务方法,而非全量加固。

加固强度属于哪个等级?

定位为增强级(L3):方法级代码虚拟化、opcode 随机映射、反注入与 Hook 检测、运行时主动防御,非 L4 白盒强度。

标识符混淆 / 控制流平坦化提供吗?

标识符混淆已提供:方法名与字段名混淆默认开启,类名混淆为可选(默认关闭,仅 APK)。控制流平坦化暂未提供,建议先用 R8/ProGuard 混淆后再提交,两者可叠加。

AAB 为什么只做 v1 签名、不绑定签名?

Google Play 会重新签名并重打包 AAB,加固时绑定的签名必然失效,故 AAB 产物只写入包名与开关。