市面上的 APK 加固方案大多是黑盒:付费、上传到对方服务器、不告诉你具体做了什么。Shellsmith 是我自己写的一个反过来的版本------本地处理、核心逻辑开源、技术边界写进文档里。这篇文章不讲功能罗列,讲两个具体的工程决策,顺便聊聊"加固到底能防住什么"这个经常被过度宣传的问题。
加固的基本流程
Android 侧的加固链路是:读取原签名证书 → 加密业务 DEX → 注入壳资源 → 用 Apktool 重新打包、对齐、按需签名。运行时由壳做安全检查,解密 DEX 缓存后加载业务代码,启动原应用。iOS 侧则是在独立工作副本里接入 Swift Confidential 做字符串保护,接入 freeRASP 做运行时威胁检测,再用 Xcode 原生工具链完成 Archive、导出和签名校验,原工程不会被修改。
密钥到底藏在哪------DEXB v6 的机密性边界
加密 DEX 用的是 DEXB v6 格式,核心是一层包裹密钥:运行时用签名证书指纹和一个叫 build_id 的值,派生出包裹密钥,再用它解出真正的 IKM(初始密钥材料),最后解密出全部业务 DEX。
问题在于,证书指纹和 build_id 这两个值都以明文形式写在包内的 DEXB 头部。也就是说,只要有这个 APK 文件本身,离线就能把整条密钥链还原出来,根本不需要运行应用、不需要 Root、不需要任何动态手段。
这意味着什么?这一层保护真正挡住的是:
- 改包后重新签名------换了证书,密钥派生就对不上,应用直接起不来;
- 低成本的自动化批量脱壳扫描------至少得写一段专门解析 DEXB 头部的代码,而不是拿现成工具一键跑。
但它挡不住认真做静态分析的人。要做到真正的载荷机密性,密钥的来源必须挪到包外------要么是设备侧硬件级不可导出的密钥(Android Keystore 的 TEE/StrongBox),要么是启动时联网向服务端请求、服务端做证书和设备校验后才下发的密钥。这两条路都还在路线图上,目前没做。
与其让用户误以为"加固了就等于防破解",不如把这件事写清楚------这也是这次开源时特意把这段机密性边界写进 README 和内部文档的原因。
一个具体的取舍:为什么资源混淆不动 mipmap 目录
严格保护模式下会对 res/ 下的资源文件路径做类似 AndResGuard 的处理:把 res/drawable/button_bg.png 这种可读路径缩短成 res/d/a.png,增加静态分析和反编译的阅读成本。
但这套逻辑里有一条硬编码的默认白名单:
arduino
private static final String[] DEFAULT_WHITELIST = {
// Launcher / adaptive icon XML sometimes loaded by path heuristics on OEM skins
// --- keep mipmap paths by default for safety; still shortens drawable/layout/xml.
"res/mipmap*/**",
};
原因是部分国产 ROM 的桌面启动器会按路径特征去找应用的自适应图标资源,如果把 mipmap 目录下的路径也缩短了,个别机型上可能出现图标加载异常。这是用一点点"资源混淆完整度"换"兼容性"的取舍,目前也还没有开放配置项让用户自己关掉这条白名单。
这种细节不会出现在功能列表里,但决定了"加固强度"这个词背后实际发生了什么------这也是我觉得加固类工具应该多讲工程细节、少讲营销话术的原因。
技术栈
Rust 写核心加固逻辑(shield-core)和 iOS 工程处理(shield-ios),CLI 用 clap,桌面 GUI 用 Tauri v2 + React + TypeScript,Android 壳是 Java + Rust JNI 混合,三平台构建走 GitHub Actions 全自动化,发布产物带 SHA-256 校验文件。
写在最后
加固工具的宣传话术里最常见的词是"无法破解""军工级加密",但凡是静态可还原的密钥、凡是没有包外秘密参与的方案,这类说法都经不起推敲。这次开源 Shellsmith,更想验证的是:一个愿意把边界讲清楚的工具,是不是也能被社区接受,而不是必须靠夸大宣传才能让人用。
仓库地址:github.com/kairowan/Sh... Issue。