使用 ProGuard 進(jìn)行代碼混淆

背景

?一個(gè) apk 包,很容易可以被逆向出源碼。逆向的過程也比較簡單,最近在 github 上看到一個(gè)工具,可以直接從 apk 包中解析出項(xiàng)目源碼,如果不對代碼做任何處理,你的項(xiàng)目就會(huì)像沒有穿衣服一樣站在別人面前。
?一種可行的辦法,就是對代碼做映射, 將我們原有的有意義的類名、變量名映射(重命名)為無意義的簡短的名稱;或者注入一些無意義的代碼,以影響別人的窺視。
?Proguard 就是這樣一種工具。當(dāng)然,它的作用也不僅僅是如此。

ProGuard 是什么

?根據(jù)官網(wǎng)的介紹,它是一個(gè)針對 Java class 文件 進(jìn)行 壓縮、優(yōu)化、混淆、預(yù)檢的工具。
?它會(huì)讀取 jar(或者 wars、ears、zips或者其他目錄),然后依次進(jìn)行壓縮、優(yōu)化、混淆、預(yù)檢工作,處理好之后,再將它們輸出。輸入中的 java 文件,它們的名字以及其中的內(nèi)容,會(huì)被映射為混淆后的名字。
?ProGuard 的處理,總是從含有 entry points (包含有main方法或者需要被系統(tǒng)調(diào)用的類)開始的。

  • 首先,進(jìn)行壓縮工作,會(huì)檢測哪些類和成員會(huì)被使用,沒有被使用的將被丟棄。
  • 然后,進(jìn)行優(yōu)化工作,主要是針對方法的字節(jié)碼,那些非 entry point 的方法或者類可能會(huì)被設(shè)置為 private 、static、final;一些不用的參數(shù)可能會(huì)被移除,一些方法可能會(huì)被設(shè)置為內(nèi)聯(lián)函數(shù)。
  • 接著,會(huì)進(jìn)行混淆,ProGuard 會(huì)對一些非 entry points 的類進(jìn)行類名和成員名的重命名。
  • 最后,預(yù)檢階段,這是唯一一個(gè)不需要知道 entry points 的階段。

主要方法

這里,主要講一下 keep 和 dontwarn。

keep

?上面也講到了 entry point 的概念。對于那些 main 方法,或者需要通過使用類名或者屬性名進(jìn)行操作的地方(系統(tǒng)回調(diào)或者反射),我們不能對它們進(jìn)行混淆,一旦混淆,在需要的時(shí)候可能引發(fā)找不到的異常。
?舉幾個(gè)例子。比如說,main方法被混淆重命名成了 a ,那么找不到 main 方法,自然也就找不到程序執(zhí)行的入口;比如說,我們把 Activity 混淆了,在啟動(dòng)該 Activity,同樣找不到該組件,因?yàn)檫@個(gè) Activity 已經(jīng)被混淆后重命名了,而在我們的 manifest.xml(xml文件不會(huì)被混淆) 文件中,還留的是未混淆之前的名字。
?因此,我們需要使用 keep 對這些 entry point 進(jìn)行保護(hù),使得在混淆過程中,跳過這些類或者成員,不進(jìn)行混淆。
?keep命令有一些變體,但是總體都是圍繞著類名和成員名展開的。
?我們比較常用的,可能就是-keep [,modifier,...] class_specification ,下面舉幾個(gè)實(shí)例

//--------------------1---------------------------
-keep class com.alibaba.fastjson.** { *; }
//--------------------2---------------------------
-keep class * implements android.os.Parcelable {
    public static final android.os.Parcelable$Creator *;
}
//--------------------3---------------------------
-keep public class * extends android.view.View {
    *** get*();
    void set*(***);
    public <init>(android.content.Context);
    public <init>(android.content.Context, android.util.AttributeSet);
    public <init>(android.content.Context, android.util.AttributeSet, int);
}
  • class 關(guān)鍵字:class 關(guān)鍵字可以代表任何 classs 或者 interface;但是當(dāng)使用 interface 的時(shí)候,就只能代表接口;使用 enum 時(shí),就只能代表枚舉。

  • 類名,必須是完全限定名。比如 String,就必須是 java.lang.String。在指定類名的時(shí)候,可以使用一些正則表達(dá)式:

    ? 通配符

    可以匹配類名中的某一個(gè)字符,但是不包括包分隔符。比如說"mypackage.Test?",可以匹配"mypackage.Test2",但是無法匹配"mypackage.Test12"。

    * 通配符

    該通配符可以表示類的全限定名中的任意一部分字符串,但是不包括包名分隔符(其實(shí)就是點(diǎn)".")。比如"mypackage.*Test*"可以匹配"mypackage.Test" 和 "mypackage.YourTestApplication",但是無法匹配"mypackage.subpackage.MyTest"。
    因此,上述代碼塊中第二個(gè)示例,即匹配了所有繼承自 Parcelable 的類。

    ** 通配符

    可以通配全限定名中任何長度的字符串,這其中包括包名分隔符。因此,上面代碼塊中的第一個(gè)示例,實(shí)際上指的就是com.alibaba.fastjson 包下的所有類以及子包中的類中的所有內(nèi)容。即,完全不對這個(gè)包下面的所有內(nèi)容進(jìn)行混淆。

  • extends 和 implements 關(guān)鍵字:它們實(shí)際上是對通配符作用的進(jìn)行限制,代表了繼承自或者實(shí)現(xiàn)了某個(gè)接口的所有類。如上面的示例3部分,表示不混淆所有繼承自 View 的類。

  • 成員和方法:可以指定一個(gè)屬性成員和方法不被混淆,在指定的時(shí)候也可以使用一些通配符:

    <init> 通配符

    該通配符會(huì)表示所有構(gòu)造方法,注意,它也可以帶參數(shù)列表。

    <fields> 通配符

    該通配符表示所有屬性成員。

    <methods> 通配符

    該通配符表示所有方法。

    * 通配符

    在這里,它代表所有屬性成員和方法。

  • 成員和方法名的通配符。我們在指定成員和方法名時(shí),同樣可以使用通配符。
    % 匹配基礎(chǔ)類型,但是不包括 void
    ? 匹配類名中的單個(gè)字符
    * 匹配類名中的一部分字符,但是同樣不包括包名分隔符
    ** 匹配類名中的一部分字符,包括包名分隔符,所匹配類不包括數(shù)組
    *** 匹配任意類型類名或數(shù)組
    ... 匹配任意數(shù)量任意類型

