Android Studio&源码混淆配置及其调试注意事项

一、 混淆的配置

1.1  AS 的混淆配置方法

1.打开对应Moudle下的build.gradle文件,然后将minifyEnabled设置为true,如下:
buildTypes{
   release{
         minifyEnabled true//是否启动混淆 ture:打开   false:关闭
         proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'

   }
}

minifyEnabled 为studio工程中默认混淆配置的开关,true为开启,反之关闭;

proguardFiles getDefaultProguardFile:代表的是默认混淆的配置文件,文件中自定义了一系列的混淆,
其中【proguard-android.txt】则是Google默认的一套混淆规则,该文件的实际路径为【SDK路径/tools/proguard/proguard-android.txt】;
而proguard-rules.pro文件则是该moudle下的开发者自定义混淆规则,一般在moudle根目录



PS:混淆后的映射对照表文件mapping.txt的对应目录为【/build/outputs/mapping/release/】,
    它提供了混淆前后类、方法、类成员等的对照表,该文件比较重要,常常用于混淆代码堆栈的反解,编译快速分析定位问题。
     
     # 抛出异常时保留代码行号 -keepattributes SourceFile,LineNumberTable


2.编译Realsed版本已验证,在此过程中,IDE工具会在目录【/build/outputs/mapping/release/】产生混淆中间产物如下:

dump.txt 
描述APK文件中所有类的内部结构

mapping.txt 
提供混淆前后类、方法、类成员等的对照表,该文件比较重要,常常用于混淆代码堆栈的反解,编译快速分析定位问题

seeds.txt 
列出没有被混淆的类和成员

usage.txt 
列出被移除的代码

我们可以根据 seeds.txt 文件检查未被混淆的类和成员中是否已包含所有期望保留的,再根据usage.txt 文件查看是否有被误移除的代码。

3.混淆堆栈调试

混淆后的类、方法名等等难以阅读,这固然会增加逆向工程的难度,但对追踪线上 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
注意事项:

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


1.2  源码下的混淆配置方法

   在模块的Android.mk中配置

   LOCAL_PROGUARD_ENABLED := full  obfuscation    //混淆开关配置,配置为disabled为关闭混淆
   LOCAL_PROGUARD_FLAG_FILES := proguard.flags     //混淆规则文件配置



PS: 如果需要调试方便话,最好在配置LOCAL_PROGUARD_ENABLED 时,增加obfuscation配置,
     此时,系统会自定加入系统建议的【proguard_options 】规则以及生成中间产物【proguard_dictionary 】,这个文件如同android studio工程中的mapping文件。

     源码混淆后的映射对照表文件【proguard_dictionary】的对用目录在【link_instr_intermediates_dir.COMMON】目录下,即【out/target/commons/obj/APPS/xxx模块_intermediates/】

    
     1.proguard_dictionary 文件&mapping文件都需要在每一次realse版本时同版本一同保存,方便后面的调试,反解堆栈

     2.请在目标模块的混淆规则中尽量增加如下规则,方面代码追踪。

     # 抛出异常时保留代码行号 -keepattributes SourceFile,LineNumberTable


系统源码混淆的配置文件:

【模块混淆】

android/build/make/core/package_internal.mk

LOCAL_PROGUARD_ENABLED:=$(strip $(LOCAL_PROGUARD_ENABLED))

ifndef LOCAL_PROGUARD_ENABLED

ifneq ($(DISABLE_PROGUARD),true)

    LOCAL_PROGUARD_ENABLED :=full

endif

endif

ifeq ($(LOCAL_PROGUARD_ENABLED),disabled)

    # the package explicitly request to disable proguard.

    LOCAL_PROGUARD_ENABLED :=

endif

proguard_options_file :=

ifneq ($(LOCAL_PROGUARD_ENABLED),custom)

ifeq ($(need_compile_res),true)

    proguard_options_file := $(intermediates.COMMON)/proguard_options

endif # need_compile_res

endif # !custom

LOCAL_PROGUARD_FLAGS := $(addprefix -include ,$(proguard_options_file)) $(LOCAL_PROGUARD_FLAGS)

【java文件混淆】

android/build/make/core/java.mk

 ifeq ($(filter obfuscation,$(LOCAL_PROGUARD_ENABLED)),)

# If no obfuscation, link in the instrmented package's classes.jar as a library.

# link_instr_classes_jar is defined in base_rule.mk

legacy_proguard_flags += -libraryjars $(link_instr_classes_jar)

legacy_proguard_lib_deps += $(link_instr_classes_jar)

else # obfuscation

# If obfuscation is enabled, the main app must be obfuscated too.

# We need to run obfuscation using the main app's dictionary,

# and treat the main app's class.jar as injars instead of libraryjars.

legacy_proguard_flags := -injars  $(link_instr_classes_jar) \

    -outjars $(intermediates.COMMON)/proguard.$(LOCAL_INSTRUMENTATION_FOR).jar \

    -include $(link_instr_intermediates_dir.COMMON)/proguard_options \

    -applymapping $(link_instr_intermediates_dir.COMMON)/proguard_dictionary \

    -verbose \

    $(legacy_proguard_flags)

混淆的原理

