在 Google I/O 大會上,Google 向 Android 引入了新 APP 動態化框架 Android APP Bundle (AAB),被看作是對 Android 未來發展具有顛覆性的動態化解決方案。本文從 AAB動態化框架的優勢出發,解析如何在 AAB 格式下的實現優質加固,助力移動應用在國內外市場間自由切換,以更小應用提供優質體驗。
圖 | AAB 格式的架構圖
01 AAB應用的瘦身術
相比於 APK,Google 主推的 APP 打包格式 AAB 在很多方面都更為優越,主要體現為如下幾點:
1.1. 動態分發
一個 APK 中往往包含各國的語言資源、ABI、螢幕密度等資源。然而,對於單個使用者來說,往往只需要這些資源中的部分。
目前,國內的開發者將所有資源統一放在單個 APK 中,這樣就會導致 APK 特別龐大,而AAB在壓縮APK體積方面具有優勢。
而為了縮小體積,部分開發者會有意縮減 APK 中的 ABI 目錄。例如,將 arm64-v8a 的 SO 從 APK 中去除,只留下 armeabi-v7a 的 SO。但這種做法使得64位 CPU 的手機無法發揮出其64位的運算優勢,降低程式執行速度。
Split APKs 是 Android 5.0 開始提供的多 APK 構建機制,藉助 Split APKs 可以將一個 APK 基於 ABI、螢幕密度和 CPU 架構拆分成多個 APK ,這樣可以有效減少單個 APK 體積。當使用者下載應用程式安裝包時,Google Play 會自動識別使用者的語言和 CPU 架構,自動將對應平臺 SO 和資源的 APK 下發給使用者。
1.2. 動態功能模組
在 Android Studio 中新增了一個模組:動態功能模組。透過該模組開發出的功能可在使用者需要時再進行下載,類似於目前在國內被廣泛使用的熱更新機制,只不過熱更新大多是用來修復功能性 BUG 的,而動態功能模組更傾向於形成一個獨立功能。
使用者在安裝 APK 時,只需要下載一個包含 APP 主要功能的 APK ,而其他附加功能可在使用者需要時進行動態下載安裝。這樣就進一步減小了 APK 的體積,為使用者改善了 APP 安裝使用體驗。
02 AAB加固解決方案
AAB 格式在給 Android APP 帶來便利的同時,也給移動安全領域帶來了新的挑戰:AAB 格式的安裝包,在組織結構和檔案內容方面都與 APK 格式有較大差異,傳統的 APP 加殼技術無法直接應用在 APP Bundle 模式生成的資料包之上。
易盾透過對 AAB 格式進行深入研究,針對新的 AAB 格式對現有加固進行了一系統調整和改進,從而能夠支援新的 AAB 格式。
那麼,現在的加固方案要相容 AAB 格式面臨著哪些難點,易盾又是如何改進的呢?
2.1. 防二次打包
我們知道,正常的 APK 在上傳應用市場時都是已經提前進行了簽名,當破解者從應用市場上將 APK 下載下來,經過重打包之後,必然需要修改 APK 的簽名,而一般的防二次打包功能都會對 APK 的簽名進行驗證,若 APK 的簽名與原始的簽名不一致,則 APP 會直接退出。如下所示:
而加固中的防二次打包方案,都是在加固時預先讀取 APK 中的簽名內容,使用加固前的 APK 簽名特徵作為校驗標準,在 APP 執行時對 APK 的簽名特徵進行校驗,若執行時的 APK 簽名特徵與加固前的 APK 簽名特徵不一致,則 APK 會直接閃退。
而使用AAB格式時,Google Play 會要求使用者上傳簽名證照檔案,由 Google Play 在下發 APK 的時候再進行簽名。換而言之,AAB格式是無法進行簽名的,嘗試使用 apksigner 對 APK 進行簽名,輸出如下所示:
可以看到,apksigner 會嘗試去尋找根目錄下的 AndroidManifest.xml,但因為該檔案是 AAB 格式,導致 apksigner 無法正確解析。
這隨之導致,加固廠商在加固時無法正確獲取到 APK 的原始簽名,也就無法正確實現防二次打包。那麼,易盾打造的加固解決方案是如何解決這個問題的呢?
眾所周知,android 使用的第一代簽名工具,是 jarsigner,而不是 apksigner,而 jarsigner 是JAVA SDK中自帶的用於對 JAR 包進行簽名的工具。既然 jarsigner 可以對 JAR 和 APK 進行簽名,那麼 jarsigner 是否也可以對 AAB 進行簽名呢?
答案是肯定的。我們嘗試使用 jarsigner 對 AAB 檔案進行簽名,簽名後的 AAB 檔案結構如下所示:
事實上,jarsigner 是一個對 ZIP 格式的壓縮包進行簽名的工具,而無論是 JAR、APK 還是 AAB,本質上都是 ZIP 格式。因此,只要使用者在加固前使用 jarsigner 對 AAB 進行簽名,加固廠商就能正確獲取到 APK 的簽名特徵。
2.2. 資源混淆
資源混淆常用於減小 APK 的體積和保護資源。
某些 APP 可能會將 APP 中的一些關鍵資料放在 res 檔案中,而破解者透過程式碼中資源的 ID 可以輕鬆找到其對應的資原始檔,從而達到在不修改程式碼的情況下輕鬆修改 APK 的一些重要邏輯的效果。而透過資源混淆,APK 中的 res 資源名會被修改為毫無意義的名稱,破解者再難以輕鬆地找到程式碼對應的資原始檔。
同時,透過對資源的檔名進行混淆,將長檔名混淆為短檔名,從而實現 APK 的"瘦身"效果。根據每個 APK 的具體情況,每個 APK 在經過資源混淆之後可縮小 1 至 N MB 的體積。
在 APK 中,所有的資源都記錄在資源索引檔案 resource.arsc 中,如下所示:
但是在 AAB 中,資源索引檔案的格式和名稱也發生了變化:
2.3. AndroidManifest.xml修改
雖然 VMP 方案和 JAVA2C 方案已經出現很多年了,但是因為效能原因,目前市面上的主流的加固方案還是 DEX 加固,VMP 方案和 JAVA2C 僅用來做核心類的保護。而 DEX 加固的基本原理就是透過修改應用的入口 Application 接管應用的啟動邏輯,在執行 DEX 的動態載入等一系列操作之後,將程式邏輯還給 APP 的原始入口 Application,如下所示:
圖 | 經易盾加固之後的入口 Application
一些粗心大意的開發者甚至會將 APP 的 debuggable 開關設為 "true",從而使得 APK 能夠被輕易地除錯。易盾在加固的過程會自動識別出這種情況,將對應的開關改為 "false"。
上述提到的資料都記錄在 AndroiManifest.xml 檔案中,而經過打包出來的 APK,其 AndroidManifest.xml 是經過編譯之後的二進位制格式,其中還涉及到一些和 APK 中的資源相關的資料,並不像開發階段的明文那麼容易修改,編譯後的 AndroidManifest.xml 內容如下所示:
可以看到,相比於開發階段的 AndroidManifest.xml,編譯後 AndroidManifest.xml 多出了很多不可見字元,這往往對應著一些資料結構。要對其進行修改,就需要對 AndroiManifest.xml 有深入的瞭解。
而 AAB 中的 AndroiManifest.xml 相比於 APK 中的 AndroiManifest.xml 又有了一定程度的變化,但原始的修改方案並不能直接拿來複用。
上述 AndroiManifest.xml 的變化 Google 並沒有在文件中記錄下來。易盾在原有修改方案基礎上,又深入研究了 Android Bundle Tool 的打包原始碼,提取出了 AAB 中的 AndroiManifest.xml 的檔案格式,從而實現了 AAB 格式中 AndroiManifest.xml 檔案的修改。
2.4. DEX和SO的加固
事實上,在 AAB 格式中,DEX 檔案和 SO 檔案的內容並沒有被修改,只不過其在 APK 中的目錄組織結構發生了變化。如下所示:
對這部分變化的相容方式較為簡單,我們只需要將相對應的檔案抽離出來,重新按 APK 的目錄結構組織成一個偽 APK。在加固之後,我們再將加固處理後的檔案還原到 AAB 中,即可在不修改原有加固方案的前提下完美適配 AAB 格式。
03 總結
AAB 格式相比於 APK 格式,在 DEX 格式和 SO 格式上並沒有多少變化,這也使得對於 DEX 和 SO 的保護方案能夠較方便地遷移過來。
而其中較困難的地方在於,各種配置檔案和資原始檔的格式發生了較大的變化,導致原始的方案直接無法使用了。
易盾從使用者的角度出發,考慮到了廣大出海應用的加固需求,積極對 AAB 格式進行適配,使原有的加固方案能夠完整地遷移到 AAB 格式上,助力應用安全地輕鬆適應各種改變。