9、DexGuard资源加密:资源文件保护、Assets加密、资源混淆
资源文件保护,说实话,在早期Android开发中经常被忽视。大家总觉得代码混淆了就安全了,资源文件嘛,无非就是些图片、布局、字符串,谁会在意?
直到我遇到一个客户,他的APK被反编译后,资源文件被直接拿来用,连图标都没换就上架了另一个市场。嗯,从那以后,资源保护就成了我加固方案里的标配。
DexGuard在这方面做得相当成熟。它不只是对代码做保护,对资源文件也有一套完整的加密方案。今天我就把这块内容拆开揉碎了讲给你听。
资源文件面临的风险
先说说资源文件为什么需要保护。你想想看,APK本质上就是个ZIP包,用任何解压工具都能直接打开。res目录下的图片、布局、字符串,全都暴露在外面。
常见的风险包括:
- 资源盗用:图片、音频、视频被直接提取使用
- 敏感信息泄露:硬编码在strings.xml中的API Key、URL等
- 逻辑分析:通过布局文件反推应用结构
- 白嫖破解:替换资源文件实现去广告、解锁功能
我在做金融类App加固时,就遇到过攻击者通过分析资源文件中的字符串,找到了后端API的测试接口,然后发起了一波攻击。所以资源保护绝对不是可有可无的。
DexGuard资源加密方案
DexGuard对资源的保护分为三个层面:资源文件加密、Assets加密、资源混淆。这三者配合使用,效果最好。
核心思路:DexGuard会在编译阶段对资源文件进行加密或混淆,然后在运行时通过自定义的ResourceLoader动态解密。攻击者即使解压了APK,看到的也是加密后的乱码。
下面这张图展示了DexGuard资源保护的整体流程:
资源文件加密
DexGuard对res目录下的资源文件,采用的是文件级加密。它会将资源文件的内容用AES算法加密,然后替换掉原始文件。
配置方式很简单,在DexGuard的配置文件中加入:
-keepresources {
encrypt
}
如果你想对特定类型的资源加密,可以指定:
-keepresources {
encrypt
resourcefilter = "*.png, *.jpg, *.mp3"
}
我的经验:不建议对所有资源都加密。像布局文件(.xml)加密后,如果频繁读取,会有性能损耗。我一般只对图片、音频、数据库文件这类体积大、敏感度高的资源做加密。
加密后的资源在运行时,DexGuard会通过Hook ResourceManager的方式,在资源被加载时自动解密。这个过程对开发者是透明的,你不需要修改任何代码。
Assets目录加密
Assets目录和res目录不同,它没有资源ID映射,访问方式是通过AssetManager.open()。所以DexGuard对Assets的处理方式也不太一样。
Assets加密的配置:
-keepassets {
encrypt
assetfilter = "*.db, *.json, *.config"
}
我记得有个项目,把SQLite数据库文件放在了assets目录下。如果不加密,攻击者直接解压就能拿到数据库,里面存着用户的离线数据。用DexGuard加密后,数据库文件在APK里是密文,运行时才解密加载。
注意:Assets加密后,你不能再用原生的AssetManager.open()直接读取了。DexGuard会提供一个自定义的AssetManager包装类,你需要用它来读取加密的Assets文件。
使用方式示例:
// 加密的Assets文件需要用DexGuard提供的API读取
InputStream is = DexGuardAssetManager.open(context, "encrypted_config.json");
// 后续操作和普通InputStream一样
资源混淆
资源混淆和资源加密是两回事。加密是把内容变成密文,混淆是把资源ID和文件名改成无意义的短名称。
为什么要做资源混淆?说白了,就是让攻击者看不懂你的资源结构。比如:
R.drawable.ic_home_selector→R.drawable.ares/layout/activity_main.xml→res/layout/a.xmlres/values/strings.xml→res/values/a.xml
DexGuard的资源混淆配置:
-keepresources {
obfuscate
obfuscationdictionary = "dict.txt" // 自定义混淆字典
}
混淆字典文件dict.txt的内容示例:
a
b
c
aa
ab
ac
...
DexGuard会从字典中随机选取短名称来替换原始资源名。
避坑指南:我曾经遇到过一个坑——资源混淆后,某些第三方SDK通过资源ID反射获取资源,结果崩溃了。解决方案是在混淆配置中排除这些SDK的资源:
-keepresources {
obfuscate
resourcefilter = "!com.thirdparty.sdk.*"
}
资源ID重映射
除了文件名混淆,DexGuard还会对资源ID做重映射。Android的资源ID格式是0xPPTTEEEE,其中PP是包ID,TT是类型ID,EEEE是条目ID。
DexGuard会重新分配这些ID,让攻击者无法通过ID推断资源类型。比如:
| 原始ID | 含义 | 混淆后ID | 含义 |
|---|---|---|---|
| 0x7F020000 | drawable类型,第0个 | 0x7F0100A3 | 看起来像attr类型 |
| 0x7F030001 | layout类型,第1个 | 0x7F0200B7 | 看起来像drawable类型 |
| 0x7F040002 | string类型,第2个 | 0x7F0300C1 | 看起来像layout类型 |
这样做的好处是,攻击者即使拿到了R.class文件,也无法通过ID快速定位到对应的资源类型。
实战配置示例
下面是一个完整的DexGuard资源保护配置,我在多个项目中都用过这个模板:
# 资源加密配置
-keepresources {
encrypt
# 只加密图片和音频,布局文件不加密
resourcefilter = "*.png, *.jpg, *.jpeg, *.mp3, *.wav"
}
# Assets加密配置
-keepassets {
encrypt
# 加密数据库和配置文件
assetfilter = "*.db, *.json, *.xml, *.config"
}
# 资源混淆配置
-keepresources {
obfuscate
# 排除第三方SDK的资源
resourcefilter = "!com.thirdparty.*"
# 使用自定义混淆字典
obfuscationdictionary = "obfuscation_dict.txt"
}
# 资源ID重映射
-keepresources {
reid
}
个人建议:资源加密和资源混淆可以同时开启,但要注意性能影响。加密会增加运行时解密开销,混淆会增加资源查找时间。我一般建议:
- 对敏感资源(数据库、配置文件、付费内容)开启加密
- 对所有资源开启混淆
- 资源ID重映射建议开启,几乎没有性能损耗
验证保护效果
配置完成后,怎么验证资源是否被保护了?我一般用这几个方法:
- 解压APK检查:用解压工具打开APK,看res目录下的文件是否还是原始名称和内容
- 反编译检查:用apktool反编译,看resources.arsc中的资源ID是否被重映射
- 运行时验证:在代码中打印资源路径,看是否正常加载
嗯,资源保护这块就讲到这里。DexGuard提供的这三层保护——加密、混淆、ID重映射——组合使用,基本能挡住90%以上的资源盗用行为。当然,没有绝对的安全,但至少让攻击者没那么容易得手。