Android代码混淆

Android知识总结

一、配置( 项目的app/build.gradle)

    buildTypes {
        release {
            /*打开混淆*/
            minifyEnabled true
            /*打开资源压缩*/
            shrinkResources true
            zipAlignEnabled true // Zipalign优化 
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }

proguard-android.txt 是官方提供的通用混淆配置,文件路径在 \sdk\tools\proguard\proguard-android.txt
proguard-rules.pro 即自定义配置文件

            proguardFiles getDefaultProguardFile('proguard-android.txt'),
                    file('proguard-rules.pro'),
                    project.ext.LiteDir + 'buildOperation/proguard.cfg',
                    file('../PluginSDK/proguard-rules.pro'),
                    file('../LibThemeEngine/proguard-rules.pro'),

二、混淆为什么要保留类名或方法名?

  • 1、让C/C++程序可以通过jni使用对应的java方法
  • 2、四大组件由于在AndroidManifest.xml里面注册了,所以需要保留。
  • 3、R文件混淆会导致引用错误。
  • 4、第三方架包有的已经经过混淆了,再次混淆会导致找不到类名或者方法名

三、什么时候不被混淆?

一般以下情况都会不混淆:

  • 1、使用了自定义控件那么要保证它们不参与混淆
  • 2、使用了枚举要保证枚举不被混淆
  • 3、对第三方库中的类不进行混淆
  • 4、运用了反射的类也不进行混淆
  • 5、使用了 Gson 之类的工具要使 JavaBean 类即实体类不被混淆
  • 6、静态成员JNI中调用的类
  • 7、有用到 WebViewJS 调用也需要保证写的接口方法不混淆,原因和第一条一样
  • 8、Serializable,Parcelable的子类和 Creator 静态成员变量不混淆,否则会产生 Android.os.BadParcelableException 异常
  • 9、四大组件自定义的Application

四、混淆语法

  • 2.1 基本规则
    两个常用的混淆命令,注意一颗星表示只是保持该包下的类名,而子包下的类名还是会被混淆;而两颗星表示把本包和所包含的子包下的类名都保留。
-keep class cn.hadcn.test.**
-keep class cn.hadcn.test.*

如果既想保持类名,又想保持里面的内容不被混淆,就执行以下方法

 -keep class com.example.bean.** { *; }

在此基础上,我们也可以使用Java的基本规则来保护特定类不被混淆,比如我们可以用extend,implement等这些Java规则。如下例子就避免所有继承Activity的类被混淆

保留我们使用的四大组件,自定义的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
  • 2.2、基本混淆模板
    对于一些基本指令的添加
#############################################
#
# 对于一些基本指令的添加
#
#############################################
#保证使用同一套 mapping 文件,从而保证前后打包一直。
#mapping文件时混淆前后文件的映射关系
-applymapping mapping.txt
# 代码混淆压缩比,在0~7之间,默认为5,一般不做修改
-optimizationpasses 5
# 混合时不使用大小写混合,混合后的类名为小写
-dontusemixedcaseclassnames
# 指定不去忽略非公共库的类
-dontskipnonpubliclibraryclasses
# 这句话能够使我们的项目混淆后产生映射文件# 包含有类名->混淆后类名的映射关系
-verbose
# 指定不去忽略非公共库的类成员
-dontskipnonpubliclibraryclassmembers
# 不做预校验,preverify是proguard的四个步骤之一,Android不需要preverify,去掉这一步能够加快混淆速度。
-dontpreverify
# 保留Annotation不混淆
-keepattributes *Annotation*,InnerClasses
# 避免混淆泛型
-keepattributes Signature
#将文件来源重命名为“SourceFile”字符串
-renamesourcefileattribute SourceFile
# 抛出异常时保留代码行号
-keepattributes SourceFile,LineNumberTable
# 避免混淆Annotation、内部类、泛型、匿名类
-keepattributes *Annotation*,InnerClasses,Signature,EnclosingMethod
# 指定混淆是采用的算法,后面的参数是一个过滤器# 这个过滤器是谷歌推荐的算法,一般不做更改
-optimizations !code/simplification/cast,!field/*,!class/merging/*
#把混淆类中的方法名也混淆了
-useuniqueclassmembernames
#优化时允许访问并修改有修饰符的类和类的成员
-allowaccessmodification

Android开发中一些需要保留的公共部分

#############################################
#
# Android开发中一些需要保留的公共部分
#
#############################################
# 保留我们使用的四大组件,自定义的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

# 保留support下的所有类及其内部类
-keep class android.support.** {*;}
# 保留继承的
-keep public class * extends android.support.v4.**
-keep public class * extends android.support.v7.**
-keep public class * extends android.support.annotation.**
# 保留R下面的资源
-keep class **.R$* {*;}
# 保留本地native方法不被混淆
-keepclasseswithmembernames class * {
    native ;
}
# 保留在Activity中的方法参数是view的方法,
# 这样以来我们在layout中写的onClick就不会被影响
-keepclassmembers class * extends android.app.Activity{
    public void *(android.view.View);
}
# 保留枚举类不被混淆
-keepclassmembers enum * {
    public static **[] values();
    public static ** valueOf(java.lang.String);
}
# 保留我们自定义控件(继承自View)不被混淆
-keep public class * extends android.view.View{
    *** get*();
    void set*(***);
    public (android.content.Context);
    public (android.content.Context, android.util.AttributeSet);
    public (android.content.Context, android.util.AttributeSet, int);
}
# 保留Parcelable序列化类不被混淆
-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;
    !static !transient ;
    !private ;
    !private ;
    private void writeObject(java.io.ObjectOutputStream);
    private void readObject(java.io.ObjectInputStream);
    java.lang.Object writeReplace();
    java.lang.Object readResolve();
}
# 对于带有回调函数的onXXEvent、**On*Listener的,不能被混淆
-keepclassmembers class * {
    void *(**On*Event);
    void *(**On*Listener);
}
# 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);
}

# assume no side effects:删除android.util.Log输出的日志
-assumenosideeffects class android.util.Log {
    public static *** v(...);
    public static *** d(...);
    public static *** i(...);
    public static *** w(...);
    public static *** e(...);
}

#保留Keep注解的类名和方法
-keep,allowobfuscation @interface android.support.annotation.Keep
-keep @android.support.annotation.Keep class *
-keepclassmembers class * {
    @android.support.annotation.Keep *;
}

#fastjson混淆
-keepattributes Signature
-dontwarn com.alibaba.fastjson.**
-keep class com.alibaba.**{*;}
-keep class com.alibaba.fastjson.**{*; }
-keep public class com.ninstarscf.ld.model.entity.**{*;}

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

# webview
-keep class android.webkit.JavascriptInterface {*;}
#自定义webview接口实现类
-keep public class com.gjmetal.app.ui.ball.**{*;}

# 微信支付
-dontwarn com.tencent.mm.**
-dontwarn com.tencent.wxop.stat.**
-keep class com.tencent.mm.** {*;}
-keep class com.tencent.wxop.stat.**{*;}

# 支付宝钱包
-dontwarn com.alipay.**
-dontwarn HttpUtils.HttpFetcher
-dontwarn com.ta.utdid2.**
-dontwarn com.ut.device.**
-keep class com.alipay.android.app.IAlixPay{*;}
-keep class com.alipay.android.app.IAlixPay$Stub{*;}
-keep class com.alipay.android.app.IRemoteServiceCallback{*;}
-keep class com.alipay.android.app.IRemoteServiceCallback$Stub{*;}
-keep class com.alipay.sdk.app.PayTask{ public *;}
-keep class com.alipay.sdk.app.AuthTask{ public *;}
-keep class com.alipay.mobilesecuritysdk.*
-keep class com.ut.*

五、其他混淆

然后需要在项目生成的混淆脚本中添加过滤混淆的条件

# Glide
-dontwarn com.bumptech.glide.**
-keep class com.bumptech.glide.**{*;}
-keep public class * implements com.bumptech.glide.module.GlideModule
-keep public class * extends com.bumptech.glide.AppGlideModule
-keep public enum com.bumptech.glide.load.resource.bitmap.ImageHeaderParser$** {
  **[] $VALUES;
  public *;
}

# Gson
-keep class sun.misc.Unsafe { *; }
-keep class com.google.gson.stream.** { *; }
# OkHttp3
-keep class okhttp3.** { *; }
-keep interface okhttp3.** { *; }
-dontwarn okhttp3.**
# Okio
-dontwarn com.squareup.**
-dontwarn okio.**
-keep public class org.codehaus.* { *; }
-keep public class java.nio.* { *; }
# Retrofit
-dontwarn retrofit2.**
-keep class retrofit2.** { *; }
-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;
}
#数据模型(实体类)
-keep class com.seekfangle.pad.bean.* {*;}
#第三方架包
-keep class  cn.com.** { *;}
-keep class  android_serialport_api.** { *;}

#butterknife
-dontwarn butterknife.internal.**
-keep class **$$ViewInjector { *; }
-keepnames class * { @butterknife.InjectView *;}

#EventBus3.0
-keepattributes *Annotation*
-keepclassmembers class ** {
    @org.greenrobot.eventbus.Subscribe ;
}
-keep enum org.greenrobot.eventbus.ThreadMode { *; }

#友盟
-keep class com.umeng.** {*;}
-keepclassmembers class * {
   public  (org.json.JSONObject);
}
-keepclassmembers enum * {
    public static **[] values();
    public static ** valueOf(java.lang.String);
}
-keep public class com.gjmetal.app.R$*{
public static final int *;
}

#个推
-dontwarn com.igexin.**
-keep class com.igexin.** { *; }
-keep class org.json.** { *; }
-keep class android.support.v4.app.NotificationCompat { *; }
-keep class android.support.v4.app.NotificationCompat$Builder { *; }

#routes
-keep public class com.alibaba.android.arouter.routes.**{*;}
-keep class * implements com.alibaba.android.arouter.facade.template.ISyringe{*;}


#ShareSdk
-keep class cn.sharesdk.**{*;}
-keep class com.sina.**{*;}
-keep class **.R$* {*;}
-keep class **.R{*;}
-keep class com.mob.**{*;}
-keep class m.framework.**{*;}
-dontwarn cn.sharesdk.**
-dontwarn com.sina.**
-dontwarn com.mob.**
-dontwarn **.R$*

#Picasso
-keep class com.parse.*{ *; }
-dontwarn com.parse.**
-dontwarn com.squareup.picasso.**
-keepclasseswithmembernames class * {
    native ;
}

#GreenDao
-keepclassmembers class * extends org.greenrobot.greendao.AbstractDao {
public static java.lang.String TABLENAME;
}
-keep class **$Properties

# If you do not use SQLCipher:
-dontwarn org.greenrobot.greendao.database.**
# If you do not use Rx:
-dontwarn rx.**

#百度定位混淆配置
-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.*
#百度地图混淆配置
-keep class com.baidu.** {*;}
-keep class vi.com.** {*;}
-dontwarn com.baidu.**
-keep class mapsdkvi.com.** {*;}

#QQ分享
-dontwarn com.tencent.**
-keep class com.tencent.** {*; }

#微信分享
-keep class com.tencent.mm.opensdk.** {*;}
-keep class com.tencent.wxop.** {*;}
-keep class com.tencent.mm.sdk.** { *;}

六、检查混淆结果

混淆过的包必须进行检查,避免因混淆引入的bug。
一方面,需要从代码层面检查。使用上文的配置进行混淆打包后在/build/outputs/mapping/release/目录下会输出以下文件:
dump.txt
描述APK文件中所有类的内部结构
mapping.txt
提供混淆前后类、方法、类成员等的对照表
seeds.txt
列出没有被混淆的类和成员
usage.txt
列出被移除的代码
我们可以根据 seeds.txt 文件检查未被混淆的类和成员中是否已包含所有期望保留的,再根据 usage.txt文件查看是否有被误移除的代码。
另一方面,需要从测试方面检查。将混淆过的包进行全方面测试,检查是否有 bug 产生。

七、解出混淆栈

混淆后的类、方法名等等难以阅读,这固然会增加逆向工程的难度,但对追踪线上 crash 也造成了阻碍。我们拿到 crash 的堆栈信息后会发现很难定位,这时需要将混淆反解。
/tools/proguard/路径下有附带的的反解工具(Window 系统为proguardgui.bat,Mac 或 Linux 系统为proguardgui.sh)。
这里以 Window 平台为例。双击运行 proguardgui.bat 后,可以看到左侧的一行菜单。点击 ReTrace,选择该混淆包对应的 mapping 文件(混淆后在 /build/outputs/mapping/release/路径下会生成 mapping.txt 文件,它的作用是提供混淆前后类、方法、类成员等的对照表),再将 crash 的 stack trace 黏贴进输入框中,点击右下角的 ReTrace ,混淆后的堆栈信息就显示出来了。
以上使用 GUI 程序进行操作,另一种方式是利用该路径下的 retrace 工具通过命令行进行反解,命令是
retrace.bat|retrace.sh [-verbose] mapping.txt []
例如:

retrace.bat -verbose mapping.txt obfuscated_trace.txt
注意事项:
所有在 AndroidManifest.xml涉及到的类已经自动被保持,因此不用特意去添加这块混淆规则。(很多老的混淆文件里会加,现在已经没必要)
proguard-android.txt已经存在一些默认混淆规则,没必要在proguard-rules.pro 重复添加

混淆的文件对照表

mapping.txt中的混淆前后的类对照如下:

//混淆前的文件 -> 混淆后的文件
org.aspectj.runtime.reflect.SourceLocationImpl -> c.a.b.b.g:
    java.lang.String fileName -> a
    int line -> b
    24:28:void (java.lang.Class,java.lang.String,int) -> 
    31:31:java.lang.String getFileName() -> a
    32:32:int getLine() -> b
    36:36:java.lang.String toString() -> toString
org.aspectj.runtime.reflect.StringMaker -> c.a.b.b.h:
    org.aspectj.runtime.reflect.StringMaker middleStringMaker -> g
    org.aspectj.runtime.reflect.StringMaker longStringMaker -> h
    org.aspectj.runtime.reflect.StringMaker shortStringMaker -> f
    int cacheOffset -> e
    boolean shortTypeNames -> a
    boolean includeArgs -> b
    boolean includeModifiers -> c
    boolean shortPrimaryTypeNames -> d
    33:69:void () -> 
    19:28:void () -> 

八、自定义混淆规则

在上文“混淆配置”中有这样一行代码

proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'

这行代码定义了混淆规则由两部分构成:位于 SDK 的tools/proguard/ 文件夹中的 proguard-android.txt的内容以及默认放置于模块根目录的proguard-rules.pro 的内容。前者是 SDK 提供的默认混淆文件,后者是开发者自定义混淆规则的地方。

九、常见的混淆指令

  • 1、 optimizationpasses代码混淆压缩比,在0~7之间,默认为5,一般不做修改
  • 2、 dontoptimize 不进行优化
  • 3、 dontusemixedcaseclassnames 混合时不使用大小写混合,混合后的类名为小写
  • 4、 dontskipnonpubliclibraryclasses 指定不去忽略非公共库的类
  • 5、 dontpreverify 不做预校验
  • 6、 dontwarn 不提示警告,和keep可以说是形影不离,尤其是处理引入的library时.
  • 7、 verbose 混淆时是否记录日志
  • 8、 optimizations 指定混淆是采用的算法,后面的参数是一个过滤器
  • 9、 keep 保留类和类中的成员,防止被混淆或移除
  • 10、keepnames 保留类和类中的成员,防止被混淆,成员没有被引用会被移除
  • 11、 keepclassmembers 只保留类中的成员,防止被混淆或移除
  • 12、keepclassmembernames 只保留类中的成员,防止被混淆,成员没有引用会被移除
  • 13、 keepclasseswithmembers 保留类和类中的成员,防止被混淆或移除,保留指明的成员
  • 14、 keepclasseswithmembernames 保留类和类中的成员,防止被混淆,保留指明的成员,成员没有引用会被移除
  • 15、dontshrink 不压缩类文件。默认情况下会压缩所有的类文件,除了那些用keep声明和被这些类依赖的class
    更多详细的请到官网

十、规则

  [保持命令] [类] {
    [成员] 
}

“类”代表类相关的限定条件,它将最终定位到某些符合该限定条件的类。它的内容可以使用:

  • 具体的类
  • 访问修饰符public、protected、private
  • 通配符*,匹配任意长度字符,但不含包名分隔符(.)
  • 通配符**,匹配任意长度字符,并且包含包名分隔符(.)
  • extends,即可以指定类的基类
    *implement,匹配实现了某接口的类
  • $,内部类

成员代表类成员相关的限定条件,它将最终定位到某些符合该限定条件的类成员。它的内容可以使用:

  • 匹配所有构造器
  • 匹配类中的所有字段
  • 匹配所有方法
  • 通配符*,匹配任意长度字符,但不含包名分隔符(.)
  • 通配符**,匹配任意长度字符,并且包含包名分隔符(.)
  • 通配符***,匹配任意参数类型
  • ,匹配任意长度的任意类型参数。比如void test(…)就能匹配任意void test(String a)或者是 void test(int a, String b)这些方法。
  • 访问修饰符public、protected、private
指定成员
\ 代表任意构造方法.
\ 代表任意域.
\ 代表任意方法.
* 代表任意成员(包括成员变量和方法).

类型描述通配符
% 表示任意基本类型(int,char等,但是不包括void).
? 表示类名中的任意单个字符.
* 表示类名中的任意多个字符,不包括分隔符(.).
** 表示类名中的任意多个字符,包括分隔符(.).
*** 表示任意类型.
... 表示任意多个任意类型的参数.

举个例子,假如需要将com.biaobiao.test包下所有继承Activity的public类及其构造函数都保持住,可以这样写:

 -keep public class com.biaobiao.test.** extends Android.app.Activity {
    
}

十一、常用自定义混淆规则

1、不混淆某个类

-keep public class com.biaobiao.example.Test { *; }

2、不混淆某个包所有的类

-keep class com.biaobiao.test.** { *; }

3、不混淆某个类的子类

-keep public class * extends com.biaobiao.example.Test { *; }

4、不混淆某个接口的实现

-keep class * implements com.biaobiao.example.TestInterface { *; }

5、不混淆某个类的构造方法

-keepclassmembers class com.biaobiao.example.Test { 
    public (); 
}
//保持指定类的所有字段
-keep class com.xy.myapp.MyClass { public ; }

6、不混淆某个类的特定的方法

-keepclassmembers class com.biaobiao.example.Test { 
    public void test(java.lang.String); 
}
//保持指定类的所有方法
-keep class com.xy.myapp.MyClass { public ; }
//保持用指定参数作为形参的方法
-keep class com.xy.myapp.MyClass { public (java.lang.String); }

7、不混淆某个类的内部类

//所有内部类
-keep class com.biaobiao.example.Test$* { *; }
//内部类 Builder
-keep class com.biaobiao.example.Test$Builder { *; }
//保持MyClass内部类JavaScriptInterface中的所有public内容。
-keepclassmembers class com.xy.myapp.MyClass$JavaScriptInterface { 
    #保持该类下所有的共有内容不被混淆
    public *;
    #保持该类下所有的私有方法不被混淆
    private * ;
 }

8、混淆引入的 library

//使指定的类不输出警告信息
-dontwarn purang.purang_shop.**
-keep class purang.purang_shop.**{*;}

十二、组件模块定义混淆规则

子模块混淆文件的指定是通过consumerProguardFiles这个属性来指定的,并不是proguardFiles属性,而且我们无需配置其他的选项,只需要配置consumerProguardFiles属性就可以。该属性表示在打包的时候会自动寻找该module下我们指定的混淆文件对代码进行混淆。可以把一些公共的混淆定义在子模块中。

   release {
            consumerProguardFiles   'proguard-rules.pro'
        }

consumerProguardFiles作用:

  • proguard.txt会被打包进aar中
  • 此配置只对aar进行混淆。
  • 此配置只对库文件有效,对应用程序无效。

当app模块将全部代码汇总混淆时,Library 模块会被打包为release aar,然后被引用汇总,通过proguard-rule.pro规则各自混淆,保证只混淆一次。

组件化工程中具体混淆用法:
我们可以将固定的第三方混淆放到base模块的proguard-rule.pro文件中,每个模块独有的第三方引用库混淆放到各自的proguard-rule.pro文件中,在app模块的proguard-rule.pro文件中放入Android基础属性的混淆声明,如四大组件和全局的混淆等配置。这样可以最大限度的完成混淆解耦操作。

十三、自定义要保持的资源

资源压缩包含了“合并资源”和“移除资源”两个流程。“合并资源”流程中,名称相同的资源被视为重复资源会被合并。需要注意的是,这一流程不受shrinkResources属性控制,也无法被禁止, gradle 必然会做这项工作。
当我们开启了资源压缩之后,系统会默认替我们移除所有未使用的资源,假如我们需要保留某些特定的资源,可以在我们项目中创建一个被 标记的 XML 文件(如 res/raw/keep.xml),并在tools:keep属性中指定每个要保留的资源,在tools:discard属性中指定每个要舍弃的资源。这两个属性都接受逗号分隔的资源名称列表。同样,我们可以使用字符 * 作为通配符。


十四、移除替代资源

一些替代资源,例如多语言支持的 strings.xml,多分辨率支持的 layout.xml 等,在我们不需要使用又不想删除掉时,可以使用资源压缩将它们移除。我们使用 resConfig 属性来指定需要支持的属性,例如

  android {
      defaultConfig {
          ...
          resConfigs "en", "fr"
      }
  }

你可能感兴趣的:(Android代码混淆)