Android混淆(Proguard)详解

12,256 阅读4分钟

目录:

1、混淆的作用及好处
2、混淆的原理
3、混淆的具体使用
--- 3.1、混淆的基本语法
--- 3.2、去除日志信息
4、更详细的语法及demo

1、混淆的作用及好处

混淆属于整个应用程序开发生命周期偏后期阶段的技术了,所以要考虑应用的安全性及性能的问题,混淆就是为了这种需求产生的一种技术,简单说,混淆就是将关键字和关键类名,修改为无意义的字符以起到迷惑试图反编译去查看源码的人。在一定程度上能过滤掉起码95%以上的反编译者,混淆是保障Android程序源码安全的第一道门槛,这是我的个人理解。
以上谈了下混淆的作用,而混淆的好处除了能保证源码安全性之外就大概是通过修改关键字为无意义字符串,或者剔除某些辅助类,比如Log,从而减少文件大小。

2、混淆的原理

以上混淆均指的是Proguard,Proguard是混淆代码的一个开源项目(proguard官网)

Java 是一种跨平台的、解释型语言,Java 源代码编译成中间”字节码”存储于 class 文件中。由于跨平台的需要,Java 字节码中包括了很多源代码信息,如变量名、方法名,并且通过这些名称来访问变量和方法,这些符号带有许多语义信息,很容易被反编译成 Java 源代码。为了防止这种现象,我们可以使用 Java 混淆器对 Java 字节码进行混淆。
混淆就是对发布出去的程序进行重新组织和处理,使得处理后的代码与处理前代码完成相同的功能,而混淆后的代码很难被反编译,即使反编译成功也很难得出程序的真正语义。被混淆过的程序代码,仍然遵照原来的档案格式和指令集,执行结果也与混淆前一样,只是混淆器将代码中的所有变量、函数、类的名称变为简短的英文字母代号,在缺乏相应的函数名和程序注释的况下,即使被反编译,也将难以阅读。同时混淆是不可逆的,在混淆的过程中一些不影响正常运行的信息将永久丢失,这些信息的丢失使程序变得更加难以理解。
混淆器的作用不仅仅是保护代码,它也有精简编译后程序大小的作用。由于以上介绍的缩短变量和函数名以及丢失部分信息的原因, 编译后 jar 文件体积大约能减少25%,这对当前费用较贵的无线网络传输是有一定意义的。

反编译之后得到的类名

3、混淆的具体使用

3.1、模块(Module)下的build.gradle的配置
android{
  buildTypes{
    release { 
       // 是否进行混淆  
       minifyEnabled false  
       // 混淆文件的位置,其中'proguard-android.txt'为sdk默认的混淆配置,
       //'proguard-rules.pro' 是该模块下的混淆配置
       proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'  
    } 
  }
}

以上是proguard在模块下build.gradle文件中的配置信息,其中proguard-android.txt为sdk默认的混淆配置,proguard-rules.pro是在默认配置的基础上针对本模块做出的针对性混淆处理。

  • 注:proguard-android.txt的位置位于android-sdk/tools/proguard/proguard-android.txt
3.1、混淆的基本语法

这个语法的作用是定义出不需要混淆的源代码,那么编译时会自动将未定义的部分全都混淆。至于为什么要保留类名或方法名,主要有三个原因:

  • 让C/C++程序可以通过jni使用对应的java方法。
  • 四大组件在AndroidManifest.xml里面注册了,所以要保留。
  • R文件混淆会导致引用错误。

1、保留类名 2、保留方法名 3、保留类名和方法名

#保留位于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);
}
#保留实现了Parcelable的类名不变,
-keep class * implements android.os.Parcelable{ 
    public static final android.os.Parcelable$Creator *;
}
 #保留R$*类中静态成员的变量名
-keepclassmembers class **.R$*{
    public static <fields>;
}
3.2 去除日志信息

通过配置proguard,将android.util.Log置为无效代码,则可以去除apk中打印日志的代码。 1、将build.gradle添加'proguard-android-optimize.txt'文件,该文件中默认打开了优化开关。

android{
  buildTypes{
    release { 
       // 是否进行混淆  
       minifyEnabled false  
       // 混淆文件的位置,其中'proguard-android-optimize.txt'为sdk默认的混淆配置,
       //'proguard-rules.pro' 是该模块下的混淆配置
       proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'  
    } 
  }
}

2、配置语法

-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(...);
}
-assumenosideeffects class com.example.Log.Logger{
   public static int v(...);
   public static int i(...);
   public static int w(...);
   public static int d(...);
   public static int e(...);
}

4、更详细的语法

-include {filename}    从给定的文件中读取配置参数   
-basedirectory {directoryname}    指定基础目录为以后相对的档案名称   
-injars {class_path}    指定要处理的应用程序jar,war,ear和目录   
-outjars {class_path}    指定处理完后要输出的jar,war,ear和目录的名称   
-libraryjars {classpath}    指定要处理的应用程序jar,war,ear和目录所需要的程序库文件   
-dontskipnonpubliclibraryclasses    指定不去忽略非公共的库类。   
-dontskipnonpubliclibraryclassmembers    指定不去忽略包可见的库类的成员。  
  
