Androd安全——混淆技术完全解析

0.前言

在上一篇Androd安全——反编译技术完全解析中介绍了反编译方面的知识,因此我们认识到为了安全我们需要对代码进行混淆。

混淆代码并不是让代码无法被反编译,而是将代码中的类、方法、变量等信息进行重命名,把它们改成一些毫无意义的名字。因为对于我们而言可能Cellphone类的call()方法意味着很多信息,而A类的b()方法则没有任何意义,但是对于计算机而言,它们都是平等的。

所以说混淆代码可以在不影响程序正常运行的前提下让破解者很头疼,从而大大提升了程序的安全性。

1.   混淆的使用

在Android Studio中借助SDK中自带的Proguard工具,如下所示,只需要修改build.gradle中minifyEnabled的值为true即可,这样Build->Generate Signed APK打出来的APK包就是混淆过的,当然,Debug版的APK是不会混淆的。

release {
minifyEnabled true //设置是否启用混淆
//用于选定混淆配置文件
proguardFiles getDefaultProguardFile('proguard-android.txt'),'proguard-rules.pro'
}

2.  默认的混淆规则

2.1  普通工具类

下面是一个非常普通的工具类,没有任何继承关系。Utils中有两个方法内部逻辑一样,唯一的据别是稍后methodNormal()方法会被调用,而methodUnused()方法不会被调用。

public class Utils {
public void methodNormal() {
String logMessage = "this is normal method";
logMessage = logMessage.toLowerCase();
System.out.println(logMessage);
} public void methodUnused() {
String logMessage = "this is unused method";
logMessage = logMessage.toLowerCase();
System.out.println(logMessage);
}
}

混淆结果如下:

Androd安全——混淆技术完全解析

总结:像Utils这样的普通类,不管是类名、方法名还是变量都会混淆。除了混淆之外反编译之后就只剩一个方法了,因为另外一个方法没有被调用,所以认为是多余的代码,在打包的时候就给移除掉了(没有被调用的资源同样也会被移除掉),因此minifyEnabled除了混淆代码之外,还起到压缩APK包的作用。

2.2  NativeUtils类

下面这个类中同样有两个方法,一个是native方法,一个是非native方法。

public class NativeUtils {
public static native void methodNative();
public static void methodNotNative() {
String logMessage = "this is not native method";
logMessage = logMessage.toLowerCase();
System.out.println(logMessage);
}
}

混淆结果如下:

Androd安全——混淆技术完全解析

总结:NativeUtils的类名没有被混淆,这是由于它有一个声明成native的方法。只要一个类中有存在native方法,它的类名就不会被混淆(因为C++代码要通过包名+类名+方法名来进行交互),其中声明成native的方法也没有被混淆。但是非native方法的方法名和局部变量都被混淆了。

2.3  MyFragment类

下面这个类继承自Fragment,并且有一个全局变量。onCreateView()方法是Fragment的生命周期函数,在onCreateView()方法中又调用了methodWithGlobalVariable()和methodWithLocalVariable()方法,这两个方法的内部分别引用了一个全局变量和一个局部变量。

public class MyFragment extends Fragment {
private String toastTip = "toast in MyFragment";
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_layout, container, false);
methodWithGlobalVariable();
methodWithLocalVariable();
return view;
} public void methodWithGlobalVariable() {
Toast.makeText(getActivity(), toastTip, Toast.LENGTH_SHORT).show();
} public void methodWithLocalVariable() {
String logMessage = "log in MyFragment";
logMessage = logMessage.toLowerCase();
System.out.println(logMessage);
}
}

混淆结果如下:

Androd安全——混淆技术完全解析

总结:这个类也是混淆的比较彻底的,基本没有任何保留。所有的方法名、全局变量、局部变量都被混淆了。 其中,onCreateView()这样的生命周期方法会不会被混淆和我们使用Fragment的方式有关,比如在本项目中使用的是android.support.v4.app.Fragment,support-v4包下的,就连Fragment的源码都一起混淆了,因此生命周期方法当然也不例外了。但如果你使用的是android.app.Fragment,这就是调用手机系统中预编译好的代码,混淆无法影响到系统内置的代码,因此这种情况下onCreateView()方法名就不会被混淆,但其它的方法以及变量仍然会被混淆。

2.4  MainActivity类

MainActivity和MyFragment类似,也是定义了methodWithGlobalVariable()和methodWithLocalVariable()这两个方法,然后MainActivity对MyFragment进行了添加,并在Button的点击事件里面调用了自身的、Utils的、以及NativeUtils中的方法。

public class MainActivity extends AppCompatActivity {
private String toastTip = "toast in MainActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
getSupportFragmentManager().beginTransaction().add(R.id.fragment, new MyFragment()).commit();
Button button = (Button) findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
methodWithGlobalVariable();
methodWithLocalVariable();
Utils utils = new Utils();
utils.methodNormal();
NativeUtils.methodNative();
NativeUtils.methodNotNative();
}
});
}
public void methodWithGlobalVariable() {
Toast.makeText(MainActivity.this, toastTip, Toast.LENGTH_SHORT).show();
}
public void methodWithLocalVariable() {
String logMessage = "log in MainActivity";
logMessage = logMessage.toLowerCase();
System.out.println(logMessage);
}
}

混淆结果如下:

Androd安全——混淆技术完全解析

总结:MainActivity的类名、onCreate()这种声明周期方法是没有混淆的(凡是需要在AndroidManifest.xml中去注册的所有类的类名以及从父类重写的方法名都自动不会被混淆,这份规则同样也适用于Service、BroadcastReceiver和ContentProvider),但是我们定义的方法、全局变量、局部变量都被混淆了。

