Android Obfuscation实践与总结

对于Android Obfuscation,相信很多人都会感觉困惑或一支半解,有些混淆规则并不能按照字面上去理解。

比如以下这条规则的含义:

-keep class com.devnn.*

这个规则看上去是保留com.devnn包里的类不被混淆。如果这么简单的理解就说明你对混淆还没有足够的了解。

如果你能自信地回答以下几个问题说明你对混淆有一定的研究。

  1. com.devnn.model包下的类会被混淆吗?
  2. com.devnn这个包名会被混淆吗?
  3. com.devnn下的类的父类会被混淆吗?
  4. com.devnn下的类的成员变量和方法会被混淆吗?
  5. -keep class com.devnn.*-keep class com.devnn.**
    -keep class com.devnn.**{ *;}三者区别是什么?
  6. 如何只混淆包下的类不混淆包的路径?
  7. 如何只混淆包的路径不混淆包下的类?
  8. 如何保留类下的部分方法不被混淆?
  9. -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;}
这些混淆规则有深刻的认识了。

原创文章 56 获赞 44 访问量 9万+

猜你喜欢

转载自blog.csdn.net/devnn/article/details/102550582