网上关于使用proguard进行混淆的文章很多,但大部分是从讲解proguard知识点这个角度去写的,为什么要混淆,如何去混淆,混淆的注意点等重要的问题反而都没有写。
所以想通过这篇文章来记录我学习proguard的过程,一来是整理android混淆相关的知识点,二来是总结此次学习混淆的方法,提高学习能力。
学习任何技术之前应该要有一个明确的目标,本次学习混淆的目标就是会使用android studio自带的proguard对APP项目进行混淆,混淆后的APP要能正常运行。
本次学习尝试采用“三段式理论”,三段式即提出问题,分析问题,解决问题。
APP为什么要进行混淆?
混淆的首要目的是保护APP项目的代码,使APP更难被破解;其次是优化APP,去除无用代码和资源,减小APP的大小。
为什么使用proguard进行混淆?
因为proguard是一个很优秀的开源混淆代码项目,而且是google的android studio默认支持的混淆插件。
怎么使用proguard进行混淆?
这个就是后续要重点要分享和实践的内容了,别着急,往后看
如何验证核心代码是否混淆了?
我配置了proguard,用proguard打包生成了APK,那我怎么保证我想要混淆的代码已经混淆了呢。
其实反过来想就行了,就是通过反编译来验证,本篇将用jadx反编译工具来做。
经过前面的分析,我们了解到主要的问题集中在如何混淆和混淆验证两个方面上。曾经有某个伟人说过“实践是检验真理的唯一标准”,不知道怎么混淆,那就用起来试试。
接下来我们将通过去混淆OkHttpDemo这个项目来进行实践,源码下载请戳这里
build.gradle
中的minifyEnabled
置为true
。apply plugin: 'com.android.application'
android {
compileSdkVersion 26
buildToolsVersion "26.0.2"
defaultConfig {
applicationId "com.torch.easydev.okhttpdemo"
minSdkVersion 17
targetSdkVersion 26
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
exclude group: 'com.android.support', module: 'support-annotations'
})
compile 'com.android.support:appcompat-v7:26.+'
testCompile 'junit:junit:4.12'
compile 'com.squareup.okhttp3:okhttp:3.7.0'
compile 'com.google.code.gson:gson:2.8.0'
}
2.使用assembleRealease
打包APK,注意这里不能直接run,默认编译运行的APP为debug版本,是不会进行混淆的。
3. 打包过程会发现报错了,提示okio包下的某个类找不到,以下是编译的详细过程。
// 此Task为proguard混淆的命令
:app:transformClassesAndResourcesWithProguardForRelease
// proguard 版本
ProGuard, version 5.3.2
Reading input...
// 读取的jar,包括libs/以及build.gradle的dependencies里用到的
Reading program jar [/Users/torch/.android/build-cache/7880ba2afce31630824a0110efd86274bc49bc30/output/jars/classes.jar] (filtered)
Reading program jar [/Users/torch/.android/build-cache/e6f71d5707a837d00ed85447f14b707f76ae20f0/output/jars/classes.jar] (filtered)
Reading program jar [/Users/torch/.gradle/caches/modules-2/files-2.1/com.squareup.okhttp3/okhttp/3.7.0/6edbebdd8868708db718d15c09c4b28037fd487e/okhttp-3.7.0.jar] (filtered)
Reading program jar [/Users/torch/.gradle/caches/modules-2/files-2.1/com.google.code.gson/gson/2.8.0/c4ba5371a29ac9b2ad6129b1d39ea38750043eff/gson-2.8.0.jar] (filtered)
Reading program jar [/Users/torch/.android/build-cache/0d42c3690aeb29caef324c71b7fc2421c8de1e5b/output/jars/classes.jar] (filtered)
Reading program jar [/Users/torch/.android/build-cache/b9555cd093d474b08b7ea2c011a82c2d4c627b86/output/jars/classes.jar] (filtered)
Reading program jar [/Users/torch/.gradle/caches/modules-2/files-2.1/com.squareup.okio/okio/1.12.0/3742beff8024d0a0073d284b7c5e4cbf73d99b25/okio-1.12.0.jar] (filtered)
Reading program jar [/Users/torch/.android/build-cache/cb09c2323f3dd0b058deff4426ce2887c32c5698/output/jars/classes.jar] (filtered)
Reading program jar [/Users/torch/Library/Android/sdk/extras/android/m2repository/com/android/support/support-annotations/26.0.0-alpha1/support-annotations-26.0.0-alpha1.jar] (filtered)
Reading program jar [/Users/torch/.android/build-cache/446d05455618d1e0c86af9ae0a41a910627b74e1/output/jars/classes.jar] (filtered)
Reading program jar [/Users/torch/.android/build-cache/5a18253d3fd47698167249e46562f1794b833a76/output/jars/classes.jar] (filtered)
Reading program jar [/Users/torch/.android/build-cache/0d75c8d0e60a38ec97c92a95e11ba8c609f3847d/output/jars/classes.jar] (filtered)
Reading program jar [/Users/torch/.android/build-cache/a23ef26dcf19d0326ebc9315975b5fd8c0e068f1/output/jars/classes.jar] (filtered)
Reading program jar [/Users/torch/Work/android/code/study/AndroidDemo/OkHttp/OkHttpDemo/app/build/intermediates/transforms/mergeJavaRes/release/jars/2/1f/main.jar] (filtered)
Reading program directory [/Users/torch/Work/android/code/study/AndroidDemo/OkHttp/OkHttpDemo/app/build/intermediates/classes/release] (filtered)
Reading library jar [/Users/torch/Library/Android/sdk/platforms/android-26/android.jar]
Reading library jar [/Users/torch/Library/Android/sdk/platforms/android-26/optional/org.apache.http.legacy.jar]
Note: duplicate definition of library class [org.apache.http.params.HttpConnectionParams]
Note: duplicate definition of library class [org.apache.http.params.HttpParams]
Note: duplicate definition of library class [org.apache.http.params.CoreConnectionPNames]
Note: duplicate definition of library class [org.apache.http.conn.ConnectTimeoutException]
Note: duplicate definition of library class [org.apache.http.conn.scheme.HostNameResolver]
Note: duplicate definition of library class [org.apache.http.conn.scheme.SocketFactory]
Note: duplicate definition of library class [org.apache.http.conn.scheme.LayeredSocketFactory]
Note: duplicate definition of library class [android.net.http.SslCertificate$DName]
Note: duplicate definition of library class [android.net.http.SslCertificate]
Note: duplicate definition of library class [android.net.http.HttpResponseCache]
Note: duplicate definition of library class [android.net.http.SslError]
// proguard 会优化、去除重复的类
Note: there were 11 duplicate class definitions.
(http://proguard.sourceforge.net/manual/troubleshooting.html#duplicateclass)
Initializing...
// 找不到okio包下的三个类
Warning: okio.DeflaterSink: can't find referenced class org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement
Warning: okio.Okio: can't find referenced class org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement
Warning: okio.Okio: can't find referenced class org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement
// 找不到gson包和okio包下的类
Note: com.google.gson.internal.UnsafeAllocator: can't find dynamically referenced class sun.misc.Unsafe
Note: okhttp3.internal.platform.AndroidPlatform: can't find dynamically referenced class com.android.org.conscrypt.SSLParametersImpl
Note: okhttp3.internal.platform.AndroidPlatform: can't find dynamically referenced class org.apache.harmony.xnet.provider.jsse.SSLParametersImpl
Note: okhttp3.internal.platform.AndroidPlatform$CloseGuard: can't find dynamically referenced class dalvik.system.CloseGuard
Note: okhttp3.internal.platform.Platform: can't find dynamically referenced class sun.security.ssl.SSLContextImpl
Note: com.google.gson.internal.UnsafeAllocator accesses a declared field 'theUnsafe' dynamically
Note: there were 5 unresolved dynamic references to classes or interfaces.
You should check if you need to specify additional program jars.
(http://proguard.sourceforge.net/manual/troubleshooting.html#dynamicalclass)
Note: there were 1 accesses to class members by means of introspection.
You should consider explicitly keeping the mentioned class members
(using '-keep' or '-keepclassmembers').
(http://proguard.sourceforge.net/manual/troubleshooting.html#dynamicalclassmember)
Warning: there were 3 unresolved references to classes or interfaces.
You may need to add missing library jars or update their versions.
If your code works fine without the missing classes, you can suppress
the warnings with '-dontwarn' options.
(http://proguard.sourceforge.net/manual/troubleshooting.html#unresolvedclass)
Warning: Exception while processing task java.io.IOException: Please correct the above warnings first.
:app:transformClassesAndResourcesWithProguardForRelease FAILED
FAILURE: Build failed with an exception.
// 执行proguard混淆失败
* What went wrong:
Execution failed for task ':app:transformClassesAndResourcesWithProguardForRelease'.
> Job failed, see logs for details
* Try:
Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output.
BUILD FAILED
Total time: 5.691 secs
以上是proguard混淆的整个编译过程,发现混淆失败报错了,但明明代码是对的,用debug编译运行都没问题,那是不是混淆的哪个步骤出错了呢?
接下来我们一起来了解一下android studio具体是怎么使用proguard进行编译混淆的。
Proguard是一个Java类文件压缩器、优化器、混淆器、预校验器。压缩环节会检测以及移除没有用到的类、字段、方法以及属性。优化环节会分析以及优化方法的字节码。混淆环节会用无意义的短变量去重命名类、变量、方法。这些步骤让代码更精简,更高效,也更难被逆向(破解)。
混淆就是移除没有用到的代码,然后对代码里面的类、变量、方法重命名为人可读性很差的简短名字。
那么有一个问题,ProGuard怎么知道这个代码没有被用到呢?
这里引入一个Entry Point(入口点)概念,Entry Point是在ProGuard过程中不会被处理的类或方法。在压缩的步骤中,ProGuard会从上述的Entry Point开始递归遍历,搜索哪些类和类的成员在使用,对于没有被使用的类和类的成员,就会在压缩段丢弃,在接下来的优化过程中,那些非Entry Point的类、方法都会被设置为private、static或final,不使用的参数会被移除,此外,有些方法会被标记为内联的,在混淆的步骤中,ProGuard会对非Entry Point的类和方法进行重命名。
那么这个入口点怎么来呢?就是从ProGuard的配置文件来,只要有这个配置了,那么就不会被移除。
buildTypes {
release {
minifyEnabled true
# 这里的proguard-android.txt以及proguard-rules.pro就是入口点的配置文件
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
proguard-android.txt
文件在Android_SDK_Home/tools/proguard
文件夹下,是google官方提供的一个android app项目的默认通用配置文件,适用于所有android app项目。而proguard-rules.pro
默认为空,可以在这里放置一些和特定app相关的配置。
## proguard-rules.pro文件
# 本文件用于添加应用特有混淆配置信息,这些信息会添加在默认配置信息中,作为入口点的判断依据
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in E:\android\android-sdk/tools/proguard/proguard-android.txt
# You can edit the include path and order by changing the proguardFiles
# directive in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# Add any project specific keep options here:
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
接下来我们来一起看一下proguard-android.txt里面有哪些东西,默认哪些东西不能被混淆,了解了这些,我们对如何编译自定义的proguard-rules就能有一个大致的理解了。
# This is a configuration file for ProGuard.
# http://proguard.sourceforge.net/index.html#manual/usage.html
#
# This file is no longer maintained and is not used by new (2.2+) versions of the
# Android plugin for Gradle. Instead, the Android plugin for Gradle generates the
# default rules at build time and stores them in the build directory.
# 混淆时不使用大小写混合,混淆后的类名为小写
-dontusemixedcaseclassnames
# 指定不去忽略非公共的库的类
-dontskipnonpubliclibraryclasses
# 有了verbose这句话,混淆后就会生成映射文件
# 包含有类名->混淆后类名的映射关系
# 然后可以使用printmapping指定映射文件的名称
-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).
# 不启用优化,建议使用此选项
# proguard的优化选项和java虚拟机中的字节码dex优化有冲突,可能会产生一些未知的问题
-dontoptimize
# 不做预校验,preverify是proguard的4个步骤之一
# Android不需要preverify,去掉这一步可加快混淆速度
-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*
# 保留google的授权服务
-keep public class com.google.vending.licensing.ILicensingService
-keep public class com.android.vending.licensing.ILicensingService
# 保留本地NDK方法
# For native methods, see http://proguard.sourceforge.net/manual/examples.html#native
-keepclasseswithmembernames class * {
native ;
}
# 保留View中的set和get方法
# keep setters in Views so that animations can still work.
# see http://proguard.sourceforge.net/manual/examples.html#beans
-keepclassmembers public class * extends android.view.View {
void set*(***);
*** get*();
}
# 保留Activity当中的View相关方法
# We want to keep methods in Activity that could be used in the XML attribute onClick
-keepclassmembers class * extends android.app.Activity {
public void *(android.view.View);
}
# 保留枚举类
# For enumeration classes, see http://proguard.sourceforge.net/manual/examples.html#enumerations
-keepclassmembers enum * {
public static **[] values();
public static ** valueOf(java.lang.String);
}
# 保留序列号相关类即方法
-keepclassmembers class * implements android.os.Parcelable {
public static final android.os.Parcelable$Creator CREATOR;
}
# 保留资源应用名
-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.
# 忽略support包下的警告
-dontwarn android.support.**
# Understand the @Keep support annotation.
# 保留support包下的动画
-keep class android.support.annotation.Keep
-keep @android.support.annotation.Keep class * {*;}
-keepclasseswithmembers class * {
@android.support.annotation.Keep ;
}
-keepclasseswithmembers class * {
@android.support.annotation.Keep ;
}
-keepclasseswithmembers class * {
@android.support.annotation.Keep (...);
}
可以看到,这里主要保留的是一些不能被混淆的类、方法或者变量名,编写配置文件的核心思想就是尽可能地去做混淆,除非有不得不混淆的理由
。
那哪些东西是不能做混淆的呢?
以下列举一些常用的不能被混淆的情况:
好了,现在回过头来看一下刚才编译错误,是不是觉得没那么难了呢?
之前编译错误提示是okio包和gson包下的某个类找不到,这两个包都是第三方开源的jar包,可以直接不混淆,并且忽略这两个包下的警告。
-keep class com.google.gson.** { *; }
-dontwarn okio.**
-keep class okhttp3.** { *; }
clean工程后重新打包编译,这次编译成功了(终于成功了,值得庆祝一下^_^)。
注意:虽然编译成功了,但一定要将应用跑起来,把各个功能都验证一下,看是否会影响。
编译后查看output文件夹,我们可以看到除了生成混淆后的apk文件后,还生成了一个mapping文件夹,如下图:
那这些文件有什么作用呢?
打开mapping.txt
看一下,可以发现这是一个混淆前和混淆后的类和类的方法、变量的映射文件。当想通过日志去排查代码问题时,就需要借助这个mapping.txt
文件了,因为混淆后,很多类名、方法名都已经面目全非了。
// 这是okhttphelper类的对应映射表
com.torch.easydev.okhttpdemo.OkHttpHelper -> com.torch.easydev.okhttpdemo.a:
okhttp3.MediaType JSON -> a
okhttp3.MediaType MARKDOWN -> b
com.google.gson.Gson gson -> c
java.util.concurrent.ScheduledExecutorService executor -> d
okhttp3.OkHttpClient httpClient -> e
okhttp3.OkHttpClient httpsClient -> f
okhttp3.OkHttpClient getOkHttpClient(android.content.Context,boolean) -> a
okhttp3.OkHttpClient getOkHttpClient(android.content.Context,boolean,boolean) -> a
okhttp3.OkHttpClient getOkHttpClientByCertificate(java.io.InputStream[]) -> a
okhttp3.OkHttpClient getUnSafedOkHttpClient() -> a
java.lang.String doGet(android.content.Context,java.lang.String) -> a
java.lang.String doGet(android.content.Context,java.lang.String,boolean) -> a
java.lang.String doPostString(android.content.Context) -> a
void downloadFile(android.content.Context,java.lang.String,java.lang.String,com.torch.easydev.okhttpdemo.OkHttpHelper$DownloadStatusListener) -> a
void () ->
seed.txt
描述项目保留的没有被混淆的类、方法及变量名。
usage.txt
描述项目使用到的类、方法及变量名
dump.txt
描述apk内所有class文件的内部结构
假定本项目核心代码为OkHttpHelper类里的方法,我们来看一下混淆后的APK,反编译出来以后变成什么样子了。
使用命令编译后,得到反编译的代码如图:
jadx-gui app-release.apk