android代码混淆恢复,Android代码混淆

什么是代码混淆

代码混淆就是将代码中的各种元素,如变量,方法,类和包的名字改写成无意义的名字,增加项目反编译后被读懂的难度。

Android代码混淆使用ProGuard工具,ProGuard是一个压缩、优化和混淆Java字节码文件的免费的工具,它可以删除无用的类、字段、方法和属性。

以下是官网对ProGuard的说明:

ProGuard是一个对Java类文件进行压缩,优化,混淆和校验的工具。

压缩过程查找并删除没有使用到的类,字段,方法和属性。优化过程对方法的字节码进行分析和优化。

混淆过程把剩余的元素名字该写成简短且无意义的名字。这些过程会使程序体积更小,运行更高效,更难被反编译。

最后的校验过程为类增加校验信息,但这个过程依赖J2ME和JDK6或以上的编译环境。

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类名,但内部成员会被混淆

-keep public class com.test.proguard.util.Util

保留Util类名及其内部成员

-keep public class com.test.proguard.util.Util {;}

保留util包及其下级包的类和内部成员

-keep public class com.test.proguard.util.* {;}

保留第三方lib库及继承自第三方的类:

======= Sina Weibo SDK =========

-dontwarn com.sina.*

-keep class com.sina.{;}

-keep interface com.sina.{;}

-keep public class * extends com.sina.**

保留util包下的所有类成员不被混淆,但包名类名会被混淆

-keepclassmembers public class com.test.proguard.util.** {*;}

保留所有名为showText并且是public void的方法不被混淆

-keepclassmembers class * {

public void showText(...);

}

保留Serializable的所有子孙类中所有的private String的属性。

-keepclassmembers class * extends java.io.Serializable {

private java.lang.String *;

}

保留Serializable的所有子孙类中所有的private String的属性以及该类名。

-keepclasseswithmembers class * extends java.io.Serializable {

private java.lang.String *;

}

-dontwarn

dontwarn和keep可以说是形影不离,尤其是处理引入的lib库时.引入的lib库可能存在一些无法找到的引用和其他问题,在build时可能会发出警告,如果我们不进行处理,通常会导致build中止.因此为了保证build继续,我们需要使用dontwarn忽略这些我们无法解决的lib库的警告.

示例:

忽略com.google.zxing包相关的警告

-dontwarn com.google.zxing.**

其他配置

-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 引入其他的配置文件

不应该混淆的元素

需要反射的元素

由于反射是通过元素名字来查找的,因此当名字改写后,无法找到目标,会导致出现ClassNotFoundException,NoSuchFiledException,NoSuchMethodException等异常。

例如如下代码会抛出ClassNotFoundException:

try {

String str = "com.test.proguard.util.Util";

Class clazz = Class.forName(str);

Object object = clazz.newInstance();

} catch (Exception e) {

e.printStackTrace();

}

有趣的是,上面这段代码如果改写成下面这样,则会顺利找到Util类:

try {

Class clazz = Class.forName("com.test.proguard.util.Util");

Object object = clazz.newInstance();

} catch (Exception e) {

e.printStackTrace();

}

这两段代码的区别在于forName传入的参数是常量还是变量,传入常量的调用方式被ProGuard混淆处理了,所以可以正常运行。

ProGuard还对其他一些反射用法进行了处理。例如:

Class.forName("SomeClass")

SomeClass.class

SomeClass.class.getField("someField")

SomeClass.class.getDeclaredField("someField")

SomeClass.class.getMethod("someMethod", new Class[] {})

AtomicIntegerFieldUpdater.newUpdater(SomeClass.class, "someField")

枚举: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文件:

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)

运行命令:

./retrace.sh ~/mapping.txt ~/trace.txt

输出:

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)

常见问题

1.反射导致找不到类、方法、属性

当反射时抛出:ClassNotFoundException,NoSuchMethodException,NoSuchFieldException时请检查反射目标是否被混淆了。

2.进程间通信传递Parcelable序列化类时报异常

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)

原因:序列化类被混淆后,与另一端的序列化类名称匹配不上,导致抛出ClassNotFoundException异常。

解决:序列化类不应该被混淆。

注意,在Android7.0上Parcelable类的keep需要跟之前的不一样,如下的做法很常见(android本身在proguard_basic_keeps.flags中也是这样写的):

// Parcelable CREATORs must be kept for Parcelable functionality

-keep class * implements android.os.Parcelable {

public static final ** CREATOR;

}

但是这样的写法在Android7上不管用。需要如下写法:

-keepclasseswithmembers class * implements android.os.Parcelable {*;}

或者:

-keepclassmembers class * implements android.os.Parcelable {

public static ;

}

3.Intent传递Parcelable序列化类时报异常

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

原因:序列化类被混淆后,CREATOR对象变量名被改写,无法被找到,导致抛出异常。

解决:序列化类不应该被混淆。

4.aidl相关类不应该混淆

Parcel : **** enforceInterface() expected 'com.xy.bizport.service.aidl.IXyRemoteCallable' but read 'com.xy.bizport.a.a.a'

原因:混淆后两端类名无法匹配,导致异常。

5.js和java不能互相调用,提示找不到方法

原因:混淆后方法名无法匹配。

解决:增加如下配置:

-keepattributes Annotation, JavascriptInterface

-keepattributes 建议只写一行,因为在odin上配置-keepattributes时,前面的会被后面的覆盖。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值