Android 混淆

一、前言

混淆的概念:打包项目时将项目里的包名、类名、变量名进行更改,使得代码不容易泄露,类似于对其apk中的文件加密.

混淆的优点是:1.增加Apk反编译之后代码泄露的困难性;2.生成的apk体积会缩小。

相应的可能带来的问题:混淆设置稍有不对可能导致项目运行崩溃,或者上线之后异常信息定位困难。

 

二、开启混淆

找到项目app目录下build文件,设置minifyEnabled为true,然后到proguard-rules.pro文件中加入混淆规则。

android{

buildTypes {
        release {
            buildConfigField "boolean", "LOG_DEBUG", "false" //不显示log
            minifyEnabled true
            shrinkResources true
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
            signingConfig signingConfigs.config
            }
        }
}

注意:开启混淆会使编译时间变长,所以debug模式下不开启。我们需要做的是:

1.将release下minifyEnabled的值改为true,打开混淆;

2.加上shrinkResources true,打开资源压缩。

3.buildConfigField 不显示log日志

4.signingConfig signingConfigs.config配置签名文件文件

 

三、Proguard

1、java 代码的混淆在Android 中是最为常见的一种混淆方式,一般依赖于Proguard或者 DexGuard 工具,其中Progurad 是免费且开源的,DexGuard 是付费的 。

Proguard 主要提供了4个功能特性:

  • 压缩:java源代码通常被编译为字节码,虽然字节码比源代码更简洁,但它本身依然会包含很多无用的代码,Proguard 的压缩功能通过分析字节码,能够检测并移除没有使用到的类,字段,方法和属性。

  • 优化:优化Java 字节码,同时移除没有使用到的指令。

  • 混淆:使用无意义的简短字母组合对类名,字段名、和方法名进行重命名。

  • 预检验:对上述处理后的代码进行预检验(Android 不需要此项 )

proguard-android.txt是 Proguard 默认的混淆配置文件 ,位于Androdi SDK 的 tools/proguard 目录中 。当然这个默认文件的配置是远远不够的。我们需要自身App 的特殊性增加或者减少相关的配置。

2、Proguard关键

Proguard关键字描述
dontwarn处理引入的library无法找到引用或无法解决的警告,以保证项目正常build
keep保留类和类中的成员,防止被混淆或移除
keepnames保留类和类中的成员,防止被混淆,成员没有被引用会被移除 
keepclassmembers只保留类中的成员,防止被混淆或移除
keepclassmembernames只保留类中的成员,防止被混淆,成员没有引用会被移除
keepclasseswithmembers保留类和类中的成员,防止被混淆或移除,保留指明的成员
keepclasseswithmembernames保留类和类中的成员,防止被混淆,保留指明的成员,成员没有引用会被移除

3、Proguard通配符

Proguard通配符描述
<field>匹配类中的所有字段
<method>匹配类中所有的方法
<init>匹配类中所有的构造函数
*匹配任意长度字符,不包含包名分隔符(.)
**匹配任意长度字符,包含包名分隔符(.)
***匹配任意参数类型

 

四、混淆方案

1、哪些不应该混淆

  • 避免混淆自定义控件类的get/set方法和构造函数

    -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);
    }

     

  • 避免混淆Android基本组件

    -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 com.android.vending.licensing.ILicensingService
    ​
    #不提示V4包下错误警告
    -dontwarn android.support.v4.**
    #保持下面的V4兼容包的类不被混淆
    -keep class android.support.v4.**{*;}

     

  • 避免混淆枚举类

    -keepclassmembers enum * {
        public static **[] values();
        public static ** valueOf(java.lang.String);
    }

     

  • 避免混淆第三方SDK

    # ==================环信混淆start=================
        -keep class com.hyphenate.** {*;}
        -dontwarn  com.hyphenate.**
        # ==================环信end======================
    ​
        # ==================bugly start==================
        -dontwarn com.tencent.bugly.**
        -keep public interface com.tencent.**
        -keep public class com.tencent.** {*;}
        -keep public class com.tencent.bugly.**{*;}
        # ==================bugly end====================
    ​
        # ===============百度定位 start====================
        -keep class vi.com.gdi.** { *; }
        -keep public class com.baidu.** {*;}
        -keep public class com.mobclick.** {*;}
        -dontwarn com.baidu.mapapi.utils.*
        -dontwarn com.baidu.platform.comapi.b.*
        -dontwarn com.baidu.platform.comapi.map.*
        # ===============百度定位 end======================
    ​
        //备注:其他的第三方包的混淆指令可以到其官方文档去拷贝

     

  • 避免混淆第三方框架

