移動安全-APK代碼混淆

概述

Android代碼混淆是讓Android項目避免輕易被逆向分析,防止代碼安全泄露的手段之一。它將工程中的Android代碼用簡單抽象的字母或單詞代替原有的代碼名稱。使代碼喪失可讀性從而使逆向工程師難以閱讀,增加逆向成本。當逆向成本大於逆向收益的時候,逆向代碼也就失去意義。

除此之外,由於代碼混淆用簡單抽象的單詞代替原有長而通俗易懂的代碼,因而減少APK的體積。而且,使用代碼混淆後,利用Gradle爲Android提供的插件,能將項目中未使用的資源安全移除,大大減少APK體積。

Android SDK 自帶了混淆工具Proguard。它位於SDK根目錄\tools\proguard下面。如果開啓了混淆,Proguard默認情況下會對所有代碼,包括第三方包都進行混淆,可是有些代碼或者第三方包是不能混淆的,這就需要我們手動編寫混淆規則來保持不能被混淆的部分

我們使用Jadx工具反編譯APK並查看源碼,然後比較下APK進行代碼混淆和不進行代碼混淆的效果。首先來看看未進行代碼混淆的時候:
在這裏插入圖片描述
接下來看看對該APK進行代碼混淆(如何混淆一會說)之後,Jadx反編譯出來的代碼情況:
在這裏插入圖片描述

代碼混淆

瞭解了代碼混淆的效果和作用,我們來看看如何實現混淆。

開啓混淆

使用Android Studio正式打包時默認是不開啓代碼混淆的,如果需要開啓代碼混淆,可以在 app 模塊下的build.gradle 文件中修改 minifyEnabled false 爲 true。代碼結構如下:

