Mach-O


Introduction

Note

The Mach-O format defines the notion of FAT binaries which can embed different architectures into a single file. always returns a with the assumption that a non-fat Mach-O can be represented as a with one architecture.
import lief

# Using filepath
macho: lief.MachO.FatBinary = lief.MachO.parse("/bin/ls")

# Using a Path from pathlib
macho: lief.MachO.FatBinary = lief.MachO.parse(pathlib.Path(r"C:\Users\test.macho"))

# Using a io object
with open("/bin/ssh", 'rb') as f:
  macho: lief.MachO.FatBinary = lief.MachO.parse(f)
fat: lief.MachO.FatBinary

# Iterate
for macho in fat:
    print(macho.entrypoint)
    print(len(macho.commands))

# Pick one at the specified index
macho: lief.MachO.Binary = fat.at(0)

# Pick one based on the architecture
macho: lief.MachO.Binary = fat.take(lief.MachO.Header.CPU_TYPE.ARM64)
macho: lief.MachO.FatBinary = ...

macho.at(0).write("fit.macho")
macho.write("fat.macho") # write-back the whole FAT binary
macho: lief.MachO.Binary = ...
new_macho: bytes = macho.write_to_bytes()

Advance Parsing/Writing

parser_config = lief.MachO.ParserConfig()
parser_config.parse_dyld_bindings = False

macho: lief.MachO.FatBinary = lief.MachO.parse("my.macho", parser_config)

builder_config = lief.MachO.Builder.config_t()
builder_config.linkedit = False

macho.write("new.macho", builder_config)

RPath and Library Path Modification

Sometimes, we need to modify the Mach-O rpath commands or the (absolute) path of a linked library in an executable. When recompiling or linking the executable is not possible, LIEF can be used for these modifications.

For instance, let’s consider a binary with the following dependencies:

$ otool -L hello.bin
hello:
      /Users/romain/dev/libmylib.dylib (compatibility version 0.0.0, current version 0.0.0)
      /usr/lib/libc++.1.dylib (compatibility version 1.0.0, current version 1700.255.0)
      /usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1345.100.2)

One can change the directory of libmylib.dylib with the following code:

macho = lief.MachO.parse("hello.bin")
lib: lief.MachO.DylibCommand = macho.find_library("libmylib.dylib")
lib.name = "/opt/hombrew/my_package/libmylib.dylib"

macho.write("hello_fixed.bin")

Note

It is worth mentioning that LIEF does not have restrictions on the length of the modified library path. LIEF manages all the internal modifications to support both longer and shorter library paths.

This kind of modification can be used in pair with the @rpath feature of Mach-O binaries:

macho = lief.MachO.parse("hello.bin")
rpath = lief.MachO.RPathCommand.create("/opt/hombrew/my_package")
macho.add(rpath)
  1. Then, we can change the library path of libmylib.dylib to include the rpath prefix:

lib: lief.MachO.DylibCommand = macho.find_library("libmylib.dylib")
lib.name = "@rpath/libmylib.dylib"

macho.write("hello_fixed.bin")

Objective-C Support

If a Mach-O binary is compiled from Objetive-C sources, it could contain metadata which are represented by the object.
This metadata can help understand the underlying structures of the binary and LIEF extended provides the support for accessing this information through: .

For more details, you can check the Obj-C section.