Android代码混淆

前言

    Android代码混淆是让Android项目避免轻易被逆向分析,防止代码安全泄露的手段之一。它将工程中的Android代码用简单抽象的字母或单词代替原有的代码名称。使代码丧失可读性从而使逆向工程师难以阅读,增加逆向成本。当逆向成本大于逆向收益的时候,逆向代码也就失去意义。
    除此之外,由于代码混淆用简单抽象的单词代替原有长而通俗易懂的代码,因而减少APK的体积。而且,使用代码混淆后,利用Gradle为Android提供的插件,能将项目中未使用的资源安全移除,大大减少APK体积。

1.代码混淆的使用

    在app的build.gradle里,我们会看到gradle为我们默认配置了如下代码:

android {
    buildTypes {
        release {
            minifyEnabled true
            proguardFiles getDefaultProguardFile('proguard-android.txt'),'proguard-rules.pro'
            shrinkResources true
        }

    }
    ...
}

    这段代码中,minifyEnabled就是我们开启代码混淆的开关,系统默认为false,既不开启代码混淆。proguardFiles是指定项目中所使用的混淆规则配置文件。其中getDefaultProguardFile(‘proguard-android.txt’)是指定系统默认的混淆规则,而proguard-rules.pro则代表当前项目的混淆规则。
    proguard-android.txt位于Android SDK目录下/tools/proguard目录下,proguard-rules.pro位于app module下。一般而言,通过修改proguard-rules.pro来改变当前项目的混淆规则。
    此外,通过添加shrinkResources true能将项目中未使用的资源移除。
注意:一般而言,项目的debug版本无需开启代码混淆,因为代码混淆会额外增加apk的构建时间。但是,用于测试的版本也要与生产版本一样开启代码混淆,以免在生产版本出现测试版本未曾出现的bug。

2.编写混淆文件proguard-rules.pro

    一般而言,默认 ProGuard 配置文件 (proguard-android.txt) 足以满足需要,ProGuard 会移除所有(并且只会移除)未使用的代码。但是,ProGuard可能会移除应用真正需要的代码。举例来说,它可能错误移除代码的情况包括:例如:
    ※当应用引用的类只来自 AndroidManifest.xml 文件时;
    ※当应用调用的方法来自 Java 原生接口 (JNI) 时;
    ※当应用在运行时(例如使用反射或自检)操作代码时。
    测试应用应该能够发现因不当移除的代码而导致的错误,但您也可以通过查看 < module-name >/build/outputs/mapping/release/ 中保存的 usage.txt 输出文件来检查移除了哪些代码。

2.1proguard-android.txt说明

    学习自定义proguard-rules.pro之前,我们先来看看系统默认的代码混淆规则proguard-android.txt文件。假如proguard-rules.pro文件没用配置任何混淆规则,系统将以proguard-android.txt里的规则为准开启混淆。

#混淆时不使用大小写混合类名
-dontusemixedcaseclassnames
#不跳过library中非public的类
-dontskipnonpubliclibraryclasses
#打印混淆的详细信息
-verbose

# Optimization is turned off by default. Dex does not like code run
# through the ProGuard optimize and preverify steps (and performs some
# of these optimizations on its own).
#不进行优化
-dontoptimize
#不进行预校验,能加快混淆速度
-dontpreverify
# Note that if you want to enable optimization, you cannot just
# include optimization flags in your own project configuration file;
# instead you will need to point to the
# "proguard-android-optimize.txt" file instead of this one from your
# project.properties file.
#不混淆注解中的参数
-keepattributes *Annotation*
#不混淆特定的类
-keep public class com.google.vending.licensing.ILicensingService
-keep public class com.android.vending.licensing.ILicensingService

# For native methods, see http://proguard.sourceforge.net/manual/examples.html#native
#不混淆包含native方法的类和native方法
-keepclasseswithmembernames class * {
    native ;
}

# keep setters in Views so that animations can still work.
# see http://proguard.sourceforge.net/manual/examples.html#beans
#不混淆继承View类的getXX()和setXX()方法以保证属性动画正常运行。
-keepclassmembers public class * extends android.view.View {
   void set*(***);
   *** get*();
}

# We want to keep methods in Activity that could be used in the XML attribute onClick
#不混淆Activity中public void修饰的含有View参数的方法以保证在xml配置的onclick正常使用
-keepclassmembers class * extends android.app.Activity {
   public void *(android.view.View);
}

