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/ assets/ DexGuard编译处理 加密 + 混淆 + 重命名 资源表重构 加密后的APK 资源已加密/混淆 运行时动态解密 自定义ResourceLoader 应用正常使用资源 getResources() 透明调用 攻击者解压APK 看到的是加密乱码 虚线表示攻击路径被阻断

资源文件加密

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_selectorR.drawable.a
  • res/layout/activity_main.xmlres/layout/a.xml
  • res/values/strings.xmlres/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重映射建议开启,几乎没有性能损耗

验证保护效果

配置完成后,怎么验证资源是否被保护了?我一般用这几个方法:

  1. 解压APK检查:用解压工具打开APK,看res目录下的文件是否还是原始名称和内容
  2. 反编译检查:用apktool反编译,看resources.arsc中的资源ID是否被重映射
  3. 运行时验证:在代码中打印资源路径,看是否正常加载

嗯,资源保护这块就讲到这里。DexGuard提供的这三层保护——加密、混淆、ID重映射——组合使用,基本能挡住90%以上的资源盗用行为。当然,没有绝对的安全,但至少让攻击者没那么容易得手。