Android 代码混淆Proguard

混淆概念
Android代码混淆,又称Android混淆,是伴随着Android系统的流行而产生的一种APP保护技术,用于保护APP不被破解和逆向分析。
在Android的具体表现就是打包时,将项目里的包名、类名、变量名根据混淆规则进行更改,使反编译工具反编译出来的代码人难以阅读,从而达到防止被逆向破解的目的。

在Android里面,由于AndroidStudio集成了ProGuard,因此我们最常用,最简单的混淆是ProGuard混淆。
ProGuard混淆主要包括有四个功能:

压缩(Shrink):用于检测和删除没有使用的类、字段、方法和属性。
优化(Optimize):对于字节码进行优化,并且移除无用指令。
混淆(Obfuscate):使用a,b,c等名称对类,字段和方法进行重命名。
预检(Preverify):主要是在Java平台上对处理后的代码进行预检。
综上我们可以总结出混淆的两大作用:

安全,提高了反编译&逆向破解的难度。
压缩代码,删除无用的代码,简单的代码重命名,都可以减少Apk体积。
其实混淆还有另一个妙用,也是今天我们重点要说的:
不同的混淆规则可以编译完全不同的代码,降低代码重复率,用于制作马甲包。

开启混淆
在主模块的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 //签名
}
}
//...
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
配置混淆开关的是minifyEnabled。
proguardFiles用于指定混淆规则,自动使用默认的混淆规则,而我们可以在proguard-rules.pro中自定义自己的混淆规则。

混淆规则
常见的混淆规则应该主要包含如下四个部分:

基本配置,设定混淆的规则等,基本配置是每个混淆文件必须存在的。
基本的keep项,多数Android工程都需要非混淆的内容,包括有四大组件,AndroidX等内容。
第三方库sdk的混淆白名单,根据对应文档添加即可(新版本构建工具可以在sdk内部添加,因此大部分sdk新版本都无须再添加)。
其他需要不混淆的内容,包括:实体类,json解析类,WebView及js的调用模块,与反射相关的类和方法。
混淆的详细规则可以参考: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 #混淆前后的映射,生成映射文件
1
2
3
混淆出不同的代码
实际上基本混淆配置完只要不去修改,相同代码每次打包得到的代码都是一样的。
对于单包常规开发来说,这是没有问题的。
但是实际上绝大多数App都需要马甲包去增加搜索关键字,进而达到引流效果。
然而马甲包上架有一个始终无法回避问题:代码相似度,在审核严格的平台,代码相似度过高甚至会导致主包下架,造成无法挽回的损失。

降低代码相似度,常规来说有两个方案:

成本高&低效率的一点点改代码。
暴力的生成垃圾代码,增大分母,降低百分比。
如何通过混淆来解决这个问题呢?核心混淆规则如下:

指定一个文本文件,其中所有有效字词都用作混淆字段和方法名称。

默认情况下,诸如“a”,“b”等短名称用作混淆名称。

使用模糊字典,您可以指定保留关键字的列表,或具有外来字符的标识符,

例如: 忽略空格,标点符号,重复字和#符号后的注释。

注意,模糊字典几乎不改善混淆。 有些编译器可以自动替换它们,并且通过使用更简单的名称再次混淆,可以很简单地撤消该效果。

最有用的是指定类文件中通常已经存在的字符串(例如'Code'),从而减少类文件的大小。 仅适用于混淆处理。

-obfuscationdictionary proguardbuild/pro_package.txt

指定一个文本文件,其中所有有效词都用作混淆类名。 与-obfuscationdictionary类似。 仅适用于混淆处理。

-classobfuscationdictionary proguardbuild/pro_class.txt

指定一个文本文件,其中所有有效词都用作混淆包名称。与-obfuscationdictionary类似。 仅适用于混淆处理。

-packageobfuscationdictionary proguardbuild/pro_func.txt
1
2
3
4
5
6
7
8
9
10
11
12
13
简单来说我们可以指定一个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()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
使用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.AdvertisingIdClientInfo getAdvertisingIdInfo(android.content.Context); } -keep class com.google.android.gms.ads.identifier.AdvertisingIdClientInfo {
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.**

----------------------------- 第三方类库 ------------------------------------------

————————————————
版权声明:本文为CSDN博主「DeMonnnnnn」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/DeMonliuhui/article/details/128295336


混淆语法主要用于定义不需要混淆的代码。
1.
-keep class com.example.MyClass {
    public void myMethod1();
    public void myMethod2();
}
上述规则会保留 com.example.MyClass 类的类名和 myMethod1() 和 myMethod2() 方法不被混淆。

2.
  -keep class com.thc.test.*
一颗星表示只是保持该包下的类名,而子包下的类名还是会被混淆;

3.
  -keep class com.thc.test.**
两颗星表示把本包和所含子包下的类名都保持;

4.
 -keep class com.thc.test.*{*;}
既可以保持该包下的类名,又可以保持类里面的内容不被混淆;

5.
  -keep class com.thc.test.**{*;}
既可以保持该包及子包下的类名,又可以保持类里面的内容不被混淆;

6.
  -keep class com.thc.gradlestudy.MyProguardBean{
      <init>; #匹配所有构造器
      <fields>;#匹配所有域
      <methods>;#匹配所有方法
  }
保持类中特定内容,而不是所有的内容

7.
-keep class com.xlpay.sqlite.cache.ProguardTest$MyClass{*;}
要保留一个类中的内部类不被混淆需要用 $ 符号

8.
  -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.view.View
使用Java的基本规则来保护特定类不被混淆,比如用extends,implement等这些Java规则,如下:保持Android底层组件和类不要混淆

9.
  #保持ProguardTest类下test(String)方法不被混淆
  -keepclassmembernames class com.xlpay.sqlite.cache.ProguardTest{
      public void test(java.lang.String);
  }
 如果不需要保持类名,只需要保持该类下的特定方法保持不被混淆,需要使用keepclassmembers,而不是keep,因为keep方法会保持类名。

10.
  #保持native方法不被混淆
  -keepclasseswithmembernames class * {    
      native <methods>; 
  }
jni方法不可混淆,因为native方法是要完整的包名类名方法名来定义的,不能修改,否则找不到;

11.
  -keep class * implements Android.os.Parcelable { 
      # 保持Parcelable不被混淆            
      public static final Android.os.Parcelable$Creator *;
  }
Parcelable的子类和Creator静态成员变量不混淆,否则会产生Android.os.BadParcelableException异常

12.
  -keepclassmembers enum * {  
      public static **[] values();  
      public static ** valueOf(java.lang.String);  
  }
使用enum类型时需要注意避免以下两个方法混淆,因为enum类的特殊性,以下两个方法会被反射调用,见第二条规则
https://www.jianshu.com/p/546733072d8d

其他常用混淆设置
通用设置
# 指定代码的压缩级别 0 - 7(指定代码进行迭代优化的次数,在Android里面默认是5,这条指令也只有在可以优化时起作用。)
-optimizationpasses 5
# 混淆时不会产生形形色色的类名(混淆时不使用大小写混合类名)
-dontusemixedcaseclassnames
# 指定不去忽略非公共的库类(不跳过library中的非public的类)
-dontskipnonpubliclibraryclasses
# 指定不去忽略包可见的库类的成员
-dontskipnonpubliclibraryclassmembers
#不进行优化,建议使用此选项,
-dontoptimize 
# 不进行预校验,Android不需要,可加快混淆速度。
-dontpreverify
# 抛出异常时保留代码行号
-keepattributes SourceFile,LineNumberTable
#打印混淆的详细信息
-verbose






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

推荐阅读更多精彩内容