---
documentID: "4db01f98ac6622c305e0d3b5235318f57e369850cc33d0ba0f21e88ca8ba349e"
docname: "tutorials/11_macho_modification"
title: "11 - Mach-O Modification - LIEF Documentation"
description: "11 - Mach-O Modification. This tutorial covers Mach-O format modification and introduces some internal aspects of the format."
canonical: "https://lief.re/doc/latest/tutorials/11_macho_modification.html"
markdownURL: "https://lief.re/doc/latest/tutorials/11_macho_modification.md"
documentationVersion: "2.0.0"
documentationChannel: "latest"
language: "en"
contentHash: "60f0f716f85b46221af442fbbc6b4358aa8f36eb09a56fb4278edab6d088e77f"
---

# [11 - Mach-O Modification](<https://lief.re/doc/latest/tutorials/11_macho_modification.html#mach-o-modification>)

This tutorial covers Mach-O format modification and introduces some internal aspects of the format.

Files and scripts used in this tutorial are available in the [tutorials repository](<https://github.com/lief-project/tutorials/tree/master/11_macho_modification>).

---

## [Introduction](<https://lief.re/doc/latest/tutorials/11_macho_modification.html#introduction>)

A basic Mach-O binary (i.e., not FAT) can be represented in four parts, as described in this diagram:![../_images/image1.png](https://lief.re/doc/latest/_images/image1.png)

The first part begins with a header that can be accessed through the [`lief.MachO.Binary.header`](<https://lief.re/doc/latest/formats/macho/python.html#lief.MachO.Binary.header> "lief.MachO.Binary.header") attribute. The second part contains the load commands table, which can be iterated over using `lief.MachO.Binary.load_commands`. This is optionally followed by padding or free space. Finally, the fourth part contains the raw data (assembly code, rebase bytecode, signatures, etc.).

Load commands such as [`SegmentCommand`](<https://lief.re/doc/latest/formats/macho/python.html#lief.MachO.SegmentCommand> "lief.MachO.SegmentCommand") or [`DyldInfo`](<https://lief.re/doc/latest/formats/macho/python.html#lief.MachO.DyldInfo> "lief.MachO.DyldInfo") can be associated with *raw data* located after the load command table and the padding section. The padding section is used by macOS to sign the binary after compilation by adding a custom command. The `codesign` utility extends the *raw data* area with the signature and adds an `LC_CODE_SIGNATURE` or `LC_DYLIB_CODE_SIGN_DRS` command in the padding area.

Since load commands are the base unit of the Mach-O format (segments, shared libraries, entry points, etc., are all *commands*), the ability to add arbitrary commands to a binary enables interesting possibilities such as code injection, anti-analysis, etc.

Different techniques exist for adding new commands to a Mach-O binary:

- Replacing an existing load command that is not mandatory for execution, such as [`UUIDCommand`](<https://lief.re/doc/latest/formats/macho/python.html#lief.MachO.UUIDCommand> "lief.MachO.UUIDCommand") or [`CodeSignature`](<https://lief.re/doc/latest/formats/macho/python.html#lief.MachO.CodeSignature> "lief.MachO.CodeSignature").
- Using the padding area to expand the command header.

The main limitation of these techniques is that the size and number of commands that can be added are tied to the padding section size or the size of the command replaced.

If the padding size is small, we cannot add a `LOAD_DYLIB` command with a very long library path. Moreover, `codesign` may complain if there is insufficient space to add the `LC_CODE_SIGNATURE` because we are using space that was reserved for it.

The following sections discuss format modifications and how LIEF addresses these limitations.

## [When PIE Makes Things Easier](<https://lief.re/doc/latest/tutorials/11_macho_modification.html#when-pie-makes-things-easier>)

macOS and iOS executables are typically compiled with flags that make them position-independent. Instructions generated by the compiler use relative addressing associated with *rebase* information.

To simplify, PIE binaries allow the *raw data* section to be mapped at a random base address. LIEF leverages this by shifting the raw data section within the format.![../_images/image2.png](https://lief.re/doc/latest/_images/image2.png)

Such a transformation also requires maintaining consistent format metadata. Specifically, when we shift the raw data, we must update relocations, segment offsets, virtual addresses, etc. Once the raw data is shifted and the metadata updated, we have arbitrary space between the load command table and the raw data section. Thus, we can extend the load command table as shown in the figure below:![../_images/image3.png](https://lief.re/doc/latest/_images/image3.png)

> **Warning**
> 
> The size of the shift must be aligned with the page size to avoid issues with section and segment alignment.

Maintaining format consistency after a shift transformation is complex. The next section presents parts of the Mach-O format that must be updated to maintain consistency.

## [When Mach-O Makes Things Harder](<https://lief.re/doc/latest/tutorials/11_macho_modification.html#when-mach-o-makes-things-harder>)

After the shift operation, we must update several load commands:

- [`lief.MachO.SymbolCommand.symbol_offset`](<https://lief.re/doc/latest/formats/macho/python.html#lief.MachO.SymbolCommand.symbol_offset> "lief.MachO.SymbolCommand.symbol_offset") / [`lief.MachO.SymbolCommand.strings_offset`](<https://lief.re/doc/latest/formats/macho/python.html#lief.MachO.SymbolCommand.strings_offset> "lief.MachO.SymbolCommand.strings_offset")
- [`lief.MachO.DataInCode.data_offset`](<https://lief.re/doc/latest/formats/macho/python.html#lief.MachO.DataInCode.data_offset> "lief.MachO.DataInCode.data_offset"), [`lief.MachO.CodeSignature.data_offset`](<https://lief.re/doc/latest/formats/macho/python.html#lief.MachO.CodeSignature.data_offset> "lief.MachO.CodeSignature.data_offset"), [`lief.MachO.SegmentSplitInfo.data_offset`](<https://lief.re/doc/latest/formats/macho/python.html#lief.MachO.SegmentSplitInfo.data_offset> "lief.MachO.SegmentSplitInfo.data_offset")
- [`lief.MachO.MainCommand.entrypoint`](<https://lief.re/doc/latest/formats/macho/python.html#lief.MachO.MainCommand.entrypoint> "lief.MachO.MainCommand.entrypoint")
- [`lief.MachO.FunctionStarts.data_offset`](<https://lief.re/doc/latest/formats/macho/python.html#lief.MachO.FunctionStarts.data_offset> "lief.MachO.FunctionStarts.data_offset") / [`lief.MachO.FunctionStarts.functions`](<https://lief.re/doc/latest/formats/macho/python.html#lief.MachO.FunctionStarts.functions> "lief.MachO.FunctionStarts.functions")
- [`DynamicSymbolCommand`](<https://lief.re/doc/latest/formats/macho/python.html#lief.MachO.DynamicSymbolCommand> "lief.MachO.DynamicSymbolCommand")
- `lief.MachO.Section.offset` / `lief.MachO.Section.virtual_address`
- `lief.MachO.SegmentCommand.offset` / [`lief.MachO.SegmentCommand.virtual_address`](<https://lief.re/doc/latest/formats/macho/python.html#lief.MachO.SegmentCommand.virtual_address> "lief.MachO.SegmentCommand.virtual_address")
- …

We also need to update:

- Relocations
- Binding information
- Export information

While the ELF and PE formats use structures for internal storage of relocations and exports, the Mach-O format uses bytecode to *rebase* the binary. Export information is stored in a [trie](<https://en.wikipedia.org/wiki/Trie>) data structure. The use of tries and bytecode reduces binary size but makes updates more difficult, as we must interpret and regenerate the bytecode.

### [Rebase Bytecode](<https://lief.re/doc/latest/tutorials/11_macho_modification.html#rebase-bytecode>)

As mentioned previously, recent Mach-O loaders use bytecode to relocate (or rebase) the binary. The offset and size of the bytecode are specified in the [`lief.MachO.DyldInfo.rebase`](<https://lief.re/doc/latest/formats/macho/python.html#lief.MachO.DyldInfo.rebase> "lief.MachO.DyldInfo.rebase") attribute. Basically, the bytecode is composed of [`REBASE_OPCODES`](<https://lief.re/doc/latest/formats/macho/python.html#lief.MachO.DyldInfo.REBASE_OPCODES> "lief.MachO.DyldInfo.REBASE_OPCODES") that define addresses to relocate.

> **Warning**
> 
> Note that the [`Section`](<https://lief.re/doc/latest/formats/macho/python.html#lief.MachO.Section> "lief.MachO.Section") object has a [`relocation_offset`](<https://lief.re/doc/latest/formats/macho/python.html#lief.MachO.Section.relocation_offset> "lief.MachO.Section.relocation_offset") attribute. This appears to be used only for Mach-O object files ([`lief.MachO.Header.FILE_TYPE.OBJECT`](<https://lief.re/doc/latest/formats/macho/python.html#lief.MachO.Header.FILE_TYPE.OBJECT> "lief.MachO.Header.FILE_TYPE.OBJECT")) or executables using an old version of the Mach-O loader.
> 
> This offset points to a list of relocation structures (not bytecode), the number of which is defined by [`numberof_relocations`](<https://lief.re/doc/latest/formats/macho/python.html#lief.MachO.Section.numberof_relocations> "lief.MachO.Section.numberof_relocations").

To determine which addresses must be relocated, we must interpret the bytecode.

The [`lief.MachO.DyldInfo.show_rebases_opcodes`](<https://lief.re/doc/latest/formats/macho/python.html#lief.MachO.DyldInfo.show_rebases_opcodes> "lief.MachO.DyldInfo.show_rebases_opcodes") attribute returns the bytecode as *pseudo-code*:

```python
import lief
app = lief.parse("MachO64_x86-64_binary_id.bin")
print(app.dyld_info.show_rebases_opcodes)
```

```text
[SET_TYPE_IMM] Type: POINTER
[SET_SEGMENT_AND_OFFSET_ULEB] Segment Index := 2 (__DATA) Segment Offset := 0x20
[DO_REBASE_ULEB_TIMES]
  for i in range(26):
      rebase(POINTER, __DATA, 0x20)
      Segment Offset += 0x8 (0x28)

      rebase(POINTER, __DATA, 0x28)
      Segment Offset += 0x8 (0x30)

      rebase(POINTER, __DATA, 0x30)
      Segment Offset += 0x8 (0x38)

      rebase(POINTER, __DATA, 0x38)
      Segment Offset += 0x8 (0x40)

      rebase(POINTER, __DATA, 0x40)
      Segment Offset += 0x8 (0x48)
      ...
[DONE]
```

From the output above, we can see that the loader will rebase **pointers** in the `__DATA` segment at offsets `0x20, 0x28, 0x38, ...`.

For those who only care about the exact addresses being relocated, this output is not very user-friendly. LIEF also provides a **representation** of this bytecode by creating [`lief.MachO.Relocation`](<https://lief.re/doc/latest/formats/macho/python.html#lief.MachO.Relocation> "lief.MachO.Relocation") objects, which are the result of interpreting the bytecode.

The [`lief.MachO.Binary.relocations`](<https://lief.re/doc/latest/formats/macho/python.html#lief.MachO.Binary.relocations> "lief.MachO.Binary.relocations") attribute returns an iterator over [`lief.MachO.Relocation`](<https://lief.re/doc/latest/formats/macho/python.html#lief.MachO.Relocation> "lief.MachO.Relocation") objects that **model** a relocation, similar to [`lief.ELF.Relocation`](<https://lief.re/doc/latest/formats/elf/python.html#lief.ELF.Relocation> "lief.ELF.Relocation") and [`lief.PE.Relocation`](<https://lief.re/doc/latest/formats/pe/python.html#lief.PE.Relocation> "lief.PE.Relocation").

```python
app: lief.MachO.Binary

for relocation in app.relocations:
    print(relocation)
```

```text
100002020 POINTER 64 DYLDINFO  __DATA.__la_symbol_ptr _err
100002028 POINTER 64 DYLDINFO  __DATA.__la_symbol_ptr _errx
100002030 POINTER 64 DYLDINFO  __DATA.__la_symbol_ptr _exit
100002038 POINTER 64 DYLDINFO  __DATA.__la_symbol_ptr _fprintf
100002040 POINTER 64 DYLDINFO  __DATA.__la_symbol_ptr _free
100002048 POINTER 64 DYLDINFO  __DATA.__la_symbol_ptr _fwrite
...
```

Using this representation, we can update relocations by adding the shift size to the [`lief.MachO.Relocation.address`](<https://lief.re/doc/latest/formats/macho/python.html#lief.MachO.Relocation.address> "lief.MachO.Relocation.address") attribute.

When the Mach-O builder reconstructs the final binary, it **regenerates** and optimizes the rebase bytecode according to the current state of the relocations. The process can be summarized by the following diagram:![../_images/lief_bytecode.png](https://lief.re/doc/latest/_images/lief_bytecode.png)

### [Binding Bytecode](<https://lief.re/doc/latest/tutorials/11_macho_modification.html#binding-bytecode>)

The Mach-O loader also uses bytecode to bind imported functions or symbols. This bytecode is used in three different binding methods:

- Normal binding
- Weak binding (used when the same symbol is defined multiple times)
- Lazy binding (bound only when the symbol is accessed)

The bytecode can be pretty-printed with [`show_bind_opcodes`](<https://lief.re/doc/latest/formats/macho/python.html#lief.MachO.DyldInfo.show_bind_opcodes> "lief.MachO.DyldInfo.show_bind_opcodes"), [`show_weak_bind_opcodes`](<https://lief.re/doc/latest/formats/macho/python.html#lief.MachO.DyldInfo.show_weak_bind_opcodes> "lief.MachO.DyldInfo.show_weak_bind_opcodes"), and [`show_lazy_bind_opcodes`](<https://lief.re/doc/latest/formats/macho/python.html#lief.MachO.DyldInfo.show_lazy_bind_opcodes> "lief.MachO.DyldInfo.show_lazy_bind_opcodes"):

```python
app: lief.MachO.Binary

print(app.dyld_info.show_bind_opcodes)
```

```text
[SET_DYLIB_ORDINAL_IMM]
    Library Ordinal := 1
[SET_SYMBOL_TRAILING_FLAGS_IMM]
    Symbol name := ___stderrp
    Is Weak ? false
[SET_TYPE_IMM]
    Type := POINTER
[SET_SEGMENT_AND_OFFSET_ULEB]
    Segment := __DATA
    Segment Offset := 0x10
[DO_BIND]
    bind(POINTER, __DATA, 0x10, ___stderrp, library_ordinal=/usr/lib/libSystem.B.dylib, addend=0, is_weak_import=false)
    Segment Offset += 0x8 (0x18)
```

The representation and update process are identical to those described in the *Rebase Bytecode* section.

### [Export Trie](<https://lief.re/doc/latest/tutorials/11_macho_modification.html#export-trie>)

For exported functions and symbols, the Mach-O format uses a *trie* structure to store export information. The trie offset and size are specified in the [`export_trie`](<https://lief.re/doc/latest/formats/macho/python.html#lief.MachO.DyldInfo.export_trie> "lief.MachO.DyldInfo.export_trie") attribute.

Once parsed, trie entries are represented via the [`ExportInfo`](<https://lief.re/doc/latest/formats/macho/python.html#lief.MachO.ExportInfo> "lief.MachO.ExportInfo") object and can be retrieved using the [`export_info`](<https://lief.re/doc/latest/formats/macho/python.html#lief.MachO.Symbol.export_info> "lief.MachO.Symbol.export_info") attribute.

```python
app = lief.parse("FAT_MachO_x86_x86-64_library_libdyld.dylib")
print(app.dyld_info.show_export_trie)
```

```text
...
_@off.0x17
    _N@off.0x21
        _NS@off.0x50
            _NSI@off.0x5d
                _NSInstallLinkEditErrorHandlers@off.0x11d
                _NSInstallLinkEditErrorHandlers{addr: 0x126b, flags: 0}
...
```

```python
app: lief.MachO.Binary

for s in app.symbols:
    if s.has_export_info:
        print(s.export_info)
```

```text
Node Offset: 128
Flags:       0
Address:     126b
Symbol:      _NSInstallLinkEditErrorHandlers

Node Offset: 5f6
Flags:       0
Address:     2168
Symbol:      _NSIsSymbolDefinedInObjectFileImage

Node Offset: 1a0
Flags:       0
Address:     1391
Symbol:      _NSIsSymbolNameDefined
...
```

After the shift operation, export information is patched by updating the [`address`](<https://lief.re/doc/latest/formats/macho/python.html#lief.MachO.ExportInfo.address> "lief.MachO.ExportInfo.address") attribute, and a new export trie is generated from the updated data.

## [Removing the Signature](<https://lief.re/doc/latest/tutorials/11_macho_modification.html#removing-the-signature>)

Removing the `LC_CODE_SIGNATURE` command is a basic modification that is very useful when modifying Mach-O files. Since the signature verifies the integrity of the binary, this command typically needs to be removed after modifying the file. The binary can be re-signed once all modifications are finished.

LIEF provides the [`lief.MachO.Binary.remove_signature()`](<https://lief.re/doc/latest/formats/macho/python.html#lief.MachO.Binary.remove_signature> "lief.MachO.Binary.remove_signature") function to remove this command:

```python
ssh: lief.MachO.Binary

ssh.remove_signature()

ssh.write("ssh.nosigned")
```

## [Code Injection with Shared Libraries](<https://lief.re/doc/latest/tutorials/11_macho_modification.html#code-injection-with-shared-libraries>)

As explained in the talk on format modification [[1]](<https://lief.re/doc/latest/tutorials/11_macho_modification.html#footnote-1>), one way to inject code into a program’s memory space is to force the loader to load a library (previously unlinked) that contains a constructor function.

For a Mach-O binary, this can be achieved by adding one of these load commands:

- [`ID_DYLIB`](<https://lief.re/doc/latest/formats/macho/python.html#lief.MachO.LoadCommand.TYPE.ID_DYLIB> "lief.MachO.LoadCommand.TYPE.ID_DYLIB")
- [`LOAD_DYLIB`](<https://lief.re/doc/latest/formats/macho/python.html#lief.MachO.LoadCommand.TYPE.LOAD_DYLIB> "lief.MachO.LoadCommand.TYPE.LOAD_DYLIB")
- …

Consider an example using `clang`. First, we create a small library that defines a constructor:

```cpp
#include <stdio.h>
#include <stdlib.h>

__attribute__((constructor))
void my_constructor(void) {
  printf("Hello World\n");
}
```

This is compiled with:

```console
$ clang -fPIC -shared libexample.c -o libexample.dylib
```

Then, we add a new [`LOAD_DYLIB`](<https://lief.re/doc/latest/formats/macho/python.html#lief.MachO.LoadCommand.TYPE.LOAD_DYLIB> "lief.MachO.LoadCommand.TYPE.LOAD_DYLIB") using the [`lief.MachO.Binary.add_library()`](<https://lief.re/doc/latest/formats/macho/python.html#lief.MachO.Binary.add_library> "lief.MachO.Binary.add_library") function:

```python
clang: lief.MachO.Binary

clang.add_library("/Users/romain/libexample.dylib")

clang.write("/tmp/clang.new")
```

Finally, we run `clang.new` and observe that `Hello World` is printed before the main execution of `clang`:

```console
$ chmod u+x /tmp/clang.new

$ /tmp/clang.new
Hello World
clang: error: no input files
```

We can also see the new [`LOAD_DYLIB`](<https://lief.re/doc/latest/formats/macho/python.html#lief.MachO.LoadCommand.TYPE.LOAD_DYLIB> "lief.MachO.LoadCommand.TYPE.LOAD_DYLIB") command using `otool`:

```console
$ otool -l /tmp/clang.new|grep -C4 LOAD_DYLIB

...
cmdsize 16
dataoff 73864
datasize 0
Load command 16
        cmd LC_LOAD_DYLIB
    cmdsize 56
       name /Users/romain/libexample.dylib (offset 24)
 time stamp 2 Thu Jan  1 01:00:02 1970
    current version 0.0.0
```

## [Adding a Section/Segment](<https://lief.re/doc/latest/tutorials/11_macho_modification.html#adding-a-section-segment>)

Since we can allocate arbitrary space between the load command table and the raw data, we can also extend an existing [`LoadCommand`](<https://lief.re/doc/latest/formats/macho/python.html#lief.MachO.LoadCommand> "lief.MachO.LoadCommand"). In particular, Mach-O segments are commands associated with the LIEF object [`lief.MachO.SegmentCommand`](<https://lief.re/doc/latest/formats/macho/python.html#lief.MachO.SegmentCommand> "lief.MachO.SegmentCommand").

To add a new section to the `__TEXT` segment, we must extend the load command associated with that segment to accommodate a new section structure. We must also reserve space for the section’s content. Since the content of the `__TEXT` segment begins at offset 0 and ends somewhere in the raw data, the appropriate place to insert the new content is between the end of the load command table and the beginning of the raw data:![../_images/extendtxt.png](https://lief.re/doc/latest/_images/extendtxt.png)

The process described above is implemented via the [`lief.MachO.Binary.add_section()`](<https://lief.re/doc/latest/formats/macho/python.html#lief.MachO.Binary.add_section> "lief.MachO.Binary.add_section") method.

In this example, we will inject assembly code that executes `/bin/sh`:

```python
app: lief.MachO.Binary
raw_shell: list[int]

section = lief.MachO.Section.create("__shell", raw_shell)
assert isinstance(section, lief.MachO.Section)

section.alignment = 2
section += lief.MachO.Section.FLAGS.SOME_INSTRUCTIONS
section += lief.MachO.Section.FLAGS.PURE_INSTRUCTIONS

section = app.add_section(section)
print(section)
```

We can then change the entry point by setting the [`lief.MachO.MainCommand.entrypoint`](<https://lief.re/doc/latest/formats/macho/python.html#lief.MachO.MainCommand.entrypoint> "lief.MachO.MainCommand.entrypoint") attribute:

```python
app: lief.MachO.Binary
section: lief.MachO.Section

__TEXT = app.get_segment("__TEXT")
assert __TEXT is not None

main = app.main_command
assert main is not None

main.entrypoint = section.virtual_address - __TEXT.virtual_address
```

Finally, we remove the signature and reconstruct the binary:

```python
app: lief.MachO.Binary

app.remove_signature()
app.write("./id.modified")
```

The execution of `id.modified` should yield a similar output:

```console
Mac-mini:tmp romain$ ./id.modified
tmp @ [romain] $
```

You can also check other tools such as optool [[2]](<https://lief.re/doc/latest/tutorials/11_macho_modification.html#footnote-2>) or insert\_dylib [[3]](<https://lief.re/doc/latest/tutorials/11_macho_modification.html#footnote-3>).

References[[1](<https://lief.re/doc/latest/tutorials/11_macho_modification.html#id1>)]

[https://www.romainthomas.fr/publication/static-instrumentation/](<https://www.romainthomas.fr/publication/static-instrumentation/>)

[[2](<https://lief.re/doc/latest/tutorials/11_macho_modification.html#id2>)]

[https://github.com/alexzielenski/optool](<https://github.com/alexzielenski/optool>)

[[3](<https://lief.re/doc/latest/tutorials/11_macho_modification.html#id3>)]

[https://github.com/Tyilo/insert\_dylib](<https://github.com/Tyilo/insert_dylib>)

API

- [`lief.MachO.Binary.add_section()`](<https://lief.re/doc/latest/formats/macho/python.html#lief.MachO.Binary.add_section> "lief.MachO.Binary.add_section")
- [`lief.MachO.Binary.add_library()`](<https://lief.re/doc/latest/formats/macho/python.html#lief.MachO.Binary.add_library> "lief.MachO.Binary.add_library")
