Android APK ファイル形式
Android アプリケーションパッケージ。コンパイル済みコード・リソース・マニフェストを含む ZIP アーカイブです。
- MIME タイプ
- application/vnd.android.package-archive
- 拡張子
- .apk
- カテゴリ
- アーカイブ・圧縮
マジックナンバー(ファイル署名)
| 0x00000000 | 504B0304 | PK·· | 'PK' — it is a ZIP |
ZIP 内に 'AndroidManifest.xml' エントリ(通常は 'classes.dex' も)があります。
Android APK 形式について
APK は、内部レイアウトが決められた ZIP アーカイブに、1 つ変わったものを足したものです。最後のファイルのデータと ZIP のセントラルディレクトリの間、形式が未使用のまま残している隙間に、署名ブロックがねじ込まれています。これのおかげで APK はエントリ単位ではなくファイル全体として検証できるようになり、同時に、APK に何かを追記したり普通の ZIP ツールで作り直したりすると v2 以降の署名が黙って壊れる理由にもなっています。中を見ると、AndroidManifest.xml はテキストではなく Android のバイナリ XML として格納され、リソースは resources.arsc で索引され、コードは 1 つ以上の 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 のエントリはほぼこの 2 つに収まります。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 ダンプには、素の ZIP なら圧縮ノイズが見えるはずのところに、読める形のデータが長々と並びます。 |
| 14 | 4 | CRC-32 | エントリの非圧縮バイト列に対する CRC-32 がオフセット 14 にあり、圧縮後サイズと非圧縮サイズが 18 と 22 に続きます。インストーラーが展開時に検査するのはこれらの値で、APK の署名とはまったく別物です。 |
| 26 | 2 | File name length | オフセット 30 から始まるエントリ名のバイト長。オフセット 28 の extra field 長と合わせて、このエントリのデータがどこから始まるかを確定させます。APK の extra field には、次のエントリを 4 バイトまたは 16 KiB 境界へ押し出すために zipalign が入れたパディングが入っていることがよくあります。 |
| 30 | variable | First entry name | ヘッダの中で、これが Android のものだと匂わせる唯一の場所です。どの名前が先頭に来るかはビルドツール次第なので、判定材料ではなく手掛かりとして扱ってください。APK であることを示すのは、セントラルディレクトリのどこかに AndroidManifest.xml があり、通常は classes.dex もあることです。 |
| variable | 16 | APK Signing Block magic | ASCII 16 バイトの '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 Sig Block 42' のマジックナンバーを持つのは APK だけです。 |
| aab/apks/xapk/apkm | どれも ZIP ですが、どれもインストールできる APK ではありません。bundletool が出す .apks には toc.pb と分割 APK が、.xapk には manifest.json と APK と OBB データが、.apkm には info.json が入っています。これらの目印はいずれもアーカイブのルートにあるので、セントラルディレクトリの先頭付近のエントリ名で見分けが付きます。そして本物のパッケージは、その中に入っている APK のほうです。 |
| AndroidManifest.xml (binary XML) | 取り出してみて意味不明なバイト列に出くわす人がいるので、知っておく価値があります。APK の中のマニフェストはテキストではありません。チャンク構造のバイナリ XML で、最初の 16 ビットがチャンク型 0x0003、続いて 16 ビットのヘッダサイズなので、hex ダンプは 03 00 08 00 で始まります。デコードには XML パーサーではなく、文字列プールとリソースマップが必要です。 |
よくある壊れ方
- 普通の ZIP ツールで作り直した。APK を展開して zip し直すと、構造としては完璧なアーカイブができますが、署名ブロックは落ち、resources.arsc は圧縮されてアライメントも崩れています。どのアーカイブビューアでも問題なく開き、インストーラーには拒否されます。自分で自分を壊す APK の失敗として断然多いパターンです。
- ファイルに何かを追記した。配布用のラッパーの中には、チャネル id や referrer id を運ぶためにセントラルディレクトリの後ろへデータを詰めるものがあります。ZIP はそれを許容しますが、v2 署名はセントラルディレクトリのオフセットまでのファイルを対象にしているので、署名検査は署名時とは別のファイルを見ることになります。
- 途中切れ。ZIP なので最初に失われるのは end-of-central-directory レコードです。途中までダウンロードした APK はローカルヘッダがすべて無傷なのに、中身を列挙する手段がありません。復旧は 50 4B 03 04 を前方に探すことになりますが、そうやって組み直した APK は、全エントリを回収できたとしても検証を通りません。
- 再パッケージツールがエントリをリネームしたり並べ替えたりした。署名はエントリ名も対象にしているので、1 ファイル名を変えるだけでも、あるいはツールに並び順を正規化させて extra field の zipalign パディングを落とさせるだけでも、v2 以降の署名は無効になります。それでも ZIP としては完全に正当なファイルが残ります。
この形式の現在地
Android がインストールするのは APK だけです。App Bundle は公開の仕方を変えましたが(開発者が .aab をアップロードし、Google Play が端末ごとの分割 APK を生成する)、端末に着地する成果物は今も APK ですし、サイドロードも法人向け配布も F-Droid も代替ストアも、直接 APK を扱っています。周辺にコンテナ形式の動物園ができているのはそのためで、公開用の .aab、分割 APK 一式を 1 ダウンロードで配るための .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 |