Android 代碼混淆(一)

文章轉(zhuǎn)自我個(gè)人博客

本文前半部分對(duì)照 Proguard 文檔 (Manul 中的 Introduce 部分)進(jìn)行翻譯同時(shí)加上個(gè)人的理解,如果有與原文不同,請(qǐng)以原文為主。后半部分是對(duì)幾個(gè)步驟的驗(yàn)證。


介紹

混淆器(ProGuard)會(huì)對(duì) Java class 文件進(jìn)行 shrinker(壓縮),optimizer(優(yōu)化),obfuscator(混淆)以及preverifier(校驗(yàn))。shrinker(壓縮)這一步會(huì)找到并移除沒(méi)用到的類,變量,方法,屬性。optimization(優(yōu)化)這一步,會(huì)分析并且優(yōu)化方法的字節(jié)碼。obfuscation(混淆)則會(huì)對(duì) class,fields,methods替換成一些短的無(wú)意義的名字。第一步會(huì)把代碼量變小,運(yùn)行更加有效率,同時(shí)更加難以被逆向。在 Java Micro Edition 和 Java 6或者更高版本中,最后一步的檢驗(yàn)過(guò)程,會(huì)向class文件中添加一些預(yù)校驗(yàn)的信息。
上述的每個(gè)步驟,都是可以選擇的(可以進(jìn)行也可以不進(jìn)行)。例如,ProGuard 可以只進(jìn)行preverify,從而更高效的運(yùn)行。

21-42-24.jpg

  • 首先,ProGuard(混淆器) 讀入輸入的 jars (也可以是 aars, wars, ears, zips, apks, 或者目錄)。隨后,開始進(jìn)行 shrinker(壓縮),optimizer(優(yōu)化),obfuscator(混淆)以及preverifier(校驗(yàn))。你可以選擇性的讓ProGuard(混淆器)進(jìn)行多種類型的優(yōu)化操作。ProGuard(混淆器)會(huì)把修改過(guò)的結(jié)果寫入一個(gè)或者多個(gè)輸出的 jars (也可以是 aars, wars, ears, zips, apks, 或者目錄)中。
  • 混淆器需要明確輸入文件(Input jars)是jars包(也可以是 aars, wars, ears, zips, apks, 或者目錄)。這些 libraries 本質(zhì)上是你將會(huì)用來(lái)編譯的代碼?;煜鳛榱四軌蛘_進(jìn)行整個(gè)過(guò)程,會(huì)重新構(gòu)建類之間的依賴。而依賴包(Library jars) 往往是不會(huì)被改變的,但你依舊需要把它們放在最終的App的環(huán)境中。

Entry points(入口點(diǎn))

  • 在壓縮步驟(shrinker),混淆器會(huì)從這些點(diǎn)(入口點(diǎn))進(jìn)入,并且遞歸尋找決定哪些類和哪些類成員會(huì)被使用。所有的其他類和類成員都會(huì)被拋棄掉
  • 在優(yōu)化步驟(optimizer),混淆器會(huì)進(jìn)一步優(yōu)化代碼。在這些優(yōu)化過(guò)程中,那些不是入口點(diǎn)的類和方法會(huì)變成private static或者final,不被用到的參數(shù)會(huì)被移除,一些方法會(huì)變成內(nèi)斂方法
  • 在混淆這一步(obfuscator),混淆器會(huì)重新命名那些不是入口點(diǎn)的類和類的成員。在這整個(gè)過(guò)程中,那些成為入口點(diǎn)的地方,依舊會(huì)為他們保留原來(lái)的名字
  • 預(yù)驗(yàn)證階段(preverifier)是唯一一個(gè)不需要知道入口點(diǎn)的階段

