毫无疑问,混淆是打包过程中最重要的流程之一,在没有特殊原因的情况下,所有 app 都应该开启混淆。
首先,这里说的的混淆其实是包括了代码压缩、代码混淆以及资源压缩等的优化过程。依靠 ProGuard,混淆流程将主项目以及依赖库中未被使用的类、类成员、方法、属性移除,这有助于规避64K方法数的瓶颈;同时,将类、类成员、方法重命名为无意义的简短名称,增加了逆向工程的难度。而依靠 Gradle 的 Android 插件,我们将移除未被使用的资源,可以有效减小 apk 安装包大小。
本文由两部分构成,第一部分给出混淆的最佳实践,力求让零基础的新手都可以直接使用混淆;第二部分会介绍一下混淆的整体、自定义混淆规则的语法与实践、自定义资源保持的规则等。
一、Android混淆最佳实践
1. 混淆配置
一般情况下,app module 的 build.gradle
文件默认会有如下结构:
android {
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
因为开启混淆会使编译时间变长,所以debug
模式下不应该开启。我们需要做的是:
1. 将release
下minifyEnabled
的值改为true
,打开混淆;
2. 加上shrinkResources true
,打开资源压缩。
修改后文件内容如下:
android {
buildTypes {
release {
minifyEnabled true
shrinkResources true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
2. 自定义混淆规则
在 app module
下默认生成了项目的自定义混淆规则文件 proguard-rules.pro
,多方调研后,一份适用于大部分项目的混淆规则最佳实践如下:
#指定压缩级别
-optimizationpasses 5
#不跳过非公共的库的类成员
-dontskipnonpubliclibraryclassmembers
#混淆时采用的算法
-optimizations !code/simplification/arithmetic,!field/*,!class/merging/*
#把混淆类中的方法名也混淆了
-useuniqueclassmembernames
#优化时允许访问并修改有修饰符的类和类的成员
-allowaccessmodification
#将文件来源重命名为“SourceFile”字符串
-renamesourcefileattribute SourceFile
#保留行号
-keepattributes SourceFile,LineNumberTable
#保持所有实现 Serializable 接口的类成员
-keepclassmembers class * implements java.io.Serializable {
static final long serialVersionUID;
private static final java.io.ObjectStreamField[] serialPersistentFields;
private void writeObject(java.io.ObjectOutputStream);
private void readObject(java.io.ObjectInputStream);
java.lang.Object writeReplace();
java.lang.Object readResolve();
}
#Fragment不需要在AndroidManifest.xml中注册,需要额外保护下
-keep public class * extends android.support.v4.app.Fragment
-keep public class * extends android.app.Fragment
# 保持测试相关的代码
-dontnote junit.framework.**
-dontnote junit.runner.**
-dontwarn android.test.**
-dontwarn android.support.test.**
-dontwarn org.junit.**
真正通用的、需要添加的就是上面这些,除此之外,需要每个项目根据自身的需求添加一些混淆规则:
- 第三方库所需的混淆规则。正规的第三方库一般都会在接入文档中写好所需混淆规则,使用时注意添加。
- 在运行时动态改变的代码,例如反射。比较典型的例子就是会与 json 相互转换的实体类。假如项目命名规范要求实体类都要放在model
包下的话,可以添加类似这样的代码把所有实体类都保持住:-keep public class **.*Model*.** {*;}
- JNI
中调用的类。
- WebView
中JavaScript
调用的方法
- Layout
布局使用的View
构造函数、android:onClick
等。
3. 检查混淆结果
混淆过的包必须进行检查,避免因混淆引入的bug。
一方面,需要从代码层面检查。使用上文的配置进行混淆打包后在 <module-name>/build/outputs/mapping/release/
目录下会输出以下文件:
- dump.txt
描述APK文件中所有类的内部结构
- mapping.txt
提供混淆前后类、方法、类成员等的对照表
- seeds.txt
列出没有被混淆的类和成员
- usage.txt
列出被移除的代码
我们可以根据 seeds.txt
文件检查未被混淆的类和成员中是否已包含所有期望保留的,再根据 usage.txt
文件查看是否有被误移除的代码。
另一方面,需要从测试方面检查。将混淆过的包进行全方面测试,检查是否有 bug 产生。
4. 解出混淆栈
混淆后的类、方法名等等难以阅读,这固然会增加逆向工程的难度,但对追踪线上 crash 也造成了阻碍。我们拿到 crash 的堆栈信息后会发现很难定位,这时需要将混淆反解。
在 <sdk-root>/tools/proguard/
路径下有附带的的反解工具(Window 系统为 proguardgui.bat
,Mac 或 Linux 系统为proguardgui.sh
)。
这里以 Window 平台为例。双击运行 proguardgui.bat
后,可以看到左侧的一行菜单。点击 ReTrace
,选择该混淆包对应的 mapping 文件(混淆后在<module-name>/build/outputs/mapping/release/
路径下会生成 mapping.txt
文件,它的作用是提供混淆前后类、方法、类成员等的对照表),再将 crash 的stack trace
黏贴进输入框中,点击右下角的 ReTrace
,混淆后的堆栈信息就显示出来了。
以上使用 GUI 程序进行操作,另一种方式是利用该路径下的 retrace
工具通过命令行进行反解,命令是
retrace.bat|retrace.sh [-verbose] mapping.txt [<stacktrace_file>]
例如:
retrace.bat -verbose mapping.txt obfuscated_trace.txt
注意事项:
1) 所有在 AndroidManifest.xml
涉及到的类已经自动被保持,因此不用特意去添加这块混淆规则。(很多老的混淆文件里会加,现在已经没必要)
2) proguard-android.txt
已经存在一些默认混淆规则,没必要在 proguard-rules.pro
重复添加,该文件具体规则见附录1:
二、混淆简介
Android中的“混淆”可以分为两部分,一部分是 Java 代码的优化与混淆,依靠 proguard 混淆器来实现;另一部分是资源压缩,将移除项目及依赖的库中未被使用的资源(资源压缩严格意义上跟混淆没啥关系,但一般我们都会放一起讲)。
1. 代码压缩
代码混淆是包含了代码压缩、优化、混淆等一系列行为的过程。如上图所示,混淆过程会有如下几个功能:
1. 压缩。移除无效的类、类成员、方法、属性等;
2. 优化。分析和优化方法的二进制代码;根据proguard-android-optimize.txt中的描述,优化可能会造成一些潜在风险,不能保证在所有版本的Dalvik上都正常运行。
3. 混淆。把类名、属性名、方法名替换为简短且无意义的名称;
4. 预校验。添加预校验信息。这个预校验是作用在Java平台上的,Android平台上不需要这项功能,去掉之后还可以加快混淆速度。
这四个流程默认开启。
在 Android 项目中我们可以选择将“优化”和“预校验”关闭,对应命令是-dontoptimize
、-dontpreverify
(当然,默认的proguard-android.txt
文件已包含这两条混淆命令,不需要开发者额外配置)。
2. 资源压缩
资源压缩将移除项目及依赖的库中未被使用的资源,这在减少 apk 包体积上会有不错的效果,一般建议开启。具体做法是在 build.grade
文件中,将shrinkResources
属性设置为 true
。需要注意的是,只有在用minifyEnabled true
开启了代码压缩后,资源压缩才会生效。
资源压缩包含了“合并资源”和“移除资源”两个流程。
“合并资源”流程中,名称相同的资源被视为重复资源会被合并。需要注意的是,这一流程不受shrinkResources
属性控制,也无法被禁止, gradle 必然会做这项工作,因为假如不同项目中存在相同名称的资源将导致错误。gradle 在四处地方寻找重复资源:
- src/main/res/
路径
- 不同的构建类型(debug、release等等)
- 不同的构建渠道
- 项目依赖的第三方库
合并资源时按照如下优先级顺序:
依赖 -> main -> 渠道 -> 构建类型
举个例子,假如重复资源同时存在于main
文件夹和不同渠道中,gradle 会选择保留渠道中的资源。
同时,如果重复资源在同一层次出现,比如src/main/res/
和 src/main/res2/
,则 gradle 无法完成资源合并,这时会报资源合并错误。
“移除资源”流程则见名知意,需要注意的是,类似代码,混淆资源移除也可以定义哪些资源需要被保留,这点在下文给出。
三、自定义混淆规则
在上文“混淆配置”中有这样一行代码
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
这行代码定义了混淆规则由两部分构成:位于 SDK 的 tools/proguard/
文件夹中的 proguard-android.txt
的内容以及默认放置于模块根目录的proguard-rules.pro
的内容。前者是 SDK 提供的默认混淆文件(内容见附录1),后者是开发者自定义混淆规则的地方。
1. 常见混淆命令:
- optimizationpasses
- dontoptimize
- dontusemixedcaseclassnames
- dontskipnonpubliclibraryclasses
- dontpreverify
- dontwarn
- verbose
- optimizations
- keep
- keepnames
- keepclassmembers
- keepclassmembernames
- keepclasseswithmembers
- keepclasseswithmembernames
在第一部分 Android 混淆最佳实践中已介绍部分需要使用到的混淆命令,这里不再赘述,详情请查阅官网。需要特别介绍的是与保持相关元素不参与混淆的规则相关的几种命令:
命令 | 作用 |
---|---|
-keep | 防止类和成员被移除或者被重命名 |
-keepnames | 防止类和成员被重命名 |
-keepclassmembers | 防止成员被移除或者被重命名 |
-keepnames | 防止成员被重命名 |
-keepclasseswithmembers | 防止拥有该成员的类和成员被移除或者被重命名 |
-keepclasseswithmembernames | 防止拥有该成员的类和成员被重命名 |
2. 保持元素不参与混淆的规则
形如:
[保持命令] [类] {
[成员]
}
“类”代表类相关的限定条件,它将最终定位到某些符合该限定条件的类。它的内容可以使用:
- 具体的类
- 访问修饰符(public
、protected
、private
)
- 通配符*
,匹配任意长度字符,但不含包名分隔符(.)
- 通配符**
,匹配任意长度字符,并且包含包名分隔符(.)
- extends
,即可以指定类的基类
- implement
,匹配实现了某接口的类
- $,内部类
“成员”代表类成员相关的限定条件,它将最终定位到某些符合该限定条件的类成员。它的内容可以使用:
- 匹配所有构造器
- 匹配所有域
- 匹配所有方法
- 通配符*
,匹配任意长度字符,但不含包名分隔符(.)
- 通配符**
,匹配任意长度字符,并且包含包名分隔符(.)
- 通配符***
,匹配任意参数类型
- …
,匹配任意长度的任意类型参数。比如void test(…)就能匹配任意 void test(String a)
或者是void test(int a, String b)
这些方法。
- 访问修饰符(public
、protected
、private
)
举个例子,假如需要将name.huihui.test
包下所有继承Activity
的public
类及其构造函数都保持住,可以这样写:
-keep public class name.huihui.test.** extends Android.app.Activity {
<init>
}
3. 常用的自定义混淆规则
- 不混淆某个类
-keep public class name.huihui.example.Test { *; }
不混淆某个包所有的类
-keep class name.huihui.test.** { *; }
不混淆某个类的子类
-keep public class * extends name.huihui.example.Test { *; }
不混淆所有类名中包含了“model”的类及其成员
-keep public class **.*model*.** {*;}
不混淆某个接口的实现
-keep class * implements name.huihui.example.TestInterface { *; }
不混淆某个类的构造方法
-keepclassmembers class name.huihui.example.Test {
public <init>();
}
不混淆某个类的特定的方法
-keepclassmembers class name.huihui.example.Test {
public void test(java.lang.String);
}
不混淆某个类的内部类
-keep class name.huihui.example.Test$* {
*;
}
不混淆某个类的内部类
-keepattributes Signature
四、自定义资源保持规则
1. keep.xml
用shrinkResources true
开启资源压缩后,所有未被使用的资源默认被移除。假如你需要定义哪些资源必须被保留,在 res/raw/
路径下创建一个 xml 文件,例如 keep.xml
。
通过一些属性的设置可以实现定义资源保持的需求,可配置的属性有:
- tools:keep
定义哪些资源需要被保留(资源之间用“,”隔开)
- tools:discard
定义哪些资源需要被移除(资源之间用“,”隔开)
- tools:shrinkMode
开启严格模式
当代码中通过 Resources.getIdentifier()
用动态的字符串来获取并使用资源时,普通的资源引用检查就可能会有问题。例如,如下代码会导致所有以“img_”开头的资源都被标记为已使用。
String name = String.format("img_%1d", angle + 1);
res = getResources().getIdentifier(name, "drawable", getPackageName());
我们可以设置 tools:shrinkMode
为 strict
来开启严格模式,使只有确实被使用的资源被保留。
以上就是自定义资源保持规则相关的配置,举个例子:
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:tools="http://schemas.android.com/tools"
tools:keep="@layout/l_used*_c,@layout/l_used_a,@layout/l_used_b*"
tools:discard="@layout/unused2"
tools:shrinkMode="strict"/>
2. 移除替代资源
一些替代资源,例如多语言支持的 strings.xml
,多分辨率支持的 layout.xml
等,在我们不需要使用又不想删除掉时,可以使用资源压缩将它们移除。
我们使用 resConfig
属性来指定需要支持的属性,例如
android {
defaultConfig {
...
resConfigs "en", "fr"
}
}
其他未显式声明的语言资源将被移除。
参考资料
- Shrink Your Code and Resources
- proguard
- Android安全攻防战,反编译与混淆技术完全解析(下)
- Android混淆从入门到精通
- Android代码混淆之ProGuard
附录
proguard-android.txt
文件内容
#包名不混合大小写
-dontusemixedcaseclassnames
#不跳过非公共的库的类
-dontskipnonpubliclibraryclasses
#混淆时记录日志
-verbose
#关闭预校验
-dontpreverify
#不优化输入的类文件
-dontoptimize
#保护注解
-keepattributes *Annotation*
#保持所有拥有本地方法的类名及本地方法名
-keepclasseswithmembernames class * {
native <methods>;
}
#保持自定义View的get和set相关方法
-keepclassmembers public class * extends android.view.View {
void set*(***);
*** get*();
}
#保持Activity中View及其子类入参的方法
-keepclassmembers class * extends android.app.Activity {
public void *(android.view.View);
}
#枚举
-keepclassmembers enum * {
**[] $VALUES;
public *;
}
#Parcelable
-keepclassmembers class * implements android.os.Parcelable {
public static final android.os.Parcelable$Creator CREATOR;
}
#R文件的静态成员
-keepclassmembers class **.R$* {
public static <fields>;
}
-dontwarn android.support.**
#keep相关注解
-keep class android.support.annotation.Keep
-keep @android.support.annotation.Keep class * {*;}
-keepclasseswithmembers class * {
@android.support.annotation.Keep <methods>;
}
-keepclasseswithmembers class * {
@android.support.annotation.Keep <fields>;
}
-keepclasseswithmembers class * {
@android.support.annotation.Keep <init>(...);
}
Android 混淆总结(直接copy)
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in D:\android-sdk-windows/tools/proguard/proguard-android.txt
# You can edit the include path and order by changing the proguardFiles
# directive in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# Add any project specific keep options here:
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# copyright zhonghanwen
#-------------------------------------------基本不用动区域--------------------------------------------
#---------------------------------基本指令区----------------------------------
-optimizationpasses 5
-dontskipnonpubliclibraryclassmembers
-printmapping proguardMapping.txt
-optimizations !code/simplification/cast,!field/*,!class/merging/*
-keepattributes *Annotation*,InnerClasses
-keepattributes Signature
-keepattributes SourceFile,LineNumberTable
#----------------------------------------------------------------------------
#---------------------------------默认保留区---------------------------------
#继承activity,application,service,broadcastReceiver,contentprovider....不进行混淆
-keep public class * extends android.app.Activity
-keep public class * extends android.app.Application
-keep public class * extends android.support.multidex.MultiDexApplication
-keep public class * extends android.app.Service
-keep public class * extends android.content.BroadcastReceiver
-keep public class * extends android.content.ContentProvider
-keep public class * extends android.app.backup.BackupAgentHelper
-keep public class * extends android.preference.Preference
-keep public class * extends android.view.View
-keep public class com.android.vending.licensing.ILicensingService
-keep class android.support.** {*;}
-keep public class * extends android.view.View{
*** get*();
void set*(***);
public <init>(android.content.Context);
public <init>(android.content.Context, android.util.AttributeSet);
public <init>(android.content.Context, android.util.AttributeSet, int);
}
-keepclasseswithmembers class * {
public <init>(android.content.Context, android.util.AttributeSet);
public <init>(android.content.Context, android.util.AttributeSet, int);
}
#这个主要是在layout 中写的onclick方法android:onclick="onClick",不进行混淆
-keepclassmembers class * extends android.app.Activity {
public void *(android.view.View);
}
-keepclassmembers class * implements java.io.Serializable {
static final long serialVersionUID;
private static final java.io.ObjectStreamField[] serialPersistentFields;
private void writeObject(java.io.ObjectOutputStream);
private void readObject(java.io.ObjectInputStream);
java.lang.Object writeReplace();
java.lang.Object readResolve();
}
-keep class **.R$* {
*;
}
-keepclassmembers class * {
void *(*Event);
}
-keepclassmembers enum * {
public static **[] values();
public static ** valueOf(java.lang.String);
}
-keep class * implements android.os.Parcelable {
public static final android.os.Parcelable$Creator *;
}
#// natvie 方法不混淆
-keepclasseswithmembernames class * {
native <methods>;
}
#保持 Parcelable 不被混淆
-keep class * implements android.os.Parcelable {
public static final android.os.Parcelable$Creator *;
}
#----------------------------------------------------------------------------
#---------------------------------webview------------------------------------
-keepclassmembers class fqcn.of.javascript.interface.for.Webview {
public *;
}
-keepclassmembers class * extends android.webkit.WebViewClient {
public void *(android.webkit.WebView, java.lang.String, android.graphics.Bitmap);
public boolean *(android.webkit.WebView, java.lang.String);
}
-keepclassmembers class * extends android.webkit.WebViewClient {
public void *(android.webkit.WebView, jav.lang.String);
}
#----------------------------------------------------------------------------
#---------------------------------------------------------------------------------------------------
#---------------------------------实体类---------------------------------
修改成你对应的包名
-keep class [your_pkg].** { *; }
#---------------------------------第三方包-------------------------------
#支付宝支付
-keep class com.alipay.android.app.IAlixPay{*;}
-keep class com.alipay.android.app.IAlixPay$Stub{*;}
-keep class com.alipay.android.app.IRemoteServiceCallback{*;}
-keep class com.alipay.android.app.IRemoteServiceCallback$Stub{*;}
-keep class com.alipay.sdk.app.PayTask{ public *;}
-keep class com.alipay.sdk.app.AuthTask{ public *;}
-keep public class * extends android.os.IInterface
#微信支付
-keep class com.tencent.mm.sdk.openapi.WXMediaMessage {*;}
-keep class com.tencent.mm.sdk.openapi.** implements com.tencent.mm.sdk.openapi.WXMediaMessage$IMediaObject {*;}
-keep class com.tencent.wxop.** { *; }
-dontwarn com.tencent.mm.**
-keep class com.tencent.mm.**{*;}
-keepclasseswithmembernames class ** {
}
-keepattributes Signature
-keep class sun.misc.Unsafe { *; }
-keep class com.taobao.** {*;}
-keep class com.alibaba.** {*;}
-keep class com.alipay.** {*;}
-dontwarn com.taobao.**
-dontwarn com.alibaba.**
-dontwarn com.alipay.**
-keep class com.ut.** {*;}
-dontwarn com.ut.**
-keep class com.ta.** {*;}
-dontwarn com.ta.**
-keep class anet.**{*;}
-keep class org.android.spdy.**{*;}
-keep class org.android.agoo.**{*;}
-dontwarn anet.**
-dontwarn org.android.spdy.**
-dontwarn org.android.agoo.**
-keepclasseswithmembernames class com.xiaomi.**{*;}
-keep public class * extends com.xiaomi.mipush.sdk.PushMessageReceiver
-dontwarn com.xiaomi.push.service.b
-keep class org.apache.http.**
-keep interface org.apache.http.**
-dontwarn org.apache.**
#okhttp3.x
-dontwarn com.squareup.okhttp3.**
-keep class com.squareup.okhttp3.** { *;}
-dontwarn okio.**
#sharesdk
-keep class cn.sharesdk.**{*;}
-keep class com.sina.**{*;}
-keep class **.R$* {*;}
-keep class **.R{*;}
-keep class com.mob.**{*;}
-dontwarn com.mob.**
-dontwarn cn.sharesdk.**
-dontwarn **.R$*
## nineoldandroids-2.4.0.jar
-keep public class com.nineoldandroids.** {*;}
####################zxing#####################
-keep class com.google.zxing.** {*;}
-dontwarn com.google.zxing.**
##百度定位
-keep class com.baidu.** {*;}
-keep class vi.com.** {*;}
-dontwarn com.baidu.**
## okhttp
-dontwarn com.squareup.okhttp.**
-keep class com.squareup.okhttp.{*;}
#retrofit
-dontwarn retrofit.**
-keep class retrofit.** { *; }
-keepattributes Signature
-keepattributes Exceptions
-dontwarn okio.**
#recyclerview-animators
-keep class jp.wasabeef.** {*;}
-dontwarn jp.wasabeef.*
#multistateview
-keep class com.kennyc.view.** { *; }
-dontwarn com.kennyc.view.*
# universal-image-loader 混淆
-dontwarn com.nostra13.universalimageloader.**
-keep class com.nostra13.universalimageloader.** { *; }
#ormlite
-keep class com.j256.**
-keepclassmembers class com.j256.** { *; }
-keep enum com.j256.**
-keepclassmembers enum com.j256.** { *; }
-keep interface com.j256.**
-keepclassmembers interface com.j256.** { *; }
#umeng
# ========= 友盟 =================
-dontshrink
-dontoptimize
-dontwarn com.google.android.maps.**
-dontwarn android.webkit.WebView
-dontwarn com.umeng.**
-dontwarn com.tencent.weibo.sdk.**
-dontwarn com.facebook.**
-keep enum com.facebook.**
-keepattributes Exceptions,InnerClasses,Signature
-keepattributes *Annotation*
-keepattributes SourceFile,LineNumberTable
-keep public interface com.facebook.**
-keep public interface com.tencent.**
-keep public interface com.umeng.socialize.**
-keep public interface com.umeng.socialize.sensor.**
-keep public interface com.umeng.scrshot.**
-keep public class com.umeng.socialize.* {*;}
-keep public class javax.**
-keep public class android.webkit.**
-keep class com.facebook.**
-keep class com.umeng.scrshot.**
-keep public class com.tencent.** {*;}
-keep class com.umeng.socialize.sensor.**
-keep class com.tencent.mm.sdk.modelmsg.WXMediaMessage {*;}
-keep class com.tencent.mm.sdk.modelmsg.** implements com.tencent.mm.sdk.modelmsg.WXMediaMessage$IMediaObject {*;}
-keep class im.yixin.sdk.api.YXMessage {*;}
-keep class im.yixin.sdk.api.** implements im.yixin.sdk.api.YXMessage$YXMessageData{*;}
下面中括号的地方需要要填你的包名
-keep public class [your_pkg].R$*{
public static final int *;
}
-keepclassmembers class * {
public <init> (org.json.JSONObject);
}
-keepclassmembers enum * {
public static **[] values();
public static ** valueOf(java.lang.String);
}
#友盟自动更新
-keep public class com.umeng.fb.ui.ThreadView {
}
-keep public class * extends com.umeng.**
# 以下包不进行过滤
-keep class com.umeng.** { *; }
#-ButterKnife 7.0
-keep class butterknife.** { *; }
-dontwarn butterknife.internal.**
-keep class **$$ViewBinder { *; }
-keepclasseswithmembernames class * {
@butterknife.* <fields>;
}
-keepclasseswithmembernames class * {
@butterknife.* <methods>;
}
#AndFix
-keep class * extends java.lang.annotation.Annotation
-keepclasseswithmembernames class * {
native <methods>;
}
#eventbus 3.0
-keepattributes *Annotation*
-keepclassmembers class ** {
@org.greenrobot.eventbus.Subscribe <methods>;
}
-keep enum org.greenrobot.eventbus.ThreadMode { *; }
-keepclassmembers class * extends org.greenrobot.eventbus.util.ThrowableFailureEvent {
<init>(java.lang.Throwable);
}
#EventBus
-keepclassmembers class ** {
public void onEvent*(**);
}
-keepclassmembers class ** {
public void xxxxxx(**);
}
################gson##################
-keep class com.google.gson.** {*;}
-keep class com.google.**{*;}
-keep class sun.misc.Unsafe { *; }
-keep class com.google.gson.stream.** { *; }
-keep class com.google.gson.examples.android.model.** { *; }
-keepclassmembers class * implements java.io.Serializable {
static final long serialVersionUID;
private static final java.io.ObjectStreamField[] serialPersistentFields;
private void writeObject(java.io.ObjectOutputStream);
private void readObject(java.io.ObjectInputStream);
java.lang.Object writeReplace();
java.lang.Object readResolve();
}
-keep public class * implements java.io.Serializable {*;}
# support-v4
#https://stackoverflow.com/questions/18978706/obfuscate-android-support-v7-widget-gridlayout-issue
-dontwarn android.support.v4.**
-keep class android.support.v4.app.** { *; }
-keep interface android.support.v4.app.** { *; }
-keep class android.support.v4.** { *; }
# support-v7
-dontwarn android.support.v7.**
-keep class android.support.v7.internal.** { *; }
-keep interface android.support.v7.internal.** { *; }
-keep class android.support.v7.** { *; }
# support design
#@link http://stackoverflow.com/a/31028536
-dontwarn android.support.design.**
-keep class android.support.design.** { *; }
-keep interface android.support.design.** { *; }
-keep public class android.support.design.R$* { *; }
#-------------------------------------------------------------------------
# picasso
-keep class com.squareup.picasso.** {*; }
-dontwarn com.squareup.picasso.**
#glide
-keep public class * implements com.bumptech.glide.module.GlideModule
-keep public enum com.bumptech.glide.load.resource.bitmap.ImageHeaderParser$** {
**[] $VALUES;
public *;
}
# # ######## greenDao混淆 ##########
# # -------------------------------------------
-keep class de.greenrobot.dao.** {*;}
-keepclassmembers class * extends de.greenrobot.dao.AbstractDao {
public static Java.lang.String TABLENAME;
}
-keep class **$Properties
# # ############### volley混淆 ###############
# # -------------------------------------------
-keep class com.android.volley.** {*;}
-keep class com.android.volley.toolbox.** {*;}
-keep class com.android.volley.Response$* { *; }
-keep class com.android.volley.Request$* { *; }
-keep class com.android.volley.RequestQueue$* { *; }
-keep class com.android.volley.toolbox.HurlStack$* { *; }
-keep class com.android.volley.toolbox.ImageLoader$* { *; }
#jpush极光推送
-dontwarn cn.jpush.**
-keep class cn.jpush.** { *; }
#activeandroid
-keep class com.activeandroid.** { *; }
-dontwarn com.ikoding.app.biz.dataobject.**
-keep public class com.ikoding.app.biz.dataobject.** { *;}
-keepattributes *Annotation*
#log4j
-dontwarn org.apache.log4j.**
-keep class org.apache.log4j.** { *;}
#下面几行 是环信即时通信的代码混淆
-keep class com.easemob.** {*;}
-keep class org.jivesoftware.** {*;}
-dontwarn com.easemob.**
#融云
-keepclassmembers class fqcn.of.javascript.interface.for.webview {
public *;
}
-keepattributes Exceptions,InnerClasses
-keep class io.rong.** {*;}
-keep class * implements io.rong.imlib.model.MessageContent{*;}
-keepattributes Signature
-keepattributes *Annotation*
-keep class sun.misc.Unsafe { *; }
-keep class com.google.gson.examples.android.model.** { *; }
-keepclassmembers class * extends com.sea_monster.dao.AbstractDao {
public static java.lang.String TABLENAME;
}
-keep class **$Properties
-dontwarn org.eclipse.jdt.annotation.**
-keep class com.ultrapower.** {*;}
#高徳地图
-dontwarn com.amap.api.**
-dontwarn com.a.a.**
-dontwarn com.autonavi.**
-keep class com.amap.api.** {*;}
-keep class com.autonavi.** {*;}
-keep class com.a.a.** {*;}
#---------------------------------反射相关的类和方法-----------------------
在这下面写反射相关的类和方法,没有就不用写!
#---------------------------------与js互相调用的类------------------------
在这下面写与js互相调用的类,没有就去掉这句话!
#---------------------------------自定义View的类------------------------
在这下面写自定义View的类的类,没有就去掉这句话!
#SuperID
#由*郭宇翔*贡献混淆代码
#作者Github地址:https://github.com/yourtion
-keep class **.R$* {*;}
-keep class com.isnc.facesdk.aty.**{*;}
-keep class com.isnc.facesdk.**{*;}
-keep class com.isnc.facesdk.common.**{*;}
-keep class com.isnc.facesdk.net.**{*;}
-keep class com.isnc.facesdk.view.**{*;}
-keep class com.isnc.facesdk.viewmodel.**{*;}
-keep class com.matrixcv.androidapi.face.**{*;}
#retrofit2.x
-dontwarn retrofit2.**
-keep class retrofit2.** { *; }
-keepattributes Signature
-keepattributes Exceptions
#Rxjava RxAndroid
-dontwarn rx.*
-dontwarn sun.misc.**
-keepclassmembers class rx.internal.util.unsafe.*ArrayQueue*Field* {
long producerIndex;
long consumerIndex;
}
-keepclassmembers class rx.internal.util.unsafe.BaseLinkedQueueProducerNodeRef {
rx.internal.util.atomic.LinkedQueueNode producerNode;
}
-keepclassmembers class rx.internal.util.unsafe.BaseLinkedQueueConsumerNodeRef {
rx.internal.util.atomic.LinkedQueueNode consumerNode;
}
#litepal
-dontwarn org.litepal.
-keep class org.litepal.* { ; }
-keep enum org.litepal.*
-keep interface org.litepal. { ; }
-keep public class extends org.litepal.
-keepattributes Annotation
-keepclassmembers class extends org.litepal.crud.DataSupport{;}
#fastJson
-dontwarn com.alibaba.fastjson.**
-keep class com.alibaba.fastjson.** { *; }
一、混淆的目的
一款发布到市场的软件原则上都应该做代码混淆,可能有人会说谁有功夫破解你的烂代码,这个嘛,开心就好......
通过代码混淆可以将项目中的类、方法、变量等信息进行重命名,变成一些无意义的简短名字,同时也可以移除未被使用的类、方法、变量等。所以直观的看,通过混淆可以提高程序的安全性,增加逆向工程的难度,同时也有效缩减了apk
的体积。一起来get这个技能吧!
二、开启混淆
在基于Android Studio
项目的app module
的build.gradle
中有如下默认代码片段:
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
代表要发布的release
包的混淆配置,默认不开启混淆,要开启混淆首先做如下修改:
minifyEnabled true
开启混淆后还可以添加shrinkResources true
配置,代表开启资源文件压缩。
这样就在release
模式下开启了混淆。一般在debug
模式下不开启混淆,因为混淆会导致编译时间变长、无法debug
问题,毕竟也是内部测试嘛,没必要!
开启混淆后,接下来就是用混淆配置文件来设置混淆规则:
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
proguard-android.txt
代表系统默认的混淆规则配置文件,该文件在<Android SDK目录>/tools/proguard
下,一般不要更改该配置文件,因为也会作用于其它项目,除非你能确保所做的更改不影响其它项目的混淆。proguard-rules.pro
代码表当前project
的混淆配置文件,在app module
下,可以通过修改该文件来添加适用当前项目的混淆规则。
三、编写混淆配置文件
为了更好的编写proguard-rules.pro
,先学习下系统的proguard-android.txt
# This is a configuration file for ProGuard.
# http://proguard.sourceforge.net/index.html#manual/usage.html
# 混淆时不使用大小写混合类名
-dontusemixedcaseclassnames
# 不跳过library中的非public的类
-dontskipnonpubliclibraryclasses
# 打印混淆的详细信息
-verbose
# Optimization is turned off by default. Dex does not like code run
# through the ProGuard optimize and preverify steps (and performs some
# of these optimizations on its own).
# 关闭优化(原因见上边的原英文注释)
-dontoptimize
# 不进行预校验,可加快混淆速度
-dontpreverify
# Note that if you want to enable optimization, you cannot just
# include optimization flags in your own project configuration file;
# instead you will need to point to the
# "proguard-android-optimize.txt" file instead of this one from your
# project.properties file.
# 保留注解中的参数
-keepattributes *Annotation*
# 不混淆如下两个谷歌服务类
-keep public class com.google.vending.licensing.ILicensingService
-keep public class com.android.vending.licensing.ILicensingService
# For native methods, see http://proguard.sourceforge.net/manual/examples.html#native
# 不混淆包含native方法的类的类名以及native方法名
-keepclasseswithmembernames class * {
native <methods>;
}
# keep setters in Views so that animations can still work.
# see http://proguard.sourceforge.net/manual/examples.html#beans
# 不混淆View中的setXxx()和getXxx()方法,以保证属性动画正常工作
-keepclassmembers public class * extends android.view.View {
void set*(***);
*** get*();
}
# We want to keep methods in Activity that could be used in the XML attribute onClick
# 不混淆Activity中参数是View的方法,例如,一个控件通过android:onClick="clickMethodName"绑定点击事件,混淆后会导致点击事件失效
-keepclassmembers class * extends android.app.Activity {
public void *(android.view.View);
}
# For enumeration classes, see http://proguard.sourceforge.net/manual/examples.html#enumerations
# 不混淆枚举类中的values()和valueOf()方法
-keepclassmembers enum * {
public static **[] values();
public static ** valueOf(java.lang.String);
}
# 不混淆Parcelable实现类中的CREATOR字段,以保证Parcelable机制正常工作
-keepclassmembers class * implements android.os.Parcelable {
public static final android.os.Parcelable$Creator CREATOR;
}
# 不混淆R文件中的所有静态字段,以保证正确找到每个资源的id
-keepclassmembers class **.R$* {
public static <fields>;
}
# The support library contains references to newer platform versions.
# Don't warn about those in case this app is linking against an older
# platform version. We know about them, and they are safe.
# 不对android.support包下的代码警告(如果我们打包的版本低于support包下某些类的使用版本,会出现警告的问题)
-dontwarn android.support.**
# Understand the @Keep support annotation.
# 不混淆Keep类
-keep class android.support.annotation.Keep
# 不混淆使用了注解的类及类成员
-keep @android.support.annotation.Keep class * {*;}
# 如果类中有使用了注解的方法,则不混淆类和类成员
-keepclasseswithmembers class * {
@android.support.annotation.Keep <methods>;
}
# 如果类中有使用了注解的字段,则不混淆类和类成员
-keepclasseswithmembers class * {
@android.support.annotation.Keep <fields>;
}
# 如果类中有使用了注解的构造函数,则不混淆类和类成员
-keepclasseswithmembers class * {
@android.support.annotation.Keep <init>(...);
}
可以看出proguard-android.txt
主要作用是防止指定内容被混淆,其中使用了以-
开头,结合keep
类关键字,*
、<>
等通配符的语法,先get这些语法吧!
- 首先看
keep
类关键字:
关键字 | 含义 |
---|---|
keep | 保留类和类成员,防止被混淆或移除 |
keepnames | 保留类和类成员,防止被混淆,但没有被引用的类成员会被移除 |
keepclassmembers | 只保留类成员,防止被混淆或移除 |
keepclassmembernames | 只保留类成员,防止被混淆,但没有被引用的成员会被移除 |
keepclasseswithmembers | 保留类和类成员,防止被混淆或移除,如果指定的类成员不存在还是会被混淆 |
keepclasseswithmembernames | 保留类和类成员,防止被混淆,如果指定的类成员不存在还是会被混淆,没有被引用的类成员会被移除 |
- 相关通配符:
通配符 | 含义 |
---|---|
* | 匹配任意长度字符,但不含包名分隔符. 。例如一个类的全包名路径是com.othershe.test.Person ,使用com.othershe.test.* 、com.othershe.test.* 都是可以匹配的,但com.othershe.* 就不能匹配 |
** | 匹配任意长度字符,并包含包名分隔符. 。例如要匹配com.othershe.test.** 包下的所有内容 |
*** | 匹配任意参数类型。例如*** getName(***) 可匹配String getName(String) |
... | 匹配任意长度的任意类型参数。例如void setName(...) 可匹配void setName(String firstName, String secondName) |
<fileds> | 匹配类、接口中所有字段 |
<methods> | 匹配类、接口中所有方法 |
<init> | 匹配类中所有构造函数 |
到这里对混淆已经有了基本的了解,系统的proguard-android.txt
已经为我们完成了大部分基础的混淆配置工作,至于编写当前app module
下的proguard-rules.pro
,只需要针对当前项目添加一些特有的配置,避免某些重要的东西被混淆掉导致错误,我们主要考虑以下几点:
- 在
AndroidManifest.xml
中注册的继承四大组件的子类的类名以及重写的方法名都不会被混淆。
如果希望项目中android.support.v4.app.Fragment
子类的类名和重写父类的方法名不被混淆可以添加如下配置:
# 不混淆Fragment的子类类名以及onCreate()、onCreateView()方法名
-keep public class * extends android.support.v4.app.Fragment {
public void onCreate(android.os.Bundle);
public android.view.View onCreateView(android.view.LayoutInflater, android.view.ViewGroup, android.os.Bundle);
}
- 不混淆某个特定的类和类中所有成员
-keep class com.othershe.test.utils.CommonUtil { *; }
- 不混淆某个目录下的文件,例如使用
Gson
时,数据bean不能被混淆,需要如下配置:
# com.othershe.test.model代表数据bean所在的全包名目录
-keep class com.othershe.test.model.** { *; }
- 上一条的具体原因是因为
Gson
用到了反射。如果我们自己使用了反射,例如
Field field = service.getField("BASE_URL");
BASE_URL
是service所属类的一个字段名,则该字段不能被混淆。
- 保留泛型
-keepattributes Signature
- 保留用于调试堆栈跟踪的行号信息(为了后期调试方便,建议配置)
-keepattributes SourceFile,LineNumberTable
如果使用了上一行配置,还需要添加如下配置将源文件重命名为SourceFile,以便通过鼠标点击直达源文件:
-renamesourcefileattribute SourceFile
WebView
中使用了JS
调用,需要添加如下配置:
-keepclassmembers class fqcn.of.javascript.interface.for.webview {
public *;
}
- 项目中使用的第三方
library
混淆规则,列举了几个常用的:
# okhttp
-dontwarn okhttp3.**
-dontwarn okio.**
-dontwarn javax.annotation.**
-keepnames class okhttp3.internal.publicsuffix.PublicSuffixDatabase
# Retrofit
-dontwarn okio.**
-dontwarn javax.annotation.**
-dontnote retrofit2.Platform
-dontwarn retrofit2.Platform$Java8
-keepattributes Signature
-keepattributes Exceptions
# RxJava RxAndroid
-dontwarn sun.misc.**
-keepclassmembers class rx.internal.util.unsafe.*ArrayQueue*Field* {
long producerIndex;
long consumerIndex;
}
-keepclassmembers class rx.internal.util.unsafe.BaseLinkedQueueProducerNodeRef {
rx.internal.util.atomic.LinkedQueueNode producerNode;
}
-keepclassmembers class rx.internal.util.unsafe.BaseLinkedQueueConsumerNodeRef {
rx.internal.util.atomic.LinkedQueueNode consumerNode;
}
# Gson
-keep class com.google.gson.stream.** { *; }
-keepattributes EnclosingMethod
# xxx代表model类的全包名路径
-keep class xxx.** { *; }
# butterknie
-keep class butterknife.** { *; }
-dontwarn butterknife.internal.**
-keep class **$$ViewBinder { *; }
-keepclasseswithmembernames class * {
@butterknife.* <fields>;
}
-keepclasseswithmembernames class * {
@butterknife.* <methods>;
}
# eventbus
-keepattributes *Annotation*
-keepclassmembers class ** {
@org.greenrobot.eventbus.Subscribe <methods>;
}
-keep enum org.greenrobot.eventbus.ThreadMode { *; }
# Only required if you use AsyncExecutor
-keepclassmembers class * extends org.greenrobot.eventbus.util.ThrowableFailureEvent {
<init>(java.lang.Throwable);
}
以上这些可以按需添加到proguard-rules.pro
!
四、查看混淆结果
混淆后打包,会在app module/build/outputs/mapping/release
目录下生成如下文件(动不动就几万行,是在没法看):
dump.txt
:描述apk
文件中所有类的内部结构mapping.txt
:混淆前后的类、类成员、方法的对照关系(重要,追溯Crash堆栈信息要用到)resources.txt
:资源文件的压缩信息seeds.txt
:未被混淆的类和成员usage.txt
:被移除的代码
混淆后的apk
包,需要系统的测试,防止混淆导致的潜在bug
。
我们还是有必要看一下混淆后的代码结构,验证混淆是否成功。一个简单的办法,Android Studio
的Build
菜单下有一个Analyze APK
选项,只需要先选择要分析的apk
包,在之后的界面点击classes.dex
即可看到混淆后的代码结构:
Analyze APK
但是这样只能看到一个类的成员变量和方法的结构,如果要看一个类的具体内容,就需要反编译apk
包了,具体可参考Android apk反编译及重新打包流程,希望一切顺利!
五、 追溯Crash堆栈信息
代码混淆后,也会导致Crash堆栈信息被混淆,难以阅读,增加定位问题位置的难度,一个混淆后的Crash堆栈信息类似这样,核心的信息都没了:
Crash Info
为了解决这个问题,可以使用<SDK目录>\tools\proguard\bin
下的proguardgui.bat脚本将Crash堆栈信息还原到混淆前的状态。步骤如下:
- 双击打开脚本,选择左边的ReTrace选项
- 选择Mapping file文件,也就是混淆后打包后在
app module/build/outputs/mapping/release
下生成的mapping.txt
- 拷贝混淆后的堆栈信息
- 点击右下角的ReTrace!按钮,完成Crash堆栈信息的追溯
如下图中间部分就是追溯到的原Crash堆栈信息:
捕获_看图王.png
代码混淆常用的内容就这些了,重点还是要理解混淆的相关语法,灵活运用!混淆一定程度增加逆向工程的难度,但还是能被破解的,如果你的代码有价值,也难免被有心的人利用!除了混淆之外,一些厂商也提供了apk
加固的服务来保证软件的安全性,但也是可以被脱壳的!有兴趣的可自行了解!下次再见......