Post

Android DexGuard 混淆指南

简介

DexGuard 是一款付费代码混淆软件,主要功能是对 Java 代码进行混淆,使得反编译后得到的源代码可读性差,从而加大破解的难度。DexGuard 与 Android 上主流的混淆工具 ProGuard 同属一家公司开发,但相比免费的 ProGuard 功能更多,混淆力度也更大。详细异同参考:DexGuard vs. ProGuard

本篇教程将使用 DexGuard 8.1.14 版本,在 Ubuntu 14.04 server 编译环境下对 Android Gradle 工程进行混淆。

入门

要使用 DexGuard,首先要在原 Android 工程中集成 DexGuard 插件。

编辑工程的 build.gradle 文件,导入 DexGuard 依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
vim project_root_build.gradle

buildscript {
    ...
    repositories {
        ...
        flatDir { dirs 'path_to_DexGuard-8.1.14/lib'}
    }
    dependencies {
        ...
        classpath ':dexguard:'
    }
}

编辑 app 模块的 build.gradle 使能 Dexguard 插件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
vim project_root_app_module_dir_build.gradle

apply plugin: 'dexguard'
    android {
        ...
        defaultConfig {
        ...
 //DedGuard will handle multidex in stead of Gradle, so if your project needs multdex support, disable Gradle's multidex support here and add -multidex to your dexguard-projext.txt.
 //multiDexEnabled true
        ndk {
            abiFilters "armeabi-v7a"
        }
    }
    buildTypes {
        release {
 proguardFile getDefaultDexGuardFile('dexguard-release.pro')
 proguardFile 'dexguard-project.txt'
            proguardFile 'proguard-project.txt'
        }
    debug {
 proguardFile getDefaultDexGuardFile('dexguard-debug.pro')
 proguardFile 'dexguard-project.txt'
            proguardFile 'proguard-project.txt'
        }
    }
 }

其中,dexguard-project.txt 为自定义规则文件,如果不存在,创建并将其放置在 app 模块目录。后续章节介绍如何制定规则。

DexGuard 是付费软件,因此还需要指定 license。将你的 license 文件放置在用户 home 根目录下即可。你也可以参考 docs/ 下的指导文档,配置环境变量来指定 license 文件。

配置好以后,就可以使用 Gradle 命令启动混淆版本的编译了

1
./gradlew assembRelease

DexGuard 会参与 Gradle 的 release 编译和 debug 编译,但为了方便开发调试,默认只有 release 版本才会应用 DexGuard 的各种功能,包括优化、混淆、压缩等。要注意尽管 debug 版本不会应用 DexGuard 功能,其打包过程还是由 DexGuard 接管( 代替 aapt) 。最后,你可通过反编译 apk 观察最终的混淆效果。

另外,随编译输出的还有 mapping.txt 文件,该文件记录了本次混淆的映射表,后续可以用来恢复 stack trace。

混淆基本原理与语法

DexGuard 对 Java 的混淆原理是将类名或方法、成员名自动替换为无意义且难以阅读的字符,这存在一个问题:DexGuard 并非完全精确,一些依赖于类名、方法名的调用,类名、方法名可能被错误地混淆,从而导致无法链接而调用失败。举例来说:如果 JNI 中的 Java Native 方法名被混淆,将导致 C++ 库无法根据方法名链接 Java 代码。对于这种情况,我们就需要额外制定混淆规则保留 native 方法名及其所在的类名。

1
2
3
-keepclasseswithmembernames,includedescriptorclasses class * {
    native <method>;
}

实际上,混淆规则的本质是帮助 DexGuard 识别程序的入口(Entry Point)。所谓入口,最容易理解的是程序的 main 方法,如果 main 方法都被混淆了,那么 Java 程序肯定就启动不了。此外, Entry Point 通常还包括 applets,midlets,activities。与此相反,类内部的 private 方法则一般不是 Entry Point,因为只在类内部调用,并不会暴露给外部。

DexGuard 的混淆规则语法与 Proguard 兼容。下面列举比较常用的语法:

keep - 将指定的类及成员保留 * - 匹配任意类名,但只匹配当前包目录,不跨界 ** - 匹配任意类名,且可跨目录匹配

例如:

1
2
3
-keep class android.support.v7.widget.SearchView {
    public <method>;
}

上面的规则表示将 class android.support.v7.widget.SearchView 类的类名名和该类的 public 方法保留。

更多语法内容请参考官网

规则制定过程总结

DexGuard 实际上已经能够自动识别一些 Entry Point,例如部分反射调用,此外还会根据你的工程所属平台启用子带的通用型规则。但是特定工程还是需要人工适配混淆规则。这里博主结合此前使用 ProGuard 的经验,提供 DexGuard 制定规则的一般步骤与思路。

Step 1 - 对于使用第三方库,或基于第三方开源软件开发的工程,首先去对应的第三方项目中寻找混淆规则,第三方库通常已经为我们做了这项工作

例如,代码量极其庞大的 Chromium 工程,比较明智的做法是站在巨人肩膀上。

Step 2 - 编译输出中包含 WarningNote 告警信息,导致编译不通过,根据告警提供的信息使用相应的混淆规则或确认误报,然后使用如下规则清除告警:

1
2
-dontwarn class_filter
-dontnote class_filter

Step 3 - 识别项目特定规则

跨 runtime 的方法调用,例如 V8 JavaScript 使用方法名调用 Java native 方法(JS/Native Bridge),显然不能让 Java 方法被混淆。

JS/Native Java 代码

1
2
@JSMethod
public void pay(String payReq, JSCallback success, JSCallback fail, JSCallback complete) {...}

混淆规则

1
2
3
-keepclassmembers class * {
    @com.taobao.weex.annotation.JSMethod <method>;
}

这里我们利用  @JSMethod annotation 来匹配工程中所有的 JS/Native 方法。

Step 4 - 观察日志,根据关键崩溃信息制定规则。典型场景如类名误混淆导致的崩溃 stack trace 如下

1
W System.err: Caused by: java.lang.ClassNotFoundException: Didn't find class "org.chromium.chrome.browser.preferences.ExpandablePreferenceGroup"

解决方法也很简单,在混淆规则中使用 -keep 语句保留住该类即可

1
-keep class org.chromium.chrome.browser.preferences.ExpandablePreferenceGroup

混淆规则制定过程中 crash 在所难免,有一个小技巧是通过大范围的 -keep 先让工程跑起来,然后逐步缩小 keep 的范围,从而定位具体误混淆的类。

ReTrace 恢复日志

混淆加大了黑客反编译的难度,但也给开发者造成了障碍:经过混淆的 apk 异常抛出错误时,trace 信息也是混淆的,不利于开发调试。

DexGuard 提供的 ReTrace 工具能够读取混淆的 stack trace 然后恢复成未混淆时的模样。这个过程依赖上文提到的 mapping 文件,该文件记录了混淆前和混淆后的类名、成员名的映射关系

1
<project_root>/<app_module>/build/outputs/mapping/release/mapping.txt

mapping 文件每个编译版本不同(除非特别指定),开发者需要记录发布版本和 mapping 文件的对应关系。

要恢复 stack trace, 准备好你的 mapping.txt 和 stacktrace_obfuscated.log,使用下面的命令完成恢复

1
2
cd DexGuard-8.1.14/bin
./retrace.sh mapping.txt stacktrace_obfuscated.log > stacktrace_restored.log

常见问题

参见此前的文章 - DexGuard Android Project Troubleshooting

This post is licensed under CC BY 4.0 by the author.