反射

  • 對(duì)于反射和introspection 進(jìn)行代碼的自動(dòng)處理時(shí),都會(huì)存在一些特殊的問(wèn)題。在混淆器進(jìn)行處理時(shí),代碼中類和類成員都是被動(dòng)態(tài)創(chuàng)建或者被動(dòng)態(tài)調(diào)用的(通過(guò)對(duì)應(yīng)類的名字,或者成員名字),這些地方都必須被定義成入口點(diǎn)。例如,Class.forName()這個(gè)構(gòu)造器會(huì)在運(yùn)行時(shí)指向任何的類。又比如,類的名字可能會(huì)從配置文件中讀入,這通常很難去計(jì)算出是那些類需要被保留(通過(guò)原始的名字)。因此,你必須得在混淆器的配置中,通過(guò)簡(jiǎn)單相同的操作-keep來(lái)指定他們。
    然而,混淆器已經(jīng)能夠幫你發(fā)現(xiàn)并處理以下的情況:
        Class.forName("SomeClass")
        SomeClass.class
        SomeClass.class.getField("someField")
        SomeClass.class.getDeclaredField("someField")
        SomeClass.class.getMethod("someMethod", new Class[] {})
        SomeClass.class.getMethod("someMethod", new Class[] { A.class })
        SomeClass.class.getMethod("someMethod", new Class[] { A.class, B.class })
        SomeClass.class.getDeclaredMethod("someMethod", new Class[] {})
        SomeClass.class.getDeclaredMethod("someMethod", new Class[] { A.class })
        SomeClass.class.getDeclaredMethod("someMethod", new Class[] { A.class, B.class })
        AtomicIntegerFieldUpdater.newUpdater(SomeClass.class, "someField")
        AtomicLongFieldUpdater.newUpdater(SomeClass.class, "someField")
        AtomicReferenceFieldUpdater.newUpdater(SomeClass.class, SomeType.class, "someField")
  • 類和類成員的名字會(huì)不一樣,但是構(gòu)造方法必然是相同的,由此,混淆器能夠認(rèn)出他們。被引用的類和類的成員在壓縮(shrinking)階段會(huì)被保留,同時(shí),string 類型的參數(shù)也會(huì)在混淆時(shí)(obfuscation)被準(zhǔn)確的修改。
  • 除此之外,混淆器會(huì)提供一些建議:是否保留一些出現(xiàn)的類和類成員。舉例,混淆器會(huì)標(biāo)記(SomeClass)Class.forName(variable).newInstance()這樣的構(gòu)造器。因?yàn)檫@些方法可能會(huì)指向其他類,這些可能是類,也可能是接口,或者是繼承自這些接口或者類的類。你需要在配置中做相應(yīng)的處理。
  • 為了能夠得到正確的混淆結(jié)果,你應(yīng)該對(duì)進(jìn)行混淆的代碼多少有所熟悉。當(dāng)面臨大量反射代碼時(shí),混淆代碼需要進(jìn)行大量的試驗(yàn),并處理錯(cuò)誤,特別是對(duì)于內(nèi)部代碼沒(méi)有足夠的信息的情況下

以上是對(duì)官方文檔首頁(yè)的翻譯內(nèi)容

具體的驗(yàn)證

該部分不是翻譯內(nèi)容,是根據(jù)ProGuard 的使用方法文檔首頁(yè),對(duì)上述三個(gè)步驟的具體驗(yàn)證。
由于大部分情況下,Android的混淆只需要考慮Obfuse這個(gè)步驟,因?yàn)楹芏嗟谌揭蕾嚢幕煜?guī)則會(huì)把 shrinkoptimize去掉(比如友盟)。所以先驗(yàn)證這一步。
下面的驗(yàn)證步驟,涉及三個(gè)類,java打包的命令(Java 環(huán)境),proguard.jar包(混淆器,進(jìn)行整個(gè)混淆過(guò)程的jar包),proguard.pro文件(寫入具體混淆的規(guī)則)和Intelij(用來(lái)查看class文件)等內(nèi)容。


Obfuse 步驟驗(yàn)證

這個(gè)步驟,如上所說(shuō),主要是對(duì)類,方法進(jìn)行名字的修改,也是 Android 混淆中最重要的部分。為了驗(yàn)證這個(gè)過(guò)程,我做了下面的demo操作

  • 首先寫了3個(gè)類:
    package com.dove.home;
    class HelloWorld {
        public HelloWorld(){
            System.out.println("Hello World");
        }
    }
    package com.dove.home;
    class HelloWorld2 {
        public HelloWorld2(){
            System.out.println("Hello World2");
        }
    }
    package com.dove.home;    
    class Main {
        public static void main(String[] args) {
            HelloWorld helloWorld = new HelloWorld();
        }
    }
  • 然后編譯,打包
    javac com/dove/home/Main.java
    javac com/dove/home/HelloWorld2.java
    javac com/dove/home/HelloWorld.java
    //注意在進(jìn)行下面步驟的時(shí)候,我把 com/dove/home 下的 java源碼刪了
    jar -cvf main_source.jar com
18-39-05.jpg

  • 然后使用混淆器,混淆器其具體使用方法,主要是調(diào)用proguard.jar包,然后配置 proguard.pro文件進(jìn)行具體的參數(shù)設(shè)置。
    下面是我proguard.pro文件內(nèi)容
    # 源碼文件
    -injars main_source.jar
    # 混淆后輸出文件
    -outjars main_source_out.jar
    # java 核心 jar 不能混淆
    -libraryjars <java.home>/lib/rt.jar
    -libraryjars <java.home>/lib/jce.jar
    # 全部不混淆,即三個(gè)class文件都會(huì)保持原樣
    -keep class com.dove.home.Main{*;}
    -keep class com.dove.home.HelloWorld{*;}
    -keep class com.dove.home.HelloWorld2{*;}

具體的混淆命令,同時(shí)參考下圖(該步驟會(huì)生成混淆后的jar包)

    java -jar proguard.jar @proguard.pro


17-45-40.jpg

注意:然后修改 proguard.pro 文件,內(nèi)容如下

    -injars main_source.jar
    # 注意輸出包的名字改了
    -outjars main_source_proguard_out.jar
    -libraryjars <java.home>/lib/rt.jar
    -libraryjars <java.home>/lib/jce.jar
    -keep class com.dove.home.Main{*;}
    # 刪除了HelloWorld的 keep
    -keep class com.dove.home.HelloWorld2{*;}

