關於Android混淆的開源框架Mess的學習與分析
Mess是用來解決什麼問題的
首先,在之前學過的關於Android混淆知識中我們知道,Android預設是不會混淆四大元件和自定義View的,或者換一個說法,出現在 xml 裡的相關 Java 類預設是不會被混淆的。
你不禁會問,為什麼Android預設要這麼做?
答:因為 Proguard 原本是為 Java 打造的,它無法搜尋到我們 AndroidManifest、佈局等檔案中引用了哪些 Java 類,因此如果 Java 程式碼變了而 XML 檔案中的引用沒變,就會造成反射失敗,所以android預設這些被 XML 使用到的類需要 keep 住。
關於這一點,我們可以簡單的驗證一下:
開啟build/intermediates/proguard-files/路徑下的proguard-android.txt檔案,這裡面幫我們聲明瞭許多混淆規則內容,包括:keep 所有繼承自 View 的類,keep 所有繼承自 Activity 的類,keep 所有 JavascriptInterface、native 方法宣告,以及 keep 一些註解了 @Keep 的內容,例如:
好,說了這麼多,Mess這個外掛的目的就是在混淆的時候把這些頑固分子一起收拾了,讓你的應用混淆的更徹底,更安全!
Mess的主要構成
我把它下載到了本地,其主要一共就四個groovy檔案:
MessExtension:用來存放需要被忽略的Proguard,比如那些本身也配置了相關混淆配置的依賴庫,Mess會對這些依賴庫 進行混淆
Util:混淆時的工具類,這裡面提供了一些方法如下
renameProguardTxt(...):對Proguard檔案進行重新命名,然後存放到指定的路徑
hideProguardTxt(...):備份要被刪除的proguard.txt 檔案,用於最後的恢復
recoverProguardTxt(...):恢復proguard.txt 檔案
sortMapping(...):根據傳進來的Map的key的長短來排序
RewriteComponentTask:根據對映檔案 mapping.txt 找到原先的類名,然後替換AndroidManifest,layout,menu和
values 這些xml檔案中的JAVA類,完成對其的混淆
MessPlugin:對transformClassesAndResourcesWithProguardForRelease這項Task的流程的排程,包括先後順序等
Mess執行的具體步驟和流程
- 清空aapt_rules.txt 中的內容
aapt_rules.txt中記錄著所有在 xml 裡宣告的 java 類(四大元件和自定義View等),並將它們都 keep 住,這個檔案最終會被新增到 app 的混淆配置中,所以既然Mess想實現更全面的混淆,就必須刪除它。在build/intermediates/proguard-rules/release下,你可以找到aapt_rules.txt,截了一部分程式碼如下:
既然知道要刪掉它,首先需要找到aapt_rules.txt是從哪裡生成的,系統的ProcessAndroidResources類中有這樣一段程式碼:
該方法的名字就是獲得AndroidResources程序中的Proguard輸出檔案,繼續追蹤我們發現,該方法所在的是一個名為VariantScope的介面,在它的實現類VariantScopeImpl中我終於看到了下面的這段程式碼:
到這裡aapt_rules.txt的出生點已經找到,拿到路徑後,在MessPlugin中是這樣刪除它的:
2.如果需要混淆依賴庫,則刪除依賴庫中的 proguard.txt 檔案
Mess中採用的方式簡單粗暴,直接將 proguard.txt 檔名最後加上 ~ ,這在linux中表示備份,以便之後檔案的恢復。在Util中提供瞭如下方法:
對proguard.txt的備份和恢復的邏輯都在這裡,然後在MessPlugin中先後呼叫:
3.hook transformClassesAndResourcesWithProguard 獲取混淆後的類對映關係 Map
注:hook翻譯過來是鉤子的意思,它能夠將自身的程式碼「融入」被勾住(Hook)的程式的程序中,成為目標程序的一個部分
在一開始沒有混淆之前:xml中的java類←→class檔案
經過前面的步驟之後:xml中的java類←→混淆過的class檔案
所以現在需要做的就是找到混淆過的class在xml中的原有java類名,然後把它給替換掉,也就是把它給混淆了。
這個對映關係要從/build/outputs/mapping/release/路徑下的mapping.txt中找,它裡面存放著混淆前後類、方法、類成員等的對照表,如下:
這一個個的箭頭就是代表著一一對應的關係:混淆前和混淆後。Mess在RewriteComponentTask中用Map解析並存儲了這些對映關係,相關程式碼如下:
在這段程式碼下邊有一段註釋:
“如果我們不對這個map進行長短的排序,就會出現這樣的情況:
me.ele.foo -> me.ele.a
me.ele.fooNew -> me.ele.b(本該是這樣)
me.ele.fooNew -> me.ele.aNew(但不排序的話,會變成這樣)
像這樣,一個類的類名是另一個類名的開始部分,後面的替換就會出現bug。所以在儲存完對映關係後,Mess呼叫了sortMapping(...)方法來進行排序,該方法的程式碼如下:
4.通過對映 Map 替換 AndroidManifest.xml 裡的 Java 原類名
在RewriteComponentTask中,緊接著上面的步驟,你就會看到下面的程式碼:
從這裡就開始著手替換 AndroidManifest.xml中的Java原類名了,writeLine(...)方法如下所示:
逐行讀取,然後進行相應的替換。
5.通過對映 Map 替換 layout、menu 和 value 資料夾下的 xml 的 Java 原類名
還是在RewriteComponentTask中,有這樣一個方法:
很明顯,它的作用就是用來判斷檔案路徑是否是 layout、menu 和 value,它在處理完AndroidManifest.xml後隨即被呼叫:
上圖中的第二個紅色框部分執行了相應的替換工作。
6.再次執行 ProcessAndroidResources Task
經過前面的5個步驟之後,xml中的java類已經全部混淆,接下來需要再重新編譯一次資原始檔,即再次執行 ProcessAndroidResources Task。
到這裡Mess六個步驟已經全部走完,再來回顧一下:
- 清空aapt_rules.txt 中的內容
- 如果需要混淆依賴庫,則刪除依賴庫中的 proguard.txt 檔案
- hook transformClassesAndResourcesWithProguard 獲取混淆後的類對映關係 Map
- 通過對映 Map 替換 AndroidManifest.xml 裡的 Java 原類名
- 通過對映 Map 替換 layout、menu 和 value 資料夾下的 xml 的 Java 原類名
- 再次執行 ProcessAndroidResources Task
Mess的開始工作的時間點
說了這麼多,那麼Mess是什麼時候開始工作的呢
答:Mess在 class 打包成 dex 前完成所有的工作