ByteScope

Mach-O 実行ファイル形式

macOS/iOS の Mach-O 実行ファイル、dylib またはバンドル。

MIME タイプ
application/x-mach-binary
拡張子
.dylib, .bundle, .o
カテゴリ
実行ファイル・バイトコード

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

0x00000000CFFAEDFE····Mach-O 64-bit LE magic

Mach-O 実行ファイル形式について

Mach-O は macOS と iOS のネイティブなバイナリ形式で、ELF と違って固定長のテーブルを一切持ちません。7〜8 個のフィールドからなるヘッダの後ろに、自分の種別とサイズを名乗る可変長のロードコマンドが並び、ローダーはそれを順に歩いていきます。セグメントの写像、動的リンカのパス、最低 OS バージョン、エントリポイント、コード署名。これらのどれにも予約枠を与えずに 1 つのファイルへ収められるのは、この設計のおかげです。マジックナンバーはエンディアンとワード幅の指標も兼ねていて、CF FA ED FE が 64 ビットのリトルエンディアン、CE FA ED FE が 32 ビット、バイト順が逆の形はビッグエンディアンの PowerPC 時代のものです。

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

オフセット長さフィールド意味
04magic64 ビットのリトルエンディアンイメージなら、ディスク上では CF FA ED FE。リトルエンディアンで読めば値 0xFEEDFACF です。CE FA ED FE が 32 ビット形式(0xFEEDFACE)、FE ED FA CF と FE ED FA CE は PowerPC のバイナリが持つビッグエンディアン版。つまりこの 4 バイトだけで、形式もワード幅もバイト順も同時に分かります。
44cputypex86-64 が 0x01000007、arm64 が 0x0100000C。上位ビットの 0x01000000 が 64 ビットを意味し、下位ビットが基本アーキテクチャ(x86 が 7、ARM が 12)です。同じアーキテクチャの 32 ビットビルドではそのフラグが落ちるので、7 単体なら i386、12 単体なら 32 ビット ARM です。
84cpusubtypeアーキテクチャ内の細分で、上位ビットは機能フラグとして使われます。Apple シリコンのバイナリでは 0x80000002 がよく現れます(サブタイプ 2 の arm64e に上位フラグが立った形)。arm64e のシステムバイナリと普通の arm64 アプリを分けているのがこのフィールドです。
124filetype1 が MH_OBJECT(.o)、2 が MH_EXECUTE、6 が MH_DYLIB、8 が MH_BUNDLE、ほかにデバッグ用の相棒である MH_DSYM。手元にあるものがプログラムなのかライブラリなのか読み込み可能なプラグインなのかに、バイトレベルで答えるのがこれです。実際には .dylib も .so も拡張子なしも使われますから。
164ncmdsヘッダの後ろに続くロードコマンドの数。ちょっとしたコマンドラインツールで 15〜20 個ほど、大きなフレームワークならもっと多くなります。インデックスが無いので、どれも順番に歩いて処理されます。
204sizeofcmds全ロードコマンドが占める合計バイト数。各コマンドの cmdsize をたどって合計すると、ちょうどこの値に着地しなければなりません。この形式が持つ単体の整合性チェックとして最も強力で、編集された疑いのあるファイルにまず走らせるべき検査です。
244flags属性ビット。イメージが PIE かどうか、2 レベル名前空間の解決を使うかどうか、未定義シンボルが無いかどうか。今どきの典型的な実行ファイルなら 0x00200085 のような値が読めます。
284reserved64 ビットのヘッダにだけあり、常にゼロです。後続を 8 バイト境界に揃えるためだけに存在するので、最初のロードコマンドは 64 ビットファイルでは 32、32 ビットファイルでは 28 に来ます。
32variableFirst load commandどのロードコマンドも 4 バイトの cmd と 4 バイトの cmdsize で始まるので、読み手は理解できないコマンドを読み飛ばせます。先頭は通常 LC_SEGMENT_64(0x19)で、切り分けで効いてくるのは LC_LOAD_DYLIB、LC_MAIN、LC_CODE_SIGNATURE、LC_BUILD_VERSION あたりです。

似た形式との見分け方

