Android混淆(ProGuard)从0到1

本文为原创,转载请注明出处:
http://www.jianshu.com/p/1b76e4c10495

说在前面的

作为一个Android开发者,或多或少都知道点关于混淆的概念,在使用的时候大家有可能也只是网上找找相关的,然后改改复制粘贴到自己的项目中使用。
公司有一个产品,工程也比较大,已经有四年多的历程了,之前一直在eclipse中开发,一直疲于应付需求bug之类的,零零散散的时间又不想去弄混淆,只是一直感觉混淆好像是个很高大上的东西。
后来转到了Android Studio上开发,我知道在Android Studio上启动混淆是一件比较容易的事情,但是由于工程比较巨大了,一直不敢轻易做尝试,自己深知工程也比较危险,可以被别人反编译,就好比扒光了衣服给人家看一样,很没有安全感,所以今天下定决心好好研究一下混淆方面的东西。网上有很多资料,但是大多同样一句话,确有不同的解释,并且每个人的配置都不一样,因此我觉得有必要对混淆做进一步的认识,并不应该只是停留在‘改改能用就完事大吉’的程度,毕竟混淆是对工程非常有用的,花点时间掌握,一劳永逸。

混淆简介

Android代码混淆,又称Android混淆,是一种Android APP保护技术,用于保护APP不被破解和逆向分析。
从Android2.3开始,Google在SDK中加入了一款叫ProGuard的代码混淆工具,ProGuard会删除这些调试信息,并用无意义的字符序列来替换类名、方法名等,使得使用反编译出来的代码难以阅读,提升逆向难度。
ProGuard它可以混淆Android项目里面的java代码,
注意:仅仅是java代码。它是无法混淆Native代码,资源文件drawable、xml等,资源压缩通过 Android Plugin for Gradle 提供,该插件会移除封装应用中未使用的资源,包括代码库中未使用的资源,两者协同使用,使得在移除未使用的代码后,任何不再被引用的资源也能安全地移除。

