编程开源技术交流,分享技术与知识

网站首页 > 开源技术 正文

Android混淆相关(apk混淆)

wxchong 2024-10-14 17:43:38 开源技术 19 ℃ 0 评论

前言

在以前没有应用加固的情况下,Android开发的应用为了防止代码被完全暴露在外面从而泄露隐私等问题的发生就对代码采用混淆的机制。那么混淆被称作什么呢?混淆被称作花指令,即是将计算机程序的代码,转换成一种功能上等价,但是难于阅读和理解的形式的行为,其实就是将代码中的各种元素,如变量,函数,类的名字改写成无意义的名字。比如改写成单个字母,或是简短的无意义字母组合,甚至改写成“__”这样的符号,使得阅读的人无法根据名字猜测其用途。这种虽然对代码有一定的保护,但是实际上还是整体的暴露了出来。因为混效过后还是将代码逻辑暴露出来了,只是读起来比较费劲而已,所以后面才出现了加固的功能。即使现在出现了加固来提升代码的安全性,也就不能说明混淆是没用的,其实混淆还是有一定的作用的。比如减小apk的大小这也是一个有效的途径,apk的大小减小了对用户来说也是非常有用的,具体原因自己体会。

混淆影响的元素

上面已经说了一部分,这里整体的罗列一下。

  1. 类名

  2. 变量名

  3. 方法名

  4. 资源文件名

  5. 包名

  6. 注解

  7. 其他相关

其实混淆影响的还是很多的,基本上项目里面所有的文件都涉及到。其中混淆的目的也是非常的清楚,就是加大反编译的难度,使其代码能够以一种比较安全的形式暴露在外面。

Android中混淆的开启

现在Android开发基本上都是基于AndroidStudio,所以这里也是基于AndroidStudio讲解的。在Android项目的app下的build.gradle文件里面一般都是第三方依赖和项目整体配置的入口,在该文件中有一个buildType配置,默认只有一个release。release里面有两个属性,分别是minifyEnabled和proguardFiles,第一个参数就是开启混淆的钥匙,第二个参数就是混淆的配置文件。当minifyEnabled为true的时候就表示开启混淆,为false的时候表示不开启混淆。开启混淆候你可以定义混淆规则,该规则就在proguardFiles指向的文件里面,你也可以指向自己添加的文件来配置混淆参数。

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

proguard的意思其实就是一个集文件压缩,优化,混淆和校验等功能的工具,它用来检测并删除没用的类,变量和方法;删除代码里面的指令(比如注释);优化字节码(以重命名的方式命名类名,方法名和变量名等);校验处理后的代码。

混淆的常见配置

  • -keep命令

    -kepp是用来保留相关的数据不被混淆,从-keep命令可以延伸出几个关于-kepp的命令。比如-keep本身,-keppclassmembers,-keppclasswithmembers。具体的使用方法如下:

  1. 针对某个包下面的类以及子包不被混淆

    -keep class com.holosense.pmsclient.db.entities.**
  2. 针对所有类中使用webView的public方法不被混淆

    -keepclassmembers class fqcn.of.javascript.interface.for.webview {
     public *;
    }
  3. 针对所有含有native方法的类的类名和native方法名不被混淆

    -keepclasseswithmember class * {
     native <methods>;
    }

三者的区别为:就统一拿一个例子来说

针对整个类不被混淆,而不会管类中的属性和方法
-keep class * {
 public <methods>;
}
针对类中的公有方法不被混淆,其他的还是会混淆,如类名,属性等
-keepclassmembers class * {
 public <methods>;
}
针对类名和类中的公有方法不被混淆,其他还是会混淆
-keepclasseswithmember class * {
 public <methods>;
 }
  • -dontwarn命令

-dontwarn命令和-keep命令基本上是伴随着的,特别是在引入其他的library的时候。主要是处理一些警告的,这些警告主要是来自于其他的library中有可能找不到的类或者引用(如资源文件的引用)和还有一些其他问题。我们在混淆时,加入-dontwarn命令防止build构建时出现太多的警告而出现终止的情况。

如我们关闭okhttp3出现的警告
-dontwarn okhttp3.**