android {
    compileSdkVersion 25
    buildToolsVersion "25.0.2"
    defaultConfig {
        applicationId "com.wiwide.soldout"
        minSdkVersion 19
        targetSdkVersion 25
        versionCode 6
        versionName "2.0.1"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    }
    buildTypes {
        release {
            // 是否進行混淆
            minifyEnabled true
            // 混淆文件的位置
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
        debug {
            ···
        }
    }
}

這段代碼中,minifyEnabled就是我們開啓代碼混淆的開關,系統默認爲false,既不開啓代碼混淆。proguardFiles是指定項目中所使用的混淆規則配置文件。其中getDefaultProguardFile(‘proguard-android.txt’)指定系統默認的混淆規則,而proguard-rules.pro代表當前項目的混淆規則

proguard-android.txt位於Android SDK目錄下/tools/proguard目錄下,proguard-rules.pro位於app module下。一般而言,通過修改proguard-rules.pro來改變當前項目的混淆規則。此外,通過添加shrinkResources true能將項目中未使用的資源移除
在這裏插入圖片描述
【說明】代碼混淆後會產生如下幾個位於< module-name >/build/outputs/mapping/release/目錄下的文件:
在這裏插入圖片描述

文件名 說明
dump.txt 說明 APK 中所有類文件的內部結構。
mapping.txt 提供原始與混淆過的類、方法和字段名稱之間的轉換。
seeds.txt 列出未進行混淆的類和成員。
usage.txt 列出從 APK 移除的代碼。

【注意】一般而言,項目的debug版本無需開啓代碼混淆,因爲代碼混淆會額外增加apk的構建時間。但是,用於測試的版本也要與生產版本一樣開啓代碼混淆,以免在生產版本出現測試版本未曾出現的bug。

配置混淆

一般而言,默認 ProGuard 配置文件 (proguard-android.txt) 足以滿足需要,ProGuard 會移除所有(並且只會移除)未使用的代碼。但是,ProGuard可能會移除應用真正需要的代碼。舉例來說,它可能錯誤移除代碼的情況包括:例如:

  • 當應用引用的類只來自 AndroidManifest.xml 文件時;
  • 當應用調用的方法來自 Java 原生接口 (JNI) 時;
  • 當應用在運行時(例如使用反射或自檢)操作代碼時。

測試應用應該能夠發現因不當移除的代碼而導致的錯誤,但您也可以通過查看 < module-name >/build/outputs/mapping/release/ 中保存的 usage.txt 輸出文件來檢查移除了哪些代碼。

默認配置

學習自定義proguard-rules.pro之前,我們先來看看系統默認的代碼混淆規則proguard-android.txt文件。假如proguard-rules.pro文件沒用配置任何混淆規則,系統將以proguard-android.txt裏的規則爲準開啓混淆

#混淆時不使用大小寫混合類名
-dontusemixedcaseclassnames
#不跳過library中非public的類
-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
#不混淆包含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類的getXX()setXX()方法以保證屬性動畫正常運行。
-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中public void修飾的含有View參數的方法以保證在xml配置的onclick正常使用
-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成員
-keepclassmembers class * implements android.os.Parcelable {
  public static final android.os.Parcelable$Creator CREATOR;
}
#不混淆R文件的所有public類成員,如android.R類
-keepclassmembers class **.R$* {
    public static <fields>;
}

# The support library contains references to newer platform versions.
# Don't warn about those in case this app is linking against an older
# platform version.  We know about them, and they are safe.
#不對android.support包下的代碼警告。例如,app使用了低於某些類的support包版本導致出現警告問題。
-dontwarn android.support.**

# Understand the @Keep support annotation.
#不混淆Keep註解
-keep class android.support.annotation.Keep

#不混淆帶Keep註解的類
-keep @android.support.annotation.Keep class * {*;}
#如果類的方法使用了Keep註解,不混淆類與類方法
-keepclasseswithmembers class * {
    @android.support.annotation.Keep <methods>;
}
#如果類的屬性使用了Keep註解,不混淆類與類成員
-keepclasseswithmembers class * {
    @android.support.annotation.Keep <fields>;
}
#如果類的構造方法使用了Keep註解,不混淆類與類成員
-keepclasseswithmembers class * {
    @android.support.annotation.Keep <init>(...);
}

通過proguard-android.txt文件,我們大致知道了當開啓混淆時,系統默認的混淆規則,當自定義proguard-rule.pro文件時我們就無需重複以上的混淆規則。此外,通過proguard-android.txt我們大致知道如何自定義混淆規則。

配置規則

1、關於keep關鍵詞:

關鍵詞 說明
keep 不混淆類和類成員
keepnames 不混淆類和類成員,但類和類成員未被引用會被移除
keepclassmembers 不混淆類成員
keepclassmembernames 不混淆類成員,但未被引用的類成員會被移除
keepclasseswithmembers 不混淆帶有指定條件類成員的類和類成員
keepclasseswithmembernames 不混淆帶有指定條件的類和類成員,但未被引用的類和類成員會被移除

或者:
在這裏插入圖片描述
2、相關通配符

通配符 說明
* 匹配任意長度字符,但不包含包名分隔符”.”
** 匹配任意長度字符,但包含包名分隔符”.”
*** 匹配任意參數類型。例如*** getSchool(***)可匹配String getSchool(String)
匹配任意長度的任意類型的參數。例如void setSchool(…)可匹配void setSchool(String name,int age)
< filed > 匹配類或接口中所有字段
< method > 匹配類、接口的所有方法
< init > 匹配類中所有構造方法

3、具體解析

先看如下兩個比較常用的命令,很多童鞋可能會比較迷惑以下兩者的區別。

-keep class cn.hadcn.test.**
-keep class cn.hadcn.test.*
  1. 一顆星
    表示只是保持該包下的類名,而子包下的類名還是會被混淆;
  2. 兩顆星
    表示把本包和所含子包下的類名都保持;用以上方法保持類後,你會發現類名雖然未混淆,但裏面的具體方法和變量命名還是變了。
  3. 這時如果既想保持類名,又想保持裏面的內容不被混淆,我們就需要以下方法了:
-keep class cn.hadcn.test.* {*;}

在此基礎上,我們也可以使用Java的基本規則來保護特定類不被混淆,比如我們可以用extends,implements等這些Java規則。如下例子就避免所有繼承Activity的類被混淆:

-keep public class * extends android.app.Activity

如果我們要保留一個類中的內部類不被混淆則需要用$符號,如下例子表示保持ScriptFragment內部類JavaScriptInterface中的所有public內容不被混淆。

-keepclassmembers class cc.ninty.chat.ui.fragment.ScriptFragment$JavaScriptInterface {
   public *;
}

到這裏對混淆配置文件的語法已經基本瞭解,系統的proguard-android.txt已經爲我們完成大部分基礎的混淆配置工作。我們只需要寫好自己項目的proguard-rules.pro文件,對照 proguard-android.txt照葫蘆畫瓢就能自定義自己項目的 proguard-rules.pro

對於開啓混淆的項目,我們應該從類的創建就要思考這個類是否混淆,大到一個模塊,小到一個類的字段,以免到項目龐大以後再來思考混淆就更加難辦了。例如,當項目使用了反射機制,對於反射的類及其成員要根據實際情況避免混淆。又例如Gson所要解析成的實體類的字段也要避免混淆。

總結

代碼混淆也是一項很有難度的事情,隨着項目越來越龐大,混淆的難度也越來越大。一個錯誤的混淆很可能導致整個APP無法正常運行。因此,學習混淆應該循序漸進,積累更多經驗。

通過代碼混淆增加逆向的難度和減少APK的體積是成熟的Android應用應該具備的,同時也會增加開發者的開發難度。對於要不要開啓代碼混淆,應該從項目本身實際情況(成本、收益、開發難度)而定,不要盲目爲了混淆而開啓混淆項目的可控性下降,當然如果開發人員擁有過硬的混淆代碼配置經驗,代碼混淆對於APP應用本身是利大於弊的。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章