# ==================picasso框架 start===============
    -keep class com.parse.*{ *; }
    -dontwarn com.parse.**
    -dontwarn com.squareup.picasso.**
    -keepclasseswithmembernames class * {
        native <methods>;
    }
    # ==================picasso end====================
​
    # ==================EventBus start=================
    -keep class org.greenrobot.** {*;}
    -keep class de.greenrobot.** {*;}
    -keepclassmembers class ** {
        public void onEvent*(**);
        void onEvent*(**);
    }
    # ==================EventBus end===================
​
    # ==================okhttp start===================
    -dontwarn com.squareup.okhttp.**
    -keep class com.squareup.okhttp.** { *;}
    -dontwarn okio.**
    -keep class okio.**{*;}
    -keep interface okio.**{*;}
    # ==================okhttp end=====================
​
    //备注:其它框架的混淆指令可以到其官方文档去拷贝  
  • 避免混淆JSON类的构造函数

    #使用GSON、fastjson等框架时,所写的JSON对象类不混淆,否则无法将JSON解析成对应的对象
    -keepclassmembers class * {
         public <init>(org.json.JSONObject);
    }

     

  • 避免混淆序列化类

    #不混淆Parcelable和它的实现子类,还有Creator成员变量
    -keep class * implements android.os.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();
    }!v

     

  • 避免混淆所有native的方法,涉及到C、C++ (JNI)

    -keepclasseswithmembernames class * {
            native <methods>;
    ​

     

  • 其他混淆避免

    #避免混淆属性动画兼容库
        -dontwarn com.nineoldandroids.*
        -keep class com.nineoldandroids.** { *;}    
    ​
        #不混淆泛型
        -keepattributes Signature
    ​
        #避免混淆注解类
        -dontwarn android.annotation
        -keepattributes *Annotation*
    ​
        #避免混淆内部类
        -keepattributes InnerClasses
    ​
        #避免混淆实体类,修改成你对应的包名
        -keep class com.wyk.test.bean.** { *; }
        -keep class com.wyk.test.event.** { *; }
        -keep public class com.wyk.test.utils.eventbus.** { *;}
    ​
        #避免混淆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;
        }
    ​
        #避免混淆js相关的接口(例如webview中使用)
        -keepattributes *JavascriptInterface*
        -keep class com.wyk.test.js.** { *; }
        
        #避免混淆运用了反射的类

     

2、基本指令

