Android APK 檔案格式
Android 應用程式套件——裝著編譯後程式碼、資源與 manifest 的 ZIP 壓縮檔。
- MIME 類型
- application/vnd.android.package-archive
- 副檔名
- .apk
- 分類
- 壓縮與封存
Magic number(檔案簽章)
| 0x00000000 | 504B0304 | PK·· | 'PK' — it is a ZIP |
ZIP 內含 'AndroidManifest.xml' 項目(通常還有 'classes.dex')。
關於 Android APK 格式
APK 是一個內部配置有硬性要求的 ZIP,外加一個不尋常的東西:一個簽章區塊,卡在最後一個檔案的資料與 ZIP 中央目錄之間,塞在這個格式本來沒有宣告用途的空隙裡。正因如此,APK 能以「整個檔案」為單位驗證,而不是逐項驗證;也正因如此,在 APK 後面追加任何東西、或用普通 ZIP 工具重新打包,都會無聲地毀掉 v2 以後的簽章。內部的 AndroidManifest.xml 是以 Android 二進位 XML 而不是文字儲存的,資源由 resources.arsc 建索引,程式碼則住在一個或多個 classes.dex 裡。
檔頭結構,一個位元組一個位元組看
| 位移 | 長度 | 欄位 | 意義 |
|---|---|---|---|
| 0 | 4 | Local file header signature | 50 4B 03 04——APK 逐位元組就是 ZIP,檔案前段沒有任何東西寫著 Android。每個儲存的項目都以這種標頭開頭,只有位移 0 那個位置是固定的。 |
| 4 | 2 | Version needed to extract | 最低讀取端版本乘以 10:直接儲存的項目是 10,deflate 是 20——APK 裡幾乎每個項目就這兩種,因為 Android 執行環境不接受一般 ZIP 允許的那些冷門壓縮方法。 |
| 6 | 2 | General purpose bit flag | 位元 11 宣告項目名稱是 UTF-8,現代建置工具都會設。位元 3 會把大小與 CRC 移到資料後面,APK 裡不得設定,因為執行環境要直接把項目映射進記憶體,必須事先知道大小。 |
| 8 | 2 | Compression method | 0 是直接儲存、8 是 deflate。resources.arsc 必須未壓縮儲存並對齊,執行環境才能把它記憶體映射;lib/ 底下的原生程式庫通常也是直接儲存,所以 APK 的 hex dump 裡會出現一長串看得懂的資料,換成普通 ZIP 那裡會是壓縮過的雜訊。 |
| 14 | 4 | CRC-32 | 項目未壓縮內容的 CRC-32,位在位移 14,壓縮後與未壓縮大小接在位移 18 和 22。安裝程式解包時檢查的是這些值,和 APK 的簽章是兩件互不相干的事。 |
| 26 | 2 | File name length | 從位移 30 開始的項目名稱長度(位元組)。它和位移 28 的額外欄位長度一起決定這個項目的資料從哪開始——而 APK 的額外欄位常常是 zipalign 塞進去的填補,用來把下一個項目推到 4 位元組或 16 KiB 的邊界上。 |
| 30 | variable | First entry name | 標頭裡唯一暗示「這跟 Android 有關」的地方。哪個名稱排第一取決於建置工具,所以把它當線索、不要當判準:真正認出 APK 的是中央目錄裡有 AndroidManifest.xml,通常還有 classes.dex。 |
| variable | 16 | APK Signing Block magic | 16 個 ASCII 位元組 'APK Sig Block 42',就緊貼在 ZIP 中央目錄前面,前面還有一個 8 位元組的大小,重複記載這個區塊自己的長度。驗證端就是從中央目錄位移往回讀這個區塊,找出 v2、v3 與 v3.1 的簽章記錄。 |
| -22 | 4 | End of central directory signature | 50 4B 05 06,沒有壓縮檔註解時位於倒數第 22 個位元組。每個讀取端都從這裡起步、往回走到中央目錄,然後才知道檔案裡有什麼——所以第一個項目完好的 APK 照樣可能裝不起來。 |
| -6 | 4 | Offset of the central directory | 中央目錄從哪裡開始,從檔案開頭起算。它同時也是簽章區塊的錨點,所以任何工具只要重寫壓縮檔、改動這個值又沒有重新簽章,做出來的 APK 安裝程式就會拒收。 |
怎麼跟長得像的格式分開
| 容易搞混的 | 怎麼分 |
|---|---|
| ZIP | 檔案前段完全沒有位元組層級的差異——APK 就是 ZIP。認出它靠的是內容:壓縮檔根目錄的 AndroidManifest.xml、通常有的 classes.dex、多半有的 resources.arsc,以及裝著 v1 簽章檔案的 META-INF。這些項目都不在的話,不管副檔名寫什麼,它就是個普通 ZIP。 |
| Java JAR | 兩者都是帶 META-INF 目錄的 ZIP,而且 APK 在歷史上本來就是 JAR。分界在其他東西:JAR 裝的是 .class 項目與 META-INF/MANIFEST.MF,APK 裝的是 .dex 項目與 AndroidManifest.xml,而且只有 APK 會在中央目錄前面帶著 'APK Sig Block 42' magic。 |
| aab/apks/xapk/apkm | 全都是 ZIP,全都不是能安裝的 APK。bundletool 產出的 .apks 裡有 toc.pb 加上各個 split APK;.xapk 裡有 manifest.json 加 APK 與 OBB 資料;.apkm 帶的是 info.json。這些標記都放在壓縮檔根目錄,所以分辨它們看的是中央目錄裡最前面那幾個項目名稱——而裡面那些 APK 才是真正的套件。 |
| AndroidManifest.xml (binary XML) | 值得知道,因為很多人把它解出來之後看到一堆亂碼。APK 裡的 manifest 不是文字:它是用 chunk 結構組成的二進位 XML,第一個 16 位元值是 chunk 型別 0x0003,後面接 16 位元的標頭大小,所以 hex dump 開頭是 03 00 08 00。要解它需要字串池與資源對應表,不是 XML 解析器。 |
常見的壞法
- 用普通 ZIP 工具重新打包。把 APK 解開再壓回去,會做出一個結構上完美無缺的壓縮檔,只是簽章區塊被丟掉了,而且 resources.arsc 現在被壓縮過、也沒對齊。這個檔案用任何壓縮軟體開都沒問題,安裝程式就是不收——這是最常見的自傷型 APK 失敗。
- 在檔案後面追加任何東西。有些散布用的包裝會把資料塞在中央目錄後面,用來夾帶通路或推薦來源 id。ZIP 容忍這件事,但 v2 簽章涵蓋的是「到中央目錄位移為止」的整個檔案,所以簽章檢查看到的已經不是當初被簽的那個檔案了。
- 被截斷。和任何 ZIP 一樣,先陣亡的是 end of central directory 記錄,所以下載一半的 APK 每個本地標頭都完好,卻沒辦法列出內容。救援方式是往後掃 50 4B 03 04,而這樣重組出來的 APK 就算每個項目都救回來了,也一定過不了驗證。
- 重新打包的工具改了項目名稱或順序。簽章涵蓋項目名稱,所以哪怕只重新命名一個檔案,或是讓工具把順序正規化、順手丟掉額外欄位裡的 zipalign 填補,v2 以後的簽章就失效了,而留下來的仍然是一個完全合法的 ZIP。
這個格式現在還在用嗎
Android 只裝 APK。App Bundle 改變的是發布方式——開發者上傳 .aab,Google Play 從中產生各機型的 split APK——但落到手機上的成品仍然是 APK,而側載、企業散布、F-Droid 與所有第三方商店處理的也直接就是 APK。所以周邊才會長出那一堆容器格式:發布用 .aab,把一組 split 併成單一下載的 .apks、.xapk、.apkm,它們全都是包著真正套件的 ZIP。就檢視而言這個格式的回報特別好:權限、元件、最低 SDK、簽章憑證、原生架構與完整類別清單,全都不必執行任何東西、直接從壓縮檔讀得出來,APK 分析工具做的就是這件事。
在這個站上打開 Android APK 檔
這些全部在你的瀏覽器裡跑,檔案不會離開你的電腦。
相關格式
容易和 Android APK 搞混的格式,可以比對它們的開頭位元組。
| 格式 | 開頭位元組 | 副檔名 |
|---|---|---|
| ZIP | 50 4B 03 04 | .zip |
| Java JAR | 50 4B 03 04 | .jar |
| Android DEX | 64 65 78 0A | .dex |