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:
Header
Contains basic information about the binary including architecture, file type, and the number of load commands.
Load Commands
A series of commands that describe the binary’s layout, dependencies, and how the dynamic linker should load it into memory.
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
│ ... │
└─────────────────────┘
The header is the first structure in a Mach-O file and identifies the file format and architecture.
Header Definition
Inspecting Header
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
MH_EXECUTE (0x2)
MH_DYLIB (0x6)
MH_BUNDLE (0x8)
MH_DYLINKER (0x7)
Standard executable binary - the main app binary.
Dynamic library (framework) that can be loaded by multiple processes.
Plugin or bundle that’s loaded dynamically.
The dynamic linker itself (/usr/lib/dyld).
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
LC_SEGMENT_64 - Segment Definition
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
LC_LOAD_DYLIB - Dynamic Library
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.
LC_CODE_SIGNATURE - Code Signature
Points to the code signature data in the __LINKEDIT segment. Contains cryptographic hashes verifying binary integrity.
LC_ENCRYPTION_INFO_64 - Binary Encryption
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
};
LC_UUID - Unique Identifier
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
__TEXT
__DATA
__LINKEDIT
__OBJC (Legacy)
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"
Read-write segment containing mutable data.Key sections:
__data: Initialized mutable data
__bss: Uninitialized static data
__objc_classlist: Objective-C class list
__objc_protolist: Objective-C protocol list
__objc_imageinfo: Objective-C image information
otool -l MyApp | grep -A 5 "segname __DATA"
Read-only segment containing linking and debugging information.Contains:
Symbol table
String table
Code signature
Dynamic loader information
This segment is used by the dynamic linker and debugging tools. Objective-C runtime information (older binaries).Modern binaries store Objective-C data in __DATA segment sections instead.
Swift-Specific Sections
Swift binaries contain additional sections for Swift metadata:
__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
otool - Display Information
nm - Symbol Table
size - Section Sizes
file - Quick Info
# 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
Hopper Disassembler
Ghidra
IDA Pro
MachOView
Powerful disassembler with Mach-O support:
Visual segment and section browser
Automatic Objective-C method detection
Control flow graph generation
Pseudo-code decompilation
Free NSA-developed reverse engineering tool:
Full Mach-O parsing support
Swift and Objective-C analysis
Scripting with Python/Java
Collaborative features
Industry-standard disassembler:
Excellent Mach-O support
Advanced debugging capabilities
Extensive plugin ecosystem
Premium pricing
Specialized Mach-O viewer:
Visual representation of Mach-O structure
Detailed header and load command inspection
Free and open-source
macOS only
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.
# 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.