Android-混淆学习

2018-05-21  本文已影响0人  OkCoco

混淆的作用

Java代码是很容易反编译的,而Android是使用Java开发的,也容易被反编译出来。一般来说,通过dex2jarjd-gui就可以反编译出一般的APK了。想要进一步了解的,可以看郭神的这篇文章。为了保护自己的源码,我们需要对编译好的class文件进行加密,就是混淆。

Proguard是一个混淆代码的开源库,配合Gradle构建工具,就可以很简单的在Android中使用了。

Proguard的作用

总之,使用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文件,这就是混淆规则,我们可以根据这个文件把混淆后的代码反推回源本的代码,所以假设该文件暴露出去,在某种程度上来说,等于没混淆。

混淆规则

混淆一般遵循以下规则:

注:

lib的混淆只对其起作用,APP主工程的混淆会影响到lib,导致其内部的变量或方法被混淆,而类名和类路径不会被混淆,这样会出问题的

关于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混淆

感谢各位大牛的分享,这也当是自己的学习笔记!

上一篇 下一篇

猜你喜欢

热点阅读