macOS Universal binaries & Mach-O Format

HackTricksのサポート

基本情報

Mac OSのバイナリは通常、universal binariesとしてコンパイルされます。Universal binary1つのファイル内で複数のアーキテクチャをサポートできます。

これらのバイナリは基本的にMach-O構造に従います。Mach-O構造は次のように構成されています:

  • ヘッダー

  • ロードコマンド

  • データ

Fatヘッダー

次のコマンドでファイルを検索します:mdfind fat.h | grep -i mach-o | grep -E "fat.h$"

#define FAT_MAGIC	0xcafebabe
#define FAT_CIGAM	0xbebafeca	/* NXSwapLong(FAT_MAGIC) */

struct fat_header {
	uint32_t	magic;		/* FAT_MAGIC or FAT_MAGIC_64 */
	uint32_t	nfat_arch;	/* 後続する構造体の数 */
};

struct fat_arch {
cpu_type_t	cputype;	/* CPU指定子(int) */
cpu_subtype_t	cpusubtype;	/* マシン指定子(int) */
uint32_t	offset;		/* このオブジェクトファイルへのファイルオフセット */
uint32_t	size;		/* このオブジェクトファイルのサイズ */
uint32_t	align;		/* 2の累乗としてのアライメント */
};

ヘッダーにはマジックバイトが続き、ファイルが含むアーキテクチャの数nfat_arch)と各アーキテクチャにはfat_arch構造体があります。

次のコマンドで確認します:

% file /bin/ls
/bin/ls: Mach-O universal binary with 2 architectures: [x86_64:Mach-O 64-bit executable x86_64] [arm64e:Mach-O 64-bit executable arm64e]
/bin/ls (for architecture x86_64):	Mach-O 64-bit executable x86_64
/bin/ls (for architecture arm64e):	Mach-O 64-bit executable arm64e

% otool -f -v /bin/ls
Fat headers
fat_magic FAT_MAGIC
nfat_arch 2
architecture x86_64
    cputype CPU_TYPE_X86_64
cpusubtype CPU_SUBTYPE_X86_64_ALL
capabilities 0x0
    offset 16384
    size 72896
    align 2^14 (16384)
architecture arm64e
    cputype CPU_TYPE_ARM64
cpusubtype CPU_SUBTYPE_ARM64E
capabilities PTR_AUTH_VERSION USERSPACE 0
    offset 98304
    size 88816
    align 2^14 (16384)

またはMach-O Viewツールを使用して:

通常、2つのアーキテクチャ向けにコンパイルされたuniversal binaryは、1つのアーキテクチャ向けにコンパイルされたものと比べてサイズが倍になることが多いです。

Mach-Oヘッダー

ヘッダーには、ファイルを識別するためのマジックバイトや対象アーキテクチャに関する情報など、ファイルに関する基本情報が含まれています。次のコマンドで確認できます:mdfind loader.h | grep -i mach-o | grep -E "loader.h$"

#define	MH_MAGIC	0xfeedface	/* the mach magic number */
#define MH_CIGAM	0xcefaedfe	/* NXSwapInt(MH_MAGIC) */
struct mach_header {
uint32_t	magic;		/* mach magic number identifier */
cpu_type_t	cputype;	/* cpu specifier (e.g. I386) */
cpu_subtype_t	cpusubtype;	/* machine specifier */
uint32_t	filetype;	/* type of file (usage and alignment for the file) */
uint32_t	ncmds;		/* number of load commands */
uint32_t	sizeofcmds;	/* the size of all the load commands */
uint32_t	flags;		/* flags */
};

#define MH_MAGIC_64 0xfeedfacf /* the 64-bit mach magic number */
#define MH_CIGAM_64 0xcffaedfe /* NXSwapInt(MH_MAGIC_64) */
struct mach_header_64 {
uint32_t	magic;		/* mach magic number identifier */
int32_t		cputype;	/* cpu specifier */
int32_t		cpusubtype;	/* machine specifier */
uint32_t	filetype;	/* type of file */
uint32_t	ncmds;		/* number of load commands */
uint32_t	sizeofcmds;	/* the size of all the load commands */
uint32_t	flags;		/* flags */
uint32_t	reserved;	/* reserved */
};