dontwarn

?混淆過程中,會(huì)出現(xiàn)一些警告,導(dǎo)致 build 失敗。
?這些警告,有些時(shí)候并不會(huì)對我們的使用產(chǎn)生任何影響。比如在使用 picasso 的時(shí)候,如果在沒有進(jìn)行如下設(shè)置:

#-dontwarn com.squareup.okhttp.**

就會(huì)產(chǎn)生這樣的警告:

Warning: com.squareup.picasso.OkHttpDownloader: can't find referenced class com.squareup.okhttp.OkHttpClient
Warning: com.squareup.picasso.OkHttpDownloader: can't find referenced class com.squareup.okhttp.OkHttpClient
Warning: com.squareup.picasso.OkHttpDownloader: can't find referenced class com.squareup.okhttp.OkHttpClient

?事實(shí)上,picasso 并不是一定需要使用 okhttp ,如果我們的項(xiàng)目中使用了 okhttp ,它才會(huì)使用 okhttp 作為下載工具。

注意事項(xiàng)

?混淆處理,實(shí)際上就是一個(gè)重命名的過程,即實(shí)現(xiàn)名字(類名、方法名、屬性名)的映射。這種映射關(guān)系可以在混淆過程中輸出到 mapping.txt 文件中。在混淆處理之后,名字就已經(jīng)變了,此時(shí)如果再試圖使用原來的名字去找到它們,必然是找不到的。比如以下幾種情況:

  1. 反射使用。比如 setName() 方法通過混淆被映射為了 a() 如果我們希望通過方法名 setName 來調(diào)用類中的該方法,在寫代碼的時(shí)候,我們也不會(huì)知道這個(gè)名字將會(huì)被映射為 a ,混淆之后,會(huì)找不到方法的。 混淆使得方法名發(fā)生改變,而我們還在使用原來的方法名進(jìn)行反射。

  2. bean 文件使用。對于 bean 文件,很多時(shí)候,它們作為和服務(wù)器之間的通信實(shí)體。如果在這種情況下進(jìn)行了混淆,當(dāng)數(shù)據(jù)發(fā)給服務(wù)器之后,服務(wù)器是看不懂的,因?yàn)閷傩悦甲兞耍?wù)端保存的是原來的 bean 文件。

  3. 回調(diào)函數(shù)。這是一個(gè)值得注意的地方。比如在 Activity 中的 onTouchEvent 回調(diào),如果被你混淆了,而系統(tǒng)實(shí)際上不知道的,混淆是你的個(gè)人行為。它不會(huì)知道到該回調(diào)的,同樣因?yàn)檎也坏健?/p>

  4. 枚舉。在使用枚舉類型的時(shí)候,應(yīng)當(dāng)注意不要對它們進(jìn)行混淆。因?yàn)槊杜e會(huì)使用反射進(jìn)行操作。

  5. native 方法不要混淆。

小結(jié)

?以上,是對混淆的一個(gè)總結(jié)?;煜哪康?,在于影響別人反編譯之后的閱讀,無法做到真正的讓別人無法破解。核心部分,還是需要做加密處理,或者干脆使用 so 動(dòng)態(tài)庫來實(shí)現(xiàn)。
?當(dāng)然,上面已經(jīng)提到,ProGuard 的作用不僅在此,還可以用來對 apk 進(jìn)行瘦身。更多詳情,可以在官網(wǎng)上找到。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 混淆(Proguard)用法 最近項(xiàng)目中遇到一些混淆相關(guān)的問題,由于之前對proguard了解不多,所以每次都是面...
    于曉飛93閱讀 57,152評論 38 230
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 178,765評論 25 709
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,506評論 19 139
  • 初開一朵鮮 柔美惹人憐 欲問春歸處 寂然山水間 ----春歸記
    凈然閱讀 240評論 0 0
  • 文/林倚墨 F讓我有空寫寫他。 幾次都忘記了,這次終于開始回憶我與F的故事。 F是我的高中同學(xué),因此我是不太習(xí)慣稱...
    林倚墨閱讀 590評論 0 1

友情鏈接更多精彩內(nèi)容