ProGuard的三大作用

  • 压缩(Shrinking):移除未被使用的类、属性、方法等,并且会在优化动作执行之后再次执行(因为优化后可能会再次暴露一些未被使用的类和成员。

  • 优化(Optimization): 优化字节码,并删除未使用的结构。

  • 混淆(Obfuscation):将类名、属性名、方法名混淆为难以读懂的字母,比如a,b,c等,增大反编译难度。

开启ProGuard

在Android Studio中开启混淆是一件非常容易的事情。
创建module的时候,编译器会在module下自动创建proguard-rules.pro文件,这个也是我们写混淆规则的地方。
在module的build.grade文件默认生成了下面的代码:

android {
    ...
    buildTypes {
        release {
            //只需将false改为true即可启用混淆
            minifyEnabled false
          
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    ...
    }
}

getDefaultProguardFile(‘proguard-android.txt') 是获取默认的 ProGuard 设置。proguard-android.txt描述了系统提供的基础混淆配置信息,文件所在路径为:\sdk\tools\proguard\proguard-android.txt。
后面会展示文件的相关内容,其中里面关闭了优化功能,所以如果要想做进一步的代码压缩,同一路径下 proguard-android-optimize.txt 文件。它包括相同的 ProGuard 规则,但还包括其他在字节码一级(方法内和方法间)执行分析的优化,以进一步减小 APK 大小和帮助提高其运行速度。

建议在debug的时候先测试混淆规则是否正确,所以需要在debug模式启用混淆,只需要增加一个debug结点的定义即可:

 buildTypes {
        release {
            minifyEnabled true
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
        debug{
            minifyEnabled true
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }

混淆规则

  • 保留(Keep options)

-keep [,modifier,...] class_specification 保留类和类的成员(字段和方法),常用规则。

  1. 保留类名
#一颗星表示只保持该包下的类名,而子包下的类名还是会被混淆
-keep class com.ms.bean.*
#两颗星表示把本包和所含子包下的类名都保持
-keep class com.ms.bean.** 

使用上面两个规则都只是保持类名不变,类中的方法和成员都会被混淆

  1. 保留类名及类的成员
#保留 com.ms.bean包下的类及类的成员
-keep class com.ms.bean.*{*;}
#保留具体的某个类及类的成员
-keep class com.ms.bean.Person{*;}

如果不希望保留类的所有成员,可以使用:

<init>;     //匹配所有构造器
<fields>;   //匹配所有域
<methods>;  //匹配所有方法方法
# 如保留构造方法
-keep class com.ms.bean.Person{
<init>;
}
# 还可以在<fields>或<methods>前面加上private 、public、native等来进一步指定不被混淆的内容。
#如保留public构造方法
-keep class com.ms.bean.Person{
public <init>;
}
  1. 保留指定类的所有子类(implement/extends)
# 保留Activity的所有子类
-keep public class * extends android.app.Activity

其它保留规则,详见官网

  • 压缩(Optimization options)

#关闭压缩
-dontshrink 
-printusage {filename}
-whyareyoukeeping {class_specification}

更多详情见:地址

  • 优化(Optimization options)

#关闭优化
-dontoptimize  
#迭代优化,n表示proguard对代码进行迭代优化的次数,Android一般为5
-optimizationpasses n 

更多详见:地址

  • 混淆(Obfuscation options)

#关闭混淆
-dontobfuscate 

默认开启,保持默认即可。
更多详见:地址

  • 预校验选项(Preverification options)

-dontpreverify #禁用预校验
-microedition

更多详见:地址

  • 常规选项(General options)

更多详见:地址

混淆的注意事项

ProGuard 难以对许多情况进行正确分析,可能会移除应用真正需要的代码,所以有些代码是需要保留的

  1. 在AndroidManifest中配置的类,比如四大组件
  2. JNI调用的方法
# 保持native方法不被混淆    
-keepclasseswithmembernames class * { 
    native <methods>;
}
  1. 反射用到的类
  2. WebView中JavaScript调用的方法,规则同第2条
-keepclassmembers class fqcn.of.javascript.interface.for.webview {
   public *;
}
  1. Layout文件引用到的自定义View
  2. 与服务端交互时,Json对象
  3. Parcelable的子类和Creator静态成员变量不混淆,否则会产生Android.os.BadParcelableException异常;
# 保持Parcelable不被混淆
-keep class * implements Android.os.Parcelable { 
    public static final Android.os.Parcelable$Creator *;
}
  1. 使用enum类型时需要注意避免以下两个方法混淆,因为enum类的特殊性,以下两个方法会被反射调用,见第3条规则
-keepclassmembers enum * {  
    public static **[] values();  
    public static ** valueOf(java.lang.String);  
}
  • 一些引入的第三方库
    这里推荐两个开源项目,里面收集了一些第三方库的混淆规则
    android-proguard-snippets
    android-proguard-cn
    但是说实话,用处应该不大,仅作参考,毕竟第三方库都应该有混淆说明,并且这些开源库的更新并不及时

以上是在混淆的时候应该避免混淆的,其实想通了是很好理解的,混淆之后对应的类会变成没有具体含义的a,b,c等,哪么在调用的地方就可能无法分清楚具体调用的是哪一个
所以混淆导致调用者无法分清楚具体调用的是哪一个时都应该避免混淆

小技巧:如果在代码中有需要保留的代码,可以对想要保留的部分添加@Keep 注解。在类上添加 @Keep 可原样保留整个类;在方法或字段上添加它可完整保留方法/字段(及其名称)以及类名称。


由于英语水平有限,规则部分只能慢慢的去研究,后续补全规则介绍,并给出在Android中混淆的常规配置。

扩展了解

  • ProGuard的输出文件说明

混淆后,会在 <module-name>/build/outputs/mapping/release(or debug)/目录下输出下面的文件

dump.txt 说明 APK 中所有类文件的内部结构。
mapping.txt 提供原始与混淆过的类、方法和字段名称之间的转换。
seeds.txt 列出未进行混淆的类和成员。
usage.txt 列出从 APK 移除的代码。

当我们需要处理crash log的时候,就可以通过mapping.txt的映射关系找到对应的类,方法,字段等。

mapping.txt的映射关系找到对应的类,方法,字段方法如下:
sdk\tools\proguard\bin 目录下有个retrace工具可以将混淆后的报错堆栈解码成正常的类名window下为retrace.bat,linux和mac为retrace.sh,

使用方法如下:

  1. 将crash log保存为yourfilename.txt
  2. 拿到版本发布时生成的mapping.txt
  3. 执行命令retrace.bat -verbose mapping.txt yourfilename.txt

所以我们每次打包版本都需要保存最新的mapping.txt文件。如果要使用到第三方的crash统计平台,比如bugly,还需要我们上传APP版本对应的mapping.txt.每次都要保存最新的mapping文件,那不就很麻烦?放心,gradle会帮到你,只需要在bulid.gradle加入下面的一句。每次我们编译的时候,都会自动帮你保存mapping文件到本地的。

android {
applicationVariants.all { variant ->
        variant.outputs.each { output ->
            if (variant.getBuildType().isMinifyEnabled()) {
                variant.assemble.doLast{
                        copy {
                            from variant.mappingFile
                            into "${projectDir}/mappings"
                            rename { String fileName ->
                                "mapping-${variant.name}.txt"
                            }
                        }
                }
            }
        }
        ......
    }
}
  • Android项目混淆配置参考

在此之前,开启ProGuard一节提到proguard-android.txt为系统提供的基础混淆配置,文件所在路径为:\sdk\tools\proguard\proguard-android.txt,我门可以找到这个文件先了解一下系统提供了哪些基础配置

通过上述了解,我门对混淆有了一定认识和理解,那么我门可以整理出一个关于Android工程混淆的配置规则(完整版,包括基础配置):

#(Basic 包名不混合大小写
-dontusemixedcaseclassnames
#(Basic)不忽略非公共的库类
-dontskipnonpubliclibraryclasses
#(Basic)输出混淆日志
-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).
#(Basic)不进行优化
-dontoptimize
#(Basic)不进行预检验
-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.

#混淆注意事项第一条,保留四大组件及Android的其它组件
-keep public class * extends android.app.Activity
#(Basic)
-keepclassmembers class * extends android.app.Activity {
   public void *(android.view.View);
}
-keep public class * extends android.app.Application
-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
#(Basic)
-keep public class com.google.vending.licensing.ILicensingService
#(Basic)
-keep public class com.android.vending.licensing.ILicensingService
#(Basic)混淆注意事项第二条,保持 native 方法不被混淆  
-keepclasseswithmembernames class * {
    native <methods>;
}
# 混淆注意事项第四条,保持WebView中JavaScript调用的方法  
-keepclassmembers class * {
    @android.webkit.JavascriptInterface <methods>;
}
#混淆注意事项第五条 自定义View (Basic)
-keepclassmembers public class * extends android.view.View {
   void set*(***);
   *** get*();
}
# (Basic)混淆注意事项第七条,保持 Parcelable 不被混淆  
-keepclassmembers class * implements android.os.Parcelable {
  public static final android.os.Parcelable$Creator CREATOR;
}
#(Basic) 混淆注意事项第八条,保持枚举 enum 类不被混淆  
-keepclassmembers enum * {
    public static **[] values();
    public static ** valueOf(java.lang.String);
}
#(Basic)
-keepclassmembers class **.R$* {
    public static <fields>;
}
#(Basic)保留注解 
-keepattributes *Annotation*
# (Basic)排除警告
-dontwarn android.support.**
# Understand the @Keep support annotation.
# (Basic)不混淆指定的类及其类成员
-keep class android.support.annotation.Keep
# (Basic)不混淆使用注解的类及其类成员
-keep @android.support.annotation.Keep class * {*;}
# (Basic)不混淆所有类及其类成员中的使用注解的方法
-keepclasseswithmembers class * {
    @android.support.annotation.Keep <methods>;
}
# (Basic)不混淆所有类及其类成员中的使用注解的字段
-keepclasseswithmembers class * {
    @android.support.annotation.Keep <fields>;
}
# 不混淆所有类及其类成员中的使用注解的初始化方法
-keepclasseswithmembers class * {
    @android.support.annotation.Keep <init>(...);
}
#保留源文件以及行号 方便查看具体的崩溃信息
-keepattributes SourceFile,LineNumberTable

其中(Basic)代表系统提供的文件(proguard-android.txt)中的基础配置包含的内容,
之前我在网上查别人的配置,就有
#(Basic) -keep public class com.android.vending.licensing.ILicensingService这么一条语句,当然从语法上讲我知道是保留com.android.vending.licensing.ILicensingService这个类,但心里纳闷这是什么鬼,为什么别人知道呢,至少从现在知道系统提供的基本混淆中包含了这句话,也知道出处了。
如果想进一步优化,配置信息请看(proguard-android-optimize.txt)
也可以看我目前在使用的混淆配置信息:https://github.com/SupLuo/MS/blob/master/msproguard/proguard-rules_basic.pro

参考

https://segmentfault.com/a/1190000004461614
https://blog.gmem.cc/proguard-study-note
https://developer.android.google.cn/studio/build/shrink-code.html?hl=zh-cn#shrink-code

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,012评论 4 359
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,589评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 106,819评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,652评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 51,954评论 3 285
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,381评论 1 210
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,687评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,404评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,082评论 1 238
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,355评论 2 241
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,880评论 1 255
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,249评论 2 250
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,864评论 3 232
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,007评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,760评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,394评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,281评论 2 259

推荐阅读更多精彩内容