Skip to main content

Overview

Mach-O (Mach Object) is the executable file format used by iOS, macOS, watchOS, and tvOS. Understanding Mach-O structure is essential for iOS reverse engineering, as it defines how executable code, data, and metadata are organized within a binary.
Mach-O files can contain multiple architectures (called “fat” or “universal” binaries), though iOS apps typically contain only ARM64 architecture.

Mach-O File Structure

A Mach-O file consists of three main regions:
1

Header

Contains basic information about the binary including architecture, file type, and the number of load commands.
2

Load Commands

A series of commands that describe the binary’s layout, dependencies, and how the dynamic linker should load it into memory.
3

Data

The actual code and data segments containing executable instructions, strings, constants, and other binary content.
┌─────────────────────┐
│   Mach-O Header     │  ← Magic number, CPU type, file type
├─────────────────────┤
│   Load Command 1    │  ← Segment definitions
│   Load Command 2    │  ← Dylib dependencies
│   Load Command 3    │  ← Code signature location
│        ...          │  ← Other metadata
├─────────────────────┤
│   __TEXT Segment    │  ← Executable code
│   __DATA Segment    │  ← Initialized data
│   __LINKEDIT       │  ← Linking information
│        ...          │
└─────────────────────┘

Mach-O Header

The header is the first structure in a Mach-O file and identifies the file format and architecture.

Header Structure

struct mach_header_64 {
    uint32_t    magic;        // Magic number (0xFEEDFACF for 64-bit)
    cpu_type_t  cputype;      // CPU type (ARM64)
    cpu_subtype_t cpusubtype; // CPU subtype
    uint32_t    filetype;     // File type (executable, dylib, etc.)
    uint32_t    ncmds;        // Number of load commands
    uint32_t    sizeofcmds;   // Total size of load commands
    uint32_t    flags;        // Flags
    uint32_t    reserved;     // Reserved (64-bit only)
};

File Types

Standard executable binary - the main app binary.

Important Flags

MH_PIE

Position Independent Executable - enables ASLR (Address Space Layout Randomization) for security.

MH_NO_HEAP_EXECUTION

Marks heap memory as non-executable, preventing code injection attacks.

MH_ALLOW_STACK_EXECUTION

Allows stack execution (security risk, rarely used in modern apps).

MH_ENCRYPTED

Indicates the binary contains an encrypted segment (App Store binaries).

Load Commands

Load commands describe how to load and link the binary. They immediately follow the header.

Common Load Commands

Defines a segment to be mapped into memory. Each segment contains one or more sections.
struct segment_command_64 {
    uint32_t  cmd;           // LC_SEGMENT_64
    uint32_t  cmdsize;       // Size of command
    char      segname[16];   // Segment name
    uint64_t  vmaddr;        // Virtual memory address
    uint64_t  vmsize;        // Virtual memory size
    uint64_t  fileoff;       // File offset
    uint64_t  filesize;      // File size
    vm_prot_t maxprot;       // Maximum protection
    vm_prot_t initprot;      // Initial protection
    uint32_t  nsects;        // Number of sections
    uint32_t  flags;         // Flags
};
View segments:
otool -l MyApp | grep -A 10 LC_SEGMENT_64
Specifies a dynamic library dependency that must be loaded.
otool -L MyApp
# Output:
MyApp:
    /System/Library/Frameworks/Foundation.framework/Foundation
    /System/Library/Frameworks/UIKit.framework/UIKit
    /usr/lib/libobjc.A.dylib
    /usr/lib/libSystem.B.dylib