Mach-Oファイルタイプ

異なるファイルタイプがあります。これらはこちらのソースコードで定義されています。最も重要なものは次のとおりです:

  • MH_OBJECT: リロケータブルオブジェクトファイル(コンパイルの中間生成物であり、まだ実行可能ではありません)。

  • MH_EXECUTE: 実行可能ファイル。

  • MH_FVMLIB: 固定VMライブラリファイル。

  • MH_CORE: コードダンプ

  • MH_PRELOAD: プリロードされた実行可能ファイル(XNUではもはやサポートされていません)

  • MH_DYLIB: ダイナミックライブラリ

  • MH_DYLINKER: ダイナミックリンカ

  • MH_BUNDLE: "プラグインファイル"。gccの-bundleを使用して生成され、NSBundleまたはdlopenによって明示的にロードされます。

  • MH_DYSM: 付随する.dSymファイル(デバッグ用のシンボルを含むファイル)。

  • MH_KEXT_BUNDLE: カーネル拡張。

# Checking the mac header of a binary
otool -arch arm64e -hv /bin/ls
Mach header
magic  cputype cpusubtype  caps    filetype ncmds sizeofcmds      flags
MH_MAGIC_64    ARM64          E USR00     EXECUTE    19       1728   NOUNDEFS DYLDLINK TWOLEVEL PIE

またはMach-O Viewを使用する:

Mach-O フラグ

ソースコードでは、ライブラリの読み込みに役立ついくつかのフラグも定義されています:

  • MH_NOUNDEFS: 未定義の参照なし(完全にリンクされています)

  • MH_DYLDLINK: Dyld リンキング

  • MH_PREBOUND: ダイナミック参照が事前にバインドされています。

  • MH_SPLIT_SEGS: ファイルが r/o および r/w セグメントに分割されています。

  • MH_WEAK_DEFINES: バイナリには弱く定義されたシンボルがあります

  • MH_BINDS_TO_WEAK: バイナリが弱いシンボルを使用しています

  • MH_ALLOW_STACK_EXECUTION: スタックを実行可能にします

  • MH_NO_REEXPORTED_DYLIBS: ライブラリに LC_REEXPORT コマンドがありません

  • MH_PIE: 位置に依存しない実行可能ファイル

  • MH_HAS_TLV_DESCRIPTORS: スレッドローカル変数を持つセクションがあります

  • MH_NO_HEAP_EXECUTION: ヒープ/データページの実行がありません

  • MH_HAS_OBJC: バイナリに oBject-C セクションがあります

  • MH_SIM_SUPPORT: シミュレータサポート

  • MH_DYLIB_IN_CACHE: 共有ライブラリキャッシュ内の dylibs/frameworks で使用されます。

Mach-O ロードコマンド

メモリ内のファイルのレイアウトがここで指定され、シンボルテーブルの位置、実行開始時のメインスレッドのコンテキスト、および必要な共有ライブラリの詳細が示されています。バイナリのメモリへの読み込みプロセスに関する指示が、動的ローダー(dyld)に提供されます。

これには、loader.hで定義されたload_command構造が使用されます。

struct load_command {
uint32_t cmd;           /* type of load command */
uint32_t cmdsize;       /* total size of command in bytes */
};

システムが異なる50種類のロードコマンドを異なる方法で処理しています。最も一般的なものは、LC_SEGMENT_64LC_LOAD_DYLINKERLC_MAINLC_LOAD_DYLIB、およびLC_CODE_SIGNATUREです。

LC_SEGMENT/LC_SEGMENT_64

基本的に、このタイプのロードコマンドは、バイナリが実行されるときに**__TEXT**(実行コード)および**__DATA**(プロセス用のデータ)セグメントをどのようにロードするかを、データセクションで示されたオフセットに従って定義します。

これらのコマンドは、プロセスの仮想メモリ空間にマップされるセグメント定義します。

異なる種類のセグメントがあり、プログラムの実行コードを保持する**__TEXTセグメントや、プロセスによって使用されるデータを含む__DATAセグメントなどがあります。これらのセグメントは、Mach-Oファイルのデータセクションに配置**されています。

