赞
踩
Android代码混淆,又称Android混淆,是伴随着Android系统的流行而产生的一种APP保护技术,用于保护APP不被破解和逆向分析。
在Android的具体表现就是打包时,将项目里的包名、类名、变量名根据混淆规则进行更改,使反编译工具反编译出来的代码人难以阅读,从而达到防止被逆向破解的目的。
在Android里面,由于AndroidStudio
集成了ProGuard,因此我们最常用,最简单的混淆是ProGuard混淆。
ProGuard混淆主要包括有四个功能:
综上我们可以总结出混淆的两大作用:
其实混淆还有另一个妙用,也是今天我们重点要说的:
不同的混淆规则可以编译完全不同的代码,降低代码重复率,用于制作马甲包。
在主模块的build.gradle中添加如下代码,即可开启混淆。
android{ //... buildTypes { release { debuggable false //是否debug jniDebuggable false // 是否打开jniDebuggable开关 minifyEnabled true //代码压缩,混淆 shrinkResources true //资源压缩 zipAlignEnabled true //压缩优化 proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' signingConfig signingConfigs.release //签名 } debug { signingConfig signingConfigs.release //签名 } } //... }
配置混淆开关的是minifyEnabled。
proguardFiles用于指定混淆规则,自动使用默认的混淆规则,而我们可以在proguard-rules.pro
中自定义自己的混淆规则。
常见的混淆规则应该主要包含如下四个部分:
混淆的详细规则可以参考:ProGuard的官方文档
混淆指令的详细解释可以参考:混淆必知必会
指定一些包名、类名、变量等不可以被混淆。假设没指定白名单就进行混淆打包,而某某类的类名被混淆了(假设变成了a),那么可能其他引用或使用该类的类就找不到该类,说不定应用就会因此崩溃或是导致相应的功能无法使用。
比如我们通过反射调用了一个类,如果该类打包时被混淆,那么运行时必定会找不到该类i,进而导致App异常。
proguard-rules.pro
中配置完常用混淆后,我们可以打一个release包。(每次混淆修改后,打包前注意clean)
打包成功后在AndroidStudio中双击打开apk,点击classes.dex
就可以看到混淆后的代码,包名,类名,方法都变成了近似于“乱码”。
混淆后虽然增强了App的安全性,上线后同时也会导致一些“副作用”:
我们可以在proguard-rules.pro
中添加如下代码,混淆后,就会在指定文件中输入混淆信息。
这对于我们排查线上问题有很大帮助,诸如bugly等异常上报平台,都可以上传mapping.txt
帮助在错误信息中定位代码。
-printseeds proguardbuild/print_seeds.txt #未混淆的类和成员
-printusage proguardbuild/print_unused.txt #列出从 apk 中删除的代码
-printmapping proguardbuild/print_mapping.txt #混淆前后的映射,生成映射文件
实际上基本混淆配置完只要不去修改,相同代码每次打包得到的代码都是一样的。
对于单包常规开发来说,这是没有问题的。
但是实际上绝大多数App都需要马甲包去增加搜索关键字,进而达到引流效果。
然而马甲包上架有一个始终无法回避问题:代码相似度,在审核严格的平台,代码相似度过高甚至会导致主包下架,造成无法挽回的损失。
降低代码相似度,常规来说有两个方案:
如何通过混淆来解决这个问题呢?核心混淆规则如下:
# 指定一个文本文件,其中所有有效字词都用作混淆字段和方法名称。
# 默认情况下,诸如“a”,“b”等短名称用作混淆名称。
# 使用模糊字典,您可以指定保留关键字的列表,或具有外来字符的标识符,
# 例如: 忽略空格,标点符号,重复字和#符号后的注释。
# 注意,模糊字典几乎不改善混淆。 有些编译器可以自动替换它们,并且通过使用更简单的名称再次混淆,可以很简单地撤消该效果。
# 最有用的是指定类文件中通常已经存在的字符串(例如'Code'),从而减少类文件的大小。 仅适用于混淆处理。
-obfuscationdictionary proguardbuild/pro_package.txt
# 指定一个文本文件,其中所有有效词都用作混淆类名。 与-obfuscationdictionary类似。 仅适用于混淆处理。
-classobfuscationdictionary proguardbuild/pro_class.txt
# 指定一个文本文件,其中所有有效词都用作混淆包名称。与-obfuscationdictionary类似。 仅适用于混淆处理。
-packageobfuscationdictionary proguardbuild/pro_func.txt
简单来说我们可以指定一个txt文件作为混淆字典,混淆过程中修改的包名,类,方法都取自于该字典。只要我们每个马甲包的字典不一致,我们自然能得到代码相似度低的马甲包。
你可以简单粗暴的Copy中的别人的混淆字典:丧心病狂的Android混淆文件生成器
打包出来的效果如下:
Tips:相同的混淆字典打包出来的代码仍是一致的,因此你每个马甲包都应该使用不同的混淆字典。
如果你有几百个马甲包,Copy别人的迟早“山穷水尽”。
作为程序员,这些都不应该难到我们。Python脚本随机生成就是啦~
import random import string import os ''' 脚本生成混淆字典 ''' //生产8000个不重复的字符串 totalNum = 8000 def main(): dir = "../app/proguardbuild" createProRules(dir+'/pro_package.txt') createProRules(dir+'/pro_class.txt') createProRules(dir+'/pro_func.txt') def createProRules(fileName): dirName = os.path.dirname(fileName) if not os.path.exists(dirName): os.mkdir(dirName) ''' 生成totalNum个随机不重复的字符串 :return: ''' new_list = [] while 1: value = ''.join(random.sample(string.ascii_letters + string.digits, random.randint(1, 8))) //生成1~8位数随机字符串 if value not in new_list: new_list.append(value) if len(new_list) == totalNum: break else: continue result = '\n'.join(new_list) file = open(fileName, 'w', encoding='UTF-8') file.write(result) file.close() if __name__ == '__main__': main()
使用Python脚本生成混淆字典后,打包效果如下:
至此,你再也不用效率低下的去改代码,也不用生成一堆垃圾代码让“强迫症”难受。
我的混淆模板,仅供参考~
# ------------------------------基本指令区--------------------------------- -optimizationpasses 5 #指定压缩级别 -optimizations !code/simplification/arithmetic,!field/*,!class/merging/* #混淆时采用的算法 -verbose #打印混淆的详细信息 -dontoptimize #关闭优化 -keepattributes *Annotation* #保留注解中的参数 -keepattributes *Annotation*,InnerClasses # 保持注解 -keepattributes Signature # 避免混淆泛型, 这在JSON实体映射时非常重要 -ignorewarnings # 屏蔽警告 -keepattributes SourceFile,LineNumberTable # 抛出异常时保留代码行号 #混淆时不使用大小写混合,混淆后的类名为小写(大小写混淆容易导致class文件相互覆盖) -dontusemixedcaseclassnames #未混淆的类和成员 -printseeds proguardbuild/print_seeds.txt #列出从 apk 中删除的代码 -printusage proguardbuild/print_unused.txt #混淆前后的映射,生成映射文件 -printmapping proguardbuild/print_mapping.txt # 指定一个文本文件,其中所有有效字词都用作混淆字段和方法名称。 # 默认情况下,诸如“a”,“b”等短名称用作混淆名称。 # 使用模糊字典,您可以指定保留关键字的列表,或具有外来字符的标识符, # 例如: 忽略空格,标点符号,重复字和#符号后的注释。 # 注意,模糊字典几乎不改善混淆。 有些编译器可以自动替换它们,并且通过使用更简单的名称再次混淆,可以很简单地撤消该效果。 # 最有用的是指定类文件中通常已经存在的字符串(例如'Code'),从而减少类文件的大小。 仅适用于混淆处理。 -obfuscationdictionary proguardbuild/pro_package.txt # 指定一个文本文件,其中所有有效词都用作混淆类名。 与-obfuscationdictionary类似。 仅适用于混淆处理。 -classobfuscationdictionary proguardbuild/pro_class.txt # 指定一个文本文件,其中所有有效词都用作混淆包名称。与-obfuscationdictionary类似。 仅适用于混淆处理。 -packageobfuscationdictionary proguardbuild/pro_func.txt # -------------------------------基本指令区-------------------------------- #---------------------------------默认保留区--------------------------------- #继承activity,application,service,broadcastReceiver,contentprovider....不进行混淆 -keep public class * extends android.app.Activity -keep public class * extends androidx.fragment.app.Fragment -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 class android.support.** {*;} # androidx 混淆 -keep class com.google.android.material.** {*;} -keep class androidx.** {*;} -keep public class * extends androidx.** -keep interface androidx.** {*;} -keep class * implements androidx.** { *; } -dontwarn com.google.android.material.** -dontnote com.google.android.material.** -dontwarn androidx.** -printconfiguration -keep,allowobfuscation interface androidx.annotation.Keep -keep @androidx.annotation.Keep class * -keepclassmembers class * { @androidx.annotation.Keep *; } #不混淆View中的set***() 和 get***()方法 以保证属性动画正常工作 某个类中的某个方法不混淆 #自定义View的set get方法 和 构造方法不混淆 -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); } -keepclasseswithmembers class * { public <init>(android.content.Context, android.util.AttributeSet); public <init>(android.content.Context, android.util.AttributeSet, int); } #这个主要是在layout 中写的onclick方法android:onclick="onClick",不进行混淆 -keepclassmembers class * extends android.app.Activity { public void *(android.view.View); } #保持 Serializable 不被混淆 -keepnames class * implements java.io.Serializable #实现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文件中所有静态字段,以保证正确找到每个资源的ID -keepclassmembers class **.R$* { public static <fields>; } -keepclassmembers class * { void *(*Event); } #保留枚举类中的values和valueOf方法 -keepclassmembers enum * { public static **[] values(); public static ** valueOf(java.lang.String); } #保留Parcelable实现类中的Creator字段,以保证Parcelable机制正常工作 -keep class * implements android.os.Parcelable { public static final android.os.Parcelable$Creator *; } #保持 Parcelable 不被混淆 -keep class * implements android.os.Parcelable { public static final android.os.Parcelable$Creator *; } #不混淆包含native方法的类的类名以及native方法名 -keepclasseswithmembernames class * { native<methods>; } #避免log打印输出 -assumenosideeffects class android.util.Log { public static *** v(...); public static *** d(...); public static *** i(...); public static *** w(...); } #对含有反射类的处理 #--------------------------------默认保留区-------------------------------------------- #----------------------------- WebView(项目中没有可以忽略) ----------------------------- #webView需要进行特殊处理 # WebView -dontwarn android.webkit.WebView -dontwarn android.net.http.SslError -dontwarn android.webkit.WebViewClient -keep public class android.webkit.WebView -keep public class android.net.http.SslError -keep public class android.webkit.WebViewClient #在app中与HTML5的JavaScript的交互进行特殊处理 #我们需要确保这些js要调用的原生方法不能够被混淆,于是我们需要做如下处理: -keepclassmembers class com.deepocean.tplh5.helper.JsInterfaceHelper { <methods>; } #----------------------------- WebView(项目中没有可以忽略) ----------------------------- #----------------------------- 实体类不可混淆 ------------------------------------------ #添加实体类混淆规则 # Application classes that will be serialized/deserialized over Gson -keep class **.entity.** { *; } -keep class **.bean.** { *; } #----------------------------- 实体类不可混淆 ------------------------------------------ #----------------------------- 第三方类库 ------------------------------------------ #添加第三方类库的混淆规则 #Adjust sdk -keep class com.adjust.sdk.**{ *; } -keep class com.google.android.gms.common.ConnectionResult { int SUCCESS; } -keep class com.google.android.gms.ads.identifier.AdvertisingIdClient { com.google.android.gms.ads.identifier.AdvertisingIdClient$Info getAdvertisingIdInfo(android.content.Context); } -keep class com.google.android.gms.ads.identifier.AdvertisingIdClient$Info { java.lang.String getId(); boolean isLimitAdTrackingEnabled(); } -keep public class com.android.installreferrer.**{ *; } # OkHttp3 去掉缺失类警告 -dontwarn org.bouncycastle.** -dontwarn org.conscrypt.** -dontwarn org.openjsse.javax.net.ssl.** -dontwarn org.openjsse.net.ssl.** #----------------------------- 第三方类库 ------------------------------------------
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。