针对Android项目中那些应该不被混淆

  1. 四大组件,为什么四大组件不应该被混淆,因为四大组件都得在AndroidManifest.xml中注册,如果混淆过后就会出现NoSuchFiledException异常。这个不被混淆的原因都应该懂。

  1. 反射不能被混淆,因为混淆就会改变类名,方法名和属性名。导致反射获取时会抛出NoSuchMethodException或者NoSuchFiledException异常。

  2. 注解其实注解是可以混淆的,但是这里为什么这样说呢?一般在项目中使用注解都是在运行时使用,为了保证能够正常工作,我们不应该对注解进行混淆。

  3. 序列化与反序列化不应该被混淆。比如说Gson的序列化与反序列化就是如此,混淆过后属性名称都变了,就会导致出现异常。

  4. 枚举类型的变量不应该被混淆,因为枚举内部的实现是通过相关的方法映射来的,如果方法名改变了导致获取不到相应的值或者相应的方法等异常。

  5. java的native方法不能被混淆,大家都知道在ndk开发阶段,java调用底层的C语言的时候会导致调用出错,同理被jni方法调用java方法的也不能被混淆

  6. js与java互调的方法不能被混淆,因为当你在js中写好某些调用方法时都已经被固定了,如果混淆导致方法名变了而导致调用不成功。

  7. 自定义view和viewgroup,还有Android SDK自带的的library不能被混淆,因为很多都涉及到xml的映射,从而导致报错。

