Android代码混淆

什么是代码混淆

代码混淆就是将代码中的各种元素,如变量,方法,类和包的名字改写成无意义的名字,增加项目反编译后被读懂的难度。
Android代码混淆使用ProGuard工具,ProGuard是一个压缩、优化和混淆Java字节码文件的免费的工具,它可以删除无用的类、字段、方法和属性。

以下是官网对ProGuard的说明:
<code>
ProGuard是一个对Java类文件进行压缩,优化,混淆和校验的工具。
压缩过程查找并删除没有使用到的类,字段,方法和属性。优化过程对方法的字节码进行分析和优化。
混淆过程把剩余的元素名字该写成简短且无意义的名字。这些过程会使程序体积更小,运行更高效,更难被反编译。
最后的校验过程为类增加校验信息,但这个过程依赖J2ME和JDK6或以上的编译环境。
</code>

  • rom编译
    Android.mk文件中,用LOCAL_PROGUARD_ENABLED来配置混淆的模式;LOCAL_PROGUARD_FLAG_FILES用来指定配置文件。LOCAL_PROGUARD_ENABLED的取值如下:
    • full:使用编译系统默认的配置:压缩但不混淆和优化,默认的混淆配置文件是build/core/proguard.flags
    • custom:和full一样,但不包括aapt生成的resource相关的混淆配置。
    • nosystem:不使用系统的默认配置,但使用aapt生成的resource相关的混淆配置,其他混淆由模块自己负责。
    • disabled:关闭混淆
    • obfuscation:和full一样,并且开启混淆
    • optimization:和full一样,并且开启优化
    • 不设置时,如果是app,默认为full,如果是library,则默认为disabled。

编译userdebug版本时,编译脚本会把app的obfuscation改成full,即不混淆;所以userdebug版本的app是不混淆的。想了解更多信息,可以自行阅读project_src/build/core/下的java.mk,package_internel.mk,java_library.mk,proguard.flags,proguard_base_keeps.flags等文件。

  • Android Studio
    项目目录下的build.gradle文件中minifyEnabled设置为true为开启,false为关闭;proguardFiles用来指定混淆配置文件。使用Build菜单下的Generate Signed APK进行打包即可。记得在Build Type:选项下选择release,否则只打包不会混淆。

  • Eclipse
    项目目录下的project.properties文件中添加配置即可开启混淆:proguard.config=xxx,xxx为混淆配置文件路径,多个配置文件用:分隔。 然后Export APK就可以了,注意直接运行程序生成的安装包是没有经过混淆的。

如何使用混淆

理想的目标是将所有元素都加入混淆,但混淆会另反射无法工作。因此反射以及反射延伸出来的功能使用到的元素都不能混淆。
因为Android开发中有些内容每次都要配置,所以sdk中提供了一份默认配置文件,我们新建项目时可以复制或引用sdk下的默认配置,在此基础上再增加自己的需求。默认配置文件在android_sdk/tools/proguard/proguard-android.txt。

