一、 混淆的配置
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);
}