# For enumeration classes, see http://proguard.sourceforge.net/manual/examples.html#enumerations
#不混淆枚举类的values()和valueOf()方法
-keepclassmembers enum * {
    public static **[] values();
    public static ** valueOf(java.lang.String);
}
#不混淆实现Parcelable接口的类的CREATOR成员
-keepclassmembers class * implements android.os.Parcelable {
  public static final android.os.Parcelable$Creator CREATOR;
}
#不混淆R文件的所有public类成员,如android.R类
-keepclassmembers class **.R$* {
    public static ;
}

# The support library contains references to newer platform versions.
# Don't warn about those in case this app is linking against an older
# platform version.  We know about them, and they are safe.
#不对android.support包下的代码警告。例如,app使用了低于某些类的support包版本导致出现警告问题。
-dontwarn android.support.**

# Understand the @Keep support annotation.
#不混淆Keep注解
-keep class android.support.annotation.Keep

#不混淆带Keep注解的类
-keep @android.support.annotation.Keep class * {*;}
#如果类的方法使用了Keep注解,不混淆类与类方法
-keepclasseswithmembers class * {
    @android.support.annotation.Keep ;
}
#如果类的属性使用了Keep注解,不混淆类与类成员
-keepclasseswithmembers class * {
    @android.support.annotation.Keep ;
}
#如果类的构造方法使用了Keep注解,不混淆类与类成员
-keepclasseswithmembers class * {
    @android.support.annotation.Keep (...);
}

    通过proguard-android.txt文件,我们大致知道了当开启混淆时,系统默认的混淆规则,当自定义proguard-rule.pro文件时我们就无需重复以上的混淆规则。此外,通过proguard-android.txt我们大致知道如何自定义混淆规则。

2.2混淆配置文件关键词

2.2.1 keep关键词

关键词 说明
keep 不混淆类和类成员
keepnames 不混淆类和类成员,但类和类成员未被引用会被移除
keepclassmembers 不混淆类成员
keepclassmembernames 不混淆类成员,但未被引用的类成员会被移除
keepclasseswithmembers 不混淆带有指定条件类成员的类和类成员
keepclasseswithmembernames 不混淆带有指定条件的类和类成员,但未被引用的类和类成员会被移除

2.2.2 相关通配符

通配符 说明
* 匹配任意长度字符,但不包含包名分隔符”.”
** 匹配任意长度字符,但包含包名分隔符”.”
*** 匹配任意参数类型。例如*** getSchool(***)可匹配String getSchool(String)
匹配任意长度的任意类型的参数。例如void setSchool(…)可匹配void setSchool(String name,int age)
< filed > 匹配类或接口中所有字段
< method > 匹配类、接口的所有方法
< init > 匹配类中所有构造方法

    到这里对混淆配置文件的语法已经基本了解,系统的proguard-android.txt已经为我们完成大部分基础的混淆配置工作。我们只需要写好自己项目的proguard-rules.pro文件,对照proguard-android.txt照葫芦画瓢就能自定义自己项目的proguard-rules.pro。
    对于开启混淆的项目,我们应该从类的创建就要思考这个类是否混淆,大到一个模块,小到一个类的字段,以免到项目庞大以后再来思考混淆就更加难办了。例如,当项目使用了反射机制,对于反射的类及其成员要根据实际情况避免混淆。又例如Gson所要解析成的实体类的字段也要避免混淆。

3.混淆的结果

    代码混淆后会产生如下几个位于Android代码混淆_第1张图片
< module-name >/build/outputs/mapping/release/目录下的文件。其中

文件名 说明
dump.txt 说明 APK 中所有类文件的内部结构。
mapping.txt 提供原始与混淆过的类、方法和字段名称之间的转换。
seeds.txt 列出未进行混淆的类和成员。
usage.txt 列出从 APK 移除的代码。

总结

    代码混淆也是一项很有难度的事情,随着项目越来越庞大,混淆的难度也越来越大。一个错误的混淆很可能导致整个app无法正常运行。因此,学习混淆应该循序渐进,积累更多经验。
    通过代码混淆增加逆向的难度和减少APK的体积是成熟的Android应用应该具备的,同时也会增加开发者的开发难度。对于要不要开启代码混淆,应该从项目本身实际情况(成本、收益、开发难度)而定,不要盲目为了混淆而开启混淆项目的可控性下降,当然如果开发人员拥有过硬的混淆代码配置经验,代码混淆对于APP应用本身是利大于弊的。

你可能感兴趣的:(Android)