下面介绍一些常用配置以及Android开发中哪些元素不应该混淆。常用配置:

  • -keep
    keep用来指定哪些元素不进行混淆,它有很多变种,比如:
  • -keep 保留指定的包,类和类成员不被混淆。
  • -keepclassmembers 保留指定的类成员不被混淆,但包名类名会被混淆。
  • -keepclasseswithmembers 保留指定的类成员及其类不被混淆。
    当未配置-dontshrink(该配置是关闭压缩功能,也就是不会删除未使用的元素,未配置时,也即是开启压缩功能)时,以上3个配置指定的元素即使未使用过,也不会被删除。 以下3个命令与以上3个命令对应,区别是在上述情况中,指定的元素未使用过就会被删除。
  • -keepnames 也可以写成-keep,allowshrinking
  • -keepclassmembernames 也可以写成-keepclassmembers,allowshrinking
  • -keepclasseswithmembernames 也可以写成-keepclasseswithmembers,allowshrinking
    示例:
    保留Util类名,但内部成员会被混淆
    <code>
    -keep public class com.test.proguard.util.Util
    </code>
    保留Util类名及其内部成员
    <code>
    -keep public class com.test.proguard.util.Util {;}
    </code>
    保留util包及其下级包的类和内部成员
    <code>
    -keep public class com.test.proguard.util.
    * {;}
    </code>
    保留第三方lib库及继承自第三方的类:
    <code>
    ======= Sina Weibo SDK =========
    -dontwarn com.sina.
    *
    -keep class com.sina.{;}
    -keep interface com.sina.
    {;}
    -keep public class * extends com.sina.**
    </code>
    保留util包下的所有类成员不被混淆,但包名类名会被混淆
    <code>
    -keepclassmembers public class com.test.proguard.util.** {*;}
    </code>
    保留所有名为showText并且是public void的方法不被混淆
    <code>
    -keepclassmembers class * {
    public void showText(...);
    }
    </code>
    保留Serializable的所有子孙类中所有的private String的属性。
    <code>
    -keepclassmembers class * extends java.io.Serializable {
    private java.lang.String *;
    }
    </code>
    保留Serializable的所有子孙类中所有的private String的属性以及该类名。
    <code>
    -keepclasseswithmembers class * extends java.io.Serializable {
    private java.lang.String *;
    }
    </code>
  • -dontwarn
    dontwarn和keep可以说是形影不离,尤其是处理引入的lib库时.引入的lib库可能存在一些无法找到的引用和其他问题,在build时可能会发出警告,如果我们不进行处理,通常会导致build中止.因此为了保证build继续,我们需要使用dontwarn忽略这些我们无法解决的lib库的警告.
    示例:
    忽略com.google.zxing包相关的警告
    <code>
    -dontwarn com.google.zxing.**
    </code>

  • 其他配置

  • -dontshrink 不压缩,作用于全局
  • -dontoptimize 不优化,作用于全局
  • -dontobfuscate 不混淆,作用于全局
  • -dontwarn 忽略所有警告,使混淆不会因为警告而停止运行,但会打印警告信息
  • -useuniqueclassmembernames 类和成员都使用唯一的名字,如果没有这个选项,会有很多变量或方法或类名都叫‘a’,‘b’
  • -dontusemixedcaseclassnames 不使用大小写混合类名
  • -verbose 混淆过程中打印更多信息,如果因为异常停止混淆,则会输出stack trace,而不仅仅是异常信息
  • -keepattributes [attribute_filter] Class文件中包含一些与运行无关的信息,比如SourceFile(从哪个源文件编译而来),SourceDir(源文件的文件目录),LineNumberTable(代码行),Exceptions,InnerClasses,Signature,Deprecated,Annotation等等,混淆过程会默认移除掉这些信息,但可以用keepattributes来指定保留那类信息,比如-keepattributes SourceFile,LineNumberTable可以保留代码行和源文件信息。
  • -include 引入其他的配置文件

更多用法可以参考官方文档:https://www.guardsquare.com/en/proguard/manual/usage

不应该混淆的元素

  • 需要反射的元素
    由于反射是通过元素名字来查找的,因此当名字改写后,无法找到目标,会导致出现ClassNotFoundException,NoSuchFiledException,NoSuchMethodException等异常。
    例如如下代码会抛出ClassNotFoundException:
    <code>
    try {
    String str = "com.test.proguard.util.Util";
    Class clazz = Class.forName(str);
    Object object = clazz.newInstance();
    } catch (Exception e) {
    e.printStackTrace();
    }
    </code>
    有趣的是,上面这段代码如果改写成下面这样,则会顺利找到Util类:
    <code>
    try {
    Class clazz = Class.forName("com.test.proguard.util.Util");
    Object object = clazz.newInstance();
    } catch (Exception e) {
    e.printStackTrace();
    }
    </code>
    这两段代码的区别在于forName传入的参数是常量还是变量,传入常量的调用方式被ProGuard混淆处理了,所以可以正常运行。
    ProGuard还对其他一些反射用法进行了处理。例如:
    <code>
    Class.forName("SomeClass")
    SomeClass.class
    SomeClass.class.getField("someField")
    SomeClass.class.getDeclaredField("someField")
    SomeClass.class.getMethod("someMethod", new Class[] {})
    AtomicIntegerFieldUpdater.newUpdater(SomeClass.class, "someField")
    </code>

更多信息请查看官网:https://www.guardsquare.com/en/proguard/manual/introduction
以下列出的内容都是反射延伸出来的用法,同样不能进行混淆。

  • 枚举:Enum.valueOf(String)用到反射,不能混淆
  • 四大组件:四大组件必须在manifest中注册,混淆后类名被改写将无法被找到,会抛出异常。
  • aidl:aidl
  • GSON:GSON是一个利用反射进行序列化的第三方lib库。
  • 实现Parcelable接口的可序列化类:进程间通信的话,要保证两端类名相同,进程内传递时反序列化时需要反射CREATOR对象。
  • 注解:很多场景下注解被用作在运行时反射来确定一些元素的特征。
  • 自定义View
  • native方法
  • jni调用的java方法
  • js调用的java方法

