ByteScope

Android APK ファイル形式

Android アプリケーションパッケージ。コンパイル済みコード・リソース・マニフェストを含む ZIP アーカイブです。

MIME タイプ
application/vnd.android.package-archive
拡張子
.apk
カテゴリ
アーカイブ・圧縮

マジックナンバー(ファイル署名)

0x00000000504B0304PK··'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 に入っています。

ヘッダーの構造(バイト単位)

オフセット長さフィールド意味
04Local file header signature50 4B 03 04 — APK はバイト単位で ZIP であり、ファイルの先頭に Android を示すものは何もありません。格納された各エントリはこのヘッダで始まりますが、位置が固定されているのはオフセット 0 のものだけです。
42Version needed to extract必要な読み取り側の最小バージョンを 10 倍した値で、無圧縮なら 10、deflate なら 20。APK のエントリはほぼこの 2 つに収まります。Android のランタイムは、汎用 ZIP が許すような珍しい圧縮方式を受け付けないからです。
62General purpose bit flagビット 11 はエントリ名が UTF-8 であることを宣言し、今どきのビルドツールはこれを立てます。ビット 3(サイズと CRC をデータの後ろへ移すもの)は APK では立ててはいけません。ランタイムがエントリを直接メモリに写像するため、サイズが先に必要だからです。
82Compression method0(無圧縮)か 8(deflate)。resources.arsc はランタイムがメモリマップできるよう、無圧縮かつアライメントを揃えて格納しなければなりません。lib/ 以下のネイティブライブラリもたいてい無圧縮です。だから APK の hex ダンプには、素の ZIP なら圧縮ノイズが見えるはずのところに、読める形のデータが長々と並びます。
144CRC-32エントリの非圧縮バイト列に対する CRC-32 がオフセット 14 にあり、圧縮後サイズと非圧縮サイズが 18 と 22 に続きます。インストーラーが展開時に検査するのはこれらの値で、APK の署名とはまったく別物です。
262File name lengthオフセット 30 から始まるエントリ名のバイト長。オフセット 28 の extra field 長と合わせて、このエントリのデータがどこから始まるかを確定させます。APK の extra field には、次のエントリを 4 バイトまたは 16 KiB 境界へ押し出すために zipalign が入れたパディングが入っていることがよくあります。
30variableFirst entry nameヘッダの中で、これが Android のものだと匂わせる唯一の場所です。どの名前が先頭に来るかはビルドツール次第なので、判定材料ではなく手掛かりとして扱ってください。APK であることを示すのは、セントラルディレクトリのどこかに AndroidManifest.xml があり、通常は classes.dex もあることです。
variable16APK Signing Block magicASCII 16 バイトの 'APK Sig Block 42'。ZIP のセントラルディレクトリの直前にあり、その前にブロック自身の長さを繰り返す 8 バイトのサイズが付きます。検証側は、セントラルディレクトリのオフセットから後ろ向きにこのブロックを読んで v2 / v3 / v3.1 の署名レコードを見つけます。
-224End of central directory signature50 4B 05 06。アーカイブコメントが無ければ末尾から 22 バイトの位置です。どの読み手もここから始めてセントラルディレクトリまで戻り、そこで初めてファイルの中身を知ります。最初のエントリが無傷でもインストールに失敗する APK があるのはそのためです。
-64Offset 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 と混同しやすい形式です。先頭バイトを見比べてください。

形式先頭バイト拡張子
ZIP50 4B 03 04.zip
Java JAR50 4B 03 04.jar
Android DEX64 65 78 0A.dex

よくある質問

ファイルのマジックナンバーとは何ですか?

ファイル先頭の数バイトで、名前が何であろうとその形式を示すものです。PNG は必ず 89 50 4E 47 で始まり、ZIP は 50 4B 03 04 で始まります。OS もブラウザも file コマンドも、拡張子を信用せずこのバイト列を読みます。だからファイル名を変えても中身の正体は変わりません。

マジックナンバーを自分で確認する方法は?

macOS や Linux なら xxd -l 16 ファイル名 で先頭 16 バイトが表示されます。Windows なら PowerShell で Format-Hex -Path ファイル名 -Count 16 です。あるいはこのサイトのファイル形式判別ツールにドロップすれば、ローカルでバイト列を読んで形式を教えてくれます。アップロードはありません。

2 つの形式が同じマジックナンバーを持つのはなぜ?

一方が他方の上に作られているからです。docx、xlsx、pptx、apk、jar、epub はすべて中身が ZIP アーカイブなので ZIP の署名で始まり、区別するには内部のエントリを見る必要があります。Java の class ファイルと macOS のユニバーサルバイナリはどちらも CA FE BA BE で始まり、直後のフィールドで見分けます。

マジックナンバーがまったくないファイルもある?

あります。XML、HTML、JSON、SVG、CSV といったテキストベースの形式には固定のバイト署名がないため、構造から判別します。先頭の <?xml 宣言、<svg 要素、あるいは { に続く引用符付きのキーなどです。これは確定ではなく推測なので、このリファレンスではそうした形式をプローブ判別として示しています。

マジックナンバーが一致すればそのファイルは安全?

いいえ。わかるのは形式であって、中身が無害かどうかではありません。正規の PDF や docx でも悪意のあるペイロードを含んでいることがあります。特に拡張子とバイト列が食い違うときに真の形式を知るのは有用な第一段階ですが、マルウェアスキャンではありません。

Android APK かもしれないファイルをお持ちですか?ファイル形式判別ツールで調べる — マジックナンバーをローカルで読み取ります。アップロードは一切ありません。またはすべての形式を見る。