Java 是一种跨平台、解释型语言,Java 源代码编译成的class文件中有大量包含语义的变量名、方法名的信息,
很容易被反编译为Java 源代码。为了防止这种现象,我们可以对Java字节码进行混淆。混淆不仅能将代码中的类名、字段、方法名变为无意义的名称,
保护代码,也由于移除无用的类、方法,并使用简短名称对类、字段、方法进行重命名缩小了程序的size。

ProGuard由shrink、optimize、obfuscate和preverify四个步骤组成,每个步骤都是可选的,需要哪些步骤都可以在脚本中配置。参见ProGuard官方介绍。

压缩(Shrink): 侦测并移除代码中无用的类、字段、方法、和特性(Attribute)。
优化(Optimize): 分析和优化字节码。
混淆(Obfuscate): 使用a、b、c、d这样简短而无意义的名称,对类、字段和方法进行重命名。
上面三个步骤使代码size更小,更高效,也更难被逆向工程。
预检(Preveirfy): 在java平台上对处理后的代码进行预检。
 

混淆的语法

===========【常用的Proguard关键字及其描述】==================

Proguard关键字描述

dontwarndontwarn是一个和keep可以说是形影不离,尤其是处理引入的library时.

keep保留类和类中的成员,防止被混淆或移除

keepnames保留类和类中的成员,防止被混淆,成员没有被引用会被移除

keepclassmembers只保留类中的成员,防止被混淆或移除

keepclassmembernames只保留类中的成员,防止被混淆,成员没有引用会被移除

keepclasseswithmembers保留类和类中的成员,防止被混淆或移除,保留指明的成员

keepclasseswithmembernames保留类和类中的成员,防止被混淆,保留指明的成员,成员没有引用会被移除

... ...

示例:

(1)保留某个包下面的类以及子包

-keeppublicclasscom.droidyue.com.widget.**

(2)保留所有类中使用otto的public方法

# Otto

-keepclassmembers class ** {

    @com.squareup.otto.Subscribe public *;
    @com.squareup.otto.Produce public *;

}

(3)保留Contants类的BOOK_NAME属性

-keepclassmembers classcom.example.admin.proguardsample.Constants {publicstatic java.lang.String BOOK_NAME;

(4)dontwarn:

引入的library可能存在一些无法找到的引用和其他问题,在build时可能会发出警告,如果我们不进行处理,通常会导致build中止.

因此为了保证build继续,我们需要使用dontwarn处理这些我们无法解决的library的警告.

#比如关闭Twittersdk的警告,我们可以这样做

-dontwarncom.twitter.sdk.**

===========【Proguard通配符及其描述】===============

 

Proguard通配符描述

匹配类中的所有字段

匹配类中所有的方法

匹配类中所有的构造函数

*匹配任意长度字符,不包含包名分隔符(.)

**匹配任意长度字符,包含包名分隔符(.)

***匹配任意参数类型

......


===========【Proguard语法如下】=================

[保持命令] [类] {

    [成员] 

}

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

具体的类

访问修饰符(public 、protected 、private )

通配符* ,匹配任意长度字符,但不含包名分隔符(.)

通配符** ,匹配任意长度字符,并且包含包名分隔符(.)

extends ,即可以指定类的基类

implement ,匹配实现了某接口的类

$,内部类

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

匹配所有构造器

匹配所有域

匹配所有方法

通配符* ,匹配任意长度字符,但不含包名分隔符(.)
通配符** ,匹配任意长度字符,并且包含包名分隔符(.)
通配符*** ,匹配任意参数类型


… ,匹配任意长度的任意类型参数。比如void test(…)就能匹配任意 void test(String a) 或者是 void test(int a, String b) 这些方法。

访问修饰符(public 、protected 、private )



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

-keep public class name.huihui.test.** extends Android.app.Activity {

    

}


===========【哪些不应该混淆】===========

使用了自定义控件那么要保证它们不参与混淆
使用了枚举要保证枚举不被混淆
对第三方库中的类不进行混淆
运用了反射的类也不进行混淆
使用了 Gson 之类的工具要使 JavaBean 类即实体类不被混淆

在引用第三方库的时候,一般会标明库的混淆规则的,建议在使用的时候就把混淆规则添加上去,免得到最后才去找

有用到 WebView 的 JS 调用也需要保证写的接口方法不混淆,原因和第一条一样

Parcelable 的子类和 Creator 静态成员变量不混淆,否则会产生 Android.os.BadParcelableException 异常
使用的四大组件,自定义的Application* 实体类
JNI中调用的类
Layout布局使用的View构造函数(自定义控件)、android:onClick等

===========【常用的自定义混淆规则】===========
 

不混淆某个类

-keep public class name.huihui.example.Test { *; }

不混淆某个包所有的类

-keep class name.huihui.test.** { *; }

不混淆某个类的子类

-keep public class * extends name.huihui.example.Test { *; }

不混淆所有类名中包含了“model”的类及其成员

-keep public class **.*model*.** {*;}

不混淆某个接口的实现

-keep class * implements name.huihui.example.TestInterface { *; }

不混淆某个类的构造方法

-keepclassmembers class name.huihui.example.Test { 

  public (); 

}

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

-keepclassmembers class name.huihui.example.Test { 

  public void test(java.lang.String); 

}


 

你可能感兴趣的:(android,工具代码)