保留选项   
-keep {Modifier} {class_specification}    保护指定的类文件和类的成员   
-keepclassmembers {modifier} {class_specification}    保护指定类的成员,如果此类受到保护他们会保护的更好  
-keepclasseswithmembers {class_specification}    保护指定的类和类的成员,但条件是所有指定的类和类成员是要存在。   
-keepnames {class_specification}    保护指定的类和类的成员的名称(如果他们不会压缩步骤中删除)   
-keepclassmembernames {class_specification}    保护指定的类的成员的名称(如果他们不会压缩步骤中删除)   
-keepclasseswithmembernames {class_specification}    保护指定的类和类的成员的名称,如果所有指定的类成员出席(在压缩步骤之后)   
-printseeds {filename}    列出类和类的成员-keep选项的清单,标准输出到给定的文件   
  
压缩   
-dontshrink    不压缩输入的类文件   
-printusage {filename}   
-whyareyoukeeping {class_specification}       
  
优化   
-dontoptimize    不优化输入的类文件   
-assumenosideeffects {class_specification}    优化时假设指定的方法,没有任何副作用   
-allowaccessmodification    优化时允许访问并修改有修饰符的类和类的成员   
  
混淆   
-dontobfuscate    不混淆输入的类文件   
-printmapping {filename}   
-applymapping {filename}    重用映射增加混淆   
-obfuscationdictionary {filename}    使用给定文件中的关键字作为要混淆方法的名称   
-overloadaggressively    混淆时应用侵入式重载   
-useuniqueclassmembernames    确定统一的混淆类的成员名称来增加混淆   
-flattenpackagehierarchy {package_name}    重新包装所有重命名的包并放在给定的单一包中   
-repackageclass {package_name}    重新包装所有重命名的类文件中放在给定的单一包中   
-dontusemixedcaseclassnames    混淆时不会产生形形色色的类名   
-keepattributes {attribute_name,...}    保护给定的可选属性,例如LineNumberTable, LocalVariableTable, SourceFile, Deprecated, Synthetic, Signature, and   
  
InnerClasses.   
-renamesourcefileattribute {string}    设置源文件中给定的字符串常量  

demo

-ignorewarnings                     # 忽略警告,避免打包时某些警告出现  
-optimizationpasses 5               # 指定代码的压缩级别  
-dontusemixedcaseclassnames         # 是否使用大小写混合  
-dontskipnonpubliclibraryclasses    # 是否混淆第三方jar  
-dontpreverify                      # 混淆时是否做预校验  
-verbose                            # 混淆时是否记录日志  
-optimizations !code/simplification/arithmetic,!field/*,!class/merging/*        # 混淆时所采用的算法  
  
-libraryjars   libs/treecore.jar  
  
-dontwarn android.support.v4.**     #缺省proguard 会检查每一个引用是否正确,但是第三方库里面往往有些不会用到的类,没有正确引用。如果不配置的话,系统就会报错。  
-dontwarn android.os.**  
-keep class android.support.v4.** { *; }        # 保持哪些类不被混淆  
-keep class com.baidu.** { *; }    
-keep class vi.com.gdi.bgl.android.**{*;}  
-keep class android.os.**{*;}  
  
-keep interface android.support.v4.app.** { *; }    
-keep public class * extends android.support.v4.**    
-keep public class * extends android.app.Fragment  
  
-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.support.v4.widget  
-keep public class * extends com.sqlcrypt.database  
-keep public class * extends com.sqlcrypt.database.sqlite  
-keep public class * extends com.treecore.**  
-keep public class * extends de.greenrobot.dao.**  
  
  
-keepclasseswithmembernames class * {       # 保持 native 方法不被混淆  
    native <methods>;  
}  
  
-keepclasseswithmembers class * {            # 保持自定义控件类不被混淆  
    public <init>(android.content.Context, android.util.AttributeSet);  
}  
  
-keepclasseswithmembers class * {            # 保持自定义控件类不被混淆  
    public <init>(android.content.Context, android.util.AttributeSet, int);  
}  
  
-keepclassmembers class * extends android.app.Activity { //保持类成员  
   public void *(android.view.View);  
}  
  
-keepclassmembers enum * {                  # 保持枚举 enum 类不被混淆  
    public static **[] values();  
    public static ** valueOf(java.lang.String);  
}  
  
-keep class * implements android.os.Parcelable {    # 保持 Parcelable 不被混淆  
  public static final android.os.Parcelable$Creator *;  
}  
  
-keep class MyClass;                              # 保持自己定义的类不被混淆  

参考

1、MOOC-Android代码混淆与加固技术
2、CSDN-Android proguard 详解
3、简书-ProGuard 最全混淆规则说明