浅析混淆

979 阅读3分钟

Android的混淆

1,是什么

不想开源自己的项目而在应用发布之前对相关的代码进行混淆,从而达到即使被反编译也难以阅读的效果,用以提高应用的安全性和性能(属于开发周期偏后期的技术)。

一般的混淆通常指的是Proguard

由于Java是一种跨平台,解释型的语言,所以会将Java源码编译成中间“字节码”存储于class文件中。由于跨平台的需要,Java自己吗中又包含了很多源码信息:变量名,方法名,并且通过这些名称来访问变量和方法,这些符号有许多予以信息,容易被反编译成Java源代码。而为了防止此现象,就可以使用Java混淆器对Java字节码进行混淆。

所谓混淆就是对发布出去的程序进行重新组织以及处理,是的处理后的代码与吹前的代码完全像用的功能,但是难以被反编译,及时反编译成功也很难得出程序真正的语义。被混淆的程序代码,仍然遵照原来的档案格式和指令集,执行结果也与混淆之前一样,只是混淆器将代码中的所有的变量,函数,类的名称变为简单的英文字母代号,在缺乏相应的函数名和程序注释的情况下,及时被反编译,也难以阅读。同时混淆也是不可逆的,在混淆的过程中一些不影响正常运行的信息将被永久丢失,也使得程序变得难以理解。

在AS中对其进行混淆:

//对release版本进行混淆处理
android{
    ···
    buildTypes{
        release{
            //是否进行混淆
            minifyEnabled true
            //proguard-android.txt为SDK默认混淆配置
            //proguard-rules.pro 模块下的混淆配置
            proguardFiles getDefaultProguardFile('proguard-android.txt'),'proguard-rules.pro'
        }
    }
}

2,Proguard作用

压缩(Shrinking):

默认开启,用以减小应用的体积大小,移除没有被使用的类以及成员,并且会在优化动作执行完毕之后再次执行(由于优化后会再次暴露一些没有被适用过的类以及成员)

-dontshrink//关闭压缩

优化(Optimization):

默认开启,在季节妈妈级别执行优化,让系统运行的更快

-dontoptimize  //关闭优化
-optimizationpasses n //表示proguard对代码进行迭代优化的次数,Android一般为5

混淆(Obfuscation):

默认开启,增大反编译难度,类和类成员会被随机命名,除非使用keep进行保护

-dontobfuscate //关闭混淆

混淆之后就会工程目录(app/build/outputs/mapping/release)下产生一个mapping.txt文件,即为混淆规则。我们可以根据这个文件将混淆后的代发反推回源本的代码,所以要保护好。而原则上,代码越无规律越乱越好,但是有些地方还是要避免进行混淆的,否则程序在运行的时候就会出错。

3,基本语法使用

定义一些不需要被混淆的源代码,即在编译期间会对未定义的源代码进行混淆

3.1.1,保留类名,方法名

#保留位于View类中的get和set方法
-keepclassmembers public class * extends android.view.View{
    void set*(***);
    *** get*();
}
#保留在Activity中以View为参数的方法不变
-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 *;
}
 #保留R$*类中静态成员的变量名
-keepclassmembers class **.R$*{
    public static <fields>;
}
1,* * 和*
//1,表示只保持该包下的类名,而子包下的类名还是会被混淆
-keep class com.example.test.*;
//2,表示把本包和所含子包下的类名都进行保持
-keep class com.example.test.**;

以上方法虽然没有对类名进行混淆,但是里面的具体方法以及便令明明明还是改变了,此时如果想要保持类名,又要保持里面的内容不被混淆,就需要使用一下的方法:

-keep class com.example.text.* {*;}

在此基础上也可以使用Java的基本规则来保护特定的类不会被混淆,比如可以使用extend,implement等。

2,其他使用

如果要保留一个类中的内部类不被混淆需要用到**$**符号,如下例所示:

-keepclassmembers class com.example.test.ui.fragment.UserFragment$JavaScriptInterface{
	public *;
}

如果一个类中不希望保持全部的内容不被混淆,而是希望保护类下的特定内容的话可以使用:

<init>		//匹配所有的构造器
<fields>	//匹配所有的域
<methods>	//匹配所有的方法

还可以在,前面加上权限修饰符(private,public)以及native来进行进一步指定不被混淆的内容

-keep class aom.example.test.One{
	public <methods>;
}
/*
表示One类下所有的public方法都不会背混淆,也可以加入参数:
public <init>(org.json.JSONObject);
表示使用JSONObject作为入参的构造函数也不会被混淆
*/

如果说不需要保持类名,而是只需要把该类下特定的方法保持不被混淆,就不需要再使用keep(会保持类名),而是使用keepclassmembers:

保留 防止移除或被重命名 防止被重命名
类和类成员 -keep -keepnames
仅类成员 -keepclassmembers -keepclassmembernames
如果拥有谋划成员,保留类和类成员 -keepclasseswithmembers -keepclasseswithmembernames
  • 移除指的是在压缩是是否会被删除。

3.1.2,去除日志信息

通过配置Proguard,将android.util.Log置为无效,就可去除apk中打印日志的代码。

两种方式:
  • 发布版本的时候封装一个log wrapper

    public static void e(String tag, String msg){
    	if(LOG_LEVEL > ERROR)
    		Log.e(tag,msg);
    }
    
    public static void w(String tag, String msg){
    	if(LOG_LEVEL > WARN)
    		Log.e(tag,msg);
    }
    
    public static void i(String tag, String msg){
    	if(LOG_LEVEL > INFO)
    		Log.e(tag,msg);
    }
    
    
  • 直接删除打印log的代码

-assumenosideeffects class android.util.Log{
   public static boolean isLoggable(java.lang.String,int);
   public static int v(...);
   public static int i(...);
   public static int w(...);
   public static int d(...);
   public static int e(...);
}
-assumenosideeffects class com.example.Log.Logger{
   public static int v(...);
   public static int i(...);
   public static int w(...);
   public static int d(...);
   public static int e(...);
}

  • 需要打开优化开关
  • 默认使用proguard-android-optimize.txt

3.1.3,注意事项

1,JNI方法不可混淆,这个方法需要和native方法保持一致

-keepclasseswithmembernames class *{
    # 保持native方法不会被混淆
	native <methods>
}

2,反射用到的类不能够被混淆(否则反射可能会出问题)

3,Mainfest中的类不可以被混淆,所以四大组件以及Application对的子类以及Framework层下所有的类默认不会进行混淆。自定义View默认也不会被混淆。

4,使用第三方开源库或者引用其他第三方的SDK包时,如果有特别的要求,需要在混淆文件中加入对应的混淆规则

5,与服务端进行交互的时候,使用GSON,fastjson等框架解析服务端数据的时候,缩写的JSON对象不混淆,否则将无法将JSON解析成对应的工具

6,有用到WebView的JS调用也需要保证写的接口犯法不混淆

7,使用Parcelable进行序列化的时候Parcelable的子类和Creator景天成员变量也不进行混淆,否则会产生Android.os.BadParcelableException异常

-keep class * implements Android.os.Parcelable{
# 保持Parcelable不被混淆
	public static final Android.os.Parcelable$Creator *;	
}

8,使用enum类型时需要注意避免以下两个方法混淆,因为enum类的特殊性,以下两个方法会被反射调用

-keepclassmembers enum * {  
    public static **[] values();  
    public static ** valueOf(java.lang.String);  
}