各セグメントはさらに複数のセクションに分割できます。ロードコマンド構造には、それぞれのセグメント内のこれらのセクションに関する情報が含まれています。

ヘッダー内にはまずセグメントヘッダーがあります:

struct segment_command_64 { /* 64ビットアーキテクチャ用 */
uint32_t	cmd;		/* LC_SEGMENT_64 */
uint32_t	cmdsize;	/* section_64構造体のサイズを含む */
char		segname[16];	/* セグメント名 */
uint64_t	vmaddr;		/* このセグメントのメモリアドレス */
uint64_t	vmsize;		/* このセグメントのメモリサイズ */
uint64_t	fileoff;	/* このセグメントのファイルオフセット */
uint64_t	filesize;	/* ファイルからマップする量 */
int32_t		maxprot;	/* 最大VM保護 */
int32_t		initprot;	/* 初期VM保護 */
	uint32_t	nsects;		/* セグメント内のセクション数 */
	uint32_t	flags;		/* フラグ */
};

セグメントヘッダーの例:

このヘッダーは、その後に表示されるセクションヘッダーの数を定義しています。

struct section_64 { /* for 64-bit architectures */
char		sectname[16];	/* name of this section */
char		segname[16];	/* segment this section goes in */
uint64_t	addr;		/* memory address of this section */
uint64_t	size;		/* size in bytes of this section */
uint32_t	offset;		/* file offset of this section */
uint32_t	align;		/* section alignment (power of 2) */
uint32_t	reloff;		/* file offset of relocation entries */
uint32_t	nreloc;		/* number of relocation entries */
uint32_t	flags;		/* flags (section type and attributes)*/
uint32_t	reserved1;	/* reserved (for offset or index) */
uint32_t	reserved2;	/* reserved (for count or sizeof) */
uint32_t	reserved3;	/* reserved */
};

例: セクションヘッダー:

もし、セクションオフセット(0x37DC)にアーキテクチャが始まるオフセット(この場合 0x18000)を追加すると、0x37DC + 0x18000 = 0x1B7DC になります。

また、コマンドラインからヘッダー情報を取得することも可能です。

otool -lv /bin/ls

以下は、このコマンドによって読み込まれる一般的なセグメントです:

  • __PAGEZERO: カーネルにアドレスゼロをマップするよう指示し、読み取り、書き込み、実行ができないようにします。この構造体内のmaxprotとminprot変数はゼロに設定され、このページには読み取り書き込み実行権限がないことを示します。

  • この割り当てはNULLポインターのデリファレンスの脆弱性を緩和するために重要です。これは、XNUが最初のメモリページ(i386を除く)がアクセスできないようにする厳格なページゼロを強制するためです。バイナリは、最初の4kをカバーする小さな__PAGEZERO(-pagezero_sizeを使用)を作成し、残りの32ビットメモリをユーザーモードとカーネルモードの両方でアクセス可能にすることで、これらの要件を満たすことができます。

  • __TEXT: 読み取りおよび実行権限(書き込み不可)を持つ実行可能なコードが含まれています。このセグメントの一般的なセクション:

    • __text: コンパイルされたバイナリコード

    • __const: 定数データ(読み取り専用)

    • __[c/u/os_log]string: C、Unicode、またはosログの文字列定数

    • __stubsおよび__stubs_helper: ダイナミックライブラリの読み込みプロセス中に関与

    • __unwind_info: スタックアンワインドデータ

  • このコンテンツはすべて署名されていますが、実行可能としてマークされています(特権が必要ないセクションの悪用のためのさらなるオプションを作成します、例えば文字列専用セクション)。

  • __DATA: 読み取りおよび書き込み可能なデータが含まれています(実行不可)。

    • __got: グローバルオフセットテーブル

    • __nl_symbol_ptr: 遅延なし(ロード時にバインド)シンボルポインター

    • __la_symbol_ptr: 遅延(使用時にバインド)シンボルポインター

    • __const: 読み取り専用データであるべき(実際にはそうではない)

    • __cfstring: CoreFoundation文字列

    • __data: 初期化されたグローバル変数

    • __bss: 初期化されていない静的変数

    • __objc_*(__objc_classlist、__objc_protolistなど): Objective-Cランタイムで使用される情報

  • __DATA_CONST: __DATA.__constは定数であることが保証されていません(書き込み権限があります)、他のポインターやGOTも同様です。このセクションは、mprotectを使用して__const、一部の初期化子、およびGOTテーブル(解決後)を読み取り専用にします。

  • __LINKEDIT: リンカー(dyld)のための情報を含み、シンボル、文字列、および再配置テーブルエントリが含まれます。これは__TEXTまたは__DATAに含まれないコンテンツのための一般的なコンテナであり、その内容は他のロードコマンドで説明されています。

  • dyld情報: リベース、遅延なし/遅延/弱いバインディングオペコードおよびエクスポート情報

  • 関数開始: 関数の開始アドレスのテーブル

  • コード内データ: __text内のデータアイランド

  • シンボルテーブル: バイナリ内のシンボル

  • 間接シンボルテーブル: ポインター/スタブシンボル

  • 文字列テーブル

  • コード署名

  • __OBJC: Objective-Cランタイムで使用される情報を含みます。ただし、この情報は、さまざまな__objc_*セクション内にも見つかる可能性があります。

  • __RESTRICT: コンテンツのないセグメントで、**__restrict**という単一のセクションがあり、バイナリを実行する際にDYLD環境変数を無視することを保証します。

