对于Android Obfuscation,一直困扰我很久了,很多混淆规则不好记并且容易混淆(概念混淆),有些混淆规则并不能按照字面上去理解。今天在这里手撸每一条规则实践一下,彻底做个总结。
举个例子,比如以下这条规则的含义:
-keep class com.devnn.*
这个规则看上去是保留com.devnn包里的类不被混淆。如果这么简单的理解就说明你对混淆还没有足够的了解。
如果你能肯定地回答以下几个疑问,这篇文章你就不用看了。
- com.devnn.model包下的类会被混淆吗?
- com.devnn这个包名会被混淆吗?
- com.devnn下的类的父类会被混淆吗?
- com.devnn下的类的成员变量和方法会被混淆吗?
-keep class com.devnn.*、-keep class com.devnn.**、
-keep class com.devnn.**{ *;}三者区别是什么?- 如何只混淆包下的类不混淆包的路径?
- 如何只混淆包的路径不混淆包下的类?
- 如何保留类下的部分方法不被混淆?
-keepclasseswithmembers xxx{xxx;}会保留"members"吗?
带着以上疑问,我们来做一个Demo试一试就知道了。实践才能出真知,凭空想象是站不住脚的。
先建一个名为ObfuscationDemo的工程,随便建两个简单的类:Engineer和Student,它们共同的接口是Person。
目录结构如下:

Student类:
package com.devnn.model;
import com.devnn.interfaces.Person;
/**
* create by devnn on 2019-10-14
*/
public class Student implements Person {
@Override
public String say() {
return "I am a student!";
}
@Override
public String work() {
return "I can wash dishes!";
}
@Override
public String study() {
return "That's what I am doing!";
}
}
Engineer类:
package com.devnn.model;
import com.devnn.interfaces.Person;
/**
* create by devnn on 2019-10-14
*/
public class Engineer implements Person {
@Override
public String say() {
return "I am en engineer!";
}
@Override
public String work() {
return "I work very hard!";
}
@Override
public String study() {
return "I do study sometimes!";
}
}
在Activity里调用一下自定义类,否则会被视为无效代码在打包时被删除:
package com.devnn.obfuscationdemo;
import android.app.Activity;
import android.os.Bundle;
import com.devnn.model.Engineer;
import com.devnn.model.Student;
import com.devnn.test.Test;
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
test();
}
public void test(){
System.out.println("test");
Engineer engineer=new Engineer();
engineer.say();
engineer.study();
engineer.work();
Student student=new Student();
student.say();
student.study();
student.work();
}
}
开启混淆:
buildTypes {
debug{
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
release {
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
跑一下assembleDebug命令,生成debug包。
反编译看一下源码:

发现混淆生效了,使用的是默认的混淆规则。我们发现默认的混淆规则就是按照小写字母表的顺序给包、类、成员重新命名。Activity类名及路径默认不被混淆,Actiivty自定义的成员默认是会被混淆的。
一、混淆规则:-keep class xxx.xxx.*
现在加上第一条混淆规则:-keep class com.devnn.*

重新assembleDebug一下。反编译后:

发现跟之前是一样的。
似乎不起作用?
将混淆规则改成-keep class com.devnn.model.*试试。
这次的结果如下:

这下子就一目了然了。
-keep class com.devnn.model.*的作用是保留com.devnn.mode包下的类名(包括路径)不被混淆,类成员和子包还是会被混淆的
-keep class com.devnn.*因为com.devnn下没有类所以这条规则没有作用。
二、混淆规则:-keep class xxx.xxx.**
下面将混淆代码改成-keep class com.devnn.**看看是什么效果。
结果如下:

可以看到两颗**表示对com.devnn下的子包的类都保留不被混淆,同时也仅仅是针对类名。
那么我们可以这样理解:
混淆规则的关键字-keep class xxx表示保留xxx这个类(包括路径)不被混淆,类成员和子路径的的类都不影响。
三、混淆规则:-keep class xxx.xxx.**{xxx;}
下面将混淆规则改成:-keep class com.devnn.**{*;}
结果如下:
我们发现,加上{*;}后表示类成员也保留不被混淆。
有时候我们会看到这样的规则:
-keep class com.xxx.xxx.*{
public <fields>;
public <methods>;
*** set*(***);
*** get*();
}
那么这个规则也自然很好理解了,就是保留com.xxx.xxx下的类及其public成员和set、get方法不被混淆。
那么如何只保留包名不被混淆,包下的类被混淆呢?
四、混淆规则:-keeppackagenames xxx.xxx.xxx
我们试试:-keeppackagenames com.devnn.model 这条规则。
结果如下:

我们发现com.devnn.model这个包名确实是被保留了,其它都不受影响。
弄懂了上面的规则,-keeppackagenames com.devnn.* 也就很好理解了,它表示保留com.devnn下的一级包名不被混淆,-keeppackagenames com.devnn.**表示保留com.devnn下的所有包名不被混淆。
验证一下-keeppackagenames com.* 的结果:

因为没有com.*这样的包,所以以上规则无效。
试一试-keeppackagenames com.**:

我们发现com开头的包都保留了。
再试试一下-keeppackagenames com.devnn.*:

我们发现,com.devnn下的两个包都保留了。
五、混淆规则:-keepclasseswithmembers class xxx.xxx.xxx{xxx;}
像-keepclasseswithmembers这样的规则使用得比较少,字面意思是保留持有某种成员的类不被混淆,比如:
-keepclasseswithmembers public class * {
public static void main(java.lang.String[]);
}
它表示main方法所在的类不被混淆,那么main方法会被混淆吗?不知道,试试。
我们在新建一个包名com.devnn.test并在里面建一个MainTest.java的类,完整内容如下:

我们在混淆文件中只设置一条规则:
-keepclasseswithmembers public class * {
public static void main(java.lang.String[]);
}
打包并查看源码:

发现main方法以及所在类路径保留了。
为什么main方法保留了呢?难道是因为main方法被特殊处理了?
我们不用mina方法,换成其它名字:

在MainActivity里面调一下Test.test1方法:
Test.test1("aaa");
修改一下混淆规则:
-keepclasseswithmembers public class * {
public static void test1(java.lang.String);
}
打包并查看源码:

我们发现-keepclasseswithmembers 确实是保留了"classes"和"members"。
是不是似乎有某种相似?那其实以下两种规则的作用是一样的?
-keepclasseswithmembers public class * {
public static void test1(java.lang.String);
}
-keep class com.** {
public static void test1(java.lang.String);
}
实践一下,我们试试将混淆规则改成上面第二种,查看结果:

我们发现这两种规则的作用并不是一样的。
-keepclasseswithmembers public class * {
public static void test1(java.lang.String);
}
上面这条规则是必须匹配到包含有 public static void test1(java.lang.String);这个方法的类,保留保含此方法的类和此方法不被混淆。
-keep class com.** {
public static void test1(java.lang.String);
}
这条规则是即使没有找到 public static void test1(java.lang.String);这个方法,也照样保留类不被混淆(类成员还是会被混淆)。
六、混淆规则:-keepclassmembers class xxx.xxx.xxx{xxx;}
下面将混淆规则改成:
-keepclassmembers class com.** {
public java.lang.String say();
}
打包并查看源码:

发现除了say()方法保留了,其它内容都被混淆了。-keepclassmembers xxx.xxx.xxx(xxx)作用就是保留某些成员不被混淆,其它都被混淆。
经过以上实践,我们应该对
-keep class com.xxx.*、
-keep class com.xxx.**、
-keep class com.xxx.**{xxx;}、
-keeppackagenames xxx.*、
-keeppackagenames xxx.**、
-keepclasseswithmembers public class * {xxx;}、
-keepclassmembers xxx.xxx.xxx{xxx;}
这些混淆规则有深刻的认识了。
本文详细解析Android代码混淆规则,通过实践演示-keepclass、-keeppackagenames、-keepclasseswithmembers等指令的具体应用,帮助开发者掌握代码保护技巧。
490

被折叠的 条评论
为什么被折叠?



