Dyld Shared Cache

Introduction

LIEF Extended can inspect Apple’s Dyld shared cache, enumerate its libraries, and extract them as Mach-O binaries.

Load a cache and list its libraries

import lief
dyld_cache: lief.dsc.DyldSharedCache | None = lief.dsc.load("macos-15.0.1/")

Note

Pass a directory to to load the whole cache, or an explicit set of files to load a subset. Keep the main cache and its matching subcache files together: an extraction may need data from more than one file.
dyld_cache: lief.dsc.DyldSharedCache

for dylib in dyld_cache.libraries:
    print(f"{dylib.address:#016x}: {dylib.path}")

Extract a library

dyld_cache: lief.dsc.DyldSharedCache

liblockdown = dyld_cache.find_lib_from_name("liblockdown.dylib")

macho = liblockdown.get()

for segment in macho.segments:
    print(segment.name)
dyld_cache: lief.dsc.DyldSharedCache

liblockdown = dyld_cache.find_lib_from_name("liblockdown.dylib")

macho = liblockdown.get()
macho.write("on-disk-liblockdown.dylib")

Warning

By default, LIEF retains Dyld shared cache optimizations. Review when the extracted library needs references and other cache-specific structures recovered. Writing a Mach-O file alone does not guarantee it can be loaded independently of the cache.

Performance Considerations

Dyld shared cache files are quite large, meaning they cannot be processed in the same way as standard or binaries.

Note

These functions parse all format structures (with decent performance) because:

  1. Most binary sizes are less than one gigabyte.

  2. A complete representation is required for modifying binaries.

From a technical perspective, LIEF uses a LIEF::FileStream to access Dyld shared cache structures on demand. Thus, in-memory consumption is limited to the size of the structures being accessed. The drawback of using FileStream is that because it uses file-based access, it takes more time compared to a LIEF::VectorStream.

Additionally, LIEF’s Dyld shared cache implementation heavily relies on the iterator pattern to follow the principle: don’t pay overhead for what you don’t access.

Where possible, LIEF implements the random access iterator trait [1] so that we can programmatically do:

dyld_cache: lief.dsc.DyldSharedCache

# No cost
libraries = dyld_cache.libraries

# O(1) cost
first_lib = libraries[0]

# O(len(libraries)) cost
for lib in libraries:
    print(lib.path)
For instance, may require iterating over the Dyld shared cache’s stub islands several times. To improve overall performance, LIEF provides a cache-based optimization that can be enabled and configured with:

When should you turn caching on?

You can skip LIEF’s caching if:

  • You don’t plan to extract libraries from the shared cache.

  • You plan to extract only one library from the shared cache and only once

  • You don’t want to have LIEF cache artifacts on your system.

By default, the cache mechanism is not enabled.

References

Python API

C++ API

Rust API: lief::dsc