混淆的作用
Java代码是很容易反编译的,而Android是使用Java开发的,也容易被反编译出来。一般来说,通过dex2jar和jd-gui就可以反编译出一般的APK了。想要进一步了解的,可以看郭神的这篇文章。为了保护自己的源码,我们需要对编译好的class文件进行加密,就是混淆。
Proguard是一个混淆代码的开源库,配合Gradle构建工具,就可以很简单的在Android中使用了。
Proguard的作用
- 压缩(Shrink) 检测并移除代码中无用的类、字段、方法和属性(Attribute)
- 优化(Optimize) 对字节码进行优化,移除无用指令
- 混淆(Obfuscate) 使用a、b、c这样简短而无意义的字母来对类、方法、字段的名称进行重命名
- 预检(Preveirfy) 在Java平台上处理后的代码进行预检测,确保加载的class文件时可执行的
总之,使用Proguard能够让代码更精简、更高效,也更难逆向。
Android Studio中开启混淆
在gradle文件中添加如下代码:
android{
buildTypes {
release {
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
minifyEnabled true表示开启混淆。既然是混淆,那得定义一个规则去定义哪些东西是可以混淆的,哪些是不能混淆的,避免发生异常。
proguard-rules.pro就是用来编写这个规则的。
混淆后默认会在工程目录app/build/outputs/mapping/release下生成一个mapping.txt文件,这就是混淆规则,我们可以根据这个文件把混淆后的代码反推回源本的代码,所以假设该文件暴露出去,在某种程度上来说,等于没混淆。
混淆规则
混淆一般遵循以下规则:
- 涉及到Java反射不能混淆(反射是通过全类名去找出对应的类,假设混淆了之后,就不能准确地找到自己需要的信息了)
- 涉及到Json转换的不能混淆(同样是反射)
- 注解不能混淆(同样是反射)
- 泛型不能混淆
- 调用jni的类和路径不能混淆(java对应jni的方法名称就是Java_包名[.换成类名方法名])
- 四大组件和Application不能混淆(Android Framework会调用到)
- 内部类一般不能混,外部类会调用到
- 对于库来说,对外提供的接口不能混
- 会被外部调用到的代码都不能混,但是可以掌握混淆的粒度
注:
lib的混淆只对其起作用,APP主工程的混淆会影响到lib,导致其内部的变量或方法被混淆,而类名和类路径不会被混淆,这样会出问题的
-
解决思路
1、两个工程的混淆交给其中一个同一控制 2、两个工程的混淆配置进行merge
-
具体实施
两个工程的混淆交给其中一个同一控制:
1、lib工程的混淆都放进app中,统一由app控制 2、在app的Proguard-rules.pro文件中加入-include xxx,把lib的混淆文件导入进来,前提是需要把lib工程的位置文件拷贝到app目录下 3、在app的build.gradle配置中配置,类似:proguardFiles getDefaultProguardFile(‘proguard-android.txt’), ‘proguard-rules.pro’,‘proguard-lib-rules.pro’,但是proguard-lib-rules.pro要在app工程目录下
连个工程的混淆配置进行merge:
在lib工程中加入consumerProguardFiles ‘proguard-rules.pro’,这个配置会在lib工程被打包的时候让lib工程中的proguard-rules.pro生效,此时lib工程就会按照它自己的proguard-rules.pro文件配置被正确混淆
例如:在realm中就是用了这种方式,见realm-gradle文件:
android{ //多渠道打包 productFlavors { //base版本 base { dimension 'api' externalNativeBuild { cmake { arguments "-DREALM_FLAVOR=base" } } consumerProguardFiles 'proguard-rules-consumer-common.pro', 'proguard-rules-consumer-base.pro' proguardFiles 'proguard-rules-build-common.pro' } objectServer { dimension 'api' externalNativeBuild { cmake { arguments "-DREALM_FLAVOR=objectServer" } } consumerProguardFiles 'proguard-rules-consumer-common.pro', 'proguard-rules-consumer-objectServer.pro' proguardFiles 'proguard-rules-build-common.pro', 'proguard-rules-build-objectServer.pro' } } }
关于consumerProguardFiles属性的更多解释,见这里
Android APP的基本混淆配置
###############################基本指令###########################
#指定代码的压缩级别,在0-7之间,一般是5,不需要修改
-optimizationpasses 5
#混淆时不使用大小写混合,混淆后的类名为小写
-dontusemixedcaseclassnames
# 混淆时是否做预校验(Android不需要preverify,去掉这一步可加快混淆速度)
-dontpreverify
#有了verbose这句话,混淆后就会生成映射文件
# 包含有类名->混淆后类名的映射关系
# 然后使用printmapping指定映射文件的名称
-verbose
-printmapping proguardMapping.txt
#混淆时所采用的算法,后面的参数是一个过滤器
#这个过滤器是谷歌推荐的算法,一般不改变
-optimizations !code/simplification/arithmetic,!field/*,!class/merging/*
# 保护代码中的Annotation不被混淆,这在JSON实体映射时非常重要,比如fastJson
-keepattributes *Annotation*
#避免混淆泛型,这在JSON实体映射时非常重要,比如fastJson
-keepattributes Signature
#抛出异常时保留代码行号,在异常分析中可以方便定位
-keepattributes SourceFile,LineNumberTable
#用于告诉ProGuard,不要跳过对非公开类的处理。默认情况下是跳过的,因为程序中不会引用它们,有些情况下人们编写的代码与类库中的类在同一个包下,并且对包中内容加以引用,此时需要加入此条声明
-dontskipnonpubliclibraryclasses
#这个是给Microsoft Windows用户的,因为ProGuard假定使用的操作系统是能区分两个只是大小写不同的文件名,但是Microsoft Windows不是这样的操作系统,所以必须为ProGuard指定-dontusemixedcaseclassnames选项
-dontusemixedcaseclassnames
##################################保留############################
#保持native方法不被混淆
#keepclasseswithmembernames 保留类和该类中所有带native限定符的方法
-keepclasseswithmembernames class * {
native <methods>;
}
#保留了继承自Activity、Application这些类的子类
# 因为这些子类,都有可能被外部调用
# 比如说,第一行就保证了所有Activity的子类不要被混淆
-keep public class * extends android.app.Activity
-keep public class * extends android.app.Application
-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.support.v4.**
-keep public class * extends android.view.View
-keep public class com.android.vending.licensing.ILicensingService
# 保留在Activity中的方法参数是view的方法,
# 从而我们在layout里面编写onClick就不会被影响
-keepclassmembers class * extends android.app.Activity {
public void *(android.view.View);
}
# 保留自定义控件(继承自View)不被混淆
-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);
}
#枚举enum类不能被混淆
-keepclassmembers enum * {
//这两个方法用到了反射
public static **[] values();
public static ** valueOf(java.lang.String);
}
#保留Parcelable序列化的类不被混淆
-keep class * implements android.os.Parcelable { # 保持 Parcelable 不被混淆
public static final android.os.Parcelable$Creator *;
}
# 保留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();
}
# 对于R(资源)下的所有类及其方法,都不能被混淆
-keep class **.R$* {
*;
}
# 对于带有回调函数onXXEvent的,不能被混淆
-keepclassmembers class * {
void *(**On*Event);
}
内部类
# 保留内嵌类不被混淆
-keep class com.example.xxx.MainActivity$* { *; }
这个$符号就是用来分割内嵌类与其母体的标志
WebView
# 对WebView的处理
-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, java.lang.String)
}
JavaScript
# 保留JS方法不被混淆
-keepclassmembers class com.example.xxx.MainActivity$JSInterface1 {
<methods>;
}
其中JSInterface是MainActivity的子类
规则解读
**表示把本包和所含子包下的类名都保持
-keep class cn.hadcn.test.**
*表示只是保持该包下的类名,而子包下的类名还是会被混淆
-keep class cn.hadcn.test.*
使用上面两种方式,具体的方法名和变量名还是会变化的
//保持类名和里面的内容
-keep class cn.hadcn.test.* {*;}
//$表示内部类
//表示ScriptFragment的内部类JavaScriptInterface中的所有public成员不能被混淆
-keepclassmembers class cc.ninty.chat.ui.fragment.ScriptFragment$JavaScriptInterface {
public *;
}
//保护类下的特定内容
<init>; //匹配所有构造器
<fields>; //匹配所有域
<methods>; //匹配所有方法方法
//使用private 、public、native等来进一步指定不被混淆的内容
//表示One类下面的所有public方法不会被混淆
-keep class cn.hadcn.test.One {
public <methods>;
}
//还可以对方法加参数以求更精准地指定不被混淆的内容
-keep class cn.hadcn.test.One {
//保留One类下参数为JSONObject类型的所有构造方法不被混淆
public <init>(org.json.JSONObject);
}
Google官网给出了便于理解这些规则的表格:
保留 | 防止被移除或者被重命名 | 防止被重命名 |
---|---|---|
类和类成员 | -keep | keepnames |
仅类成员 | -keepclassmembers | -keepclassmembernames |
如果拥有某成员,保留类和类成员 | -keepclasseswithmembers | -keepclasseswithmembernames |
移除是指在压缩(Shrinking)时是否会被删除
致谢
Android混淆从入门到精通
5分钟搞定android混淆
感谢各位大牛的分享,这也当是自己的学习笔记!
网友评论