2018 年第一篇,新年快乐!
一款发布到市场的软件原则上都应该做代码混淆,可能有人会说谁有功夫破解你的烂代码,这个嘛,开心就好......
通过代码混淆可以将项目中的类、方法、变量等信息进行重命名,变成一些无意义的简短名字,同时也可以移除未被使用的类、方法、变量等。所以直观的看,通过混淆可以提高程序的安全性,增加逆向工程的难度,同时也有效缩减了 apk 的体积。一起来 get 这个技能吧!
在基于 Android Studio 项目的 app module 的 build.gradle 中有如下默认代码片段:
- buildTypes {
- release {
- minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'),
- 'proguard-rules.pro'
- }
- }
代表要发布的 release 包的混淆配置,默认不开启混淆,要开启混淆首先做如下修改:
minifyEnabled true
开启混淆后还可以添加
配置,代表开启资源文件压缩。
- shrinkResources true
这样就在 release 模式下开启了混淆。一般在 debug 模式下不开启混淆,因为混淆会导致编译时间变长、无法 debug 问题,毕竟也是内部测试嘛,没必要!
开启混淆后,接下来就是用混淆配置文件来设置混淆规则:
- proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
代表系统默认的混淆规则配置文件,该文件在
- proguard-android.txt
下,一般不要更改该配置文件,因为也会作用于其它项目,除非你能确保所做的更改不影响其它项目的混淆。
- <Android SDK目录>/tools/proguard
为了更好的编写 proguard-rules.pro,先学习下系统的
- proguard-android.txt
- # This is a configuration file for ProGuard.
- # http://proguard.sourceforge.net/index.html#manual/usage.html
- # 混淆时不使用大小写混合类名
- -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 <methods>;
- }
- # keep setters in Views so that animations can still work.
- # see http://proguard.sourceforge.net/manual/examples.html#beans
- # 不混淆View中的setXxx()和getXxx()方法,以保证属性动画正常工作
- -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中参数是View的方法,例如,一个控件通过android:onClick="clickMethodName"绑定点击事件,混淆后会导致点击事件失效
- -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字段,以保证Parcelable机制正常工作
- -keepclassmembers class * implements android.os.Parcelable {
- public static final android.os.Parcelable$Creator CREATOR;
- }
- # 不混淆R文件中的所有静态字段,以保证正确找到每个资源的id
- -keepclassmembers class **.R$* {
- public static <fields>;
- }
- # 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包下的代码警告(如果我们打包的版本低于support包下某些类的使用版本,会出现警告的问题)
- -dontwarn android.support.**
- # Understand the @Keep support annotation.
- # 不混淆Keep类
- -keep class android.support.annotation.Keep
- # 不混淆使用了注解的类及类成员
- -keep @android.support.annotation.Keep class * {*;}
- # 如果类中有使用了注解的方法,则不混淆类和类成员
- -keepclasseswithmembers class * {
- @android.support.annotation.Keep <methods>;
- }
- # 如果类中有使用了注解的字段,则不混淆类和类成员
- -keepclasseswithmembers class * {
- @android.support.annotation.Keep <fields>;
- }
- # 如果类中有使用了注解的构造函数,则不混淆类和类成员
- -keepclasseswithmembers class * {
- @android.support.annotation.Keep <init>(...);
- }
可以看出
主要作用是防止指定内容被混淆,其中使用了以 - 开头,结合 keep 类关键字,*、<> 等通配符的语法,先 get 这些语法吧!
- proguard-android.txt
关键字 | 含义 |
---|---|
keep | 保留类和类成员,防止被混淆或移除 |
keepnames | 保留类和类成员,防止被混淆,但没有被引用的类成员会被移除 |
keepclassmembers | 只保留类成员,防止被混淆或移除 |
keepclassmembernames | 只保留类成员,防止被混淆,但没有被引用的成员会被移除 |
keepclasseswithmembers | 保留类和类成员,防止被混淆或移除,如果指定的类成员不存在还是会被混淆 |
keepclasseswithmembernames | 保留类和类成员,防止被混淆,如果指定的类成员不存在还是会被混淆,没有被引用的类成员会被移除 |
通配符 | 含义 |
---|---|
* | 匹配任意长度字符,但不含包名分隔符.。例如一个类的全包名路径是 ,使用 com.othershe.test.*、com.othershe.test.* 都是可以匹配的,但 com.othershe.* 就不能匹配 |
** | 匹配任意长度字符,并包含包名分隔符.。例如要匹配 包下的所有内容 |
*** | 匹配任意参数类型。例如 *** getName(***) 可匹配
|
... | 匹配任意长度的任意类型参数。例如 void setName(...) 可匹配
|
<fileds> | 匹配类、接口中所有字段 |
<methods> | 匹配类、接口中所有方法 |
<init> | 匹配类中所有构造函数 |
到这里对混淆已经有了基本的了解,系统的
已经为我们完成了大部分基础的混淆配置工作,至于编写当前 app module 下的 proguard-rules.pro,只需要针对当前项目添加一些特有的配置,避免某些重要的东西被混淆掉导致错误,我们主要考虑以下几点:
- proguard-android.txt
子类的类名和重写父类的方法名不被混淆可以添加如下配置:
- android.support.v4.app.Fragment
- # 不混淆Fragment的子类类名以及onCreate()、onCreateView()方法名
- -keep public class * extends android.support.v4.app.Fragment {
- public void onCreate(android.os.Bundle);
- public android.view.View onCreateView(android.view.LayoutInflater, android.view.ViewGroup, android.os.Bundle);
- }
- -keep class com.othershe.test.utils.CommonUtil { *; }
- #com.othershe.test.model代表数据bean所在的全包名目录 - keep class com.othershe.test.model. * *{ * ;
- }
- Field field = service.getField("BASE_URL");
BASE_URL 是 service 所属类的一个字段名,则该字段不能被混淆。
- - keepattributes Signature
- -keepattributes SourceFile,LineNumberTable
如果使用了上一行配置,还需要添加如下配置将源文件重命名为 SourceFile,以便通过鼠标点击直达源文件:
- - renamesourcefileattribute SourceFile
- -keepclassmembers class fqcn.of.javascript.interface.for.webview {
- public *;
- }
- # okhttp
- -dontwarn okhttp3.**
- -dontwarn okio.**
- -dontwarn javax.annotation.**
- -keepnames class okhttp3.internal.publicsuffix.PublicSuffixDatabase
- # Retrofit
- -dontwarn okio.**
- -dontwarn javax.annotation.**
- -dontnote retrofit2.Platform
- -dontwarn retrofit2.Platform$Java8
- -keepattributes Signature
- -keepattributes Exceptions
- # 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;
- }
- # Gson
- -keep class com.google.gson.stream.** { *; }
- -keepattributes EnclosingMethod
- # xxx代表model类的全包名路径
- -keep class xxx.** { *; }
- # butterknie
- -keep class butterknife.** { *; }
- -dontwarn butterknife.internal.**
- -keep class **$ViewBinder { *; }
- -keepclasseswithmembernames class * {
- @butterknife.* <fields>;
- }
- -keepclasseswithmembernames class * {
- @butterknife.* <methods>;
- }
- # eventbus
- -keepattributes *Annotation*
- -keepclassmembers class ** {
- @org.greenrobot.eventbus.Subscribe <methods>;
- }
- -keep enum org.greenrobot.eventbus.ThreadMode { *; }
- # Only required if you use AsyncExecutor
- -keepclassmembers class * extends org.greenrobot.eventbus.util.ThrowableFailureEvent {
- <init>(java.lang.Throwable);
- }
以上这些可以按需添加到 proguard-rules.pro!
混淆后打包,会在
目录下生成如下文件(动不动就几万行,是在没法看):
- app module/build/outputs/mapping/release
混淆后的 apk 包,需要系统的测试,防止混淆导致的潜在 bug。
我们还是有必要看一下混淆后的代码结构,验证混淆是否成功。一个简单的办法,Android Studio 的 Build 菜单下有一个 Analyze APK 选项,只需要先选择要分析的 apk 包,在之后的界面点击 classes.dex 即可看到混淆后的代码结构:
Analyze APK
但是这样只能看到一个类的成员变量和方法的结构,如果要看一个类的具体内容,就需要反编译 apk 包了,具体可参考 Android apk 反编译及重新打包流程 ,希望一切顺利!
代码混淆后,也会导致 Crash 堆栈信息被混淆,难以阅读,增加定位问题位置的难度,一个混淆后的 Crash 堆栈信息类似这样,核心的信息都没了:
Crash Info
为了解决这个问题,可以使用
下的 proguardgui.bat 脚本将 Crash 堆栈信息还原到混淆前的状态。步骤如下:
- <SDK目录>\tools\proguard\bin
下生成的 mapping.txt
- app module/build/outputs/mapping/release
如下图中间部分就是追溯到的原 Crash 堆栈信息:
捕获_看图王. png
代码混淆常用的内容就这些了,重点还是要理解混淆的相关语法,灵活运用!混淆一定程度增加逆向工程的难度,但还是能被破解的,如果你的代码有价值,也难免被有心的人利用!除了混淆之外,一些厂商也提供了 apk 加固的服务来保证软件的安全性,但也是可以被脱壳的!有兴趣的可自行了解!下次再见......
来源: http://www.jianshu.com/p/84114b7feb38