如何恢复被混淆的trace

Proguard进行混淆时会生成一个映射表,文件名是mapping.txt,通过sdk下的retrace.sh脚本和mapping.txt就可以把混淆的trace恢复到原来的样子

示例:
trace.txt文件:
<code>
java.lang.Exception
at com.test.proguard.a.b.a(Util.java:39)
at com.test.proguard.a.a.a(TestStart.java:14)
at com.test.proguard.MainActivity.a(MainActivity.java:32)
at com.test.proguard.MainActivity.a(MainActivity.java:31)
at com.test.proguard.b.onClick(MainActivity.java:26)
at android.view.View.performClick(View.java:5217)
at android.view.View$PerformClick.run(View.java:21278)
at android.os.Handler.handleCallback(Handler.java:739)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:148)
at android.app.ActivityThread.main(ActivityThread.java:5547)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:935)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:726)
</code>
运行命令:
<code>
./retrace.sh ~/mapping.txt ~/trace.txt
</code>
输出:
<code>
java.lang.Exception
at com.test.proguard.util.Util.showText(Util.java:39)
at com.test.proguard.util.TestStart.start(TestStart.java:14)
at com.test.proguard.MainActivity.test(MainActivity.java:32)
at com.test.proguard.MainActivity.access$0(MainActivity.java:31)
at com.test.proguard.MainActivity$1.onClick(MainActivity.java:26)
at android.view.View.performClick(View.java:5217)
at android.view.View$PerformClick.run(View.java:21278)
at android.os.Handler.handleCallback(Handler.java:739)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:148)
at android.app.ActivityThread.main(ActivityThread.java:5547)
at java.lang.reflect.Method.invoke(Method.java)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:935)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:726)
</code>

常见问题

1.反射导致找不到类、方法、属性
当反射时抛出:ClassNotFoundException,NoSuchMethodException,NoSuchFieldException时请检查反射目标是否被混淆了。

2.进程间通信传递Parcelable序列化类时报异常
<code>
java.lang.RuntimeException: Unable to start activity ComponentInfo{com.test.parcel/com.test.parcel.MainActivity}:
android.os.BadParcelableException: ClassNotFoundException when unmarshalling: com.test.model.Student
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2514)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2575)
at android.app.ActivityThread.access$900(ActivityThread.java:160)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1415)
</code>
原因:序列化类被混淆后,与另一端的序列化类名称匹配不上,导致抛出ClassNotFoundException异常。
解决:序列化类不应该被混淆。

注意,在Android7.0上Parcelable类的keep需要跟之前的不一样,如下的做法很常见(android本身在proguard_basic_keeps.flags中也是这样写的):
<code>
// Parcelable CREATORs must be kept for Parcelable functionality
-keep class * implements android.os.Parcelable {
public static final ** CREATOR;
}
</code>
但是这样的写法在Android7上不管用。需要如下写法:
<code>
-keepclasseswithmembers class * implements android.os.Parcelable {*;}
</code>
或者:
<code>
-keepclassmembers class * implements android.os.Parcelable {
public static <fields>;
}
</code>

3.Intent传递Parcelable序列化类时报异常
<code>
java.lang.RuntimeException: Unable to start service com.smartisan.feedbackhelper.upload.ReliableUploader@431b6290
with Intent { cmp=com.smartisan.gamestore/com.smartisan.feedbackhelper.upload.ReliableUploader (has extras) }:
android.os.BadParcelableException: Parcelable protocol requires a Parcelable.Creator object called CREATOR
on class com.smartisan.feedbackhelper.utils.e
</code>
原因:序列化类被混淆后,CREATOR对象变量名被改写,无法被找到,导致抛出异常。
解决:序列化类不应该被混淆。

4.aidl相关类不应该混淆
<code>
Parcel : **** enforceInterface() expected 'com.xy.bizport.service.aidl.IXyRemoteCallable' but read 'com.xy.bizport.a.a.a'
</code>
原因:混淆后两端类名无法匹配,导致异常。

5.js和java不能互相调用,提示找不到方法
原因:混淆后方法名无法匹配。
解决:增加如下配置:
<code>
-keepattributes Annotation, JavascriptInterface
</code>
-keepattributes 建议只写一行,因为在odin上配置-keepattributes时,前面的会被后面的覆盖。

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

推荐阅读更多精彩内容