These dependencies are essential for understanding what frameworks and libraries the app uses.
Points to the code signature data in the __LINKEDIT segment.
codesign -d -vv MyApp
Contains cryptographic hashes verifying binary integrity.
Indicates which parts of the binary are encrypted (App Store DRM).
struct encryption_info_command_64 {
    uint32_t cmd;        // LC_ENCRYPTION_INFO_64
    uint32_t cmdsize;
    uint32_t cryptoff;   // File offset of encrypted range
    uint32_t cryptsize;  // Size of encrypted range
    uint32_t cryptid;    // Encryption type (0 = decrypted, 1 = encrypted)
};
Binaries downloaded from the App Store are encrypted. You must decrypt them (using a jailbroken device) before analysis.
Specifies the main entry point of the executable.
struct entry_point_command {
    uint32_t  cmd;        // LC_MAIN
    uint32_t  cmdsize;
    uint64_t  entryoff;   // File offset of entry point
    uint64_t  stacksize;  // Initial stack size
};
A unique identifier for this specific build of the binary, used for symbolication and crash report matching.
otool -l MyApp | grep uuid
# uuid 4A3B2C1D-5E6F-7890-ABCD-EF1234567890

Segments and Sections

Segments are large regions of the binary mapped into memory with specific permissions. Each segment contains one or more sections.

Key Segments

Read-only, executable segment containing code and read-only data.Key sections:
  • __text: Executable machine code
  • __cstring: C string constants
  • __const: Constant data
  • __stubs: Symbol stubs for dynamic linking
  • __objc_methname: Objective-C method names
otool -l MyApp | grep -A 5 "segname __TEXT"

Swift-Specific Sections

Swift binaries contain additional sections for Swift metadata: Swift sections in Ghidra

__swift5_types

Type metadata for Swift types

__swift5_proto

Protocol conformance descriptors

__swift5_fieldmd

Field metadata for reflection

__swift5_capture

Closure capture descriptors
# List all sections in binary
otool -l MyApp | grep sectname

# Extract specific section
size -m -l MyApp

Analyzing Mach-O Files

Using Command-Line Tools

# Show all headers
otool -h MyApp

# Display load commands
otool -l MyApp

# Show shared libraries
otool -L MyApp

# Disassemble text section
otool -tv MyApp

# Show Objective-C metadata
otool -o MyApp

Using Reverse Engineering Tools

Powerful disassembler with Mach-O support:
  • Visual segment and section browser
  • Automatic Objective-C method detection
  • Control flow graph generation
  • Pseudo-code decompilation

Practical Examples

Example 1: Checking Encryption Status

# Check if binary is encrypted
otool -l MyApp | grep -A 5 LC_ENCRYPTION_INFO

# If cryptid = 1, binary is encrypted
# If cryptid = 0, binary is decrypted
Use tools like frida-ios-dump or Clutch on a jailbroken device to decrypt App Store binaries.

Example 2: Extracting Objective-C Class Names

# Using class-dump (must be decrypted first)
class-dump MyApp > class_headers.txt

# Or extract from binary directly
otool -oV MyApp | grep "class_name"

Example 3: Finding String References

# Extract all strings
strings MyApp > strings.txt

# Search for API endpoints
strings MyApp | grep -i "http"

# Search for interesting keywords
strings MyApp | grep -iE "(password|secret|key|token|api)"

Example 4: Analyzing Example IPA

# Extract example IPA
unzip ~/workspace/source/ObfuscatedAppExamples/ControlFlowFlattening.ipa -d /tmp/cff/
cd /tmp/cff/Payload/*.app/

# Examine Mach-O header
otool -h ControlFlowFlattening

# Check architecture
lipo -info ControlFlowFlattening

# List load commands
otool -l ControlFlowFlattening | less

# Extract symbols
nm -g ControlFlowFlattening | head -50

Security Considerations

Understanding Mach-O format is crucial for identifying security issues:
  • Missing PIE flag = No ASLR protection
  • Stack execution enabled = Code injection risk
  • Missing code signature = Tampering possible
  • Unencrypted binary = Easy to analyze

Next Steps

Code Signing

Learn how code signatures protect Mach-O binaries.

iOS App Structure

Understand the complete app bundle structure.

Dynamic Analysis

Learn to analyze Mach-O binaries at runtime.

Static Analysis

Deep dive into static analysis techniques.

Build docs developers (and LLMs) love