コードで見られたように、セグメントにはフラグもサポートされています(あまり使用されていませんが):

  • SG_HIGHVM: コアのみ(使用されていません)

  • SG_FVMLIB: 使用されていません

  • SG_NORELOC: セグメントに再配置がない

  • SG_PROTECTED_VERSION_1: 暗号化。例えば、Finderが__TEXTセグメントのテキストを暗号化するために使用されます。

LC_UNIXTHREAD/LC_MAIN

LC_MAINにはentryoff属性内のエントリーポイントが含まれています。ロード時に、dyldは単にこの値を(メモリ内の)バイナリのベースに追加し、その後この命令にジャンプしてバイナリのコードの実行を開始します。

**LC_UNIXTHREADには、メインスレッドを開始する際にレジスタが持つ必要がある値が含まれています。これはすでに非推奨となっていますが、dyld**はまだ使用しています。これによって設定されるレジスタの値を次のように確認できます:

otool -l /usr/lib/dyld
[...]
Load command 13
cmd LC_UNIXTHREAD
cmdsize 288
flavor ARM_THREAD_STATE64
count ARM_THREAD_STATE64_COUNT
x0  0x0000000000000000 x1  0x0000000000000000 x2  0x0000000000000000
x3  0x0000000000000000 x4  0x0000000000000000 x5  0x0000000000000000
x6  0x0000000000000000 x7  0x0000000000000000 x8  0x0000000000000000
x9  0x0000000000000000 x10 0x0000000000000000 x11 0x0000000000000000
x12 0x0000000000000000 x13 0x0000000000000000 x14 0x0000000000000000
x15 0x0000000000000000 x16 0x0000000000000000 x17 0x0000000000000000
x18 0x0000000000000000 x19 0x0000000000000000 x20 0x0000000000000000
x21 0x0000000000000000 x22 0x0000000000000000 x23 0x0000000000000000
x24 0x0000000000000000 x25 0x0000000000000000 x26 0x0000000000000000
x27 0x0000000000000000 x28 0x0000000000000000  fp 0x0000000000000000
lr 0x0000000000000000 sp  0x0000000000000000  pc 0x0000000000004b70
cpsr 0x00000000

[...]

LC_CODE_SIGNATURE

Macho-Oファイルのコード署名に関する情報を含みます。署名ブロブを指すオフセットのみを含んでいます。通常、ファイルの最後にあります。 ただし、このセクションに関する情報は、このブログ投稿とこのgistsで見つけることができます。

LC_ENCRYPTION_INFO[_64]

バイナリの暗号化をサポートします。ただし、攻撃者がプロセスを侵害してメモリを復号化することができる場合があります。

LC_LOAD_DYLINKER

共有ライブラリをプロセスのアドレス空間にマップする動的リンカー実行ファイルへのパスを含みます。値は常に/usr/lib/dyldに設定されています。macOSでは、dylibのマッピングがカーネルモードではなくユーザーモードで行われることに注意することが重要です。

