Android安全攻防战,反编译与混淆技术全然解析(下)

转载请注明出处:http://blog.csdn.net/guolin_blog/article/details/50451259
在上一篇文章其中,我们学习了Android程序反编译方面的知识,包括反编译代码、反编译资源、以及又一次打包等内容。通过这些内容我们也能看出来,事实上我们的程序并没有那么的安全。可能资源被反编译影响还不是非常大,又一次打包又由于有签名的保护导致非常难被盗版。但代码被反编译就有可能会泄漏核心技术了,因此一款安全性高的程序最起码要做到的一件事就是:对代码进行混淆。
混淆代码并非让代码无法被反编译,而是将代码中的类、方法、变量等信息进行重命名,把它们改成一些毫无意义的名字。由于对于我们而言可能Cellphone类的call()方法意味着非常多信息。而A类的b()方法则没有不论什么意义,可是对于计算机而言。它们都是平等的。计算机不会试图去理解Cellphone是什么意思,它仅仅会依照设定好的逻辑来去执行这些代码。所以说混淆代码能够在不影响程序正常执行的前提下让破解者非常头疼,从而大大提升了程序的安全性。
今天是我们Android安全攻防战系列的下篇,本篇文章的内容建立在上篇的基础之上。还没有阅读过的朋友能够先去參考 Android安全攻防战,反编译与混淆技术全然解析(上)


混淆

本篇文章中介绍的混淆技术都是基于Android Studio的,Eclipse的使用方法也基本相似,可是就不再为Eclipse专门做解说了。
我们要建立一个Android Studio项目,并在项目中加入一些能够帮助我们理解混淆知识的代码。这里我准备好了一些,我们将它们加入到Android Studio其中。


首先新建一个MyFragment类。代码例如以下所看到的:

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);
    }

}

能够看到,MyFragment是继承自Fragment的,而且MyFragment中有一个全局变量。onCreateView()方法是Fragment的生命周期函数。这个不用多说。在onCreateView()方法中又调用了methodWithGlobalVariable()和methodWithLocalVariable()方法,这两个方法的内部分别引用了一个全局变量和一个局部变量。
接下来新建一个Utils类,代码例如以下所看到的:

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);
    }

}

这是一个非常普通的工具类,没有不论什么继承关系。

Utils中有两个方法methodNormal()和methodUnused(),它们的内部逻辑都是一样的,唯一的据别是稍后methodNormal()方法会被调用,而methodUnused()方法不会被调用。


以下再新建一个NativeUtils类。代码例如以下所看到的:

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);
    }

}

这个类中相同有两个方法。一个是native方法,一个是非native方法。
最后。改动MainActivity中的代码,例如以下所看到的:

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();
                Connector.getDatabase();
            }
        });
    }

    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);
    }

}

能够看到,MainActivity和MyFragment相似,也是定义了methodWithGlobalVariable()和methodWithLocalVariable()这两个方法,然后MainActivity对MyFragment进行了加入,并在Button的点击事件里面调用了自身的、Utils的、以及NativeUtils中的方法。注意调用native方法须要有对应的so库实现,不然的话就会报UnsatisefiedLinkError,只是这里事实上我也并没有真正的so库实现。仅仅是演示一下让大家看看混淆结果。点击事件的最后一行调用的是LitePal中的方法。由于我们还要測试一下引用第三方Jar包的场景。到LitePal项目的主页去下载最新的Jar包。然后放到libs文件夹下就可以。
完整的build.gradle内容例如以下所看到的:

apply plugin: 'com.android.application'

