Mach-O バイナリ検査ツール
otool や lipo と同じように、バイトから直接 Mach-O バイナリを読み取ります。処理はすべてブラウザ内で完結します。macOS・iOS のファイルは、アーキテクチャが 1 つのシンなファイルか、ビッグエンディアンのアーキテクチャ表がそれぞれ完全なイメージを指すユニバーサルファイルのどちらかです。ユニバーサルなら、各スライスのオフセット・サイズ・アラインメントを並べたアーキテクチャ表をまず表示し、スライスを選ぶと残りの詳細が出ます。ヘッダからは CPU タイプとサブタイプ(arm64、arm64e、x86_64、i386、ppc)、ファイル種別(実行ファイル・動的ライブラリ・バンドル・オブジェクト・dSYM)、そして MH_PIE、MH_TWOLEVEL、MH_DYLDLINK、MH_ALLOW_STACK_EXECUTION、MH_NO_HEAP_EXECUTION といったフラグを読み取ります。ロードコマンドからは、各セグメントの VM アドレス・VM サイズ・ファイルオフセット・ファイルサイズと、その中のセクション(__TEXT,__text や __DATA,__const)、LC_LOAD_DYLIB と LC_LOAD_WEAK_DYLIB の現在バージョン・互換バージョン、@rpath が展開される LC_RPATH、ライブラリが名乗る LC_ID_DYLIB のインストール名、dSYM と対応づける UUID、LC_BUILD_VERSION(あるいは古い LC_VERSION_MIN_MACOSX)のプラットフォーム・最小 OS・SDK が得られます。セグメントのサイズは「なぜこのバイナリは 40MB あるのか」への答えですが、メモリ上のサイズとファイル上のサイズは別物です。__bss のようなゼロフィルセクションや 4GB の __PAGEZERO はディスクを一切使いません。やらないことも明確です。逆アセンブルはしませんし、コード署名の検証もしません。LC_CODE_SIGNATURE はファイルが署名ブロブを持つという事実にすぎず、その署名が有効か、誰の証明書か、公証済みかに答えられるのは Mac の codesign と spctl だけです。バイト順もワードサイズもマジックから読み取るため、20 年前のビッグエンディアン 32 ビット PowerPC バイナリも arm64e と同じように開けます。ロードコマンド表がファイルより大きいと主張するファイルは、中途半端に読まずに名前付きのエラーで拒否します。
使い方
- バイナリを枠にドロップします。拡張子のないコマンドラインツール、.dylib、.bundle、.o、dSYM 内の DWARF ファイル、.app 内の実行ファイルなどに対応します。
- ユニバーサルファイルなら、まずアーキテクチャ表を見ます。「arm64 が入っているか」の答えです。次にアーキテクチャを選び、ヘッダ・セグメント・ライブラリを確認します。
- 保護設定のチップを確認します。近年のビルドなら PIE は「あり」のはずで、スタック実行が許可されている場合は配布前に理由を説明できるべきです。
- リンクされたライブラリで実行時に必要なものを、実行時検索パスで @rpath が展開される先を、インストール名でライブラリが名乗るパスを確認します。
- サイズを追うときはセクション一覧を開きます。__TEXT はコード、__DATA は書き込み可能なデータ、__LINKEDIT はシンボルテーブルと署名で、dSYM では __DWARF セクションが最大になるのが普通です。
よくある質問
- アプリに arm64(Apple シリコン)のスライスが入っているか、どう確かめますか?
- .app 内の Contents/MacOS/<名前> を開き、アーキテクチャ表を見てください。ユニバーサルファイルはビッグエンディアンの fat ヘッダで始まり、各アーキテクチャとファイル内オフセット、スライスを配置したアラインメントを並べます。そこに無いアーキテクチャはファイルにも無いので、x86_64 しかないアプリはネイティブではなく Rosetta で動きます。各スライスは自前のヘッダとロードコマンドを持つ完全な Mach-O イメージであり、だからユニバーサルバイナリのサイズはおおよそ各部の合計になり、lipo は 1 スライスだけ取り出して細くできます。
- コード署名があれば信頼できるバイナリですか?
- いいえ。LC_CODE_SIGNATURE が示すのは、__LINKEDIT 内のどこかに署名ブロブがあるという事実だけです。ハッシュが現在のページと一致するか、証明書が Apple まで連なるか、失効していないか、公証済みかは何も言いません。それらを確かめるには証明書チェーンが要り、公証には Apple のサーバも要ります。つまり Mac で codesign --verify --deep --strict と spctl --assess を実行する必要があります。Hardened Runtime もヘッダではなく署名のエンタイトルメントとフラグにあるため、ヘッダだけでは有効かどうか分かりません。
- コード量のわりにファイルが大きいのはなぜですか?
- セグメント表の 2 つのサイズ列を並べて見てください。__TEXT はコードと読み取り専用データで通常は読み取り・実行としてマップされ、__DATA と __DATA_CONST は書き込み可能なデータ、__LINKEDIT はシンボルテーブル・function starts・dyld の fixup・コード署名を含み、配布バイナリでは最大の部分になることも珍しくありません。メモリ上とファイル上のサイズが違うのは意図的で、__bss のようなゼロフィルセクションはアドレス空間だけを占めてディスクを使わず、__PAGEZERO はファイルから決してマップされない 4GB を予約するだけです。ユニバーサルなら全アーキテクチャを同時に抱えている点も忘れずに。
- インストール名とは何ですか。@rpath をよく見かけるのはなぜですか?
- 動的ライブラリは自分が置かれるはずのパスを LC_ID_DYLIB に記録し、それにリンクするプログラムはその文字列を自分の LC_LOAD_DYLIB にそのままコピーします。絶対パスなら、どのマシンでも寸分違わずそこに存在しなければなりません。今のフレームワークは代わりに @rpath/Something.framework/Something を使い、解決をプログラム側に任せます。ここに並ぶ LC_RPATH が、dyld が @rpath の代わりに順に試すディレクトリで、アプリバンドルをどこへ移しても動くよう @executable_path や @loader_path 基準で書かれるのが普通です。起動時の「image not found」の典型的な原因は、この rpath の欠落です。
- iOS バイナリの「暗号化」チップは何を意味しますか?
- App Store 配信の iOS バイナリには cryptid が 1 の LC_ENCRYPTION_INFO(または 64 ビット版)があり、通常は __TEXT の大半にあたる範囲がストアの鍵で暗号化されていることを示します。カーネルが読み込み時にその範囲を復号するため、ディスク上のファイルではその領域を読めません。strings は有用な文字列を返さず、逆アセンブラにはノイズに見えます。自分でビルドしたバイナリや、ストアの再署名前の IPA から取り出したものは cryptid が 0 で平文です。コマンドはあっても値が 0 のファイルも多いので、このツールはコマンドの有無ではなく cryptid を見ます。
- バイナリはどこかにアップロードされますか?
- いいえ。ファイルはブラウザの File API で読み、ページ内の JavaScript が解析します。サーバへは送りませんし、送る先のサーバも存在しません。未公開ビルドや顧客のアプリを Web サイトに渡したくはないはずなので、他の多くのツールより重要な点です。ページを開いたあとネットワークを切っても、すべて同じように動きます。
関連ツール
ELF バイナリ検査ツール
Linux の実行ファイル・.so・.o をブラウザーで開きます。アーキテクチャ、必要な共有ライブラリ、ビルド ID、PIE・NX・RELRO の有無まで。
PE インスペクター: Windows EXE・DLL ビューア
Windows の .exe、.dll、.sys、.efi をブラウザで開きます。アーキテクチャ、サブシステム、インポート、エクスポート、セクション、ASLR・DEP・CFG の有無まで。
Java クラスファイル解析ツール
コンパイル済みの .class ファイルをブラウザーで解析します。クラスファイルバージョンと Java リリース、アクセスフラグ、定数プール、フィールド、メソッド、参照クラスを表示します。
フォントファイル検査ツール
.ttf、.otf、.woff を開いて、本当のファミリー名、グリフ数と対応文字数、バージョン、ライセンス文、埋め込み許諾を読み取ります。
gzip(.gz)インスペクター
.gz のヘッダーを項目ごとに読み、全メンバーをたどり、ブラウザ内で CRC32 と実サイズを再計算してトレーラーを検証します。
xz(.xz)ストリーム解析ツール
xz コンテナを読み取ります。ストリームヘッダー、各ブロックのヘッダーとフィルターチェーン、インデックス、フッター、そして展開せずに分かる本当のサイズまで。