Android代码混淆与反编译
有问题可以加QQ讨论:1070800492
- 在去学习混淆与反编译的同时,我先去看了几遍大神的博客
- 郭林的Android安全攻防战,反编译与混淆技术完全解析(下)
- 郭林——Android安全攻防战,反编译与混淆技术完全解析(上)
- 好的 让我们先一起跟着大神学习Android的代码混淆
第一步:混淆apk
- 1.AS借助了SDK的自带Proguard工具,我们只需要修改build.gradle中的一行配置即可。
- build.gradle中minifyEnabled的值是false,这里我们只需要把值改成true,打出来的APK包就会是混淆过的了。如下所示
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nrt0bk99-1593505504190)(http://i.imgur.com/HWFVbgA.png)] - minifyEnabled用于设置是否启用混淆,proguardFiles用于选定混淆配置文件。注意这里是在release闭包内进行配置的,因此只有打出正式版的APK才会进行混淆,Debug版的APK是不会混淆的。
- 这种混淆后的代码:
- build.gradle中minifyEnabled的值是false,这里我们只需要把值改成true,打出来的APK包就会是混淆过的了。如下所示
1.普通类肯定是会被混淆的,不管是类名、方法名还是变量都不会放过
2.为是多余的代码,在打包的时候就给移除掉了。不仅仅是代码,没有被调用的资源同样也会被移除掉,因此minifyEnabled除了混淆代码之外,还可以起到压缩APK包的作用
3.AndroidManifest.xml中去注册的所有类的类名以及从父类重写的方法名都自动不会被混淆,Activity之外,这份规则同样也适用于Service、BroadcastReceiver和ContentProvider
4.第三方的Jar包都是会被混淆的
-
- 当然我们也可以自己定义代码混淆规则:在build.gradle的release闭包下配置的proguard-android.txt文件
-dontusemixedcaseclassnames 表示混淆时不使用大小写混合类名。
-dontskipnonpubliclibraryclasses 表示不跳过library中的非public的类。
-verbose 表示打印混淆的详细信息。
-dontoptimize 表示不进行优化,建议使用此选项,因为根据proguard-android-optimize.txt中的描述,优化可能会造成一些潜在风险,不能保证在所有版本的Dalvik上都正常运行。
-dontpreverify 表示不进行预校验。这个预校验是作用在Java平台上的,Android平台上不需要这项功能,去掉之后还可以加快混淆速度。
-keepattributes *Annotation* 表示对注解中的参数进行保留。
-keep public class com.google.vending.licensing.ILicensingService
-keep public class com.android.vending.licensing.ILicensingService
表示不混淆上述声明的两个类,这两个类我们基本也用不上,是接入Google原生的一些服务时使用的
-keepclasseswithmembernames class * {
native <methods>;
}
表示不混淆任何包含native方法的类的类名以及native方法名,这个和我们刚才验证的结果是一致的。
-keepclassmembers public class * extends android.view.View {
void set*(***);
*** get*();
}
表示不混淆任何一个View中的setXxx()和getXxx()方法,因为属性动画需要有相应的setter和getter的方法实现,混淆了就无法工作了。
-keepclassmembers class * extends android.app.Activity {
public void *(android.view.View);
}
表示不混淆Activity中参数是View的方法,因为有这样一种用法,在XML中配置android:onClick=”buttonClick”属性,当用户点击该按钮时就会调用Activity中的buttonClick(View view)方法,如果这个方法被混淆的话就找不到了。
-keepclassmembers enum * {
public static **[] values();
public static ** valueOf(java.lang.String);
}
表示不混淆枚举中的values()和valueOf()方法,枚举我用的非常少,这个就不评论了。
-keepclassmembers class * implements android.os.Parcelable {
public static final android.os.Parcelable$Creator CREATOR;
}
表示不混淆Parcelable实现类中的CREATOR字段,毫无疑问,CREATOR字段是绝对不能改变的,包括大小写都不能变,不然整个Parcelable工作机制都会失败。
-keepclassmembers class **.R$* {
public static <fields>;
}
表示不混淆R文件中的所有静态字段,我们都知道R文件是通过字段来记录每个资源的id的,字段名要是被混淆了,id也就找不着了。
-dontwarn android.support.** 表示对android.support包下的代码不警告,因为support包中有很多代码都是在高版本中使用的,如果我们的项目指定的版本比较低在打包时就会给予警告。不过support包中所有的代码都在版本兼容性上做足了判断,因此不用担心代码会出问题,所以直接忽略警告就可以了。
好了,这就是proguard-android.txt文件中所有默认的配置,而我们混淆代码也是按照这些配置的规则来进行混淆的。经过我上面的讲解之后,相信大家对这些配置的内容基本都能理解了。不过proguard语法中还真有几处非常难理解的地方,我自己也是研究了好久才搞明白,下面和大家分享一下这些难懂的语法部分。
proguard中一共有三组六个keep关键字,很多人搞不清楚它们的区别,这里我们通过一个表格来直观地看下:
虽说上面表格已经解释的很详细了,但是很多人对于keep和keepclasseswithmembers这两个关键字的区别还是搞不懂。确实,它们之间用法有点太像了,我做了很多次试验它们的结果都是相同的。其实唯一的区别就在于类中声明的成员存不存在,我们还是通过一个例子来直接地看一下,先看keepclasseswithmember关键字:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-13hRcaG1-1593505504192)(http://i.imgur.com/b5FDdY4.png)]
虽说上面表格已经解释的很详细了,但是很多人对于keep和keepclasseswithmembers这两个关键字的区别还是搞不懂。确实,它们之间用法有点太像了,我做了很多次试验它们的结果都是相同的。其实唯一的区别就在于类中声明的成员存不存在,我们还是通过一个例子来直接地看一下,先看keepclasseswithmember关键字:
-keepclasseswithmember class * {
native <methods>;
}
这段代码的意思其实很明显,就是保留所有含有native方法的类的类名和native方法名,而如果某个类中没有含有native方法,那就还是会被混淆。
但是如果改成keep关键字,结果会完全不一样:
-keep class * {
native <methods>;
}
使用keep关键字后,你会发现代码中所有类的类名都不会被混淆了,因为keep关键字看到class *就认为应该将所有类名进行保留,而不会关心该类中是否含有native方法。当然这样写只会保证类名不会被混淆,类中的成员还是会被混淆的。
-include {filename} 从给定的文件中读取配置参数
-basedirectory {directoryname} 指定基础目录为以后相对的档案名称
-injars {class_path} 指定要处理的应用程序jar,war,ear和目录
-outjars {class_path} 指定处理完后要输出的jar,war,ear和目录的名称
-libraryjars {classpath} 指定要处理的应用程序jar,war,ear和目录所需要的程序库文件
-dontskipnonpubliclibraryclasses 指定不去忽略非公共的库类。
-dontskipnonpubliclibraryclassmembers 指定不去忽略包可见的库类的成员。
保留选项
-keep {Modifier} {class_specification} 保护指定的类文件和类的成员
-keepclassmembers {modifier} {class_specification} 保护指定类的成员,如果此类受到保护他们会保护的更好
-keepclasseswithmembers {class_specification} 保护指定的类和类的成员,但条件是所有指定的类和类成员是要存在。
-keepnames {class_specification} 保护指定的类和类的成员的名称(如果他们不会压缩步骤中删除)
-keepclassmembernames {class_specification} 保护指定的类的成员的名称(如果他们不会压缩步骤中删除)
-keepclasseswithmembernames {class_specification} 保护指定的类和类的成员的名称,如果所有指定的类成员出席(在压缩步骤之后)
-printseeds {filename} 列出类和类的成员-keep选项的清单,标准输出到给定的文件
压缩
-dontshrink 不压缩输入的类文件
-printusage {filename}
-whyareyoukeeping {class_specification}
优化
-dontoptimize 不优化输入的类文件
-assumenosideeffects {class_specification} 优化时假设指定的方法,没有任何副作用
-allowaccessmodification 优化时允许访问并修改有修饰符的类和类的成员
混淆
-dontobfuscate 不混淆输入的类文件
-printmapping {filename}
-applymapping {filename} 重用映射增加混淆
-obfuscationdictionary {filename} 使用给定文件中的关键字作为要混淆方法的名称
-overloadaggressively 混淆时应用侵入式重载
-useuniqueclassmembernames 确定统一的混淆类的成员名称来增加混淆
-flattenpackagehierarchy {package_name} 重新包装所有重命名的包并放在给定的单一包中
-repackageclass {package_name} 重新包装所有重命名的类文件中放在给定的单一包中
-dontusemixedcaseclassnames 混淆时不会产生形形色色的类名
-keepattributes {attribute_name,...} 保护给定的可选属性,例如LineNumberTable, LocalVariableTable, SourceFile, Deprecated, Synthetic, Signature, and
InnerClasses.
-renamesourcefileattribute {string} 设置源文件中给定的字符串常量
-optimizationpasses 5
#包明不混合大小写
-dontusemixedcaseclassnames
#不去忽略非公共的库类
-dontskipnonpubliclibraryclasses
#优化 不优化输入的类文件
-dontoptimize
#预校验
-dontpreverify
# 混淆时所采用的算法
-optimizations !code/simplification/arithmetic,!field/*,!class/merging/*
#保护注解
-keepattributes *Annotation*
# 保持哪些类不被混淆
-keep public class * extends android.app.Fragment
-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 com.android.vending.licensing.ILicensingService
#如果有引用v4包可以添加下面这行
#-keep public class * extends android.support.v4.app.Fragment
-keep public class * extends android.support.** { *; }
#如果引用了v4或者v7包
-dontwarn android.support.*
#忽略警告
-ignorewarning
#####################记录生成的日志数据,gradle build时在本项目根目录输出################
#混淆时是否记录日志
-verbose
#apk 包内所有 class 的内部结构
-dump class_files.txt
#未混淆的类和成员
-printseeds seeds.txt
#列出从 apk 中删除的代码
-printusage unused.txt
#混淆前后的映射
-printmapping mapping.txt
#####################记录生成的日志数据,gradle build时 在本项目根目录输出-end################
#####混淆保护自己项目的部分代码以及引用的第三方jar包library - start #######
#如果不想混淆 keep 掉 保留一个完整的包
#-keep class com.lippi.recorder.iirfilterdesigner.** {*; }
#项目特殊处理代码
#忽略警告
#-dontwarn com.lippi.recorder.utils**
#如果用用到Gson解析包的,直接添加下面这几行就能成功混淆,不然会报错。
#//原因分析,可能是高版本的 sdk 通过 proguard 混淆代码时默认已经将 lib目录中的 jar 都已经添加到打包脚本中,所以不需要再次手动添加
# 混淆jar
#-libraryjars libs/gson-2.2.4.jar
# 混淆类
#-keep class sun.misc.Unsafe { *; }
# 混淆包
#-keep class com.google.gson.examples.android.model.** { *; }
#dialog
-keep class me.drakeet.materialdialog.** { *; }
#加载框
-keep class com.kaopiz.kprogresshud.** { *; }
#下拉刷新
-keep class in.srain.cube.views.ptr.** { *; }
#实体类不混淆
-keep class com.ousrslook.shimao.commen.ioc.** { *; } #不能混淆 否则注解无效
-keep class com.ousrslook.shimao.model.** { *; } #不能混淆
-keep class com.ousrslook.shimao.net.XaResult{ *; }#统一返回的实体类泛型不能混淆
#-keep class com.ousrslook.shimao.net.** { *; }
####混淆保护自己项目的部分代码以及引用的第三方jar包library-end####
-keep public class * extends android.view.View {
public <init>(android.content.Context);
public <init>(android.content.Context, android.util.AttributeSet);
public <init>(android.content.Context, android.util.AttributeSet, int);
public void set*(...);
}
#保持 native 方法不被混淆
-keepclasseswithmembernames class * {
native <methods>;
}
#保持自定义控件类不被混淆
-keepclasseswithmembers class * {
public <init>(android.content.Context, android.util.AttributeSet);
}
#保持自定义控件类不被混淆
-keepclassmembers class * extends android.app.Activity {
public void *(android.view.View);
}
#保持 Parcelable 不被混淆
-keep class * implements android.os.Parcelable {
public static final android.os.Parcelable$Creator *;
}
#保持 Serializable 不被混淆
-keepnames class * implements java.io.Serializable
#保持 Serializable 不被混淆并且enum 类也不被混淆
-keepclassmembers class * implements java.io.Serializable {
static final long serialVersionUID;
private static final java.io.ObjectStreamField[] serialPersistentFields;
!static !transient <fields>;
!private <fields>;
!private <methods>;
private void writeObject(java.io.ObjectOutputStream);
private void readObject(java.io.ObjectInputStream);
java.lang.Object writeReplace();
java.lang.Object readResolve();
}
#保持枚举 enum 类不被混淆 如果混淆报错,建议直接使用上面的 -keepclassmembers class * implements java.io.Serializable即可
-keepclassmembers enum * {
public static **[] values();
public static ** valueOf(java.lang.String);
}
-keepclassmembers class * {
public void *ButtonClicked(android.view.View);
}
#不混淆资源类
-keepclassmembers class **.R$* {
public static <fields>;
}
-keep class **.R$* { *; }
#避免混淆泛型 如果混淆报错建议关掉
-keepattributes Signature</span>
Android中不能混淆的代码有
1,Android系统组件,系统组件有固定的回调方法被系统调用的
2,被Resource 文件引用到的,名字已经固定,也不能混淆,比如自定义的View ,
3, Parcelable ,Serializable,需要使用android 序列化的。
4,其他Anroid 官方建议 不混淆的,如
android.app.backup.BackupAgentHelper
android.preference.Preference
com.android.vending.licensing.ILicensingService
5,enum 枚举 ,系统需要处理枚举的固定方法。
6,native 本地方法,不能修改本地方法名
7,annotations 注释
8,数据库驱动jdbc
9,使用反射方法的
10,使用第三方库引用不能混淆
11,运行时动态调用的函数或者成员变量
12,我们代码依赖于系统的接口,比如被系统代码调用的回调方法,这种情况最复杂,最费时间地方。
如果你不确定哪些需要手动配置,可以以默认的配置生成程序,当运行中发现ClassNotFoundException异常时,即可找到哪个类不该被混淆。
所以使用proguard时,我们需要有个配置文件告诉proguard 那些 元素是不能混淆的。
第二步:了解命令
1.代码混淆的压缩比例,值在0-7之间:
-optimizationpasses
2.混淆后的类名为小写:
-dontusemixedcaseclassnames
3.指定不去忽略非公共的库的类:
-dontskipnonpubliclibraryclasses
4.指定不去忽略非公共的库的类的成员:
-dontskipnonpubliclibraryclassmembers
5.不做预校验的操作
-dontpreverify
6.生成原类名和混淆后的类名的映射文件
-verbose
-printmapping proguardMapping.txt
7.指定混淆是采用的算法
-optimizations !code/simplification/cast,!field/*,!class/merging/*
8.不混淆Annotation
-keepattributes *Annotation*,InnerClasses
9.不混淆泛型
-keepattributes Signature
10.抛出异常时保留代码行号
-keepattributes SourceFile,LineNumberTable
11.保留类名不变,也就是类名不混淆,而类中的成员名不保证。当然也可以是继承XXX类的所有类名不混淆。
-keep class XXXX
12.保留类名和成员名。当然也可以是类中特定方法,代码不贴了。
-keepclasseswithmembers class XXXX
13.不要笔记
-dontnote
14.防止类和成员被移除或者被重命名
-keep
15.防止类和成员被重命名
-keepnames
16.防止成员被移除或者被重命名
-keepclassmembers
17.防止成员被重命名
-keepnames
18.防止拥有该成员的类和成员被移除或者被重命名
-keepclasseswithmembers
19.防止拥有该成员的类和成员被重命名
-keepclasseswithmembernames
- 2.保持元素不参与混淆规则:
[保持命令] [类] {
[成员]
}
“类”代表类相关的限定条件,它将最终定位到某些符合该限定条件的类。它的内容可以使用:
- 1.具体的类
- 2.访问修饰符(public、protected、private)
- 3.通配符**,匹配任意长度字符,并且包含包名分隔符(.)
- 4.extends,即可以指定类的基类
- implement,匹配实现了某接口的类
- 5.$,内部类
“成员”代表类成员相关的限定条件,它将最终定位到某些符合该限定条件的类成员。它的内容可以使用: - 匹配所有构造器
- 匹配所有域
- 匹配所有方法
- 通配符*,匹配任意长度字符,但不含包名分隔符(.)
- 通配符**,匹配任意长度字符,并且包含包名分隔符(.)
- 通配符***,匹配任意参数类型
- …,匹配任意长度的任意类型参数。比如void test(…)就能匹配任意 void test(String a) 或者是 void test(int a, String b) 这些方法。
- 访问修饰符(public、protected、private)
举个例子,假如需要将name.huihui.test包下所有继承Activity的public类及其构造函数都保持住,可以这样写:
-keep public class name.huihui.test.** extends Android.app.Activity {
<init>
}
常用的自定义混淆规则
1.不混淆某个类:
-keep public class name.huihui.example.Test { *; }
2.不混淆某个包所有的类
-keep class name.huihui.test.** { *; }
3.不混淆某个类的子类
-keep public class * extends name.huihui.example.Test { *; }
4.不混淆所有类名中包含了“model”的类及其成员
-keep public class **.*model*.** {*;}
5.不混淆某个接口的实现
-keep class * implements name.huihui.example.TestInterface { *; }
6.不混淆某个类的构造方法
-keepclassmembers class name.huihui.example.Test {
public <init>();
}
7.不混淆某个类的特定的方法
-keepclassmembers class name.huihui.example.Test {
public void test(java.lang.String);
}
8.不混淆某个类的内部类
-keep class name.huihui.example.Test$* {
*;
}
9.
题外话:谢谢大家观看,有不足之处欢迎大家一起讨论;码字不易,大家喜欢,麻烦点赞哦。
灵魂三问:
- 有没有觉得技术得不到系统的提升,技术成长慢?
- 有没面试懵逼,升职加薪难?
- 有没有想过去大一点的世界看看?
有期望JAVA技术巩固的、Android知识进阶的、期望升职加薪的、Android面试技巧的、大厂面试真题的;大家可以加我QQ哦:1070800492。我们一起学习,一起进步!
重要的事情说三遍:
- 学习、挣钱、自由
- 学习、挣钱、自由
- 学习、挣钱、自由
疫情当下,唯有自强