同樣運(yùn)行上面的混淆命令,生成另一個(gè)混淆后的包

最后對(duì)三個(gè)包進(jìn)行對(duì)比,通過(guò)代碼逆向,進(jìn)行驗(yàn)證,最快的方式是把生成的 jar 包,當(dāng)做第三方依賴包直接導(dǎo)入Intellij 中(有decode的功能),如下圖,三個(gè)包的區(qū)別


18-48-53.jpg

最初是的源碼包和保留HelloWorld,HelloWorld2以及Main入口點(diǎn)的包是一樣的,不同的是最后沒(méi)有保留HelloWorld入口點(diǎn)的包,它的HelloWorld變成了a,而MainHelloWorld2都正常沒(méi)有被修改


Optimize 步驟驗(yàn)證

同樣,修改 proguard.pro 文件,內(nèi)容如下,然后運(yùn)行混淆命令,生成新的 jar 包

    -injars main_source.jar
    # 輸出包名改了,方便對(duì)比
    -outjars main_source_proguard_not_optimize_out.jar
    -libraryjars <java.home>/lib/rt.jar
    -libraryjars <java.home>/lib/jce.jar
    # 加上不進(jìn)行優(yōu)化的限制
    -dontoptimize
    -keep class com.dove.home.Main{*;}
    -keep class com.dove.home.HelloWorld2{*;}

同上,導(dǎo)入IntelliJ,對(duì)比上一步中混淆后的 jar 包,發(fā)現(xiàn)名字沒(méi)啥變化,但內(nèi)容不一樣了


18-51-28.jpg

首先是沒(méi)有添加 -dontoptimize

package com.dove.home;
final class a {
    public a() {
        System.out.println("Hello World");
    }
}

然后是添加了 -dontoptimize

package com.dove.home;
class a {
    public a() {
        System.out.println("Hello World");
    }
}

如上述譯文中所說(shuō),optimize 會(huì)進(jìn)行代碼優(yōu)化,不是入口點(diǎn)的代碼,會(huì)變成final,private等等,該步驟驗(yàn)證完畢。


Shrink 步驟驗(yàn)證

修改 proguard.pro 文件,進(jìn)行壓縮,同時(shí)不對(duì) HelloWorld,HelloWorld2進(jìn)行入口點(diǎn)的保留

    -injars main_source.jar
    -outjars main_source_proguard_shrink_out.jar
    -libraryjars <java.home>/lib/rt.jar
    -libraryjars <java.home>/lib/jce.jar
    
    -keep class com.dove.home.Main{*;}
    # 注意對(duì)比之前,刪除了HelloWorld和HelloWorld2的 keep

修改 proguard.pro 文件,不進(jìn)行壓縮,同樣不對(duì) HelloWorld,HelloWorld2進(jìn)行入口點(diǎn)的保留

    -injars main_source.jar
    -outjars main_source_proguard_not_shrink_out.jar
    -libraryjars <java.home>/lib/rt.jar
    -libraryjars <java.home>/lib/jce.jar
    # 添加不進(jìn)行壓縮
    -dontshrink
    -keep class com.dove.home.Main{*;}

其結(jié)果對(duì)比
添加了 -dontshrink標(biāo)志

18-58-04.jpg

未添加 -dontshrink標(biāo)志

18-58-16.jpg

此處消失的b其實(shí)就是HelloWorld2,而留下的a則是HelloWorld,原因很簡(jiǎn)單,因?yàn)镸ain里面持有了HelloWorld的引用,而HelloWorld2則從未被用到,所以就被拋棄了。
由此驗(yàn)證,shrink階段,Proguard(混淆器)會(huì)把無(wú)用類文件等刪除,一些被動(dòng)態(tài)獲取的類就需要注意了,需要進(jìn)行-keep操作,使其成為入口點(diǎn)。


以上就是對(duì)混淆整個(gè)過(guò)程的驗(yàn)證

對(duì)于 Android 混淆,一些需要注意的東西,會(huì)在下一篇文章中記錄

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

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

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,697評(píng)論 19 139
  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 179,351評(píng)論 25 708
  • 混淆(Proguard)用法 最近項(xiàng)目中遇到一些混淆相關(guān)的問(wèn)題,由于之前對(duì)proguard了解不多,所以每次都是面...
    于曉飛93閱讀 57,176評(píng)論 38 230
  • Android插件化基礎(chǔ)的主要內(nèi)容包括 Android插件化基礎(chǔ)1-----加載SD上APKAndroid插件化基...
    隔壁老李頭閱讀 7,400評(píng)論 13 48
  • 當(dāng)興致勃勃地說(shuō)到一個(gè)話題,卻被無(wú)情地打斷;當(dāng)心情不好想要得到安慰的時(shí)候,卻被指責(zé)說(shuō)這件事做的怎么怎么不對(duì);當(dāng)努力...
    小天使會(huì)施星心魔法閱讀 271評(píng)論 0 1

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