【Android】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;}
这些混淆规则有深刻的认识了。

  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
ProGuard is a tool used in Android development to optimize and obfuscate the compiled code. It helps reduce the size of the APK file and makes it harder for reverse engineers to understand and modify the code. ProGuard performs several tasks to achieve this, including: 1. Shrinking: It analyzes the code and removes unused classes, fields, and methods, thereby reducing the size of the application. 2. Optimization: ProGuard applies various optimizations, such as inlining methods, removing dead code, and simplifying constant expressions, to improve the app's performance. 3. Obfuscation: It renames classes, fields, and methods with short, meaningless names, making it difficult for others to understand the code. To enable ProGuard in an Android project, you need to add the following lines to your project's `build.gradle` file: ```groovy android { // ... buildTypes { release { // ... minifyEnabled true proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } } } ``` Here, `minifyEnabled true` enables code shrinking and obfuscation, while `proguardFiles` specifies the ProGuard configuration files. The `getDefaultProguardFile('proguard-android-optimize.txt')` line includes a default set of rules provided by the Android SDK, and you can customize these rules in your own `proguard-rules.pro` file. It's important to note that ProGuard can sometimes cause issues if it mistakenly removes code that is actually used, or if it conflicts with certain libraries or reflection-based code. In such cases, you may need to tweak the ProGuard configuration to exclude specific classes or methods. Overall, ProGuard is a useful tool for optimizing and securing your Android app.

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值