最后,第三方的Jar包(包名、类名以及方法名)都是会被混淆的。

3.  认识混淆规则

这些混淆规则是在哪里定义的呢?其实就是刚才在build.gradle的release闭包下配置的proguard-android.txt文件,这个文件存放于<Android SDK>/tools/proguard目录下,我们打开来看一下,每句话的意义已经在注释里标明了:

-dontusemixedcaseclassnames //表示混淆时不使用大小写混合类名
-dontskipnonpubliclibraryclasses //表示不跳过library中的非public的类
-verbose //表示打印混淆的详细信息 # Optimization is turned off by default. Dex does not like code run
# through the ProGuard optimize and preverify steps (and performs some
# of these optimizations on its own).
-dontoptimize//不进行优化,建议使用此选项,因为优化可能不保证在所有版本的Dalvik上都正常运行。
-dontpreverify//不进行预校验,预校验是作用在Java平台上的,Android平台上去掉可加快混淆速度
# Note that if you want to enable optimization, you cannot just
# include optimization flags in your own project configuration file;
# instead you will need to point to the
# "proguard-android-optimize.txt" file instead of this one from your
# project.properties file. -keepattributes *Annotation* //表示对注解中的参数进行保留
//表示不混淆上面声明的两个类,这两个类基本用不上,用于接入Google原生的一些服务
-keep public class com.google.vending.licensing.ILicensingService
-keep public class com.android.vending.licensing.ILicensingService # For native methods, see http://proguard.sourceforge.net/manual/examples.html#native
//不混淆任何包含native方法的类的类名以及native方法名,我们已验证过了
-keepclasseswithmembernames class * {
native <methods>;
} # keep setters in Views so that animations can still work.
# see http://proguard.sourceforge.net/manual/examples.html#beans
//不混淆任何一个View中的setXxx()和getXxx()方法
//属性动画需要有相应的setter和getter的方法实现,混淆了就无法工作了
-keepclassmembers public class * extends android.view.View {
void set*(***);
*** get*();
} # We want to keep methods in Activity that could be used in the XML attribute onClick
//表示不混淆Activity中参数是View的方法
//如android:onClick=”click”,用户点击按钮会调用Activity中的click(View view)方法
//如果这个方法被混淆的话就找不到了
-keepclassmembers class * extends android.app.Activity {
public void *(android.view.View);
} # For enumeration classes, see http://proguard.sourceforge.net/manual/examples.html#enumerations
//不混淆枚举中的values()和valueOf()方法
-keepclassmembers enum * {
public static **[] values();
public static ** valueOf(java.lang.String);
}
//不混淆Parcelable实现类中的CREATOR字段
// CREATOR字段是绝对不能改变的(包括大小写),否则整个Parcelable工作机制都会失败
-keepclassmembers class * implements android.os.Parcelable {
public static final android.os.Parcelable$Creator CREATOR;
}
//表示不混淆R文件中的所有静态字段
// R文件是通过字段来记录每个资源的id的,字段名若被混淆了,id就找不到了
-keepclassmembers class **.R$* {
public static <fields>;
} # The support library contains references to newer platform versions.
# Dont warn about those in case this app is linking against an older
# platform version. We know about them, and they are safe.
//版本比较低在打包时就会给予警告
//不过support包中所有的代码都在版本兼容性上做足了判断
//因此不用担心代码会出问题,所以直接忽略警告就可以了
-dontwarn android.support.**

4.  自定义混淆规则

我们可以修改proguard-android.txt中的规则,但是直接在proguard-android.txt中修改会影响对本机上所有项目的混淆规则,app模块目录下都有一个proguard-rules.pro文件,这个文件就是用于让我们编写只适用于当前项目的混淆规则的,下面我们对混淆规则做一次修改。

4.1  对MyFragment类进行完全保留,不混淆其类名、方法名、以及变量名

//keep后声明完整的类名,然后保留类中的所有内容可以使用*通配符实现
-keep class com.example.guolin.androidtest.MyFragment {
*;
}


4.2  对Utils类中的未调用方法进行保留,防止其被移除掉

//使用keepclassmembers关键字,后跟Utils完整类名,然后在内部声明未调用的方法
-keepclassmembers class com.example.guolin.androidtest.Utils {
public void methodUnused();
}


4.3  对第三方库进行保留,不混淆android-support库

//引入第三方库,一种是通过本地jar包引入的,一种是通过remote引入
//这两种方式没什么区别,要保留代码都可以使用**这种通配符来实现,支持extends关键字
-keep class <第三方包名>.<类名> {
*;
} -keep class android.support.** {
*;
}


5.  拓展资料

Androd安全——混淆技术完全解析

虽说上面表格已经解释的很详细了,但是很多人对于keep和keepclasseswithmembers这两个关键字的区别还是搞不懂。确实,它们之间用法有点太像了,唯一的区别就在于类中声明的成员存不存在,我们还是通过一个例子来直接地看一下。

//保留所有含有native方法的类的类名和native方法名
//而如果某个类中没有含有native方法,那就还是会被混淆
-keepclasseswithmember class * {
native <methods>;
}

//所有类的类名都不会被混淆了
//因为keep看到class *就认为应将所有类名进行保留,不关心该类是否含有native方法
//当然这样写只会保证类名不会被混淆,类中的成员还是会被混淆的
-keep class * {
native <methods>;
}

本文转载整理自郭大侠博客:http://blog.csdn.net/guolin_blog/article/details/50451259

Androd安全——混淆技术完全解析

上一篇:洛谷 P1036 选数


下一篇:Python实现ParseDuration-支持解析字符串格式的时间单位,例如将小时或者分钟数转换为秒