#设置混淆的压缩比率 0 ~ 7
    -optimizationpasses 5
    # 混淆后类名都为小写   Aa aA
    -dontusemixedcaseclassnames
    # 指定不去忽略非公共库的类
    -dontskipnonpubliclibraryclasses
    #不做预校验的操作
    -dontpreverify
    # 混淆时不记录日志
    -verbose
    # 混淆采用的算法.
    -optimizations !code/simplification/arithmetic,!field/*,!class/merging/*
    #保留代码行号,方便异常信息的追踪
    -keepattributes SourceFile,LineNumberTable

    #dump文件列出apk包内所有class的内部结构
    -dump class_files.txt        
    #seeds.txt文件列出未混淆的类和成员
    -printseeds seeds.txt
    #usage.txt文件列出从apk中删除的代码
    -printusage unused.txt
    #mapping文件列出混淆前后的映射
    -printmapping mapping.txt


//不混淆某个类
-keep public class name.huihui.example.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$* {
        *;
 }
//两个常用的混淆命令,注意:
//一颗星表示只是保持该包下的类名,而子包下的类名还是会被混淆;
//两颗星表示把本包和所含子包下的类名都保持;
-keep class com.suchengkeji.android.ui.**
-keep class com.suchengkeji.android.ui.*
//用以上方法保持类后,你会发现类名虽然未混淆,但里面的具体方法和变量命名还是变了,
//如果既想保持类名,又想保持里面的内容不被混淆,我们就需要以下方法了

//不混淆某个包所有的类
-keep class com.suchengkeji.android.bean.** { *; }
//在此基础上,我们也可以使用Java的基本规则来保护特定类不被混淆,比如我们可以用extend,implement等这些Java规则。如下
# 保留我们使用的四大组件,自定义的Application等等这些类不被混淆
# 因为这些子类都有可能被外部调用
-keep public class * extends android.app.Activity
-keep public class * extends android.app.Appliction
-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

 3、常用参考模板

#
#-------------------------------------------基本不用动区域----------------------------------------------
#
#
# -----------------------------基本 -----------------------------
#

# 指定代码的压缩级别 0 - 7(指定代码进行迭代优化的次数,在Android里面默认是5,这条指令也只有在可以优化时起作用。)
-optimizationpasses 5
# 混淆时不会产生形形色色的类名(混淆时不使用大小写混合类名)
-dontusemixedcaseclassnames
# 指定不去忽略非公共的库类(不跳过library中的非public的类)
-dontskipnonpubliclibraryclasses
# 指定不去忽略包可见的库类的成员
-dontskipnonpubliclibraryclassmembers
#不进行优化,建议使用此选项,
-dontoptimize
 # 不进行预校验,Android不需要,可加快混淆速度。
-dontpreverify
# 屏蔽警告
-ignorewarnings
# 指定混淆是采用的算法,后面的参数是一个过滤器
# 这个过滤器是谷歌推荐的算法,一般不做更改
-optimizations !code/simplification/arithmetic,!field/*,!class/merging/*
# 保护代码中的Annotation不被混淆
-keepattributes *Annotation*
# 避免混淆泛型, 这在JSON实体映射时非常重要
-keepattributes Signature
# 抛出异常时保留代码行号
-keepattributes SourceFile,LineNumberTable
 #优化时允许访问并修改有修饰符的类和类的成员,这可以提高优化步骤的结果。
# 比如,当内联一个公共的getter方法时,这也可能需要外地公共访问。
# 虽然java二进制规范不需要这个,要不然有的虚拟机处理这些代码会有问题。当有优化和使用-repackageclasses时才适用。
#指示语:不能用这个指令处理库中的代码,因为有的类和类成员没有设计成public ,而在api中可能变成public
-allowaccessmodification
#当有优化和使用-repackageclasses时才适用。
-repackageclasses ''
 # 混淆时记录日志(打印混淆的详细信息)
 # 这句话能够使我们的项目混淆后产生映射文件
 # 包含有类名->混淆后类名的映射关系
-verbose

#
# ----------------------------- 默认保留 -----------------------------
#
#----------------------------------------------------
# 保持哪些类不被混淆
#继承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 class android.support.** {*;}## 保留support下的所有类及其内部类

-keep public class com.google.vending.licensing.ILicensingService
-keep public class com.android.vending.licensing.ILicensingService
#表示不混淆上面声明的类,最后这两个类我们基本也用不上,是接入Google原生的一些服务时使用的。
#----------------------------------------------------

# 保留继承的
-keep public class * extends android.support.v4.**
-keep public class * extends android.support.v7.**
-keep public class * extends android.support.annotation.**


#表示不混淆任何包含native方法的类的类名以及native方法名,这个和我们刚才验证的结果是一致
-keepclasseswithmembernames class * {
    native <methods>;
}


#这个主要是在layout 中写的onclick方法android:onclick="onClick",不进行混淆
#表示不混淆Activity中参数是View的方法,因为有这样一种用法,在XML中配置android:onClick=”buttonClick”属性,
#当用户点击该按钮时就会调用Activity中的buttonClick(View view)方法,如果这个方法被混淆的话就找不到了
-keepclassmembers class * extends android.app.Activity{
    public void *(android.view.View);
}

#表示不混淆枚举中的values()和valueOf()方法,枚举我用的非常少,这个就不评论了
-keepclassmembers enum * {
    public static **[] values();
    public static ** valueOf(java.lang.String);
}

#表示不混淆任何一个View中的setXxx()和getXxx()方法,
#因为属性动画需要有相应的setter和getter的方法实现,混淆了就无法工作了。
-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);
}

#表示不混淆Parcelable实现类中的CREATOR字段,
#毫无疑问,CREATOR字段是绝对不能改变的,包括大小写都不能变,不然整个Parcelable工作机制都会失败。
-keep class * implements android.os.Parcelable {
  public static final android.os.Parcelable$Creator *;
}
# 这指定了继承Serizalizable的类的如下成员不被移除混淆
-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$* {
# *;
#}
#不混淆资源类下static的
-keepclassmembers class **.R$* {
    public static <fields>;
}

# 对于带有回调函数的onXXEvent、**On*Listener的,不能被混淆
-keepclassmembers class * {
    void *(**On*Event);
    void *(**On*Listener);
}

# 保留我们自定义控件(继承自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);
}

#
#----------------------------- WebView(项目中没有可以忽略) -----------------------------
#
#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);
}
#在app中与HTML5的JavaScript的交互进行特殊处理
#我们需要确保这些js要调用的原生方法不能够被混淆,于是我们需要做如下处理:
-keepclassmembers class com.ljd.example.JSInterface {
    <methods>;
}

#
#---------------------------------实体类---------------------------------
#--------(实体Model不能混淆,否则找不到对应的属性获取不到值)-----
#
-dontwarn com.suchengkeji.android.confusiondemo.md.**
#对含有反射类的处理
-keep class com.suchengkeji.android.confusiondemo.md.** { *; }
#
# ----------------------------- 其他的 -----------------------------
#
# 删除代码中Log相关的代码
-assumenosideeffects class android.util.Log {
    public static boolean isLoggable(java.lang.String, int);
    public static int v(...);
    public static int i(...);
    public static int w(...);
    public static int d(...);
    public static int e(...);
}

# 保持测试相关的代码
-dontnote junit.framework.**
-dontnote junit.runner.**
-dontwarn android.test.**
-dontwarn android.support.test.**
-dontwarn org.junit.**


#
# ----------------------------- 第三方 -----------------------------
#
-dontwarn com.orhanobut.logger.**
-keep class com.orhanobut.logger.**{*;}
-keep interface com.orhanobut.logger.**{*;}

-dontwarn com.google.gson.**
-keep class com.google.gson.**{*;}
-keep interface com.google.gson.**{*;}
#        。。。。。。v

五、混淆结果及反解

1、混淆过的包必须进行检查,避免因混淆引入的bug。一方面,需要从代码层面检查。使用上文的配置进行混淆打包后 <module-name>/build/outputs/mapping/release/目录下会输出以下文件:

  • dump.txt 描述APK文件中所有类的内部结构

  • mapping.txt 提供混淆前后类、方法、类成员等的对照表

  • seeds.txt 列出没有被混淆的类和成员

  • usage.txt 列出被移除的代码

我们可以根据 seeds.txt 文件检查未被混淆的类和成员中是否已包含所有期望保留的,再根据 usage.txt文件查看是否有被误移除的代码。另一方面,需要从测试方面检查。将混淆过的包进行全方面测试,检查是否有 bug 产生。

 

2、混淆后的类、方法名等等难以阅读,这固然会增加逆向工程的难度,但对追踪线上 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

注意事项:

所有在 AndroidManifest.xml 涉及到的类已经自动被保持,因此不用特意去添加这块混淆规则。(很多老的混淆文件里会加,现在已经没必要)

proguard-android.txt已经存在一些默认混淆规则,没必要在 proguard-rules.pro 重复添加

 

参考链接:

https://blog.csdn.net/yk377657321/article/details/60501880

https://www.jianshu.com/p/b5b2a5dfaaf4

https://www.jianshu.com/p/e9d3c57ab92f?utm_campaign=haruki&utm_content=note&utm_medium=reader_share&utm_source=qq

https://blog.csdn.net/qq_26057629/article/details/81867787

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

KWMax

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值