android {
    compileSdkVersion 23
    buildToolsVersion "23.0.2"

    defaultConfig {
        applicationId "com.example.guolin.androidtest"
        minSdkVersion 15
        targetSdkVersion 23
        versionCode 1
        versionName "1.0"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    compile 'com.android.support:appcompat-v7:23.2.0'
}

好的。到这里准备工作就已经基本完毕了,接下来我们就開始对代码进行混淆吧。

混淆APK

在Android Studio其中混淆APK实在是太简单了,借助SDK中自带的Proguard工具,仅仅须要改动build.gradle中的一行配置就可以。能够看到,如今build.gradle中minifyEnabled的值是false,这里我们仅仅须要把值改成true。打出来的APK包就会是混淆过的了。

例如以下所看到的:

release {
    minifyEnabled true
    proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}

其中minifyEnabled用于设置是否启用混淆,proguardFiles用于选定混淆配置文件。注意这里是在release闭包内进行配置的,因此仅仅有打出正式版的APK才会进行混淆,Debug版的APK是不会混淆的。当然这也是非常合理的,由于Debug版的APK文件我们仅仅会用来内部測试。不用操心被人破解。
那么如今我们来打一个正式版的APK文件,在Android Studio导航栏中点击Build->Generate Signed APK。然后选择签名文件并输入password。假设没有签名文件就创建一个,终于点击Finish完毕打包。生成的APK文件会自己主动存放在app文件夹下。除此之外也能够在build.gradle文件其中加入签名文件配置。然后通过gradlew assembleRelease来打出一个正式版的APK文件,这种方式APK文件会自己主动存放在app/build/outputs/apk文件夹下。
那么如今已经得到了APK文件,接下来就用上篇文章中学到的反编译知识来对这个文件进行反编译吧。结果例如以下图所看到的:


非常明显能够看出,我们的代码混淆功能已经生效了。
以下我们尝试来阅读一下这个混淆过后的代码,最顶层的包名结构主要分为三部分,第一个a.a已经被混淆的面目全非了,可是能够推測出这个包下是LitePal的全部代码。

第二个android.support能够推測出是我们引用的android support库的代码。第三个com.example.guolin.androidtest则非常明显就是我们项目的主包名了。以下将里面全部的类一个个打开看一下。
首先MainActivity中的代码例如以下所看到的:


能够看到,MainActivity的类名是没有混淆的,onCreate()方法也没有被混淆,可是我们定义的方法、全局变量、局部变量都被混淆了。
再来打开下一个类NativeUtils。例如以下所看到的:

NativeUtils的类名没有被混淆,其中声明成native的方法也没有被混淆,可是非native方法的方法名和局部变量都被混淆了。
接下来是a类的代码,例如以下所看到的:

非常明显,这个是MainActivity中button点击事件的匿名类,在onClick()方法中的调用代码尽管都被混淆了。可是调用顺序是不会改变的。对比源代码就能够看出哪一行是调用的什么方法了。


再接下来是b类。代码例如以下所看到的:


尽管被混淆的非常严重,可是我们还是能够看出这个是MyFragment类。其中全部的方法名、全局变量、局部变量都被混淆了。
最后再来看下c类。代码例如以下所看到的:

c类中仅仅有一个a方法。从字符串的内容我们能够看出,这个是Utils类中的methodNormal()方法。
我为什么要创建这种一个项目呢?由于从这几个类其中非常能看出一些问题,接下来我们就分析一下上面的混淆结果。
首先像Utils这种普通类肯定是会被混淆的,无论是类名、方法名还是变量都不会放过。除了混淆之外Utils类还说明了一个问题。就是minifyEnabled会对资源进行压缩,由于Utils类中我们明明定义了两个方法。可是反编译之后就仅仅剩一个方法了,由于另外一个方法没有被调用。所以觉得是多余的代码,在打包的时候就给移除掉了。不仅仅是代码,没有被调用的资源相同也会被移除掉。因此minifyEnabled除了混淆代码之外,还能够起到压缩APK包的作用。


接着看一下MyFragment,这个类也是混淆的比較彻底的,基本没有不论什么保留。

那有些朋友可能会有疑问。Fragment怎么说也算是系统组件吧,就算普通方法名被混淆了,至少像onCreateView()这种生命周期方法不应该被混淆吧?事实上生命周期方法会不会被混淆和我们使用Fragment的方式有关,比方在本项目中,我使用的是android.support.v4.app.Fragment,support-v4包下的,就连Fragment的源代码都被一起混淆了,因此生命周期方法当然也不例外了。

但假设你使用的是android.app.Fragment,这就是调用手机系统中预编译好的代码了,非常明显我们的混淆无法影响到系统内置的代码。因此这种情况下onCreateView()方法名就不会被混淆。但其他的方法以及变量仍然会被混淆。
接下来看一下MainActivity。相同也是系统组件之中的一个,但MainActivity的保留程度就比MyFragment好多了,至少像类名、生命周期方法名都没有被混淆。这是为什么呢?依据我亲身測试得出结论。凡是须要在AndroidManifest.xml中去注冊的全部类的类名以及从父类重写的方法名都自己主动不会被混淆。因此,除了Activity之外。这份规则相同也适用于Service、BroadcastReceiver和ContentProvider。


最后看一下NativeUtils类,这个类的类名也没有被混淆,这是由于它有一个声明成native的方法。仅仅要一个类中有存在native方法,它的类名就不会被混淆。native方法的方法名也不会被混淆,由于C++代码要通过包名+类名+方法名来进行交互。 可是类中的别的代码还是会被混淆的。
除此之外,第三方的Jar包都是会被混淆的,LitePal无论是包名还是类名还是方法名都被完全然全混淆掉了。
这些就是Android Studio打正式APK时默认的混淆规则。
那么这些混淆规则是在哪里定义的呢?事实上就是刚才在build.gradle的release闭包下配置的proguard-android.txt文件,这个文件存放于<Android SDK>/tools/proguard文件夹下,我们打开来看一下:

# This is a configuration file for ProGuard.
# http://proguard.sourceforge.net/index.html#manual/usage.html

-dontusemixedcaseclassnames
-dontskipnonpubliclibraryclasses
-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
-dontpreverify
# 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*
-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
-keepclasseswithmembernames class * {
    native <methods>;
}

# keep setters in Views so that animations can still work.
# see http://proguard.sourceforge.net/manual/examples.html#beans
-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
-keepclassmembers class * extends android.app.Activity {
   public void *(android.view.View);
}

# For enumeration classes, see http://proguard.sourceforge.net/manual/examples.html#enumerations
-keepclassmembers enum * {
    public static **[] values();
    public static ** valueOf(java.lang.String);
}

-keepclassmembers class * implements android.os.Parcelable {
  public static final android.os.Parcelable$Creator CREATOR;
}

-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.
-dontwarn android.support.**

这个就是默认的混淆配置文件了,我们来一起逐行阅读一下。
-dontusemixedcaseclassnames 表示混淆时不使用大写和小写混合类名。


-dontskipnonpubliclibraryclasses 表示不跳过library中的非public的类。
-verbose 表示打印混淆的具体信息。
-dontoptimize 表示不进行优化,建议使用此选项,由于依据proguard-android-optimize.txt中的描写叙述。优化可能会造成一些潜在风险,不能保证在全部版本号的Dalvik上都正常执行。


-dontpreverify 表示不进行预校验。

这个预校验是作用在Java平台上的,Android平台上不须要这项功能,去掉之后还能够加快混淆速度。


-keepattributes *Annotation* 表示对注解中的參数进行保留。

-keep public class com.google.vending.licensing.ILicensingService
-keep public class com.android.vending.licensing.ILicensingService

表示不混淆上述声明的两个类,这两个类我们基本也用不上,是接入Google原生的一些服务时使用的。

-keepclasseswithmembernames class * {
    native <methods>;
}

表示不混淆不论什么包括native方法的类的类名以及native方法名,这个和我们刚才验证的结果是一致的。

-keepclassmembers public class * extends android.view.View {
   void set*(***);
   *** get*();
}

表示不混淆不论什么一个View中的setXxx()和getXxx()方法,由于属性动画须要有对应的setter和getter的方法实现。混淆了就无法工作了。

-keepclassmembers class * extends android.app.Activity {
   public void *(android.view.View);
}

表示不混淆Activity中參数是View的方法。由于有这样一种使用方法,在XML中配置android:onClick=”buttonClick”属性,当用户点击该button时就会调用Activity中的buttonClick(View view)方法。假设这种方法被混淆的话就找不到了。

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

表示不混淆枚举中的values()和valueOf()方法,枚举我用的非常少,这个就不评论了。

-keepclassmembers class * implements android.os.Parcelable {
  public static final android.os.Parcelable$Creator CREATOR;
}

表示不混淆Parcelable实现类中的CREATOR字段。毫无疑问,CREATOR字段是绝对不能改变的,包括大写和小写都不能变。不然整个Parcelable工作机制都会失败。

-keepclassmembers class **.R$* {
    public static <fields>;
}

表示不混淆R文件里的全部静态字段,我们都知道R文件是通过字段来记录每一个资源的id的,字段名要是被混淆了,id也就找不着了。
-dontwarn android.support.** 表示对android.support包下的代码不警告。由于support包中有非常多代码都是在高版本号中使用的。假设我们的项目指定的版本号比較低在打包时就会给予警告。只是support包中全部的代码都在版本号兼容性上做足了推断。因此不用操心代码会出问题,所以直接忽略警告就能够了。
好了,这就是proguard-android.txt文件里全部默认的配置,而我们混淆代码也是依照这些配置的规则来进行混淆的。经过我上面的解说之后,相信大家对这些配置的内容基本都能理解了。只是proguard语法中还真有几处非常难理解的地方,我自己也是研究了好久才搞明确,以下和大家分享一下这些难懂的语法部分。
proguard中一共同拥有三组六个keep关键字。非常多人搞不清楚它们的差别,这里我们通过一个表格来直观地看下:

关键字 描写叙述
keep 保留类和类中的成员。防止它们被混淆或移除。
keepnames 保留类和类中的成员,防止它们被混淆,但当成员没有被引用时会被移除。
keepclassmembers 仅仅保留类中的成员。防止它们被混淆或移除。
keepclassmembernames 仅仅保留类中的成员,防止它们被混淆,但当成员没有被引用时会被移除。

keepclasseswithmembers 保留类和类中的成员,防止它们被混淆或移除,前提是指名的类中的成员必须存在。假设不存在则还是会混淆。

keepclasseswithmembernames 保留类和类中的成员,防止它们被混淆,但当成员没有被引用时会被移除,前提是指名的类中的成员必须存在,假设不存在则还是会混淆。

除此之外,proguard中的通配符也比較让人难懂,proguard-android.txt中就使用到了非常多通配符,我们来看一下它们之间的差别:

通配符 描写叙述
<field> 匹配类中的全部字段
<method> 匹配类中的全部方法
<init> 匹配类中的全部构造函数
* 匹配随意长度字符,但不含包名分隔符(.)。比方说我们的完整类名是com.example.test.MyActivity,使用com.*,或者com.exmaple.*都是无法匹配的。由于*无法匹配包名中的分隔符,正确的匹配方式是com.exmaple.*.*,或者com.exmaple.test.*,这些都是能够的。但假设你不写不论什么其他内容。仅仅有一个*。那就表示匹配全部的东西。

** 匹配随意长度字符。而且包括包名分隔符(.)。比方proguard-android.txt中使用的-dontwarn android.support.**就能够匹配android.support包下的全部内容,包括随意长度的子包。
*** 匹配随意參数类型。

比方void set*(***)就能匹配随意传入的參数类型。*** get*()就能匹配随意返回值的类型。

匹配随意长度的随意类型參数。比方void test(…)就能匹配随意void test(String a)或者是void test(int a, String b)这些方法。

虽说上面表格已经解释的非常具体了,可是非常多人对于keep和keepclasseswithmembers这两个关键字的差别还是搞不懂。确实,它们之间使用方法有点太像了。我做了非常多次试验它们的结果都是相同的。事实上唯一的差别就在于类中声明的成员存不存在,我们还是通过一个样例来直接地看一下,先看keepclasseswithmember关键字:

-keepclasseswithmember class * {
    native <methods>;
}

这段代码的意思事实上非常明显,就是保留全部含有native方法的类的类名和native方法名,而假设某个类中没有含有native方法,那就还是会被混淆。


可是假设改成keep关键字。结果会全然不一样:

-keep class * {
    native <methods>;
}

使用keep关键字后,你会发现代码中全部类的类名都不会被混淆了,由于keep关键字看到class *就觉得应该将全部类名进行保留。而不会关心该类中是否含有native方法。当然这样写仅仅会保证类名不会被混淆,类中的成员还是会被混淆的。
比較难懂的使用方法大概就这些吧。掌握了这些内容之后我们就能继续前进了。
回到Android Studio项目其中,刚才打出的APK尽管已经成功混淆了,可是混淆的规则都是依照proguard-android.txt中默认的规则来的,当然我们也能够改动proguard-android.txt中的规则。可是直接在proguard-android.txt中改动会对我们本机上全部项目的混淆规则都生效,那么有没有什么办法仅仅针对当前项目的混淆规则做改动呢?当然是有办法的了,你会发现不论什么一个Android Studio项目在app模块文件夹下都有一个proguard-rules.pro文件。这个文件就是用于让我们编写仅仅适用于当前项目的混淆规则的,那么接下来我们就利用刚才学到的全部知识来对混淆规则做改动吧。
这里我们先列出来要实现的目标:

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

  • 对Utils类中的未调用方法进行保留,防止其被移除掉。
  • 对第三方库进行保留,不混淆android-support库,以及LitePal库中的代码。

以下我们就来逐一实现这些目标。


首先要对MyFragment类进行全然保留能够使用keep关键字,keep后声明完整的类名,然后保留类中的全部内容能够使用*通配符实现,例如以下所看到的:

-keep class com.example.guolin.androidtest.MyFragment {
    *;
}

然后保留Utils类中的未调用方法能够使用keepclassmembers关键字,后跟Utils完整类名,然后在内部声明未调用的方法,例如以下所看到的:

-keepclassmembers class com.example.guolin.androidtest.Utils {
    public void methodUnused();
}

最后不要混淆第三方库。眼下我们使用了两种方式来引入第三方库。一种是通过本地jar包引入的。一种是通过remote引入的。事实上这两种方式没什么差别,要保留代码都能够使用**这种通配符来实现,例如以下所看到的:

-keep class org.litepal.** {
    *;
}

-keep class android.support.** {
    *;
}

全部内容都在这里了。如今我们又一次打一个正式版的APK文件,然后再反编译看看效果:


能够看到,如今android-support包中全部代码都被保留下来了。无论是包名、类名、还是方法名都没有被混淆。LitePal中的代码也是相同的情况:

再来看下MyFragment中的代码,例如以下所看到的:

能够看到。MyFragment中的代码也没有被混淆。依照我们的要求被全然保留下来了。
最后再来看一下Utils类中的代码:

非常明显。Utils类并没有被全然保留下来。类名还是被混淆了。methodNormal()方法也被混淆了。可是methodUnused()没有被混淆,当然也没有被移除。由于我们的混淆配置生效了。
经过这些样例的演示,相信大家已经对Proguard的使用方法有了相当不错的理解了,那么依据自己的业务需求来去编写混淆配置相信也不是什么难事了吧?
Progaurd的使用非常灵活,基本上能够覆盖你所能想到的全部业务逻辑。这里再举个样例,之前一直有人问我使用LitePal时的混淆配置怎么写。事实上真的非常easy,LitePal作为开源库并不须要混淆,上面的配置已经演示了怎样不混淆LitePal代码,然后全部代码中的Model是须要进行反射的,也不能混淆,那么仅仅须要这样写就可以了:

-keep class * extends org.litepal.crud.DataSupport {
    *;
}

由于LitePal中全部的Model都是应该继承DataSupport类的,所以这里我们将全部继承自DataSupport的类都进行保留就能够了。
关于混淆APK的使用方法就讲这么多。假设你还想继续了解关于Proguard的很多其他使用方法,能够參考官方文档:http://proguard.sourceforge.net/index.html#manual/usage.html

混淆Jar

在本篇文章的第二部分我想讲一讲混淆Jar包的内容。由于APK不一定是我们交付的唯一产品。就比方说我自己,我在公司是负责写SDK的,对于我来说交付出去的产品就是Jar包,而假设Jar包不混淆的话将会非常easy就被别人反编译出来。从而泄漏程序逻辑。


实际上Android对混淆Jar包的支持在非常早之前就有了,无论你使用多老版本号的SDK,都能在 <Android SDK>/tools文件夹下找到proguard这个文件夹。

然后打开里面的bin文件夹。你会看到例如以下文件:


其中proguardgui.bat文件是同意我们以图形化的方式来对Jar包进行混淆的一个工具,今天我们就来解说一下这个工具的使用方法。
在開始解说这个工具之前,首先我们须要先准备一个Jar包,当然你从哪里搞到一个Jar包都是能够的,只是这里为了和刚才的混淆逻辑统一,我们就把本篇文章中的项目代码打成一个Jar包吧。


Eclipse中导出Jar包的方法非常easy,相信全部人都会,可是Android Studio其中就比較让人头疼了。由于Android Studio并没有提供一个专门用于导出Jar包的工具,因此我们仅仅能自己动手了。


我们须要知道。不论什么一个Android Studio项目,仅仅要编译成功之后就会在项目模块的build/intermediates/classes/debug文件夹下生成代码编译过后的class文件。因此仅仅需通过打包命令将这些class文件打包成Jar包就可以了,打开cmd,切换到项目的根文件夹,然后输入例如以下命令:

jar -cvf androidtest.jar -C app/build/intermediates/classes/debug .

在项目的根文件夹下就会生成androidtest.jar这个文件,这样我们就把Jar包准备好了。
如今双击proguardgui.bat打开混淆工具。假设是Mac或Ubuntu系统则使用sh proguardgui.sh命令打开混淆工具,界面例如以下图所看到的:


事实上从主界面上我们就能看出。这个Proguard工具支持Shrinking、Optimization、Obfuscation、Preverification四项操作。在左側的側边栏上也能看到对应的这些选项。Proguard的工作机制仍然还是要依赖于配置文件。当然我们也能够通过proguardgui工具来生成配置文件,只是由于配置选项太多了,每一个都去一一设置太复杂,而且大多数还都是我们用不到的配置。因此最简单的方式就是直接拿现有的配置文件,然后再做些改动就可以了。
那么我们从<Android SDK>/tools/proguard文件夹下将proguard-android.txt文件复制一份出来,然后点击主界面上的Load configurationbutton来载入复制出来的这份proguard-android.txt文件。完毕后点击Next将进入Input/Output界面。
Input/Output界面是用于导入要混淆的Jar包、配置混淆后文件的输出路径、以及导入该Jar包所依赖的全部其他Jar包的。我们要混淆的当然就是androidtest.jar这个文件,那么这个Jar包又依赖了哪些Jar包呢?这里就须要整理一下了。

  • 首先我们写的都是Java代码。Java代码的执行要基于Jre基础之上,没有Jre计算机将无法识别Java的语法。因此第一个要依赖的就是Jre的rt.jar。
  • 然后由于我们导出的Jar包中有Android相关的代码。比方Activity、Fragment等,因此还须要加入Android的编译库,android.jar。
  • 除此之外。我们使用的AppCompatActivity和Fragment分别来自于appcompat-v7包和support-v4包,那么这两个Jar包也是须要引入的。
  • 最后就是代码中还引入了litepal-1.3.1.jar。

整理清楚了之后我们就来一个个加入。Input/Output有上下两个操作界面,上面是用于导入要混淆的Jar包和配置混淆后文件的输出路径的,以下则是导入该Jar包所依赖的全部其他Jar包的。全部导入后结果例如以下图所看到的:


这些依赖的Jar包所存在的路径每台电脑都不一样,你所须要做的就是在你自己的电脑上成功找到这些依赖的Jar包并导入就可以。
只是细心的朋友可能会发现,我在上面整理出了五个依赖的Jar包,可是在图中却加入了六个。这是我在写这篇文章时碰到的一个新的坑,也是定位了好久才解决的,我觉得有必要重点提一下。

由于我平时混淆Jar包时里面非常少会有Activity,所以没遇到过这个问题,可是本篇文章中的演示Jar包中不仅包括了Activty。还是继承自AppCompatActivity的。而AppCompatActivity的继承结构并不简单,例如以下图所看到的:


其中AppCompatActivity是在appcompat-v7包中的,它的父类FragmentActivity是在support-v4包中的。这两个包我们都已经加入依赖了。

可是FragmentActivity的父类就坑爹了。假设你去看BaseFragmentActivityHoneycomb和BaseFragmentActivityDonut这两个类的源代码。你会发现它们都是在support-v4包中的:



可是假设你去support-v4的Jar包中找一下,你会发现压根就没有这两个类,所以我当时一直混淆报错就是由于这两个类不存在。继承结构在这里断掉了。而这两个类事实上被规整到了另外一个internal的Jar包中。所以当你要混淆的Jar包中有Activity,而且还是继承自AppCompatActivity或FragmentActivity的话。那么就一定要记得导入这个internal Jar包的依赖,例如以下图所看到的:

接下来点击Next进入Shrink界面。这个界面没什么须要配置的东西,但记得要将Shrink选项钩掉,由于我们这个Jar包是独立存在的,没有不论什么项目引用。假设钩中Shrink选项的话就会觉得我们全部的代码都是没用的,从而把全部代码全压缩掉。导出一个空的Jar包。
继续点击Next进入Obfuscation界面。在这里能够加入一些混淆的逻辑,和混淆APK时不同的是,这里并不会自己主动帮我们排除混淆四大组件,因此必须要手动声明一下才行。

点击最下方的Addbutton,然后在弹出的界面上编写排除逻辑,例如以下图所看到的:


非常easy。就是在继承那一栏写上android.app.Activity就可以了,其他的组件原理也相同。
继续点击Next进入Optimiazation界面。不用改动不论什么东西。由于我们本身就不启用Optimization功能。继续点击Next进入Information界面,也不用改动不论什么东西,由于我们也不启用Preverification功能。


接着点击Next,进入Process界面,在这里能够通过点击View configurationbutton来预览一下眼下我们的混淆配置文件,内容例如以下所看到的:

-injars /Users/guolin/AndroidStudioProjects/AndroidTest/androidtest.jar
-outjars /Users/guolin/androidtest_obfuscated.jar

-libraryjars /Library/Java/JavaVirtualMachines/jdk1.7.0_79.jdk/Contents/Home/jre/lib/rt.jar
-libraryjars /Users/guolin/Library/Android/sdk/platforms/android-23/android.jar
-libraryjars /Users/guolin/AndroidStudioProjects/AndroidTest/app/build/intermediates/exploded-aar/com.android.support/appcompat-v7/23.2.0/jars/classes.jar
-libraryjars /Users/guolin/AndroidStudioProjects/AndroidTest/app/build/intermediates/exploded-aar/com.android.support/support-v4/23.2.0/jars/classes.jar
-libraryjars /Users/guolin/AndroidStudioProjects/AndroidTest/app/build/intermediates/exploded-aar/com.android.support/support-v4/23.2.0/jars/libs/internal_impl-23.2.0.jar
-libraryjars /Users/guolin/AndroidStudioProjects/AndroidTest/app/libs/litepal-1.3.1.jar

-dontshrink
-dontoptimize
-dontusemixedcaseclassnames
-keepattributes *Annotation*
-dontpreverify
-verbose
-dontwarn android.support.**


-keep public class com.google.vending.licensing.ILicensingService

-keep public class com.android.vending.licensing.ILicensingService

# keep setters in Views so that animations can still work.
# see http://proguard.sourceforge.net/manual/examples.html#beans
-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
-keepclassmembers class * extends android.app.Activity {
    public void *(android.view.View);
}

-keepclassmembers class * extends android.os.Parcelable {
    public static final android.os.Parcelable$Creator CREATOR;
}

-keepclassmembers class **.R$* {
    public static <fields>;
}

-keep class * extends android.app.Activity

-keep class * extends android.app.Service

-keep class * extends android.content.BroadcastReceiver

-keep class * extends android.content.ContentProvider

# Also keep - Enumerations. Keep the special static methods that are required in
# enumeration classes.
-keepclassmembers enum  * {
    public static **[] values();
    public static ** valueOf(java.lang.String);
}

# Keep names - Native method names. Keep all native class/method names.
-keepclasseswithmembers,allowshrinking class * {
    native <methods>;
}

恩,由此可见事实上GUI工具仅仅是给我们提供了一个方便操作的平台。背后工作的原理还是通过这些配置来实现的,相信上面的配置内容大家应该都能看得懂了吧。


接下来我们还能够点击Save configurationbutton来保存一下当前的配置文件,这样下次混淆的时候就能够直接Load进来而不用改动不论什么东西了。


最后点击Process!button来開始混淆处理,中间会提示一大堆的Note信息,我们不用理会,仅仅要看到终于显示Processing completed successfully,就说明混淆Jar包已经成功了,例如以下图所看到的:


混淆后的文件我将它配置在了/Users/guolin/androidtest_obfuscated.jar这里,假设反编译一下这个文件,你会发现和刚才反编译APK得到的结果是差点儿相同的:MainActivity的类名以及从父类继承的方法名不会被混淆,NativeUtils的类名和其中的native方法名不会被混淆,Utils的methodUnsed方法不会被移除,由于我们禁用了Shrink功能,其余的代码都会被混淆。由于结果实在是太相似了。我就不再贴图了,參考本篇文章第一部分的截图就可以。


好了,本篇文章的内容就到这里,混淆技术掌握这么多相信已经足够大家在平时的工作其中使用了。当然除了使用混淆之外,另一些加固软件也能提升程序的安全性。只是这些软件都是第三方的,并非Google原生支持。所以我就不进行解说和推荐了。

那么我们Android安全攻防战系列的文章到此结束,感谢大家有耐心看到最后。

关注我的技术公众号,每天都有优质技术文章推送。关注我的娱乐公众号,工作、学习累了的时候放松一下自己。

微信扫一扫下方二维码就可以关注:

        

原文地址:https://www.cnblogs.com/jzssuanfa/p/7120743.html