LC_IDENT

古いですが、パニック時にダンプを生成するように構成されている場合、Mach-Oコアダンプが作成され、カーネルバージョンがLC_IDENTコマンドに設定されます。

LC_UUID

ランダムUUIDです。直接的には何にも役立ちませんが、XNUはプロセス情報と一緒にキャッシュします。クラッシュレポートで使用できます。

LC_DYLD_ENVIRONMENT

プロセスが実行される前にdyldに環境変数を示すことを許可します。これはプロセス内で任意のコードを実行できる可能性があるため、非常に危険です。このロードコマンドは、#define SUPPORT_LC_DYLD_ENVIRONMENTで構築されたdyldでのみ使用され、DYLD_..._PATH形式の変数のみを指定して処理をさらに制限します。

LC_LOAD_DYLIB

このロードコマンドは、ローダー(dyld)にライブラリをロードしてリンクするよう指示する 動的ライブラリ依存関係を記述します。Mach-Oバイナリが必要とする各ライブラリにはLC_LOAD_DYLIBロードコマンドがあります。

  • このロードコマンドは、**dylib_command**型の構造体であり(実際の依存する動的ライブラリを記述するstruct dylibを含む)、

struct dylib_command {
uint32_t        cmd;            /* LC_LOAD_{,WEAK_}DYLIB */
uint32_t        cmdsize;        /* includes pathname string */
struct dylib    dylib;          /* the library identification */
};

struct dylib {
union lc_str  name;                 /* library's path name */
uint32_t timestamp;                 /* library's build time stamp */
uint32_t current_version;           /* library's current version number */
uint32_t compatibility_version;     /* library's compatibility vers number*/
};

次のコマンドラインからもこの情報を取得できます:

otool -L /bin/ls
/bin/ls:
/usr/lib/libutil.dylib (compatibility version 1.0.0, current version 1.0.0)
/usr/lib/libncurses.5.4.dylib (compatibility version 5.4.0, current version 5.4.0)
/usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1319.0.0)

いくつかの潜在的なマルウェア関連ライブラリは次のとおりです:

  • DiskArbitration: USB ドライブの監視

  • AVFoundation: 音声とビデオのキャプチャ

  • CoreWLAN: Wifi スキャン

Mach-O バイナリには、LC_MAIN で指定されたアドレスの前に実行される1つ以上のコンストラクタが含まれる可能性があります。 任意のコンストラクタのオフセットは、__DATA_CONST セグメントの**__mod_init_func** セクションに保持されます。

Mach-O データ

ファイルの中心には、ロードコマンド領域で定義された複数のセグメントで構成されるデータ領域があります。各セグメントにはさまざまなデータセクションが収められており、各セクションにはコードまたはデータが特定のタイプに固有のものが含まれています。

データは基本的に、ロードコマンドLC_SEGMENTS_64によって読み込まれるすべての情報を含む部分です。

これには次のものが含まれます:

  • 関数テーブル:プログラム関数に関する情報を保持します。

  • シンボルテーブル:バイナリで使用される外部関数に関する情報を含みます

  • 内部関数、変数名なども含まれる可能性があります。

確認するには、Mach-O View ツールを使用できます:

または、CLI から:

size -m /bin/ls

Objective-C共通セクション

__TEXTセグメント(r-x)内:

  • __objc_classname: クラス名(文字列)

  • __objc_methname: メソッド名(文字列)

  • __objc_methtype: メソッドタイプ(文字列)

__DATAセグメント(rw-)内:

  • __objc_classlist: すべてのObjective-Cクラスへのポインタ

  • __objc_nlclslist: 遅延ロードされないObjective-Cクラスへのポインタ

  • __objc_catlist: カテゴリへのポインタ

  • __objc_nlcatlist: 遅延ロードされないカテゴリへのポインタ

  • __objc_protolist: プロトコルリスト

  • __objc_const: 定数データ

  • __objc_imageinfo, __objc_selrefs, objc__protorefs...

Swift

  • _swift_typeref, _swift3_capture, _swift3_assocty, _swift3_types, _swift3_proto, _swift3_fieldmd, _swift3_builtin, _swift3_reflstr

Last updated