Hexter

バイナリファイルフォーマットの内側:PNG、ZIP、Mach-O のヘッダーを解説

日常的に使うすべてのファイルフォーマット、画像、アーカイブ、実行ファイルには、バイト単位まで定義された構造がありますが、その構造のほとんどは、それらのファイルを開くのに普段使うツールからは見えません。プレビューは画像を見せます。アーカイブユーティリティは展開されたファイルを見せます。どちらも、その結果を可能にしたバイトは見せてくれません。ここでは、一般的な 3 つのフォーマットが実際に内部でどう見えるのか、そしてそれを自分で見る方法を紹介します。

PNG:ラベル付きチャンクの並び

すべての PNG ファイルは同じ 8 バイトで始まります:89 50 4E 47 0D 0A 1A 0A。これは画像データではなく署名で、ファイルが誤ってテキストとして転送された場合に使えなくなるバイトを意図的に含むよう選ばれています(0x0A と 0x0D は改行文字なので、壊れたテキストモードの転送はそれらを検出しやすい形で破壊します)。

署名の後は、PNG のすべてがチャンクであり、すべてのチャンクは同じ 4 部構成のレイアウトに従います。

PNG チャンクの構造の図:4 バイトの長さフィールド、IHDR や IDAT のような 4 バイトのタイプフィールド、チャンクデータそのもの、そして末尾の 4 バイトの CRC チェックサム。
最初(IHDR)から最後(IEND)まで、すべての PNG チャンクはこの同じ 4 フィールドのレイアウトに従います。
  • 長さ(4 バイト):この後に続くデータのバイト数。タイプや CRC は数えません。
  • タイプ(4 バイト、常に ASCII):IHDR(画像ヘッダー、常に最初)、IDAT(圧縮されたピクセルデータ、複数存在しうる)、IEND(ファイルの終わりを示す)のような 4 文字のコード。
  • データ:そのチャンクタイプが指定するもの。IHDR のデータは幅、高さ、ビット深度、カラータイプで、常に同じ固定レイアウトです。
  • CRC(4 バイト):タイプとデータのチェックサム。デコーダが壊れたチャンクを黙って誤読するのではなく検出できるようにします。

これでフォーマットの全体です。PNG デコーダは、IEND に達するまで、長さを読んでどれだけ飛ばすか把握しながら、チャンクからチャンクへとたどっていくだけです。

ZIP:ローカルヘッダー、そして末尾のディレクトリ

ZIP アーカイブは、各ファイルを独自のローカルファイルヘッダーとともに格納します。これは署名 50 4B 03 04(先頭の PK はフォーマットの作者 Phil Katz への参照)で示されます。そのヘッダーにはファイル名、圧縮方式、圧縮後および圧縮前の両方のサイズが含まれ、その直後にファイルの圧縮データが続きます。

人がつまずく部分:ローカルヘッダー内のファイルごとのサイズやチェックサムは、ときにゼロで、本当の値は後の「データデスクリプタ」に格納されます。これは、データがすでに書き込まれるまで最終サイズが分からないストリーミング書き込みをサポートするよう ZIP が設計されたためです。アーカイブに実際に何が入っているかの決定版リストは、ファイルの一番末尾にあるセントラルディレクトリに存在します。これはまた、一部の壊れたり切り詰められたりした ZIP が、先頭からスキャンすると中身があるように見えても開けない理由でもあります。使っているツールがまずセントラルディレクトリを読み、それが欠けているか損傷しているからです。

Mach-O:マジックナンバー、そしてロードコマンド

Mach-O は、コンパイル済みバイナリのために macOS が使う実行ファイルフォーマットです。これは、ローダに 2 つのことを同時に伝えるマジックナンバーで始まります:32 ビットか 64 ビットか、そしてファイルの残りがどのバイト順を使うか。0xFEEDFACE は 32 ビット、0xFEEDFACF は 64 ビットで、バイトが逆順になっている(0xCEFAEDFE または 0xCFFAEDFE)のを見たら、それはファイルが、読み取るマシンとは反対のバイト順で書かれたことを意味します。

マジックナンバーの後には固定ヘッダー(CPU タイプ、ファイルタイプ、ロードコマンドの数)が続き、その後にその数だけのロードコマンドが順番に並びます。ロードコマンドは、Mach-O ファイルがローダに必要なすべて、つまりどのセグメントをメモリにマップするか、どのダイナミックライブラリをリンクするか、エントリポイントがどこにあるかを記述する方法です。PNG のチャンクと似た考え方です。固定ヘッダー、そして自己記述的なレコードの並びで、それぞれが読み手に次へ進むためにどれだけ飛ばすかを教えます。

これを自分で見る

このどれにもパーサーを書く必要はありません。任意の PNG、ZIP、Mach-O ファイルを Hexter で開けば、アナライズタブが自動的にフォーマットを認識し、実際のチャンクまたはロードコマンドの構造をたどって、生のヘックスの隣にツリーとして表示します。フィールドをクリックすれば、それがどのバイトから来たのかを正確に見られます。デフォルトで読み取り専用なので、これは実際のファイル、アプリのバイナリ、スクリーンショット、ダウンロードしたアーカイブに対して、それを変更するリスクなく本当に安全に行えます。

上記を代わりに手作業で確認したいなら:PNG を開いて最初の 8 バイトが署名と一致することを確認し、次にバイト 8 から 11(最初のチャンクの長さ)とバイト 12 から 15(そのタイプ。IHDR と読めるはず)を見ます。数分でできるほど小さな練習で、一生使ってきたのに実際には見たことのないファイルフォーマットを理解する、本当に有用な方法です。