美文网首页
Android-混淆学习

Android-混淆学习

作者: OkCoco | 来源:发表于2018-05-21 22:42 被阅读0次

    混淆的作用

    Java代码是很容易反编译的,而Android是使用Java开发的,也容易被反编译出来。一般来说,通过dex2jarjd-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混淆

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

    相关文章

      网友评论

          本文标题:Android-混淆学习

          本文链接:https://www.haomeiwen.com/subject/wvhcjftx.html