Skip to content

Mach-O Format

macOS executables and dynamic libraries are compiled into Mach-O (Mach Object) files, a binary format that defines how code and data are organized for execution. Understanding Mach-O is critical for red teaming, as it underpins how binaries load, execute, and persist on macOS systems. This section provides an overview of the Mach-O structure and its role in execution.


Header and File Type

The Mach-O header contains metadata about the binary, including:
- CPU type (e.g., x86_64, arm64, or universal binaries with multiple architectures).
- File type (e.g., MH_EXECUTE for executables, MH_DYLIB for dynamic libraries).
- Magic number (e.g., 0xfeedface for 32-bit, 0xfeedfacf for 64-bit).
- Number of load commands (instructions for the loader).

Example:

$ otool -h /bin/ls
/usr/bin/otool: 64-bit file, format mach-o, 64-bit architecture x86_64


Load Commands

Load commands direct the macOS loader on how to map the binary into memory. Common types include:
- LC_SEGMENT: Defines memory segments (e.g., __TEXT for code, __DATA for data).
- LC_SYMTAB: Symbol table for debugging and linking.
- LC_DYLD_INFO: Dynamic linking information (used by dyld to resolve symbols).

Example:

$ otool -l /bin/ls
...  
Load command 1:
    cmd LC_SEGMENT_64
    cmdsize 140
    segname __TEXT
    vmaddr 0x0000000000001000
    vmsize 0x000000000000f000
    fileoff 0x0000000000000000
    filesize 0x000000000000f000
    maxprot 0x0000000000000007
    initprot 0x0000000000000007
    nsects 1
    flags 0x00000000


Segments and Sections

Segments group related sections (e.g., code, data, resources). Key segments include:
- __TEXT: Contains executable code (e.g., .text, .cfi, .objc_methprops).
- __DATA: Holds initialized data (e.g., .data, .rodata) and BSS (uninitialized data).
- __LINKEDIT: Contains dynamic linking information (e.g., symbol tables, relocation data).

Sections within segments define specific roles. For example:
- .text: Machine code.
- .const: Read-only data.
- .bss: Uninitialized global variables.


Execution Flow

When a Mach-O binary is executed:
1. The kernel loads the file into memory using load commands.
2. Segments are mapped to virtual memory addresses.
3. The loader resolves symbols and applies relocations.
4. Execution begins at the entry point (specified in the header or LC_MAIN command).
5. Dynamic linking (via dyld) resolves dependencies (e.g., shared libraries).

Example:

$ nm /bin/ls | grep 'T _start'
0000000000001000 T _start


Tools for Analysis

Use these tools to inspect Mach-O files:
- otool: Disassemble and inspect headers/load commands.
- nm: List symbols (e.g., nm -g /path/to/binary).
- objdump: Disassemble code (e.g., objdump -d /path/to/binary).
- haxm: For advanced memory and execution analysis.


Key takeaways

  • Mach-O files define how macOS binaries are structured and executed.
  • Headers and load commands guide memory mapping and execution.
  • Segments and sections organize code, data, and metadata.
  • Tools like otool and nm are essential for reverse engineering.
  • Understanding Mach-O is foundational for persistence and evasion techniques.