Android安全攻防戰,反編譯與混淆技術徹底解析(下)

原文出處: 郭霖   html

在上一篇文章當中,咱們學習了Android程序反編譯方面的知識,包括反編譯代碼、反編譯資源、以及從新打包等內容。經過這些內容咱們也能看出來,其實咱們的程序並無那麼的安全。可能資源被反編譯影響還不是很大,從新打包又因爲有簽名的保護致使很難被盜版,但代碼被反編譯就有可能會泄漏核心技術了,所以一款安全性高的程序最起碼要作到的一件事就是:對代碼進行混淆。
混淆代碼並非讓代碼沒法被反編譯,而是將代碼中的類、方法、變量等信息進行重命名,把它們改爲一些毫無心義的名字。由於對於咱們而言可能Cellphone類的call()方法意味着不少信息,而A類的b()方法則沒有任何意義,可是對於計算機而言,它們都是平等的,計算機不會試圖去理解Cellphone是什麼意思,它只會按照設定好的邏輯來去執行這些代碼。因此說混淆代碼能夠在不影響程序正常運行的前提下讓破解者很頭疼,從而大大提高了程序的安全性。
今天是咱們Android安全攻防戰系列的下篇,本篇文章的內容創建在上篇的基礎之上,尚未閱讀過的朋友能夠先去參考 Android安全攻防戰,反編譯與混淆技術徹底解析(上) 。java

混淆

本篇文章中介紹的混淆技術都是基於Android Studio的,Eclipse的用法也基本相似,可是就再也不爲Eclipse專門作講解了。
咱們要創建一個Android Studio項目,並在項目中添加一些可以幫助咱們理解混淆知識的代碼。這裏我準備好了一些,咱們將它們添加到Android Studio當中。
首先新建一個MyFragment類,代碼以下所示:android

Javagit

1github

2編程

3安全

4閉包

5架構

6app

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

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類,代碼以下所示:

Java

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

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類,代碼以下所示:

Java

1

2

3

4

5

6

7

8

9

10

11

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中的代碼,以下所示:

Java

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

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內容以下所示:

Java

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

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包就會是混淆過的了。以下所示:

Java

1

2

3

4

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,而後選擇簽名文件並輸入密碼,若是沒有簽名文件就建立一個,最終點擊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中按鈕點擊事件的匿名類,在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目錄下,咱們打開來看一下:

Java

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

# 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* 表示對註解中的參數進行保留。

Java

1

2

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

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

表示不混淆上述聲明的兩個類,這兩個類咱們基本也用不上,是接入Google原生的一些服務時使用的。

Java

1

2

3

-keepclasseswithmembernames class * {

    native <methods>;

}

表示不混淆任何包含native方法的類的類名以及native方法名,這個和咱們剛纔驗證的結果是一致的。

Java

1

2

3

4

-keepclassmembers public class * extends android.view.View {

   void set*(***);

   *** get*();

}

表示不混淆任何一個View中的setXxx()和getXxx()方法,由於屬性動畫須要有相應的setter和getter的方法實現,混淆了就沒法工做了。

Java

1

2

3

-keepclassmembers class * extends android.app.Activity {

   public void *(android.view.View);

}

表示不混淆Activity中參數是View的方法,由於有這樣一種用法,在XML中配置android:onClick=」buttonClick」屬性,當用戶點擊該按鈕時就會調用Activity中的buttonClick(View view)方法,若是這個方法被混淆的話就找不到了。

Java

1

2

3

4

-keepclassmembers enum * {

    public static **[] values();

    public static ** valueOf(java.lang.String);

}

表示不混淆枚舉中的values()和valueOf()方法,枚舉我用的很是少,這個就不評論了。

Java

1

2

3

-keepclassmembers class * implements android.os.Parcelable {

  public static final android.os.Parcelable$Creator CREATOR;

}

表示不混淆Parcelable實現類中的CREATOR字段,毫無疑問,CREATOR字段是絕對不能改變的,包括大小寫都不能變,否則整個Parcelable工做機制都會失敗。

Java

1

2

3

-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關鍵字:

Java

1

2

3

-keepclasseswithmember class * {

    native <methods>;

}

這段代碼的意思其實很明顯,就是保留全部含有native方法的類的類名和native方法名,而若是某個類中沒有含有native方法,那就仍是會被混淆。
可是若是改爲keep關鍵字,結果會徹底不同:

Java

1

2

3

-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後聲明完整的類名,而後保留類中的全部內容可使用*通配符實現,以下所示:

Java

1

2

3

-keep class com.example.guolin.androidtest.MyFragment {

    *;

}

而後保留Utils類中的未調用方法可使用keepclassmembers關鍵字,後跟Utils完整類名,而後在內部聲明未調用的方法,以下所示:

Java

1

2

3

-keepclassmembers class com.example.guolin.androidtest.Utils {

    public void methodUnused();

}

最後不要混淆第三方庫,目前咱們使用了兩種方式來引入第三方庫,一種是經過本地jar包引入的,一種是經過remote引入的,其實這兩種方式沒什麼區別,要保留代碼均可以使用**這種通配符來實現,以下所示:

Java

1

2

3

4

5

6

7

-keep class org.litepal.** {

    *;

}

 

-keep class android.support.** {

    *;

}

全部內容都在這裏了,如今咱們從新打一個正式版的APK文件,而後再反編譯看看效果:


能夠看到,如今android-support包中全部代碼都被保留下來了,無論是包名、類名、仍是方法名都沒有被混淆。LitePal中的代碼也是一樣的狀況:


再來看下MyFragment中的代碼,以下所示:


能夠看到,MyFragment中的代碼也沒有被混淆,按照咱們的要求被徹底保留下來了。
最後再來看一下Utils類中的代碼:


很明顯,Utils類並無被徹底保留下來,類名仍是被混淆了,methodNormal()方法也被混淆了,可是methodUnused()沒有被混淆,固然也沒有被移除,由於咱們的混淆配置生效了。
通過這些例子的演示,相信你們已經對Proguard的用法有了至關不錯的理解了,那麼根據本身的業務需求來去編寫混淆配置相信也不是什麼難事了吧?
Progaurd的使用很是靈活,基本上可以覆蓋你所能想到的全部業務邏輯。這裏再舉個例子,以前一直有人問我使用LitePal時的混淆配置怎麼寫,其實真的很簡單,LitePal做爲開源庫並不須要混淆,上面的配置已經演示瞭如何不混淆LitePal代碼,而後全部代碼中的Model是須要進行反射的,也不能混淆,那麼只須要這樣寫就好了:

Java

1

2

3

-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包不混淆的話將會很容易就被別人反編譯出來,從而泄漏程序邏輯。
實際上Android對混淆Jar包的支持在很早以前就有了,無論你使用多老版本的SDK,都能在 <Android SDK>/tools目錄下找到proguard這個文件夾。而後打開裏面的bin目錄,你會看到以下文件:


其中proguardgui.bat文件是容許咱們以圖形化的方式來對Jar包進行混淆的一個工具,今天咱們就來說解一下這個工具的用法。
在開始講解這個工具以前,首先咱們須要先準備一個Jar包,固然你從哪裏搞到一個Jar包都是能夠的,不過這裏爲了和剛纔的混淆邏輯統一,咱們就把本篇文章中的項目代碼打成一個Jar包吧。
Eclipse中導出Jar包的方法很是簡單,相信全部人都會,但是Android Studio當中就比較讓人頭疼了,由於Android Studio並無提供一個專門用於導出Jar包的工具,所以咱們只能本身動手了。
咱們須要知道,任何一個Android Studio項目,只要編譯成功以後就會在項目模塊的build/intermediates/classes/debug目錄下生成代碼編譯事後的class文件,所以只需經過打包命令將這些class文件打包成Jar包就好了,打開cmd,切換到項目的根目錄,而後輸入以下命令:

Java

1

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 configuration按鈕來加載複製出來的這份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時不一樣的是,這裏並不會自動幫咱們排除混淆四大組件,所以必需要手動聲明一下才行。點擊最下方的Add按鈕,而後在彈出的界面上編寫排除邏輯,以下圖所示:


很簡單,就是在繼承那一欄寫上android.app.Activity就好了,其它的組件原理也相同。
繼續點擊Next進入Optimiazation界面,不用修改任何東西,由於咱們自己就不啓用Optimization功能。繼續點擊Next進入Information界面,也不用修改任何東西,由於咱們也不啓用Preverification功能。
接着點擊Next,進入Process界面,在這裏能夠經過點擊View configuration按鈕來預覽一下目前咱們的混淆配置文件,內容以下所示:

Java

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

-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 configuration按鈕來保存一下當前的配置文件,這樣下次混淆的時候就能夠直接Load進來而不用修改任何東西了。
最後點擊Process!按鈕來開始混淆處理,中間會提示一大堆的Note信息,咱們不用理會,只要看到最終顯示Processing completed successfully,就說明混淆Jar包已經成功了,以下圖所示:


混淆後的文件我將它配置在了/Users/guolin/androidtest_obfuscated.jar這裏,若是反編譯一下這個文件,你會發現和剛纔反編譯APK獲得的結果是差很少的:MainActivity的類名以及從父類繼承的方法名不會被混淆,NativeUtils的類名和其中的native方法名不會被混淆,Utils的methodUnsed方法不會被移除,由於咱們禁用了Shrink功能,其他的代碼都會被混淆。因爲結果實在是太類似了,我就再也不貼圖了,參考本篇文章第一部分的截圖便可。

http://cxy.liuzhihengseo.com/469.html

問啊-定製化IT教育平臺,牛人一對一服務,有問必答,開發編程社交頭條 官方網站:www.wenaaa.com 下載問啊APP,參與官方懸賞,賺百元現金。QQ羣290551701 彙集不少互聯網精英,技術總監,架構師,項目經理!開源技術研究,歡迎業內人士,大牛及新手有志於從事IT行業人員進入!

相關文章
相關標籤/搜索