这里基本上都涉及到关于混淆的基本知识,其实还有一部分关于生成混淆的配置。比如说压缩等级(就是混淆程度),是否生成mapping映射等。由于自己的能力有限,部分知识准备得还不充分,待后续更新。下面是我项目中的一个混淆模板,可供大家参考:

  1. 基本的指令

    -optimizationpasses 5 # 代码混淆压缩比,在0和7之间,默认为5,一般不需要改
    -dontusemixedcaseclassnames # 混淆时不使用大小写混合,混淆后的类名为小写
    -dontskipnonpubliclibraryclasses # 指定不去忽略非公共的库的类
    -dontskipnonpubliclibraryclassmembers # 指定不去忽略非公共的库的类的成员
    -dontpreverify # 不做预校验,preverify是proguard的4个步骤之一,Android不需要preverify,去掉这一步可加快混淆速度
    -verbose # 有了verbose这句话,混淆后就会生成映射文件,包含有类名->混淆后类名的映射关系,然后使用printmapping指定映射文件的名称
    -printmapping proguardMapping.txt #输出mapping.txt,文件路径在app/build/outputs/mapping/..
    -optimizations !code/simplification/cast,!field/*,!class/merging/* #指定混淆时候采用的算法,后面的参数是一个过滤器,这个过滤器是谷歌推荐的算法,一般不做更改
    -keepattributes *Annotation*,InnerClasses #保留注解和内部类
    -keepattributes Signature #保留泛型
    -keepattributes EnclosingMethod #保留反射
    -keepattributes SourceFile,LineNumberTable ##抛出异常时保留代码行号
  2. 默认保留的部分

    # 保留了继承自Activity、Application这些类的子类
    # 因为这些子类,都有可能被外部调用
    # 比如说,第一行就保证了所有Activity的子类不要被混淆
    -keep public class * extends android.app.Activity
    -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
    -keep public class * extends android.view.View
    -keep public class com.android.vending.licensing.ILicensingService
    # 如果有引用android-support-v4.jar包,可以添加下面这行
    -keep class android.support.** {*;}
    -keep public class * extends android.support.v4.view {*;}
  3. 针对部分类中默认保留的部分

    # 保留所有的本地native方法不被混淆
    -keepclasseswithmembernames class * {
     native <methods>;
    }
    # 保留在Activity中的方法参数是view的方法,
    # 从而我们在layout里面编写onClick就不会被影响
    -keepclassmembers class * extends android.app.Activity{
     public void *(android.view.View);
    }
    # 枚举类不能被混淆
    -keepclassmembers enum * {
     public static **[] values();
     public static ** valueOf(java.lang.String);
    }
    # 保留自定义控件(继承自View)不被混淆
    -keep public class * extends android.view.View{
     *** get*();
     void set*(***);
     public <init>(android.content.Context);
     public <init>(android.content.Context, android.util.AttributeSet);
     public <init>(android.content.Context, android.util.AttributeSet, int);
    }
    # 保留自定义控件(继承自View或者自定义容器)不被混淆
    -keepclasseswithmembers class * {
     public <init>(android.content.Context, android.util.AttributeSet);
     public <init>(android.content.Context, android.util.AttributeSet, int);
    }
    # 保留Parcelable序列化的类不被混淆
    -keep class * implements android.os.Parcelable {
     public static final android.os.Parcelable$Creator *;
    }
    # 保留Serializable序列化的类不被混淆
    -keepclassmembers class * implements java.io.Serializable {
     static final long serialVersionUID;
     private static final java.io.ObjectStreamField[] serialPersistentFields;
     private void writeObject(java.io.ObjectOutputStream);
     private void readObject(java.io.ObjectInputStream);
     java.lang.Object writeReplace();
     java.lang.Object readResolve();
    }
    # 对于R(资源)下的所有类及其方法,都不能被混淆
    -keep class **.R$* {
     *;
    }
    # 对于带有回调函数onXXEvent的,不能被混淆,针对eventbus和Rxbus等事件相关的
    -keepclassmembers class * {
     void *(**On*Event);
    }
  4. 第三方的jar包或者aar文件不被混淆

    # httpClient jar包
    -dontwarn org.apache.**
    -keep class org.apache.** { *;}
    # fastjson jar包
    -dontwarn com.alibaba.fastjson.**
    -keep class com.alibaba.fastjson.** {*;}
    # RootShell
    -keep class com.stericson.** {*;}
    -dontwarn com.stericson.**
    # sqlcipher
    -dontwarn net.sqlcipher.**
    -keep class net.sqlcipher.** {*;}
    -dontwarn net.sqlcipher.database.**
    -keep class net.sqlcipher.database.** {*;}
  5. 第三方的依赖

    # ButterKnife
    -keep public class * implements butterknife.Unbinder {
     public <init>(**, android.view.View);
    }
    -keep class butterknife.*
    -keepclasseswithmembernames class * {
     @butterknife.* <methods>;
    }
    -keepclasseswithmembernames class * {
     @butterknife.* <fields>;
    }
    # Glide
    -keep public class * implements com.bumptech.glide.module.GlideModule
    -keep public enum com.bumptech.glide.load.resource.bitmap.ImageHeaderParser$** {
     **[] $VALUES;
     public *;
    }
    # Gson
    -keepattributes Signature
    -keepattributes *Annotation*
    -keep class sun.misc.Unsafe { *; }
    -keep class com.google.gson.stream.** { *; }
    -keep class com.holosense.canteen.network.response.** { *; }
    # OkHttp
    -dontwarn okio.**
    -dontwarn okhttp3.**
    -dontwarn javax.annotation.Nullable
    -dontwarn javax.annotation.ParametersAreNonnullByDefault
    # Okio
    -dontwarn com.squareup.**
    -dontwarn okio.**
    -keep public class org.codehaus.* { *; }
    -keep public class java.nio.* { *; }
    # Retrofit
    -keep class retrofit2.** { *; }
    -dontwarn retrofit2.**
    -keepattributes Signature
    -keepattributes Exceptions
    -dontwarn okio.**
    -dontwarn javax.annotation.**
    # RxJava 1 RxAndroid 2 .注意rx 2是支持混淆的。不必配置
    -dontwarn sun.misc.**
    -keepclassmembers class rx.internal.util.unsafe.*ArrayQueue*Field* {
     long producerIndex;
     long consumerIndex;
    }
    -keepclassmembers class rx.internal.util.unsafe.BaseLinkedQueueProducerNodeRef {
     rx.internal.util.atomic.LinkedQueueNode producerNode;
    }
    -keepclassmembers class rx.internal.util.unsafe.BaseLinkedQueueConsumerNodeRef {
     rx.internal.util.atomic.LinkedQueueNode consumerNode;
    }
    -dontnote rx.internal.util.PlatformDependent
    # Jackson
    -dontwarn org.codehaus.jackson.**
    -dontwarn com.fasterxml.jackson.databind.**
    -keep class org.codehaus.jackson.** { *;}
    -keep class com.fasterxml.jackson.** { *; }
    # storio
    -dontwarn com.pushtorefresh.storio.**
    # MPAndroidChart
    -keep class com.github.mikephil.charting.** { *; }
    # edmodo
    #-dontwarn com.theartofdev.edmodo.cropper.R$id
    #If you don't use the missing resources, or something like
    -keepclassmembers class com.theartofdev.edmodo.cropper.R$id {
     public static <fields>;
    }
  6. 项目内部部分文件不被混淆

    -keep class com.aaa.xxx.** { *; } #针对数据库实体类和数据库操作类及内部类不被混淆
    -keep class com.aaa.xxx.** { *; } #针对网络处理相关类及内部类不被混淆

如有想深入了解的可以点击这个链接

总结

针对上述的说明可以得出其实混淆其实也不是那么的难,只要相关指令配好,了解几个命令就OK。如果你的项目是加了固的。也不要以为混淆就没必要了,其实也是很有必要的。就单单压缩代码,减少apk体积这一项优点就已经足够了。注意在调试阶段不要开启混淆。不然调试的时候会令你很头痛的,因为你调试模式开启时,你就会发现你定义的xxx变量怎么没有了,怎么会多出abcd这些东西。

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表