紛らわしい形式見分け方
Mach-O(ユニバーサル)ユニバーサルバイナリは、フィールドが増えた Mach-O ファイルではありません。完全な Mach-O イメージを複数抱えたラッパーです。先頭は CF FA ED FE ではなく CA FE BA BE(ビッグエンディアン)で、単体バイナリのマジックナンバーはもっと奥、各 fat_arch エントリが示すオフセットに現れます。fat のファイルを単体として読むと、いきなり意味不明な cputype が出てきます。
Java class ファイル有名な衝突ですが、ぶつかるのは fat のほうだけです。Java の class ファイルも CA FE BA BE で始まります。決着を付けるのは次の 4 バイトで、class ファイルの major_version(6〜7 バイト目)は 45 以上、一方 fat バイナリの nfat_arch は小さな個数なので、その上位半分はゼロになります。
ELF 実行ファイルどちらもオブジェクトファイルに .o を使い、共有ライブラリも素っ気ない名前で呼ぶので、拡張子は助けになりません。Linux でビルドした .so と macOS でビルドした .dylib は、ディレクトリ一覧では見分けが付きません。決めるのはオフセット 0 で、7F 45 4C 46 なら ELF、CF FA ED FE なら Mach-O です。なお Mach-O のマジックナンバーはディスク上ではバイト順が逆に格納されるので、文書が 0xFEEDFACF と書くものが hex ダンプでは CF FA ED FE と見えます。
Big-endian (CIGAM) Mach-OPowerPC 時代のファイルは同じマジックナンバーをバイト入れ替えで格納します。FE ED FA CE と FE ED FA CF です。この場合はヘッダも全ロードコマンドもすべてビッグエンディアンになるので、リトルエンディアンのマジックナンバーしか知らないパーサーがこれらを読み違えることはありません。正しく拒否します。

よくある壊れ方

  • Apple シリコン上では、その場での編集はどんなものでも致命的です。arm64 のコードはアドホック署名であっても署名されているので、テキストセグメントの 1 バイトを変えただけで LC_CODE_SIGNATURE 内のハッシュが無効になり、カーネルは変更後のコードを実行せず、起動時にプロセスを殺します。Mach-O にパッチを当てるとは署名し直すことであり、ELF のようにバイトをこっそり書き換えるのに相当する行為は存在しません。
  • 途中切れ。ロードコマンドは先頭にあってきちんとパースできるので、途中までのダウンロードでもアーキテクチャとファイル種別は正しく報告され、切断より先のセグメントデータだけが欠けます。確認方法はロードコマンドを歩き、各セグメントの fileoff + filesize を実際のファイル長と突き合わせることです。
  • sizeofcmds がコマンドと合わなくなっている。ncmds と sizeofcmds を直さずにロードコマンドを足したり削ったりすると、ヘッダはもっともらしいまま、走査がセグメントデータへはみ出します。ツールがエラーメッセージではなく、あり得ないロードコマンド種別を報告するのはこのためです。
  • fat バイナリから長さを間違えて切り出したスライス。fat ヘッダのオフセットは合っているのに他人のサイズを使ってコピーすると、Mach-O ヘッダは完璧なのに末尾が切れたファイルができます。lipo を使わずに手でアーキテクチャを切り出したときの、いつもの結果です。
  • テキストモード転送、あるいは紛れ込んだ BOM。どのバイナリでも同じ話で、改行変換は最初の 0D 0A より後ろを全部ずらし、先頭に付いた BOM はマジックナンバーをオフセット 0 から押し出します。そうなると、コードのバイトは 1 つ残らず揃っているのにファイルは認識されなくなります。

この形式の現在地

macOS と iOS の実行ファイル、フレームワーク、dylib、カーネル拡張、.o ファイルはすべて Mach-O であり、Apple のローダーが受け付ける唯一の形式なので、この場所で何かに取って代わられたことはありません。Mach を経由して NeXTSTEP から受け継いだ形式で、同じ概念に対する語彙(ロードコマンド、セグメントとセクション、dyld)が ELF と違うのはそのためです。実際に今日出荷される Mach-O のほとんどは、アプリバンドルの中の単一アーキテクチャの arm64 イメージです。ユニバーサル形式は、移行期に開発者がビルドするものであり、Homebrew や Rosetta 時代のソフトウェアが今も配っているものです。バイトを覗く側にとって Apple のコード署名規則が意味するのは、Mach-O が実質読み取り専用だということ。分解するのは自由ですが、変更したコピーは署名し直すまで起動しません。

Mach-O 実行ファイルをこのサイトで開く

どれもブラウザ内で動きます。ファイルが端末から出ることはありません。

関連する形式

Mach-O 実行ファイルと混同しやすい形式です。先頭バイトを見比べてください。

形式先頭バイト拡張子
Mach-O(ユニバーサル)CA FE BA BE.dylib
ELF 実行ファイル7F 45 4C 46.elf
PE(Windows 実行ファイル)4D 5A.exe

よくある質問

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

ファイル先頭の数バイトで、名前が何であろうとその形式を示すものです。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 でも悪意のあるペイロードを含んでいることがあります。特に拡張子とバイト列が食い違うときに真の形式を知るのは有用な第一段階ですが、マルウェアスキャンではありません。

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