From 442fb46395901c57402ef6f4c947b4fc52d467e3 Mon Sep 17 00:00:00 2001 From: Romain Thomas Date: Sat, 19 Oct 2024 06:29:24 +0200 Subject: [PATCH] Initial support for Apple's dyld shared cache --- README.md | 12 +- api/python/README.rst | 1 + .../examples/dyld_shared_cache_reader.py | 40 +++ api/python/lief/__init__.pyi | 2 +- api/python/lief/dsc.pyi | 167 +++++++++ api/python/src/CMakeLists.txt | 1 + api/python/src/DyldSharedCache/CMakeLists.txt | 8 + api/python/src/DyldSharedCache/init.cpp | 61 ++++ api/python/src/DyldSharedCache/init.hpp | 8 + .../src/DyldSharedCache/pyDyldSharedCache.cpp | 266 ++++++++++++++ .../src/DyldSharedCache/pyDyldSharedCache.hpp | 12 + api/python/src/DyldSharedCache/pyDylib.cpp | 144 ++++++++ .../src/DyldSharedCache/pyMappingInfo.cpp | 37 ++ api/python/src/DyldSharedCache/pySubCache.cpp | 44 +++ api/python/src/pyLIEF.cpp | 2 + api/rust/autocxx_ffi.rs | 30 ++ api/rust/cargo/lief/src/dsc.rs | 92 +++++ api/rust/cargo/lief/src/dsc/caching.rs | 32 ++ .../cargo/lief/src/dsc/dyld_shared_cache.rs | 331 +++++++++++++++++ api/rust/cargo/lief/src/dsc/dylib.rs | 165 +++++++++ api/rust/cargo/lief/src/dsc/mapping_info.rs | 68 ++++ api/rust/cargo/lief/src/dsc/subcache.rs | 63 ++++ api/rust/cargo/lief/src/dsc/uuid.rs | 2 + api/rust/cargo/lief/src/lib.rs | 5 +- api/rust/cargo/lief/src/objc/decl_opt.rs | 1 + .../lief/tests/dyld_shared_cache_test.rs | 83 +++++ api/rust/cargo/lief/tests/utils.rs | 5 + .../src/bin/dyld_shared_cache_reader.rs | 34 ++ .../include/LIEF/rust/DyldSharedCache.hpp | 22 ++ .../rust/DyldSharedCache/DyldSharedCache.hpp | 103 ++++++ .../LIEF/rust/DyldSharedCache/Dylib.hpp | 60 ++++ .../LIEF/rust/DyldSharedCache/MappingInfo.hpp | 30 ++ .../LIEF/rust/DyldSharedCache/SubCache.hpp | 30 ++ .../LIEF/rust/DyldSharedCache/caching.hpp | 10 + api/rust/include/LIEF/rust/LIEF.hpp | 1 + api/rust/include/LIEF/rust/MachO/Binary.hpp | 1 + api/rust/include/LIEF/rust/Mirror.hpp | 5 + api/rust/src/CMakeLists.txt | 1 + api/rust/src/dsc_SubCache.cpp | 7 + cmake/LIEFOptions.cmake | 11 +- doc/sphinx/_cross_api.rst | 58 +++ doc/sphinx/api/cpp/index.rst | 16 +- doc/sphinx/api/utilities/index.rst | 2 +- doc/sphinx/changelog.rst | 1 + doc/sphinx/extended/dsc/cpp.rst | 47 +++ doc/sphinx/extended/dsc/index.rst | 278 +++++++++++++++ doc/sphinx/extended/dsc/python.rst | 37 ++ doc/sphinx/extended/dsc/rust.rst | 6 + doc/sphinx/extended/intro.rst | 53 +-- doc/sphinx/extended/objc/index.rst | 14 +- doc/sphinx/index.rst | 1 + examples/cpp/CMakeLists.txt | 1 + examples/cpp/dyld_shared_cache_reader.cpp | 34 ++ include/LIEF/BinaryStream/FileStream.hpp | 2 +- include/LIEF/DyldSharedCache.hpp | 23 ++ .../LIEF/DyldSharedCache/DyldSharedCache.hpp | 269 ++++++++++++++ include/LIEF/DyldSharedCache/Dylib.hpp | 158 +++++++++ include/LIEF/DyldSharedCache/MappingInfo.hpp | 103 ++++++ include/LIEF/DyldSharedCache/SubCache.hpp | 103 ++++++ include/LIEF/DyldSharedCache/caching.hpp | 53 +++ include/LIEF/DyldSharedCache/uuid.hpp | 29 ++ include/LIEF/LIEF.hpp | 1 + include/LIEF/config.h.in | 7 +- src/CMakeLists.txt | 3 + src/dyld-shared-cache/CMakeLists.txt | 3 + src/dyld-shared-cache/dyldsc.cpp | 335 ++++++++++++++++++ src/messages.hpp | 6 + tests/dyld-shared-cache/test_dsc_misc.py | 19 + tests/dyld-shared-cache/test_ios.py | 65 ++++ tests/run_pytest.py | 1 + tests/utils.py | 21 ++ 71 files changed, 3698 insertions(+), 48 deletions(-) create mode 100755 api/python/examples/dyld_shared_cache_reader.py create mode 100644 api/python/lief/dsc.pyi create mode 100644 api/python/src/DyldSharedCache/CMakeLists.txt create mode 100644 api/python/src/DyldSharedCache/init.cpp create mode 100644 api/python/src/DyldSharedCache/init.hpp create mode 100644 api/python/src/DyldSharedCache/pyDyldSharedCache.cpp create mode 100644 api/python/src/DyldSharedCache/pyDyldSharedCache.hpp create mode 100644 api/python/src/DyldSharedCache/pyDylib.cpp create mode 100644 api/python/src/DyldSharedCache/pyMappingInfo.cpp create mode 100644 api/python/src/DyldSharedCache/pySubCache.cpp create mode 100644 api/rust/cargo/lief/src/dsc.rs create mode 100644 api/rust/cargo/lief/src/dsc/caching.rs create mode 100644 api/rust/cargo/lief/src/dsc/dyld_shared_cache.rs create mode 100644 api/rust/cargo/lief/src/dsc/dylib.rs create mode 100644 api/rust/cargo/lief/src/dsc/mapping_info.rs create mode 100644 api/rust/cargo/lief/src/dsc/subcache.rs create mode 100644 api/rust/cargo/lief/src/dsc/uuid.rs create mode 100644 api/rust/cargo/lief/tests/dyld_shared_cache_test.rs create mode 100644 api/rust/examples/src/bin/dyld_shared_cache_reader.rs create mode 100644 api/rust/include/LIEF/rust/DyldSharedCache.hpp create mode 100644 api/rust/include/LIEF/rust/DyldSharedCache/DyldSharedCache.hpp create mode 100644 api/rust/include/LIEF/rust/DyldSharedCache/Dylib.hpp create mode 100644 api/rust/include/LIEF/rust/DyldSharedCache/MappingInfo.hpp create mode 100644 api/rust/include/LIEF/rust/DyldSharedCache/SubCache.hpp create mode 100644 api/rust/include/LIEF/rust/DyldSharedCache/caching.hpp create mode 100644 api/rust/src/dsc_SubCache.cpp create mode 100644 doc/sphinx/extended/dsc/cpp.rst create mode 100644 doc/sphinx/extended/dsc/index.rst create mode 100644 doc/sphinx/extended/dsc/python.rst create mode 100644 doc/sphinx/extended/dsc/rust.rst create mode 100644 examples/cpp/dyld_shared_cache_reader.cpp create mode 100644 include/LIEF/DyldSharedCache.hpp create mode 100644 include/LIEF/DyldSharedCache/DyldSharedCache.hpp create mode 100644 include/LIEF/DyldSharedCache/Dylib.hpp create mode 100644 include/LIEF/DyldSharedCache/MappingInfo.hpp create mode 100644 include/LIEF/DyldSharedCache/SubCache.hpp create mode 100644 include/LIEF/DyldSharedCache/caching.hpp create mode 100644 include/LIEF/DyldSharedCache/uuid.hpp create mode 100644 src/dyld-shared-cache/CMakeLists.txt create mode 100644 src/dyld-shared-cache/dyldsc.cpp create mode 100644 tests/dyld-shared-cache/test_dsc_misc.py create mode 100644 tests/dyld-shared-cache/test_ios.py diff --git a/README.md b/README.md index f3fa752479..d8904b32ae 100644 --- a/README.md +++ b/README.md @@ -50,16 +50,22 @@ # About -The purpose of this project is to provide a cross platform library which can parse, +The purpose of this project is to provide a cross-platform library to parse, modify and abstract ELF, PE and MachO formats. Main features: - * **Parsing**: LIEF can parse ELF, PE, MachO, OAT, DEX, VDEX, ART and provides an user-friendly API to access to format internals. - * **Modify**: LIEF enables to modify some parts of these formats + * **Parsing**: LIEF can parse ELF, PE, MachO, OAT, DEX, VDEX, ART and provides an user-friendly API to access to internals. + * **Modify**: LIEF can use to modify some parts of these formats (adding a section, changing a symbol's name, ...) * **Abstract**: Three formats have common features like sections, symbols, entry point... LIEF factors them. * **API**: LIEF can be used in C++, Python, Rust and C +Extended features: + + * **DWAF/PDB** Support + * **Objective-C** Metadata + * **Dyld Shared Cache** with support for extracting Dylib + # Content - [About](#about) diff --git a/api/python/README.rst b/api/python/README.rst index e48a67c480..7e4fc87a90 100644 --- a/api/python/README.rst +++ b/api/python/README.rst @@ -15,6 +15,7 @@ LIEF Extended: * DWARF/PDB Support * Objective-C Metadata + * dyld shared cache Checkout: https://lief.re/doc/latest/extended/intro.html for the details diff --git a/api/python/examples/dyld_shared_cache_reader.py b/api/python/examples/dyld_shared_cache_reader.py new file mode 100755 index 0000000000..505ac9b99f --- /dev/null +++ b/api/python/examples/dyld_shared_cache_reader.py @@ -0,0 +1,40 @@ +#!/usr/bin/env python +''' +This script lists libraries embedded in a dyld shared cache file + +Note: this script is only working with the extended version of LIEF +''' + +import sys +import lief +import argparse +from pathlib import Path +from typing import Optional + +def main() -> int: + parser = argparse.ArgumentParser() + parser.add_argument("target", type=Path, + help='Target dyld shared file (file or dir)') + args = parser.parse_args() + + target: Path = args.target + if not target.exists(): + print(f"'{target}' does not exist") + return 1 + + dyld_cache = lief.dsc.load(target) + if dyld_cache is None: + print(f"Can't parse '{target}'") + + for lib in dyld_cache.libraries: + print(f"0x{lib.address:016x} {lib.path}") + + for idx, info in enumerate(dyld_cache.mapping_info): + print(f"mapping_info[{idx:02d}]: [{info.address:016x}, {info.end_address:016x}] -> 0x{info.file_offset:016x}") + + for idx, subcache in enumerate(dyld_cache.subcaches): + uuid_str = ':'.join(map(lambda e: f"{e:02x}", subcache.uuid)) + print(f"cache[{idx:02d}]: {uuid_str} {subcache.suffix}") + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/api/python/lief/__init__.pyi b/api/python/lief/__init__.pyi index 830523a257..5bcc5bb6de 100644 --- a/api/python/lief/__init__.pyi +++ b/api/python/lief/__init__.pyi @@ -1,6 +1,6 @@ from typing import Any, ClassVar, Optional, Union -from . import ART, Android, DEX, ELF, MachO, OAT, PE, VDEX, dwarf, logging, objc, pdb # type: ignore +from . import ART, Android, DEX, ELF, MachO, OAT, PE, VDEX, dsc, dwarf, logging, objc, pdb # type: ignore from typing import overload import io import lief # type: ignore diff --git a/api/python/lief/dsc.pyi b/api/python/lief/dsc.pyi new file mode 100644 index 0000000000..488f685d21 --- /dev/null +++ b/api/python/lief/dsc.pyi @@ -0,0 +1,167 @@ +from typing import Any, ClassVar, Optional + +from typing import overload +import collections.abc +import lief.MachO # type: ignore +import lief.dsc # type: ignore +import lief.dsc.DyldSharedCache # type: ignore +import lief.dsc.Dylib # type: ignore +import os + +class DyldSharedCache: + class ARCH: + ARM64: ClassVar[DyldSharedCache.ARCH] = ... + ARM64E: ClassVar[DyldSharedCache.ARCH] = ... + ARMV5: ClassVar[DyldSharedCache.ARCH] = ... + ARMV6: ClassVar[DyldSharedCache.ARCH] = ... + ARMV7: ClassVar[DyldSharedCache.ARCH] = ... + I386: ClassVar[DyldSharedCache.ARCH] = ... + UNKNOWN: ClassVar[DyldSharedCache.ARCH] = ... + X86_64: ClassVar[DyldSharedCache.ARCH] = ... + X86_64H: ClassVar[DyldSharedCache.ARCH] = ... + __name__: str + def __init__(self, *args, **kwargs) -> None: ... + def __ge__(self, other) -> bool: ... + def __gt__(self, other) -> bool: ... + def __hash__(self) -> int: ... + def __index__(self) -> Any: ... + def __int__(self) -> int: ... + def __le__(self, other) -> bool: ... + def __lt__(self, other) -> bool: ... + + class PLATFORM: + ANY: ClassVar[DyldSharedCache.PLATFORM] = ... + BRIDGEOS: ClassVar[DyldSharedCache.PLATFORM] = ... + DRIVERKIT: ClassVar[DyldSharedCache.PLATFORM] = ... + FIRMWARE: ClassVar[DyldSharedCache.PLATFORM] = ... + IOS: ClassVar[DyldSharedCache.PLATFORM] = ... + IOSMAC: ClassVar[DyldSharedCache.PLATFORM] = ... + IOS_SIMULATOR: ClassVar[DyldSharedCache.PLATFORM] = ... + MACOS: ClassVar[DyldSharedCache.PLATFORM] = ... + SEPOS: ClassVar[DyldSharedCache.PLATFORM] = ... + TVOS: ClassVar[DyldSharedCache.PLATFORM] = ... + TVOS_SIMULATOR: ClassVar[DyldSharedCache.PLATFORM] = ... + UNKNOWN: ClassVar[DyldSharedCache.PLATFORM] = ... + VISIONOS: ClassVar[DyldSharedCache.PLATFORM] = ... + VISIONOS_SIMULATOR: ClassVar[DyldSharedCache.PLATFORM] = ... + WATCHOS: ClassVar[DyldSharedCache.PLATFORM] = ... + WATCHOS_SIMULATOR: ClassVar[DyldSharedCache.PLATFORM] = ... + __name__: str + def __init__(self, *args, **kwargs) -> None: ... + def __ge__(self, other) -> bool: ... + def __gt__(self, other) -> bool: ... + def __hash__(self) -> int: ... + def __index__(self) -> Any: ... + def __int__(self) -> int: ... + def __le__(self, other) -> bool: ... + def __lt__(self, other) -> bool: ... + + class VERSION: + DYLD_1042_1: ClassVar[DyldSharedCache.VERSION] = ... + DYLD_195_5: ClassVar[DyldSharedCache.VERSION] = ... + DYLD_239_3: ClassVar[DyldSharedCache.VERSION] = ... + DYLD_360_14: ClassVar[DyldSharedCache.VERSION] = ... + DYLD_421_1: ClassVar[DyldSharedCache.VERSION] = ... + DYLD_832_7_1: ClassVar[DyldSharedCache.VERSION] = ... + DYLD_940: ClassVar[DyldSharedCache.VERSION] = ... + DYLD_95_3: ClassVar[DyldSharedCache.VERSION] = ... + UNKNOWN: ClassVar[DyldSharedCache.VERSION] = ... + UNRELEASED: ClassVar[DyldSharedCache.VERSION] = ... + __name__: str + def __init__(self, *args, **kwargs) -> None: ... + def __ge__(self, other) -> bool: ... + def __gt__(self, other) -> bool: ... + def __hash__(self) -> int: ... + def __index__(self) -> Any: ... + def __int__(self) -> int: ... + def __le__(self, other) -> bool: ... + def __lt__(self, other) -> bool: ... + def __init__(self, *args, **kwargs) -> None: ... + def enable_caching(self, target_dir: str) -> None: ... + def find_lib_from_name(self, name: str) -> Optional[lief.dsc.Dylib]: ... + def find_lib_from_path(self, path: str) -> Optional[lief.dsc.Dylib]: ... + def find_lib_from_va(self, virtual_address: int) -> Optional[lief.dsc.Dylib]: ... + def flush_cache(self) -> None: ... + @staticmethod + def from_files(files: list[str]) -> Optional[lief.dsc.DyldSharedCache]: ... + @staticmethod + def from_path(path: str, arch: str = ...) -> Optional[lief.dsc.DyldSharedCache]: ... + @property + def arch(self) -> lief.dsc.DyldSharedCache.ARCH: ... + @property + def arch_name(self) -> str: ... + @property + def filename(self) -> str: ... + @property + def filepath(self) -> str: ... + @property + def has_subcaches(self) -> bool: ... + @property + def libraries(self) -> collections.abc.Sequence[Optional[lief.dsc.Dylib]]: ... + @property + def load_address(self) -> int: ... + @property + def mapping_info(self) -> collections.abc.Sequence[Optional[lief.dsc.MappingInfo]]: ... + @property + def platform(self) -> lief.dsc.DyldSharedCache.PLATFORM: ... + @property + def subcaches(self) -> collections.abc.Sequence[Optional[lief.dsc.SubCache]]: ... + @property + def version(self) -> lief.dsc.DyldSharedCache.VERSION: ... + +class Dylib: + class extract_opt_t: + create_dyld_chained_fixup_cmd: bool + fix_branches: bool + fix_memory: bool + fix_objc: bool + fix_relocations: bool + pack: bool + def __init__(self) -> None: ... + def __init__(self, *args, **kwargs) -> None: ... + def get(self, opt: lief.dsc.Dylib.extract_opt_t = ...) -> Optional[lief.MachO.Binary]: ... + @property + def address(self) -> int: ... + @property + def inode(self) -> int: ... + @property + def modtime(self) -> int: ... + @property + def padding(self) -> int: ... + @property + def path(self) -> str: ... + +class MappingInfo: + def __init__(self, *args, **kwargs) -> None: ... + @property + def address(self) -> int: ... + @property + def end_address(self) -> int: ... + @property + def file_offset(self) -> int: ... + @property + def init_prot(self) -> int: ... + @property + def max_prot(self) -> int: ... + @property + def size(self) -> int: ... + +class SubCache: + def __init__(self, *args, **kwargs) -> None: ... + @property + def cache(self) -> Optional[lief.dsc.DyldSharedCache]: ... + @property + def suffix(self) -> str: ... + @property + def uuid(self) -> list[int]: ... + @property + def vm_offset(self) -> int: ... + +@overload +def enable_cache() -> bool: ... +@overload +def enable_cache(target_cache_dir: str) -> bool: ... +@overload +def load(files: list[str]) -> Optional[lief.dsc.DyldSharedCache]: ... +@overload +def load(path: os.PathLike, arch: str = ...) -> Optional[lief.dsc.DyldSharedCache]: ... diff --git a/api/python/src/CMakeLists.txt b/api/python/src/CMakeLists.txt index af49461189..1be617790e 100644 --- a/api/python/src/CMakeLists.txt +++ b/api/python/src/CMakeLists.txt @@ -16,6 +16,7 @@ add_subdirectory(platforms) add_subdirectory(DWARF) add_subdirectory(PDB) add_subdirectory(ObjC) +add_subdirectory(DyldSharedCache) if(LIEF_ELF) add_subdirectory(ELF) diff --git a/api/python/src/DyldSharedCache/CMakeLists.txt b/api/python/src/DyldSharedCache/CMakeLists.txt new file mode 100644 index 0000000000..b0f4013774 --- /dev/null +++ b/api/python/src/DyldSharedCache/CMakeLists.txt @@ -0,0 +1,8 @@ +target_sources(pyLIEF PRIVATE + init.cpp + pyDyldSharedCache.cpp + pyDylib.cpp + pyMappingInfo.cpp + pySubCache.cpp +) + diff --git a/api/python/src/DyldSharedCache/init.cpp b/api/python/src/DyldSharedCache/init.cpp new file mode 100644 index 0000000000..08a42dac59 --- /dev/null +++ b/api/python/src/DyldSharedCache/init.cpp @@ -0,0 +1,61 @@ +#include "DyldSharedCache/init.hpp" +#include "DyldSharedCache/pyDyldSharedCache.hpp" +#include "LIEF/DyldSharedCache/caching.hpp" + +#include +#include + +namespace LIEF::dsc { +class DyldSharedCache; +class Dylib; +class SubCache; +class MappingInfo; +} + +namespace LIEF::dsc::py { +void init(nb::module_& m) { + nb::module_ mod = m.def_submodule("dsc"); + + mod.def("enable_cache", nb::overload_cast<>(&enable_cache), + R"doc( + Enable globally cache/memoization. One can also leverage this function + by setting the environment variable ``DYLDSC_ENABLE_CACHE`` to ``1`` + + By default, LIEF will use the directory specified by the environment + variable ``DYLDSC_CACHE_DIR`` as its cache-root directory: + + .. code-block:: console + + DYLDSC_ENABLE_CACHE=1 DYLDSC_CACHE_DIR=/tmp/my_dir python ./my-script.py + + Otherwise, if ``DYLDSC_CACHE_DIR`` is not set, LIEF will use the following + directory (in this priority): + + 1. System or user cache directory + + - macOS: ``DARWIN_USER_TEMP_DIR`` / ``DARWIN_USER_CACHE_DIR`` + ``/dyld_shared_cache`` + - Linux: ``${XDG_CACHE_HOME}/dyld_shared_cache`` + - Windows: ``%LOCALAPPDATA%\dyld_shared_cache`` + + 2. Home directory + + - macOS/Linux: ``$HOME/.dyld_shared_cache`` + - Windows: ``%USERPROFILE%\.dyld_shared_cache`` + + See :meth:`lief.dsc.DyldSharedCache.enable_caching` for a finer granularity + )doc"_doc + ); + + mod.def("enable_cache", nb::overload_cast(&enable_cache), + R"doc( + Same behavior as the other :meth:`~.enable_cache` function but using a + user-provided cache directory instead of an inferred one. + )doc"_doc, "target_cache_dir"_a + ); + + create(mod); + create(mod); + create(mod); + create(mod); +} +} diff --git a/api/python/src/DyldSharedCache/init.hpp b/api/python/src/DyldSharedCache/init.hpp new file mode 100644 index 0000000000..dd85a42fe7 --- /dev/null +++ b/api/python/src/DyldSharedCache/init.hpp @@ -0,0 +1,8 @@ +#ifndef PY_LIEF_DSC_INIT_H +#define PY_LIEF_DSC_INIT_H +#include "pyLIEF.hpp" + +namespace LIEF::dsc::py { +void init(nb::module_& m); +} +#endif diff --git a/api/python/src/DyldSharedCache/pyDyldSharedCache.cpp b/api/python/src/DyldSharedCache/pyDyldSharedCache.cpp new file mode 100644 index 0000000000..8c0ded3791 --- /dev/null +++ b/api/python/src/DyldSharedCache/pyDyldSharedCache.cpp @@ -0,0 +1,266 @@ +#include "LIEF/DyldSharedCache/DyldSharedCache.hpp" +#include "DyldSharedCache/pyDyldSharedCache.hpp" + +#include +#include +#include +#include + +#include "nanobind/extra/random_access_iterator.hpp" + +#include "typing.hpp" +#include "pyutils.hpp" + +struct PathLike : public nanobind::object { + LIEF_PY_DEFAULT_CTOR(PathLike, nanobind::object); + + NB_OBJECT_DEFAULT(PathLike, object, "os.PathLike", check) + + static bool check(handle h) { + return true; + } +}; + + +namespace LIEF::dsc::py { +template<> +void create(nb::module_& m) { + nb::class_ cache(m, "DyldSharedCache", + R"doc( + This class represents a dyld shared cache file. + )doc"_doc + ); + + nb::enum_(cache, "VERSION") + .value("UNKNOWN", dsc::DyldSharedCache::VERSION::UNKNOWN) + .value("DYLD_95_3", dsc::DyldSharedCache::VERSION::DYLD_95_3, + "dyld-95.3 (2007-10-30)"_doc) + .value("DYLD_195_5", dsc::DyldSharedCache::VERSION::DYLD_195_5, + "dyld-195.5 (2011-07-13)"_doc) + .value("DYLD_239_3", dsc::DyldSharedCache::VERSION::DYLD_239_3, + "dyld-239.3 (2013-10-29)"_doc) + .value("DYLD_360_14", dsc::DyldSharedCache::VERSION::DYLD_360_14, + "dyld-360.14 (2015-09-04)"_doc) + .value("DYLD_421_1", dsc::DyldSharedCache::VERSION::DYLD_421_1, + "dyld-421.1 (2016-09-22)"_doc) + .value("DYLD_832_7_1", dsc::DyldSharedCache::VERSION::DYLD_832_7_1, + "dyld-832.7.1 (2020-11-19)"_doc) + .value("DYLD_940", dsc::DyldSharedCache::VERSION::DYLD_940, + "dyld-940 (2021-02-09)"_doc) + .value("DYLD_1042_1", dsc::DyldSharedCache::VERSION::DYLD_1042_1, + "dyld-1042.1 (2022-10-19)"_doc) + .value("UNRELEASED", dsc::DyldSharedCache::VERSION::UNRELEASED, + R"doc( + This value is used for versions of dyld not publicly released or + not yet supported by LIEF. + )doc"_doc) + ; + + + nb::enum_(cache, "PLATFORM", + R"doc(Platforms supported by the dyld shared cache)doc"_doc + ) + .value("UNKNOWN", dsc::DyldSharedCache::DYLD_TARGET_PLATFORM::UNKNOWN) + .value("MACOS", dsc::DyldSharedCache::DYLD_TARGET_PLATFORM::MACOS) + .value("IOS", dsc::DyldSharedCache::DYLD_TARGET_PLATFORM::IOS) + .value("TVOS", dsc::DyldSharedCache::DYLD_TARGET_PLATFORM::TVOS) + .value("WATCHOS", dsc::DyldSharedCache::DYLD_TARGET_PLATFORM::WATCHOS) + .value("BRIDGEOS", dsc::DyldSharedCache::DYLD_TARGET_PLATFORM::BRIDGEOS) + .value("IOSMAC", dsc::DyldSharedCache::DYLD_TARGET_PLATFORM::IOSMAC) + .value("IOS_SIMULATOR", dsc::DyldSharedCache::DYLD_TARGET_PLATFORM::IOS_SIMULATOR) + .value("TVOS_SIMULATOR", dsc::DyldSharedCache::DYLD_TARGET_PLATFORM::TVOS_SIMULATOR) + .value("WATCHOS_SIMULATOR", dsc::DyldSharedCache::DYLD_TARGET_PLATFORM::WATCHOS_SIMULATOR) + .value("DRIVERKIT", dsc::DyldSharedCache::DYLD_TARGET_PLATFORM::DRIVERKIT) + .value("VISIONOS", dsc::DyldSharedCache::DYLD_TARGET_PLATFORM::VISIONOS) + .value("VISIONOS_SIMULATOR", dsc::DyldSharedCache::DYLD_TARGET_PLATFORM::VISIONOS_SIMULATOR) + .value("FIRMWARE", dsc::DyldSharedCache::DYLD_TARGET_PLATFORM::FIRMWARE) + .value("SEPOS", dsc::DyldSharedCache::DYLD_TARGET_PLATFORM::SEPOS) + .value("ANY", dsc::DyldSharedCache::DYLD_TARGET_PLATFORM::ANY) + ; + + nb::enum_(cache, "ARCH", + R"doc(Architecture supported by the dyld shared cache)doc"_doc + ) + .value("UNKNOWN", dsc::DyldSharedCache::DYLD_TARGET_ARCH::UNKNOWN) + .value("I386", dsc::DyldSharedCache::DYLD_TARGET_ARCH::I386) + .value("X86_64", dsc::DyldSharedCache::DYLD_TARGET_ARCH::X86_64) + .value("X86_64H", dsc::DyldSharedCache::DYLD_TARGET_ARCH::X86_64H) + .value("ARMV5", dsc::DyldSharedCache::DYLD_TARGET_ARCH::ARMV5) + .value("ARMV6", dsc::DyldSharedCache::DYLD_TARGET_ARCH::ARMV6) + .value("ARMV7", dsc::DyldSharedCache::DYLD_TARGET_ARCH::ARMV7) + .value("ARM64", dsc::DyldSharedCache::DYLD_TARGET_ARCH::ARM64) + .value("ARM64E", dsc::DyldSharedCache::DYLD_TARGET_ARCH::ARM64E) + ; + + cache + .def_static("from_path", &dsc::DyldSharedCache::from_path, + R"doc( + See: :meth:`lief.dsc.load` for the details + )doc"_doc, "path"_a, "arch"_a = "" + ) + .def_static("from_files", &dsc::DyldSharedCache::from_files, + R"doc( + See: :meth:`lief.dsc.load` for the details + )doc"_doc, "files"_a + ) + .def_prop_ro("filename", &dsc::DyldSharedCache::filename, + R"doc( + Filename of the dyld shared file associated with this object. + + For instance: ``dyld_shared_cache_arm64e, dyld_shared_cache_arm64e.62.dyldlinkedit`` + )doc"_doc + ) + .def_prop_ro("version", &dsc::DyldSharedCache::version, + R"doc( + Version of dyld used by this cache + )doc"_doc + ) + .def_prop_ro("filepath", &dsc::DyldSharedCache::filepath, + R"doc( + Full path to the original dyld shared cache file associated with object + (e.g. ``/cache/visionos/dyld_shared_cache_arm64e.42``) + )doc"_doc + ) + .def_prop_ro("load_address", &dsc::DyldSharedCache::load_address, + R"doc(Based address of this cache)doc"_doc + ) + .def_prop_ro("arch_name", &dsc::DyldSharedCache::arch_name, + R"doc(Name of the architecture targeted by this cache (``x86_64h``))doc"_doc + ) + .def_prop_ro("platform", &dsc::DyldSharedCache::platform, + R"doc(Platform targeted by this cache (e.g. vision-os))doc"_doc + ) + .def_prop_ro("arch", &dsc::DyldSharedCache::arch, + R"doc(Architecture targeted by this cache)doc"_doc + ) + .def_prop_ro("has_subcaches", &dsc::DyldSharedCache::has_subcaches, + R"doc(True if the subcaches are associated with this cache)doc"_doc + ) + .def("find_lib_from_va", &dsc::DyldSharedCache::find_lib_from_va, + R"doc( + Find the :class:`lief.dsc.Dylib` that encompasses the given virtual address. + It returns ``None`` if a Dylib can't be found. + )doc", "virtual_address"_a, + nb::keep_alive<0, 1>() + ) + .def("find_lib_from_path", &dsc::DyldSharedCache::find_lib_from_path, + R"doc( + Find the Dylib whose :attr:`lief.dsc.Dylib.path` matches the provided path. + )doc", "path"_a, + nb::keep_alive<0, 1>() + ) + .def("find_lib_from_name", &dsc::DyldSharedCache::find_lib_from_name, + R"doc( + Find the Dylib whose filename of :attr:`lief.dsc.Dylib.path` matches the + provided name. + + If multiple libraries have the same name (but with a different path), + the **first one** matching the provided name is returned. + )doc", "name"_a, + nb::keep_alive<0, 1>() + ) + .def_prop_ro("libraries", + [] (const DyldSharedCache& self) { + auto libraries = self.libraries(); + return nb::make_random_access_iterator(nb::type(), "dylib_iterator", libraries); + }, nb::keep_alive<0, 1>(), + R"doc( + Return a list-like of the :class:`~.Dylib` embedded in this dyld shared cache + )doc"_doc + ) + + .def_prop_ro("mapping_info", + [] (const DyldSharedCache& self) { + auto mapping = self.mapping_info(); + return nb::make_random_access_iterator(nb::type(), "mapping_info_iterator", mapping); + }, nb::keep_alive<0, 1>(), + R"doc( + Return a list-like of the :class:`~.MappingInfo` embedded in this dyld shared cache + )doc"_doc + ) + + .def_prop_ro("subcaches", + [] (const DyldSharedCache& self) { + auto subcaches = self.subcaches(); + return nb::make_random_access_iterator(nb::type(), "subcache_iterator", subcaches); + }, nb::keep_alive<0, 1>(), + R"doc( + Return a list-like of :class:`~.SubCache` embedded in this (main) + dyld shared cache + )doc"_doc + ) + + .def("enable_caching", &DyldSharedCache::enable_caching, + R"doc( + When enabled, this function allows to record and to keep in *cache*, + dyld shared cache information that are costly to access. + + For instance, GOT symbols, rebases information, stub symbols, ... + + It is **highly** recommended to enable this function when processing + a dyld shared cache several times or when extracting a large number of + :class:`lief.dsc.Dylib` with enhanced extraction options + (e.g. :attr:`lief.dsc.Dylib.extract_opt_t.fix_branches`) + + One can enable caching by calling this function: + + .. code-block:: python + + dyld_cache = lief.dsc.load("macos-15.0.1/"); + dyld_cache.enable_caching("~/.cache/lief-dsc"); + + One can also enable this cache optimization **globally** using the + function: :func:`lief.dsc.enable_cache` or by setting the environment variable + ``DYLDSC_ENABLE_CACHE`` to 1. + )doc"_doc, "target_dir"_a + ) + + .def("flush_cache", &DyldSharedCache::flush_cache, + R"doc( + Flush internal information into the on-disk cache (see: :meth:`~.enable_caching`) + )doc"_doc + ) + ; + + m.def("load", nb::overload_cast&>(&dsc::load), + R"doc( + Load a shared cache from a list of files. + + .. code-block:: cpp + + files = [ + "/tmp/dsc/dyld_shared_cache_arm64e", + "/tmp/dsc/dyld_shared_cache_arm64e.1" + ] + cache = lief.dsc.load(files); + )doc"_doc, "files"_a); + + m.def("load", [] (PathLike path, const std::string& arch) -> std::unique_ptr { + if (auto path_str = LIEF::py::path_to_str(path)) { + return load(*path_str, arch); + } + return nullptr; + }, + R"doc( + Load a shared cache from the a single file or from a directory specified + by the ``path`` parameter. + + In the case where multiple architectures are + available in the ``path`` directory, the ``arch`` parameter can be used to + define which architecture should be prefered. + + **Example:** + + .. code-block:: python + + // From a directory (split caches) + cache = lief.dsc.load("vision-pro-2.0/"); + + // From a single cache file + cache = lief.dsc.load("ios-14.2/dyld_shared_cache_arm64"); + + // From a directory with multiple architectures + cache = lief.dsc.load("macos-12.6/", "x86_64h"); + )doc"_doc, "path"_a, "arch"_a = ""); +} +} diff --git a/api/python/src/DyldSharedCache/pyDyldSharedCache.hpp b/api/python/src/DyldSharedCache/pyDyldSharedCache.hpp new file mode 100644 index 0000000000..f10e12c8a8 --- /dev/null +++ b/api/python/src/DyldSharedCache/pyDyldSharedCache.hpp @@ -0,0 +1,12 @@ +#ifndef PY_LIEF_DSC_H +#define PY_LIEF_DSC_H +#include "pyLIEF.hpp" + +namespace LIEF::dsc::py { + +namespace dsc = LIEF::dsc; + +template +void create(nb::module_&); +} +#endif diff --git a/api/python/src/DyldSharedCache/pyDylib.cpp b/api/python/src/DyldSharedCache/pyDylib.cpp new file mode 100644 index 0000000000..59f83637e8 --- /dev/null +++ b/api/python/src/DyldSharedCache/pyDylib.cpp @@ -0,0 +1,144 @@ +#include "LIEF/DyldSharedCache/Dylib.hpp" +#include "LIEF/MachO/Binary.hpp" +#include "DyldSharedCache/pyDyldSharedCache.hpp" + +#include +#include +#include +#include + + +namespace LIEF::dsc::py { +template<> +void create(nb::module_& m) { + nb::class_ obj(m, "Dylib", + R"doc( + This class represents a library embedded in a dyld shared cache. + It mirrors the original ``dyld_cache_image_info`` structure. + )doc"_doc + ); + + nb::class_(obj, "extract_opt_t", + R"doc( + This structure is used to tweak the extraction process while calling + :meth:`lief.dsc.Dylib.get`. These options allow to deoptimize the dylib and + get an accurate representation of the origin Mach-O binary. + )doc" + ) + .def(nb::init<>()) + .def_rw("pack", &Dylib::extract_opt_t::pack, + R"doc( + Whether the segment's offsets should be packed to avoid + an in-memory size while writing back the binary. + + .. note:: + + This option does not have an impact on the performances + )doc"_doc + ) + + .def_rw("fix_branches", &Dylib::extract_opt_t::fix_branches, + R"doc( + Fix call instructions that target addresses outside the current dylib + virtual space. + + .. warning:: + + Enabling this option can have a significant impact on the + performances. Make sure to enable the internal cache mechanism: + :func:`lief.dsc.enable_cache` or :meth:`lief.dsc.DyldSharedCache.enable_caching` + )doc"_doc + ) + + .def_rw("fix_memory", &Dylib::extract_opt_t::fix_memory, + R"doc( + Fix memory accesses performed outside the dylib's virtual space + + .. warning:: + + Enabling this option can have a significant impact on the + performances. Make sure to enable the internal cache mechanism: + :func:`lief.dsc.enable_cache` or :meth:`lief.dsc.DyldSharedCache.enable_caching` + )doc"_doc + ) + + .def_rw("fix_relocations", &Dylib::extract_opt_t::fix_relocations, + R"doc( + Recover and fix relocations + + .. warning:: + + Enabling this option can have a significant impact on the + performances. Make sure to enable the internal cache mechanism: + :func:`lief.dsc.enable_cache` or :meth:`lief.dsc.DyldSharedCache.enable_caching` + )doc"_doc + ) + + .def_rw("fix_objc", &Dylib::extract_opt_t::fix_relocations, + R"doc( + Fix Objective-C information + + .. warning:: + + Enabling this option can have a significant impact on the + performances. Make sure to enable the internal cache mechanism: + :func:`lief.dsc.enable_cache` or :meth:`lief.dsc.DyldSharedCache.enable_caching` + )doc"_doc + ) + + .def_prop_rw("create_dyld_chained_fixup_cmd", + [] (const Dylib::extract_opt_t& opt) -> bool { + return opt.create_dyld_chained_fixup_cmd.value_or(false); + }, + [] (Dylib::extract_opt_t& self, bool value) { + self.create_dyld_chained_fixup_cmd = value; + }, + R"doc( + Whether the ``LC_DYLD_CHAINED_FIXUPS`` command should be (re)created. + + If this value is not set, LIEF will add the command only if it's + meaningful regarding the other options + )doc"_doc + ) + ; + + obj + .def_prop_ro("path", &dsc::Dylib::path, + R"doc(Original path of the library (e.g. ``/usr/lib/libcryptex.dylib``))doc"_doc + ) + .def_prop_ro("address", &dsc::Dylib::address, + R"doc(In-memory address of the library)doc"_doc + ) + .def_prop_ro("modtime", &dsc::Dylib::modtime, + R"doc( + Modification time of the library matching ``stat.st_mtime``, or 0 + )doc"_doc + ) + .def_prop_ro("inode", &dsc::Dylib::inode, + R"doc( + File serial number matching ``stat.st_ino`` or 0 + + Note that for shared cache targeting iOS, this value can hold a hash of + the path (if modtime is set to 0) + )doc"_doc + ) + .def_prop_ro("padding", &dsc::Dylib::padding, + R"doc(Padding alignment value (should be 0))doc"_doc + ) + + .def("get", &dsc::Dylib::get, + R"doc( + Get a :class:`lief.MachO.Binary` representation for this Dylib. + + One can use this function to write back the Mach-O binary on the disk: + + .. code-block:: cpp + + dyld_cache: lief.dsc.DyldSharedCache = ... + dyld_cache.libraries[10].get().write("libsystem.dylib") + + )doc"_doc, "opt"_a = Dylib::extract_opt_t() + ) + ; +} +} diff --git a/api/python/src/DyldSharedCache/pyMappingInfo.cpp b/api/python/src/DyldSharedCache/pyMappingInfo.cpp new file mode 100644 index 0000000000..b939a8dc74 --- /dev/null +++ b/api/python/src/DyldSharedCache/pyMappingInfo.cpp @@ -0,0 +1,37 @@ +#include "LIEF/DyldSharedCache/MappingInfo.hpp" +#include "DyldSharedCache/pyDyldSharedCache.hpp" + +namespace LIEF::dsc::py { +template<> +void create(nb::module_& m) { + nb::class_ obj(m, "MappingInfo", + R"doc( + This class represents a ``dyld_cache_mapping_info`` entry. + + It provides information about the relationshiop between on-disk shared cache + and in-memory shared cache. + )doc"_doc + ); + + obj + .def_prop_ro("address", &dsc::MappingInfo::address, + R"doc(The in-memory address where this dyld shared cache region is mapped)doc"_doc + ) + .def_prop_ro("size", &dsc::MappingInfo::size, + R"doc(Size of the region being mapped)doc"_doc + ) + .def_prop_ro("end_address", &dsc::MappingInfo::end_address, + R"doc(End virtual address of the region)doc"_doc + ) + .def_prop_ro("file_offset", &dsc::MappingInfo::file_offset, + R"doc(On-disk file offset)doc"_doc + ) + .def_prop_ro("max_prot", &dsc::MappingInfo::max_prot, + R"doc(Max memory protection)doc"_doc + ) + .def_prop_ro("init_prot", &dsc::MappingInfo::init_prot, + R"doc(Initial memory protection)doc"_doc + ) + ; +} +} diff --git a/api/python/src/DyldSharedCache/pySubCache.cpp b/api/python/src/DyldSharedCache/pySubCache.cpp new file mode 100644 index 0000000000..6e8ffafbf1 --- /dev/null +++ b/api/python/src/DyldSharedCache/pySubCache.cpp @@ -0,0 +1,44 @@ +#include "LIEF/DyldSharedCache/SubCache.hpp" +#include "LIEF/DyldSharedCache/DyldSharedCache.hpp" +#include "DyldSharedCache/pyDyldSharedCache.hpp" + +#include +#include +#include +#include + +namespace LIEF::dsc::py { +template<> +void create(nb::module_& m) { + nb::class_ obj(m, "SubCache", + R"doc( + This class represents a subcache in the case of large/split dyld shared + cache. + + It mirror (and abstracts) the original ``dyld_subcache_entry`` / ``dyld_subcache_entry_v1`` + )doc"_doc + ); + + obj + .def_prop_ro("uuid", &SubCache::uuid, + R"doc(The uuid of the subcache file)doc"_doc + ) + .def_prop_ro("vm_offset", &SubCache::vm_offset, + R"doc(The offset of this subcache from the main cache base address)doc"_doc + ) + .def_prop_ro("suffix", &SubCache::suffix, + R"doc( + The file name suffix of the subCache file + (e.g. ``.25.data``, ``.03.development``) + )doc"_doc + ) + .def_prop_ro("cache", [] (const SubCache& self) { + return std::unique_ptr(const_cast(self.cache().release())); + }, + R"doc( + The associated :class:`~.DyldSharedCache` object for this subcache + )doc"_doc + ) + ; +} +} diff --git a/api/python/src/pyLIEF.cpp b/api/python/src/pyLIEF.cpp index e33e3f5d18..33ef45a2ba 100644 --- a/api/python/src/pyLIEF.cpp +++ b/api/python/src/pyLIEF.cpp @@ -38,6 +38,7 @@ #include "DWARF/init.hpp" #include "PDB/init.hpp" #include "ObjC/init.hpp" +#include "DyldSharedCache/init.hpp" #if defined(LIEF_ELF_SUPPORT) #include "ELF/init.hpp" @@ -243,6 +244,7 @@ void init(nb::module_& m) { LIEF::dwarf::py::init(m); LIEF::pdb::py::init(m); LIEF::objc::py::init(m); + LIEF::dsc::py::init(m); #if defined(LIEF_ELF_SUPPORT) LIEF::ELF::py::init(m); diff --git a/api/rust/autocxx_ffi.rs b/api/rust/autocxx_ffi.rs index fffe1188d5..0f71f074a3 100644 --- a/api/rust/autocxx_ffi.rs +++ b/api/rust/autocxx_ffi.rs @@ -594,5 +594,35 @@ include_cpp! { generate_pod!("ObjC_DeclOpt") block_constructors!("ObjC_DeclOpt") + // ------------------------------------------------------------------------- + // Dyld Shared Cache + // ------------------------------------------------------------------------- + generate!("dsc_enable_cache") + generate!("dsc_enable_cache_from_dir") + + generate!("dsc_DyldSharedCache") + block_constructors!("dsc_DyldSharedCache") + + generate!("dsc_DyldSharedCache_it_libraries") + block_constructors!("dsc_DyldSharedCache_it_libraries") + + generate!("dsc_DyldSharedCache_it_mapping_info") + block_constructors!("dsc_DyldSharedCache_it_mapping_info") + + generate!("dsc_DyldSharedCache_it_subcaches") + block_constructors!("dsc_DyldSharedCache_it_subcaches") + + generate!("dsc_Dylib") + block_constructors!("dsc_Dylib") + + generate_pod!("dsc_Dylib_extract_opt") + block_constructors!("dsc_Dylib_extract_opt") + + generate!("dsc_MappingInfo") + block_constructors!("dsc_MappingInfo") + + generate!("dsc_SubCache") + block_constructors!("dsc_SubCache") + safety!(unsafe) } diff --git a/api/rust/cargo/lief/src/dsc.rs b/api/rust/cargo/lief/src/dsc.rs new file mode 100644 index 0000000000..d0321bd18e --- /dev/null +++ b/api/rust/cargo/lief/src/dsc.rs @@ -0,0 +1,92 @@ +//! Module for Dyld shared cache support +//! +//! ### Getting Started +//! +//! ```rust +//! let dyld_cache = lief::dsc::from_path("macos-15.0.1/"); +//! for dylib in dyld_cache.libraries() { +//! println!("0x{:016x}: {}", dylib.address(), dylib.path()); +//! let macho: lief::macho::Binary = dylib.get().expect("Can't get Mach-O representation"); +//! } +//! ``` +//! +//! ### Performance Considerations +//! +//!
+//! If you aim at extracting several libraries from a dyld shared cache, it is +//! highly recommended to enable caching. Otherwise, performances can be +//! impacted. +//!
+//! +//! See: [`crate::dsc::enable_cache`] and [`crate::dsc::enable_cache_from_dir`] +use lief_ffi as ffi; +use std::ffi::{CString, c_char}; +use crate::common::into_optional; + +pub mod dyld_shared_cache; +pub mod mapping_info; +pub mod subcache; +pub mod dylib; +pub mod uuid; + +mod caching; + +#[doc(inline)] +pub use dyld_shared_cache::DyldSharedCache; + +#[doc(inline)] +pub use dylib::Dylib; + +#[doc(inline)] +pub use subcache::SubCache; + +#[doc(inline)] +pub use mapping_info::MappingInfo; + +#[doc(inline)] +pub use uuid::UUID; + +#[doc(inline)] +pub use caching::enable_cache; + +#[doc(inline)] +pub use caching::enable_cache_from_dir; + + +/// Load a shared cache from a single file or from a directory specified +/// by the `path` parameter. +/// +/// In the case where multiple architectures are +/// available in the `path` directory, the `arch` parameter can be used to +/// define which architecture should be prefered. +/// +/// **Example:** +/// +/// ```rust +/// // From a directory (split caches) +/// let cache = lief::dsc::load("vision-pro-2.0/", ""); +/// +/// // From a single cache file +/// let cache = lief::dsc::load("ios-14.2/dyld_shared_cache_arm64", ""); +/// +/// // From a directory with multiple architectures +/// let cache = LIEF::dsc::load("macos-12.6/", /*arch=*/"x86_64h"); +/// ``` +pub fn load_from_path(path: &str, arch: &str) -> Option { + into_optional(ffi::dsc_DyldSharedCache::from_path(path, arch)) +} + +pub fn load_from_files(files: &[String]) -> Option { + let mut c_strings = Vec::new(); + let mut c_ptrs = Vec::new(); + for file in files.iter() { + let c_str = CString::new(file.as_str()).unwrap(); + c_strings.push(c_str); + let c_ptr = c_strings.last().unwrap().as_ptr() as *const c_char; + c_ptrs.push(c_ptr); + } + let files_ptr: *const *const c_char = c_ptrs.as_ptr(); + unsafe { + into_optional(ffi::dsc_DyldSharedCache::from_files(files_ptr as *const c_char, c_ptrs.len())) + } +} diff --git a/api/rust/cargo/lief/src/dsc/caching.rs b/api/rust/cargo/lief/src/dsc/caching.rs new file mode 100644 index 0000000000..c55bdf11c2 --- /dev/null +++ b/api/rust/cargo/lief/src/dsc/caching.rs @@ -0,0 +1,32 @@ +use lief_ffi as ffi; + +/// Enable globally cache/memoization. One can also leverage this function +/// by setting the environment variable `DYLDSC_ENABLE_CACHE` to `1` +/// +/// By default, LIEF will use the directory specified by the environment +/// variable `DYLDSC_CACHE_DIR` as its cache-root directory: +/// +/// ```bash +/// DYLDSC_ENABLE_CACHE=1 DYLDSC_CACHE_DIR=/tmp/my_dir ./my-program +/// ``` +/// +/// Otherwise, if `DYLDSC_CACHE_DIR` is not set, LIEF will use the following +/// directory (in this priority): +/// +/// 1. System or user cache directory +/// - macOS: `DARWIN_USER_TEMP_DIR` / `DARWIN_USER_CACHE_DIR` + `/dyld_shared_cache` +/// - Linux: `${XDG_CACHE_HOME}/dyld_shared_cache` +/// - Windows: `%LOCALAPPDATA%\dyld_shared_cache` +/// 2. Home directory +/// - macOS/Linux: `$HOME/.dyld_shared_cache` +/// - Windows: `%USERPROFILE%\.dyld_shared_cache` +/// +/// See [`crate::dsc::DyldSharedCache::enable_caching`] for a finer granularity +pub fn enable_cache() -> bool { + ffi::dsc_enable_cache() +} + +/// Same behavior as [`enable_cache`] but with a user-provided cache directory +pub fn enable_cache_from_dir(target_dir: &str) -> bool { + ffi::dsc_enable_cache_from_dir(target_dir) +} diff --git a/api/rust/cargo/lief/src/dsc/dyld_shared_cache.rs b/api/rust/cargo/lief/src/dsc/dyld_shared_cache.rs new file mode 100644 index 0000000000..83b709aabe --- /dev/null +++ b/api/rust/cargo/lief/src/dsc/dyld_shared_cache.rs @@ -0,0 +1,331 @@ +use lief_ffi as ffi; + +use crate::common::{FromFFI, into_optional}; +use crate::declare_iterator; +use super::{SubCache, Dylib, MappingInfo}; + +#[allow(non_camel_case_types)] +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +/// This enum wraps the dyld's git tags for which the structure of +/// dyld shared cache evolved +pub enum Version { + /// dyld-95.3 (2007-10-30) + DYLD_95_3, + /// dyld-195.5 (2011-07-13) + DYLD_195_5, + /// dyld-239.3 (2013-10-29) + DYLD_239_3, + /// dyld-360.14 (2015-09-04) + DYLD_360_14, + /// dyld-421.1 (2016-09-22) + DYLD_421_1, + /// dyld-832.7.1 (2020-11-19) + DYLD_832_7_1, + /// dyld-940 (2021-02-09) + DYLD_940, + /// dyld-1042.1 (2022-10-19) + DYLD_1042_1, + /// This value is used for versions of dyld not publicly released or not yet + /// supported by LIEF + UNRELEASED, + UNKNOWN(u32), +} + +impl From for Version { + fn from(value: u32) -> Self { + match value { + 0x00000001 => Version::DYLD_95_3, + 0x00000002 => Version::DYLD_195_5, + 0x00000003 => Version::DYLD_239_3, + 0x00000004 => Version::DYLD_360_14, + 0x00000005 => Version::DYLD_421_1, + 0x00000006 => Version::DYLD_832_7_1, + 0x00000007 => Version::DYLD_940, + 0x00000008 => Version::DYLD_1042_1, + 0x00000009 => Version::UNRELEASED, + _ => Version::UNKNOWN(value), + + } + } +} +impl From for u32 { + fn from(value: Version) -> u32 { + match value { + Version::DYLD_95_3 => 0x00000001, + Version::DYLD_195_5 => 0x00000002, + Version::DYLD_239_3 => 0x00000003, + Version::DYLD_360_14 => 0x00000004, + Version::DYLD_421_1 => 0x00000005, + Version::DYLD_832_7_1 => 0x00000006, + Version::DYLD_940 => 0x00000007, + Version::DYLD_1042_1 => 0x00000008, + Version::UNRELEASED => 0x00000009, + Version::UNKNOWN(_) => 0, + + } + } +} + + +#[allow(non_camel_case_types)] +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +/// Platforms supported by the dyld shared cache +pub enum Platform { + MACOS, + IOS, + TVOS, + WATCHOS, + BRIDGEOS, + IOSMAC, + IOS_SIMULATOR, + TVOS_SIMULATOR, + WATCHOS_SIMULATOR, + DRIVERKIT, + VISIONOS, + VISIONOS_SIMULATOR, + FIRMWARE, + SEPOS, + ANY, + UNKNOWN(u32), +} + +impl From for Platform { + fn from(value: u32) -> Self { + match value { + 0x00000001 => Platform::MACOS, + 0x00000002 => Platform::IOS, + 0x00000003 => Platform::TVOS, + 0x00000004 => Platform::WATCHOS, + 0x00000005 => Platform::BRIDGEOS, + 0x00000006 => Platform::IOSMAC, + 0x00000007 => Platform::IOS_SIMULATOR, + 0x00000008 => Platform::TVOS_SIMULATOR, + 0x00000009 => Platform::WATCHOS_SIMULATOR, + 0x0000000a => Platform::DRIVERKIT, + 0x0000000b => Platform::VISIONOS, + 0x0000000c => Platform::VISIONOS_SIMULATOR, + 0x0000000d => Platform::FIRMWARE, + 0x0000000e => Platform::SEPOS, + 0xffffffff => Platform::ANY, + _ => Platform::UNKNOWN(value), + + } + } +} +impl From for u32 { + fn from(value: Platform) -> u32 { + match value { + Platform::MACOS => 0x00000001, + Platform::IOS => 0x00000002, + Platform::TVOS => 0x00000003, + Platform::WATCHOS => 0x00000004, + Platform::BRIDGEOS => 0x00000005, + Platform::IOSMAC => 0x00000006, + Platform::IOS_SIMULATOR => 0x00000007, + Platform::TVOS_SIMULATOR => 0x00000008, + Platform::WATCHOS_SIMULATOR => 0x00000009, + Platform::DRIVERKIT => 0x0000000a, + Platform::VISIONOS => 0x0000000b, + Platform::VISIONOS_SIMULATOR => 0x0000000c, + Platform::FIRMWARE => 0x0000000d, + Platform::SEPOS => 0x0000000e, + Platform::ANY => 0xffffffff, + Platform::UNKNOWN(_) => 0, + + } + } +} + +#[allow(non_camel_case_types)] +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +/// Architecture supported by the dyld shared cache +pub enum Arch { + I386, + X86_64, + X86_64H, + ARMV5, + ARMV6, + ARMV7, + ARM64, + ARM64E, + UNKNOWN(u32), +} + +impl From for Arch { + fn from(value: u32) -> Self { + match value { + 0x00000001 => Arch::I386, + 0x00000002 => Arch::X86_64, + 0x00000003 => Arch::X86_64H, + 0x00000004 => Arch::ARMV5, + 0x00000005 => Arch::ARMV6, + 0x00000006 => Arch::ARMV7, + 0x00000007 => Arch::ARM64, + 0x00000008 => Arch::ARM64E, + _ => Arch::UNKNOWN(value), + + } + } +} +impl From for u32 { + fn from(value: Arch) -> u32 { + match value { + Arch::I386 => 0x00000001, + Arch::X86_64 => 0x00000002, + Arch::X86_64H => 0x00000003, + Arch::ARMV5 => 0x00000004, + Arch::ARMV6 => 0x00000005, + Arch::ARMV7 => 0x00000006, + Arch::ARM64 => 0x00000007, + Arch::ARM64E => 0x00000008, + Arch::UNKNOWN(_) => 0, + + } + } +} + +/// This struct interfaces a dyld shared cache file. +pub struct DyldSharedCache { + ptr: cxx::UniquePtr, +} + +impl FromFFI for DyldSharedCache { + fn from_ffi(info: cxx::UniquePtr) -> Self { + Self { + ptr: info, + } + } +} + +impl DyldSharedCache { + /// Filename of the dyld shared file associated with this object. + /// + /// For instance: `dyld_shared_cache_arm64e, dyld_shared_cache_arm64e.62.dyldlinkedit` + pub fn filename(&self) -> String { + self.ptr.filename().to_string() + } + + /// Full path to the original dyld shared cache file associated with object + /// (e.g. `/home/lief/downloads/visionos/dyld_shared_cache_arm64e.42`) + pub fn filepath(&self) -> String { + self.ptr.filepath().to_string() + } + + /// Based address of this cache + pub fn load_address(&self) -> u64 { + self.ptr.load_address() + } + + /// Version of dyld used by this cache + pub fn version(&self) -> Version { + Version::from(self.ptr.version()) + } + + /// Name of the architecture targeted by this cache (`x86_64h`) + pub fn arch_name(&self) -> String { + self.ptr.arch_name().to_string() + } + + /// Platform targeted by this cache (e.g. vision-os) + pub fn platform(&self) -> Platform { + Platform::from(self.ptr.platform()) + } + + /// Architecture targeted by this cache + pub fn arch(&self) -> Arch { + Arch::from(self.ptr.arch()) + } + + /// Find the [`Dylib`] that encompasses the given virtual address. + pub fn find_lib_from_va(&self, va: u64) -> Option { + into_optional(self.ptr.find_lib_from_va(va)) + } + + /// Find the [`Dylib`] whose [`Dylib::path`] matches the provided path. + pub fn find_lib_from_path(&self, path: &str) -> Option { + into_optional(self.ptr.find_lib_from_path(path)) + } + + /// Find the [`Dylib`] whose filename of [`Dylib::path`] matches the provided name. + /// + /// If multiple libraries have the same name (but with a different path), + /// the **first one** matching the provided name is returned. + pub fn find_lib_from_name(&self, name: &str) -> Option { + into_optional(self.ptr.find_lib_from_name(name)) + } + + /// True if the subcaches are associated with this cache + pub fn has_subcaches(&self) -> bool { + self.ptr.has_subcaches() + } + + /// Return an iterator over the different [`Dylib`] libraries embedded + /// in this dyld shared cache + pub fn libraries(&self) -> Dylibs { + Dylibs::new(self.ptr.libraries()) + } + + /// Return an iterator over the different [`MappingInfo`] associated + /// with this dyld shared cache + pub fn mapping_info(&self) -> MappingInfoIt { + MappingInfoIt::new(self.ptr.mapping_info()) + } + + /// Return an interator over the subcaches associated with this (main) dyld shared + /// cache. + pub fn subcaches(&self) -> SubCacheIt { + SubCacheIt::new(self.ptr.subcaches()) + } + + /// When enabled, this function allows to record and to keep in *cache*, + /// dyld shared cache information that are costly to access. + /// + /// For instance, GOT symbols, rebases information, stub symbols, ... + /// + /// It is **highly** recommended to enable this function when processing + /// a dyld shared cache several times or when extracting a large number of + /// [`Dylib`] with enhanced extraction options (e.g. [`crate::dsc::dylib::ExtractOpt::fix_memory`]) + /// + /// One can enable caching by calling this function: + /// + /// ```rust + /// let dyld_cache = lief::dsc::load_from_path("macos-15.0.1/", ""); + /// dyld_cache.enable_caching("/home/user/.cache/lief-dsc"); + /// ``` + /// + /// One can also enable this cache optimization **globally** using the + /// function: [`crate::dsc::enable_cache`] or by setting the environment variable + /// `DYLDSC_ENABLE_CACHE` to 1. + pub fn enable_caching(&self, target_cache_dir: &str) { + self.ptr.enable_caching(target_cache_dir) + } + + /// Flush internal information into the on-disk cache (see: enable_caching) + pub fn flush_cache(&self) { + self.ptr.flush_cache() + } +} + +declare_iterator!( + Dylibs, + Dylib<'a>, + ffi::dsc_Dylib, + ffi::dsc_DyldSharedCache, + ffi::dsc_DyldSharedCache_it_libraries +); + +declare_iterator!( + MappingInfoIt, + MappingInfo<'a>, + ffi::dsc_MappingInfo, + ffi::dsc_DyldSharedCache, + ffi::dsc_DyldSharedCache_it_mapping_info +); + +declare_iterator!( + SubCacheIt, + SubCache<'a>, + ffi::dsc_SubCache, + ffi::dsc_DyldSharedCache, + ffi::dsc_DyldSharedCache_it_subcaches +); diff --git a/api/rust/cargo/lief/src/dsc/dylib.rs b/api/rust/cargo/lief/src/dsc/dylib.rs new file mode 100644 index 0000000000..3aa39928df --- /dev/null +++ b/api/rust/cargo/lief/src/dsc/dylib.rs @@ -0,0 +1,165 @@ +use lief_ffi as ffi; + +use crate::common::{FromFFI, into_optional}; +use std::marker::PhantomData; +use crate::macho::Binary; + +/// This structure represents a library embedded in a dyld shared cache. +/// It mirrors the original `dyld_cache_image_info` structure. +pub struct Dylib<'a> { + ptr: cxx::UniquePtr, + _owner: PhantomData<&'a ()>, +} + +impl FromFFI for Dylib<'_> { + fn from_ffi(ptr: cxx::UniquePtr) -> Self { + Self { + ptr, + _owner: PhantomData, + } + } +} + +impl Dylib<'_> { + /// Original path of the library (e.g. `/usr/lib/libcryptex.dylib`) + pub fn path(&self) -> String { + self.ptr.path().to_string() + } + + /// In-memory address of the library + pub fn address(&self) -> u64 { + self.ptr.address() + } + + /// Modification time of the library matching `stat.st_mtime`, or 0 + pub fn modtime(&self) -> u64 { + self.ptr.modtime() + } + + /// File serial number matching `stat.st_ino` or 0 + /// + /// Note that for shared cache targeting iOS, this value can hold a hash of + /// the path (if modtime is set to 0) + pub fn inode(&self) -> u64 { + self.ptr.inode() + } + + /// Padding alignment value (should be 0) + pub fn padding(&self) -> u64 { + self.ptr.padding() + } + + /// Get a [`Binary`] representation for this Dylib. + /// + /// One can use this function to write back the Mach-O binary on the disk: + /// + /// ```cpp + /// dylib.get().expect("Can't extract").write("liblockdown.dylib"); + /// ``` + pub fn get(&self) -> Option { + self.get_with_opt(&ExtractOpt::default()) + } + /// Get a [`Binary`] representation for this Dylib with the provided [`ExtractOpt`] options. + pub fn get_with_opt(&self, opt: &ExtractOpt) -> Option { + into_optional(self.ptr.get_macho(opt.to_ffi())) + } +} + +/// This structure is used to tweak the extraction process while calling +/// [`Dylib::get_with_opt`]. These options allow to deoptimize the dylib and get an +/// accurate representation of the origin Mach-O binary. +pub struct ExtractOpt { + /// Whether the segment's offsets should be packed to avoid + /// an in-memory size while writing back the binary. + /// + ///
This option does not have an impact on the performances
+ pub pack: bool, + + /// Fix call instructions that target addresses outside the current dylib + /// virtual space. + /// + ///
+ /// Enabling this option can have a significant impact on the performances. + /// Make sure to enable the internal cache mechanism. + ///
+ /// + /// [`crate::dsc::enable_cache`] or [`crate::dsc::DyldSharedCache::enable_caching`] + pub fix_branches: bool, + + /// Fix memory accesses performed outside the dylib's virtual space + /// + ///
+ /// Enabling this option can have a significant impact on the performances. + /// Make sure to enable the internal cache mechanism. + ///
+ /// + /// [`crate::dsc::enable_cache`] or [`crate::dsc::DyldSharedCache::enable_caching`] + pub fix_memory: bool, + + /// Recover and fix relocations + /// + ///
+ /// Enabling this option can have a significant impact on the performances. + /// Make sure to enable the internal cache mechanism. + ///
+ /// + /// [`crate::dsc::enable_cache`] or [`crate::dsc::DyldSharedCache::enable_caching`] + pub fix_relocations: bool, + + /// Fix Objective-C information + /// + ///
+ /// Enabling this option can have a significant impact on the performances. + /// Make sure to enable the internal cache mechanism. + ///
+ /// + /// [`crate::dsc::enable_cache`] or [`crate::dsc::DyldSharedCache::enable_caching`] + pub fix_objc: bool, + + /// Whether the `LC_DYLD_CHAINED_FIXUPS` command should be (re)created. + /// + /// If this value is not set, LIEF will add the command only if it's + /// meaningful regarding the other options + pub create_dyld_chained_fixup_cmd: Option, +} + +impl Default for ExtractOpt { + fn default() -> ExtractOpt { + ExtractOpt { + pack: true, + fix_branches: false, + fix_memory: true, + fix_relocations: true, + fix_objc: true, + create_dyld_chained_fixup_cmd: None, + } + } +} + +impl ExtractOpt { + #[doc(hidden)] + fn to_ffi(&self) -> ffi::dsc_Dylib_extract_opt { + ffi::dsc_Dylib_extract_opt { + pack: self.pack, + fix_branches: self.fix_branches, + fix_memory: self.fix_memory, + fix_relocations: self.fix_relocations, + fix_objc: self.fix_objc, + create_dyld_chained_fixup_cmd: self.create_dyld_chained_fixup_cmd.unwrap_or(false), + create_dyld_chained_fixup_cmd_set: self.create_dyld_chained_fixup_cmd.is_some() + } + } +} + +impl std::fmt::Debug for Dylib<'_> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("Dylib") + .field("path", &self.path()) + .field("address", &self.address()) + .field("modtime", &self.modtime()) + .field("inode", &self.inode()) + .field("padding", &self.padding()) + .finish() + + } +} diff --git a/api/rust/cargo/lief/src/dsc/mapping_info.rs b/api/rust/cargo/lief/src/dsc/mapping_info.rs new file mode 100644 index 0000000000..0db7ad100e --- /dev/null +++ b/api/rust/cargo/lief/src/dsc/mapping_info.rs @@ -0,0 +1,68 @@ +use lief_ffi as ffi; + +use crate::common::FromFFI; +use std::marker::PhantomData; + +/// This structure represents a `dyld_cache_mapping_info` entry. +/// +/// It provides information about the relationshiop between on-disk shared cache +/// and in-memory shared cache. +pub struct MappingInfo<'a> { + ptr: cxx::UniquePtr, + _owner: PhantomData<&'a ()>, +} + +impl FromFFI for MappingInfo<'_> { + fn from_ffi(ptr: cxx::UniquePtr) -> Self { + Self { + ptr, + _owner: PhantomData, + } + } +} + +impl MappingInfo<'_> { + /// The in-memory address where this dyld shared cache region is mapped + pub fn address(&self) -> u64 { + self.ptr.address() + } + + /// Size of the region being mapped + pub fn size(&self) -> u64 { + self.ptr.size() + } + + /// End virtual address of the region + pub fn end_address(&self) -> u64 { + self.ptr.end_address() + } + + /// On-disk file offset + pub fn file_offset(&self) -> u64 { + self.ptr.file_offset() + } + + /// Max memory protection + pub fn max_prot(&self) -> u32 { + self.ptr.max_prot() + } + + /// Initial memory protection + pub fn init_prot(&self) -> u32 { + self.ptr.init_prot() + } +} + +impl std::fmt::Debug for MappingInfo<'_> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("MappingInfo") + .field("address", &self.address()) + .field("end_address", &self.end_address()) + .field("size", &self.size()) + .field("file_offset", &self.file_offset()) + .field("max_prot", &self.max_prot()) + .field("init_prot", &self.init_prot()) + .finish() + + } +} diff --git a/api/rust/cargo/lief/src/dsc/subcache.rs b/api/rust/cargo/lief/src/dsc/subcache.rs new file mode 100644 index 0000000000..b79e0dcbb7 --- /dev/null +++ b/api/rust/cargo/lief/src/dsc/subcache.rs @@ -0,0 +1,63 @@ +use lief_ffi as ffi; + +use crate::common::{FromFFI, into_optional}; +use std::marker::PhantomData; +use crate::dsc::UUID; +use super::DyldSharedCache; + +/// This class represents a subcache in the case of large/split dyld shared +/// cache. +/// +/// It mirror (and abstracts) the original `dyld_subcache_entry` / `dyld_subcache_entry_v1` +pub struct SubCache<'a> { + ptr: cxx::UniquePtr, + _owner: PhantomData<&'a ()>, +} + +impl FromFFI for SubCache<'_> { + fn from_ffi(ptr: cxx::UniquePtr) -> Self { + Self { + ptr, + _owner: PhantomData, + } + } +} + +impl SubCache<'_> { + /// The uuid of the subcache file + pub fn uuid(&self) -> UUID { + let vec = Vec::from(self.ptr.uuid().as_slice()); + assert!(vec.len() == 16); + let mut uuid: UUID = [0; 16]; + for i in 0..16 { + uuid[i] = vec[i] as u8; + } + uuid + } + + /// The offset of this subcache from the main cache base address + pub fn vm_offset(&self) -> u64 { + self.ptr.vm_offset() + } + + /// The file name suffix of the subCache file (e.g. `.25.data`, `.03.development`) + pub fn suffix(&self) -> String { + self.ptr.suffix().to_string() + } + + /// The associated DyldSharedCache object for this subcache + pub fn cache(&self) -> Option { + into_optional(self.ptr.cache()) + } +} + +impl std::fmt::Debug for SubCache<'_> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("SubCache") + .field("uuid", &self.uuid()) + .field("vm_offset", &self.vm_offset()) + .field("suffix", &self.suffix()) + .finish() + + } +} diff --git a/api/rust/cargo/lief/src/dsc/uuid.rs b/api/rust/cargo/lief/src/dsc/uuid.rs new file mode 100644 index 0000000000..a62fa2f801 --- /dev/null +++ b/api/rust/cargo/lief/src/dsc/uuid.rs @@ -0,0 +1,2 @@ +/// UUID used in different places of the shared cache +pub type UUID = [u8; 16]; diff --git a/api/rust/cargo/lief/src/lib.rs b/api/rust/cargo/lief/src/lib.rs index 3710c1ef82..472432e2b9 100644 --- a/api/rust/cargo/lief/src/lib.rs +++ b/api/rust/cargo/lief/src/lib.rs @@ -57,7 +57,7 @@ //! ## Additional Information //! //! For more details about the install procedure and the configuration, please check: -//! https://lief.re/doc/latest/api/rust/index.html +//! #![doc(html_no_source)] @@ -80,6 +80,9 @@ pub mod pdb; pub mod dwarf; pub mod objc; + +pub mod dsc; + pub mod debug_info; mod range; diff --git a/api/rust/cargo/lief/src/objc/decl_opt.rs b/api/rust/cargo/lief/src/objc/decl_opt.rs index 78231ce8a0..2da8fab51c 100644 --- a/api/rust/cargo/lief/src/objc/decl_opt.rs +++ b/api/rust/cargo/lief/src/objc/decl_opt.rs @@ -16,6 +16,7 @@ impl Default for DeclOpt { } impl DeclOpt { + #[doc(hidden)] pub fn to_ffi(&self) -> ffi::ObjC_DeclOpt { ffi::ObjC_DeclOpt { show_annotations: self.show_annotations diff --git a/api/rust/cargo/lief/tests/dyld_shared_cache_test.rs b/api/rust/cargo/lief/tests/dyld_shared_cache_test.rs new file mode 100644 index 0000000000..eb0df18e2c --- /dev/null +++ b/api/rust/cargo/lief/tests/dyld_shared_cache_test.rs @@ -0,0 +1,83 @@ +mod utils; +use std::env; +use std::path::PathBuf; +use std::str::FromStr; + +fn has_dyld_shared_cache_samples() -> bool { + let dsc_dir = utils::get_sample_path().join("dyld_shared_cache"); + if dsc_dir.is_dir() { + return true; + } + if let Some(dir) = env::var_os("LIEF_DSC_SAMPLES_DIR") { + return PathBuf::from_str(&dir.into_string().unwrap_or("".to_string())).unwrap().is_dir(); + } + return false; +} + +fn get_dsc_sample(suffix: &str) -> PathBuf { + let dir = utils::get_sample_path().join("dyld_shared_cache"); + if dir.is_dir() { + return dir.join(suffix); + } + let env_dir = env::var_os("LIEF_DSC_SAMPLES_DIR") + .unwrap() + .into_string() + .unwrap(); + PathBuf::from_str(&env_dir).unwrap().join(suffix) +} + +fn explore_dylib(cache: &lief::dsc::DyldSharedCache, dylib: &lief::dsc::Dylib) { + println!("{:?}", dylib); +} + +fn explore_mapping_info(cache: &lief::dsc::DyldSharedCache, info: &lief::dsc::MappingInfo) { + println!("{:?}", info); +} + +fn explore_subcache(cache: &lief::dsc::DyldSharedCache, sc: &lief::dsc::SubCache) { + println!("{:?}", sc); +} + +fn run_ios_181(cache: &lief::dsc::DyldSharedCache) { + println!("{}", cache.filename()); + println!("{}", cache.filepath()); + println!("{}", cache.load_address()); + println!("{:?}", cache.version()); + println!("{}", cache.arch_name()); + println!("{:?}", cache.platform()); + println!("{:?}", cache.arch()); + + assert!(cache.find_lib_from_va(0x20d0a4010).is_some()); + assert!(cache.find_lib_from_va(0).is_none()); + + assert!(cache.find_lib_from_path("/usr/lib/libobjc.A.dylib").is_some()); + assert!(cache.find_lib_from_path("/usr/lib/libobjc.X.dylib").is_none()); + + assert!(cache.find_lib_from_name("liblockdown.dylib").is_some()); + assert!(cache.find_lib_from_path("liblockdown.A.dylib").is_none()); + + assert!(cache.has_subcaches()); + + for dylib in cache.libraries() { + explore_dylib(cache, &dylib); + } + + for sc in cache.subcaches() { + explore_subcache(cache, &sc); + for info in sc.cache().expect("Missing cache").mapping_info() { + explore_mapping_info(cache, &info); + } + } + +} + +#[test] +fn test_api() { + if !lief::is_extended() || !has_dyld_shared_cache_samples() { + return; + } + let cache = lief::dsc::load_from_path(get_dsc_sample("ios-18.1/").to_str().unwrap(), /*arch*/""); + assert!(cache.is_some()); + run_ios_181(&cache.unwrap()); +} + diff --git a/api/rust/cargo/lief/tests/utils.rs b/api/rust/cargo/lief/tests/utils.rs index 60e29131a6..6fe9ecb77f 100644 --- a/api/rust/cargo/lief/tests/utils.rs +++ b/api/rust/cargo/lief/tests/utils.rs @@ -11,6 +11,11 @@ pub fn get_sample_dir() -> Option { } } +pub fn get_sample_path() -> PathBuf { + let sample_dir = get_sample_dir().unwrap(); + PathBuf::from_str(sample_dir.as_str()).expect("Can't create a PathBuf") +} + pub fn get_sample(path: &Path) -> Option { let sample_dir = get_sample_dir()?; let sample_dir_path = PathBuf::from_str(sample_dir.as_str()).ok()?; diff --git a/api/rust/examples/src/bin/dyld_shared_cache_reader.rs b/api/rust/examples/src/bin/dyld_shared_cache_reader.rs new file mode 100644 index 0000000000..03978e3120 --- /dev/null +++ b/api/rust/examples/src/bin/dyld_shared_cache_reader.rs @@ -0,0 +1,34 @@ +/// This example shows how to inspect a dyld shared cache with LIEF using the Rust API +use std::process::{self, ExitCode}; + +fn main() -> ExitCode { + if !lief::is_extended() { + println!("This example requires the extended version of LIEF") + } + let mut args = std::env::args(); + if args.len() != 2 { + println!("Usage: {} ", args.next().unwrap()); + return ExitCode::FAILURE; + } + + let path = std::env::args().last().unwrap(); + + let cache = lief::dsc::load_from_path(&path.as_str(), "").unwrap_or_else(|| { + process::exit(1); + }); + + for dylib in cache.libraries() { + println!("0x{:016x} {}", dylib.address(), dylib.path()); + } + + for minfo in cache.mapping_info() { + println!( + "[0x{:016x}, 0x{:016x}]: 0x{:016x}", + minfo.address(), + minfo.end_address(), + minfo.file_offset() + ) + } + + ExitCode::SUCCESS +} diff --git a/api/rust/include/LIEF/rust/DyldSharedCache.hpp b/api/rust/include/LIEF/rust/DyldSharedCache.hpp new file mode 100644 index 0000000000..5a46778902 --- /dev/null +++ b/api/rust/include/LIEF/rust/DyldSharedCache.hpp @@ -0,0 +1,22 @@ +/* Copyright 2022 - 2024 R. Thomas + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#pragma once +#include "LIEF/rust/DyldSharedCache/DyldSharedCache.hpp" +#include "LIEF/rust/DyldSharedCache/Dylib.hpp" +#include "LIEF/rust/DyldSharedCache/SubCache.hpp" +#include "LIEF/rust/DyldSharedCache/MappingInfo.hpp" +#include "LIEF/rust/DyldSharedCache/caching.hpp" + + diff --git a/api/rust/include/LIEF/rust/DyldSharedCache/DyldSharedCache.hpp b/api/rust/include/LIEF/rust/DyldSharedCache/DyldSharedCache.hpp new file mode 100644 index 0000000000..8772fb2134 --- /dev/null +++ b/api/rust/include/LIEF/rust/DyldSharedCache/DyldSharedCache.hpp @@ -0,0 +1,103 @@ +/* Copyright 2022 - 2024 R. Thomas + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#pragma once +#include "LIEF/DyldSharedCache/DyldSharedCache.hpp" +#include "LIEF/rust/DyldSharedCache/Dylib.hpp" +#include "LIEF/rust/DyldSharedCache/MappingInfo.hpp" +#include "LIEF/rust/DyldSharedCache/SubCache.hpp" + +#include "LIEF/rust/Mirror.hpp" +#include "LIEF/rust/helpers.hpp" +#include "LIEF/rust/Iterator.hpp" + +class dsc_DyldSharedCache : private Mirror { + public: + using lief_t = LIEF::dsc::DyldSharedCache; + using Mirror::Mirror; + + class it_libraries : + public RandomRangeIterator + { + public: + it_libraries(const dsc_DyldSharedCache::lief_t& src) + : RandomRangeIterator(src.libraries()) { } + auto next() { return RandomRangeIterator::next(); } + auto size() const { return RandomRangeIterator::size(); } + }; + + class it_mapping_info : + public RandomRangeIterator + { + public: + it_mapping_info(const dsc_DyldSharedCache::lief_t& src) + : RandomRangeIterator(src.mapping_info()) { } + auto next() { return RandomRangeIterator::next(); } + auto size() const { return RandomRangeIterator::size(); } + }; + + class it_subcaches : + public RandomRangeIterator + { + public: + it_subcaches(const dsc_DyldSharedCache::lief_t& src) + : RandomRangeIterator(src.subcaches()) { } + auto next() { return RandomRangeIterator::next(); } + auto size() const { return RandomRangeIterator::size(); } + }; + + static auto from_path(std::string file, std::string arch) { // NOLINT(performance-unnecessary-value-param) + return std::make_unique(LIEF::dsc::DyldSharedCache::from_path(file, arch)); + } + + static auto from_files(const char* ptr, size_t size) { // NOLINT(performance-unnecessary-value-param) + const auto* files = (const char**)ptr; + std::vector files_vec; + files_vec.reserve(size); + for (size_t i = 0; i < size; ++i) { + files_vec.push_back(files[i]); + } + return std::make_unique(LIEF::dsc::DyldSharedCache::from_files(files_vec)); + } + + auto libraries() const { return std::make_unique(get()); } + auto mapping_info() const { return std::make_unique(get()); } + auto subcaches() const { return std::make_unique(get()); } + + auto filename() const { return get().filename(); } + auto version() const { return to_int(get().version()); } + auto filepath() const { return get().filepath(); } + auto load_address() const { return get().load_address(); } + + auto arch_name() const { return get().arch_name(); } + auto platform() const { return to_int(get().platform()); } + auto arch() const { return to_int(get().arch()); } + auto has_subcaches() const { return get().has_subcaches(); } + + auto find_lib_from_va(uint64_t va) const { + return details::try_unique(get().find_lib_from_va(va)); + } + + auto find_lib_from_path(std::string path) const { + return details::try_unique(get().find_lib_from_path(path)); + } + + auto find_lib_from_name(std::string name) const { + return details::try_unique(get().find_lib_from_name(name)); + } + + auto enable_caching(std::string dir) const { get().enable_caching(dir); } + auto flush_cache() const { get().flush_cache(); } + +}; diff --git a/api/rust/include/LIEF/rust/DyldSharedCache/Dylib.hpp b/api/rust/include/LIEF/rust/DyldSharedCache/Dylib.hpp new file mode 100644 index 0000000000..8980aa77bf --- /dev/null +++ b/api/rust/include/LIEF/rust/DyldSharedCache/Dylib.hpp @@ -0,0 +1,60 @@ +/* Copyright 2022 - 2024 R. Thomas + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#pragma once +#include "LIEF/DyldSharedCache/Dylib.hpp" +#include "LIEF/rust/Mirror.hpp" +#include "LIEF/rust/MachO/Binary.hpp" + +class dsc_Dylib_extract_opt { + public: + bool pack; + bool fix_branches; + bool fix_memory; + bool fix_relocations; + bool fix_objc; + + bool create_dyld_chained_fixup_cmd; + bool create_dyld_chained_fixup_cmd_set; +}; + +inline LIEF::dsc::Dylib::extract_opt_t from_rust(const dsc_Dylib_extract_opt& opt) { + LIEF::dsc::Dylib::extract_opt_t out; + + out.pack = opt.pack; + out.fix_branches = opt.fix_branches; + out.fix_memory = opt.fix_memory; + out.fix_relocations = opt.fix_relocations; + out.fix_objc = opt.fix_objc; + if (opt.create_dyld_chained_fixup_cmd_set) { + out.create_dyld_chained_fixup_cmd = opt.create_dyld_chained_fixup_cmd; + } + return out; +} + +class dsc_Dylib : private Mirror { + public: + using lief_t = LIEF::dsc::Dylib; + using Mirror::Mirror; + + auto path() const { return get().path(); } + auto address() const { return get().address(); } + auto modtime() const { return get().modtime(); } + auto inode() const { return get().inode(); } + auto padding() const { return get().padding(); } + + auto get_macho(dsc_Dylib_extract_opt opt) const { + return details::try_unique(get().get(from_rust(opt))); + } +}; diff --git a/api/rust/include/LIEF/rust/DyldSharedCache/MappingInfo.hpp b/api/rust/include/LIEF/rust/DyldSharedCache/MappingInfo.hpp new file mode 100644 index 0000000000..eb1d1fa28b --- /dev/null +++ b/api/rust/include/LIEF/rust/DyldSharedCache/MappingInfo.hpp @@ -0,0 +1,30 @@ +/* Copyright 2022 - 2024 R. Thomas + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#pragma once +#include "LIEF/DyldSharedCache/MappingInfo.hpp" +#include "LIEF/rust/Mirror.hpp" + +class dsc_MappingInfo : private Mirror { + public: + using lief_t = LIEF::dsc::MappingInfo; + using Mirror::Mirror; + + auto address() const { return get().address(); } + auto size() const { return get().size(); } + auto end_address() const { return get().end_address(); } + auto file_offset() const { return get().file_offset(); } + auto max_prot() const { return get().max_prot(); } + auto init_prot() const { return get().init_prot(); } +}; diff --git a/api/rust/include/LIEF/rust/DyldSharedCache/SubCache.hpp b/api/rust/include/LIEF/rust/DyldSharedCache/SubCache.hpp new file mode 100644 index 0000000000..eb97e6d075 --- /dev/null +++ b/api/rust/include/LIEF/rust/DyldSharedCache/SubCache.hpp @@ -0,0 +1,30 @@ +/* Copyright 2022 - 2024 R. Thomas + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#pragma once +#include "LIEF/DyldSharedCache/SubCache.hpp" +#include "LIEF/rust/Mirror.hpp" + +class dsc_DyldSharedCache; + +class dsc_SubCache : private Mirror { + public: + using lief_t = LIEF::dsc::SubCache; + using Mirror::Mirror; + + auto vm_offset() const { return get().vm_offset(); } + auto suffix() const { return get().suffix(); } + auto uuid() const { return details::make_vector(get().uuid()); } + LIEF_API std::unique_ptr cache() const; +}; diff --git a/api/rust/include/LIEF/rust/DyldSharedCache/caching.hpp b/api/rust/include/LIEF/rust/DyldSharedCache/caching.hpp new file mode 100644 index 0000000000..893285034a --- /dev/null +++ b/api/rust/include/LIEF/rust/DyldSharedCache/caching.hpp @@ -0,0 +1,10 @@ +#pragma once +#include "LIEF/DyldSharedCache/caching.hpp" + +inline bool dsc_enable_cache() { + return LIEF::dsc::enable_cache(); +} + +inline bool dsc_enable_cache_from_dir(std::string dir) { + return LIEF::dsc::enable_cache(dir); +} diff --git a/api/rust/include/LIEF/rust/LIEF.hpp b/api/rust/include/LIEF/rust/LIEF.hpp index 04e35a34c2..0a03ed12fc 100644 --- a/api/rust/include/LIEF/rust/LIEF.hpp +++ b/api/rust/include/LIEF/rust/LIEF.hpp @@ -22,6 +22,7 @@ #include "LIEF/rust/PDB.hpp" #include "LIEF/rust/DWARF.hpp" #include "LIEF/rust/ObjC.hpp" +#include "LIEF/rust/DyldSharedCache.hpp" #include "LIEF/rust/Span.hpp" #include "LIEF/rust/range.hpp" diff --git a/api/rust/include/LIEF/rust/MachO/Binary.hpp b/api/rust/include/LIEF/rust/MachO/Binary.hpp index dc082b0635..24c39e5435 100644 --- a/api/rust/include/LIEF/rust/MachO/Binary.hpp +++ b/api/rust/include/LIEF/rust/MachO/Binary.hpp @@ -148,6 +148,7 @@ class MachO_Binary : public AbstractBinary { }; MachO_Binary(const lief_t& bin) : AbstractBinary(bin) {} + MachO_Binary(std::unique_ptr ptr) : AbstractBinary(std::move(ptr)) {} auto header() const { return std::make_unique(impl().header()); diff --git a/api/rust/include/LIEF/rust/Mirror.hpp b/api/rust/include/LIEF/rust/Mirror.hpp index 17e8a3846e..bc2401dfd3 100644 --- a/api/rust/include/LIEF/rust/Mirror.hpp +++ b/api/rust/include/LIEF/rust/Mirror.hpp @@ -65,6 +65,11 @@ inline std::unique_ptr try_unique(std::unique_ptr value) { return value ? std::make_unique(std::move(value)) : nullptr; } +template +inline std::unique_ptr try_unique(std::unique_ptr value) { + return try_unique(std::unique_ptr(const_cast(value.release()))); +} + template inline std::unique_ptr from_result(const LIEF::result value) { return value ? std::make_unique(std::move(*value)) : nullptr; diff --git a/api/rust/src/CMakeLists.txt b/api/rust/src/CMakeLists.txt index a68a45bc7e..41c3299d1f 100644 --- a/api/rust/src/CMakeLists.txt +++ b/api/rust/src/CMakeLists.txt @@ -1,4 +1,5 @@ target_sources(LIB_LIEF PRIVATE symbol.cpp stream.cpp + dsc_SubCache.cpp ) diff --git a/api/rust/src/dsc_SubCache.cpp b/api/rust/src/dsc_SubCache.cpp new file mode 100644 index 0000000000..b69e97cbce --- /dev/null +++ b/api/rust/src/dsc_SubCache.cpp @@ -0,0 +1,7 @@ +#include "LIEF/rust/DyldSharedCache/SubCache.hpp" +#include "LIEF/MachO/Binary.hpp" +#include "LIEF/rust/DyldSharedCache/DyldSharedCache.hpp" + +std::unique_ptr dsc_SubCache::cache() const { + return details::try_unique(get().cache()); // NOLINT(clang-analyzer-cplusplus.NewDeleteLeaks +} diff --git a/cmake/LIEFOptions.cmake b/cmake/LIEFOptions.cmake index 8766fc7ec0..1776287231 100644 --- a/cmake/LIEFOptions.cmake +++ b/cmake/LIEFOptions.cmake @@ -31,8 +31,9 @@ option(LIEF_DEX "Build LIEF with DEX module" ON) option(LIEF_ART "Build LIEF with ART module" ON) # Extended features -option(LIEF_DEBUG_INFO "Build LIEF with DWARF/PDB support" OFF) -option(LIEF_OBJC "Build LIEF with ObjC metadata support" OFF) +option(LIEF_DEBUG_INFO "Build LIEF with DWARF/PDB support" OFF) +option(LIEF_OBJC "Build LIEF with ObjC metadata support" OFF) +option(LIEF_DYLD_SHARED_CACHE "Build LIEF with Dyld shared cache support" OFF) cmake_dependent_option(LIEF_PYTHON_EDITABLE "Make an editable build " OFF "LIEF_PYTHON_API" OFF) @@ -210,6 +211,10 @@ if (LIEF_OBJC) set(LIEF_OBJC_SUPPORT 1) endif() -if (LIEF_DEBUG_INFO OR LIEF_OBJC) # or any other extended feature +if (LIEF_DYLD_SHARED_CACHE) + set(LIEF_DYLD_SHARED_CACHE_SUPPORT 1) +endif() + +if (LIEF_DEBUG_INFO OR LIEF_OBJC OR LIEF_DYLD_SHARED_CACHE) # or any other extended feature set(LIEF_EXTENDED 1) endif() diff --git a/doc/sphinx/_cross_api.rst b/doc/sphinx/_cross_api.rst index 67fd691289..9330702e45 100644 --- a/doc/sphinx/_cross_api.rst +++ b/doc/sphinx/_cross_api.rst @@ -343,3 +343,61 @@ :py:class:`lief.MachO.Builder.config_t` :cpp:class:`LIEF::MachO::Builder::config_t` + +.. dyld shared cache =========================================================== + +.. |lief-dsc-load| lief-api:: lief.dsc.load() + + :rust:func:`lief::dsc::load_from_path` + :rust:func:`lief::dsc::load_from_files` + :py:func:`lief.dsc.load` + :cpp:func:`LIEF::dsc::load` + +.. |lief-dsc-dyldsharedcache| lief-api:: lief.dsc.DyldSharedCache + + :rust:struct:`lief::dsc::DyldSharedCache` + :py:class:`lief.dsc.DyldSharedCache` + :cpp:class:`LIEF::dsc::DyldSharedCache` + +.. |lief-dsc-dyldsharedcache-libraries| lief-api:: lief.dsc.DyldSharedCache.libraries() + + :rust:method:`lief::dsc::DyldSharedCache::libraries [struct]` + :py:attr:`lief.dsc.DyldSharedCache.libraries` + :cpp:func:`LIEF::dsc::DyldSharedCache::libraries` + +.. |lief-dsc-dylib| lief-api:: lief.dsc.Dylib + + :rust:struct:`lief::dsc::Dylib` + :py:class:`lief.dsc.Dylib` + :cpp:class:`LIEF::dsc::Dylib` + +.. |lief-dsc-dylib-eopt| lief-api:: lief.dsc.Dylib.extract_opt_t + + :rust:struct:`lief::dsc::dylib::ExtractOpt` + :py:class:`lief.dsc.Dylib.extract_opt_t` + :cpp:struct:`LIEF::dsc::Dylib::extract_opt_t` + +.. |lief-dsc-dylib-eopt-fix_branches| lief-api:: lief.dsc.Dylib.extract_opt_t.fix_branches + + :rust:member:`lief::dsc::dylib::ExtractOpt::fix_branches [struct]` + :py:attr:`lief.dsc.Dylib.extract_opt_t.fix_branches` + :cpp:member:`LIEF::dsc::Dylib::extract_opt_t::fix_branches` + +.. |lief-dsc-enable_cache| lief-api:: lief.dsc.enable_cache() + + :rust:func:`lief::dsc::enable_cache` + :py:func:`lief.dsc.enable_cache` + :cpp:func:`LIEF::dsc::enable_cache` + +.. |lief-dsc-dylib-get| lief-api:: lief.dsc.Dylib.get() + + :rust:method:`lief::dsc::Dylib::get [struct]` + :py:meth:`lief.dsc.Dylib.get` + :cpp:func:`LIEF::dsc::Dylib::get` + +.. |lief-dsc-dyldsharedcache-enable_caching| lief-api:: lief.dsc.DyldSharedCache.enable_caching + + :rust:method:`lief::dsc::DyldSharedCache::enable_caching [struct]` + :py:meth:`lief.dsc.DyldSharedCache.enable_caching` + :cpp:func:`LIEF::dsc::DyldSharedCache::enable_caching` + diff --git a/doc/sphinx/api/cpp/index.rst b/doc/sphinx/api/cpp/index.rst index 2045d4db0a..28c1a1395d 100644 --- a/doc/sphinx/api/cpp/index.rst +++ b/doc/sphinx/api/cpp/index.rst @@ -12,17 +12,25 @@ Iterators --------- .. doxygenclass:: LIEF::ref_iterator - :project: lief .. doxygentypedef:: LIEF::const_ref_iterator - :project: lief .. doxygenclass:: LIEF::filter_iterator - :project: lief .. doxygentypedef:: LIEF::const_filter_iterator - :project: lief +.. doxygenclass:: LIEF::iterator_range +BinaryStream +------------ +.. doxygenclass:: LIEF::BinaryStream + +.. doxygenclass:: LIEF::FileStream + +.. doxygenclass:: LIEF::SpanStream + +.. doxygenclass:: LIEF::VectorStream + +.. doxygenclass:: LIEF::MemoryStream diff --git a/doc/sphinx/api/utilities/index.rst b/doc/sphinx/api/utilities/index.rst index c212b59a6b..c7334e1469 100644 --- a/doc/sphinx/api/utilities/index.rst +++ b/doc/sphinx/api/utilities/index.rst @@ -23,7 +23,7 @@ LIEF exposes a demangling API for the following formats: void __cdecl h(int) - .. tab:: :fa:`brands fa-windows` Rust + .. tab:: :fa:`brands fa-rust` Rust **Input** diff --git a/doc/sphinx/changelog.rst b/doc/sphinx/changelog.rst index 2938f4d3fa..3935bd2a96 100644 --- a/doc/sphinx/changelog.rst +++ b/doc/sphinx/changelog.rst @@ -115,6 +115,7 @@ |lief-objc-declopt|. :Extended: + * Support for :ref:`Dyld shared cache ` * |lief-elf-symbol-demangled_name| is working on **all** platforms (not only unix-based builds) * |lief-macho-symbol-demangled_name| is working on **all** platforms diff --git a/doc/sphinx/extended/dsc/cpp.rst b/doc/sphinx/extended/dsc/cpp.rst new file mode 100644 index 0000000000..b08e438eb2 --- /dev/null +++ b/doc/sphinx/extended/dsc/cpp.rst @@ -0,0 +1,47 @@ +:fa:`regular fa-file-code` C++ +-------------------------------- + +.. note:: + + You can also find the Doxygen documentation here: `here <../../doxygen/>`_ + +.. doxygenfunction:: LIEF::dsc::load(const std::string &path, const std::string &arch = "") + +.. doxygenfunction:: LIEF::dsc::load(const std::vector &files) + +Cache Processing +**************** + +.. warning:: + + If you aim at extracting several libraries from a dyld shared cache, it is + **highly** recommended to enable caching. Otherwise, performances can be + impacted. + +.. doxygenfunction:: LIEF::dsc::enable_cache() + +.. doxygenfunction:: LIEF::dsc::enable_cache(const std::string &dir) + + +DyldSharedCache +*************** + +.. doxygenclass:: LIEF::dsc::DyldSharedCache + + +Dylib +***** + +.. doxygenclass:: LIEF::dsc::Dylib + + +MappingInfo +*********** + +.. doxygenclass:: LIEF::dsc::MappingInfo + + +SubCache +******** + +.. doxygenclass:: LIEF::dsc::SubCache diff --git a/doc/sphinx/extended/dsc/index.rst b/doc/sphinx/extended/dsc/index.rst new file mode 100644 index 0000000000..68d80496e8 --- /dev/null +++ b/doc/sphinx/extended/dsc/index.rst @@ -0,0 +1,278 @@ +.. _extended-dsc: + +:fa:`solid fa-diagram-predecessor` Dyld Shared Cache +----------------------------------------------------- + +.. toctree:: + :caption:  API + :maxdepth: 1 + + cpp + python + rust + +---- + +Introduction +************ + +LIEF's dyld shared cache support allows the inspection and extraction of libraries +from Apple dyld shared cache. + +One can load a shared cache using the |lief-dsc-load| function: + +.. tabs:: + + .. tab:: :fa:`brands fa-python` Python + + .. code-block:: python + + import lief + + dyld_cache: lief.dsc.DylibSharedCache = lief.dsc.load("macos-15.0.1/") + + .. tab:: :fa:`regular fa-file-code` C++ + + .. code-block:: cpp + + #include + + std::unique_ptr dyld_cache = LIEF::dsc::load("macos-15.0.1/") + + .. tab:: :fa:`brands fa-rust` Rust + + .. code-block:: rust + + let dyld_cache = lief::dsc::load_from_path("macos-15.0.1/", ""); + +From this |lief-dsc-dyldsharedcache| object, we can inspect the embedded +|lief-dsc-dylib| as follows: + +.. tabs:: + + .. tab:: :fa:`brands fa-python` Python + + .. code-block:: python + + dyld_cache: lief.dsc.DylibSharedCache = ... + + for dylib in dyld_cache.libraries(): + print("0x{:016x}: {}".format(dylib.address, dylib.path)) + + .. tab:: :fa:`regular fa-file-code` C++ + + .. code-block:: cpp + + std::unique_ptr dyld_cache; + for (std::unique_ptr dylib : dyld_cache->libraries()) { + std::cout << dylib->address() << ' ' << dylib->path() << '\n'; + } + + .. tab:: :fa:`brands fa-rust` Rust + + .. code-block:: rust + + let dyld_cache: lief::dsc::DyldSharedCache; + + for dylib in dyld_cache.libraries() { + println!("0x{:016x}: {}", dylib.address(), dylib.path()); + } + +It is worth mentioning that |lief-dsc-dylib| exposes the |lief-dsc-dylib-get| +method which can be used to **extract** a |lief-macho-binary| instance from a +dyld shared cache libraries: + +.. tabs:: + + .. tab:: :fa:`brands fa-python` Python + + .. code-block:: python + + dyld_cache: lief.dsc.DylibSharedCache = ... + + liblockdown = dyld_cache.find_lib_from_name("liblockdown.dylib") + + macho: lief.MachO.Binary = liblockdown.get() + + for segment in macho.segments: + print(segment.name) + + .. tab:: :fa:`regular fa-file-code` C++ + + .. code-block:: cpp + + std::unique_ptr dyld_cache; + std::unique_ptr liblockdown = dyld_cache->find_lib_from_name("liblockdown.dylib"); + + std::unique_ptr macho = liblockdown.get(); + for (const LIEF::MachO::SegmentCommand& segment : macho->segments()) { + std::cout << segment.name() << '\n'; + } + + .. tab:: :fa:`brands fa-rust` Rust + + .. code-block:: rust + + let dyld_cache: lief::dsc::DyldSharedCache; + + let liblockdown = dyld_cache.find_lib_from_name("liblockdown.dylib").unwrap(); + + let macho = liblockdown.get().unwrap(); + + for segment in macho.segments() { + println!("{}", segment.name()); + } + +Finally, we can leverage the |lief-macho-binary-write| function to write back +the |lief-macho-binary| object: + +.. tabs:: + + .. tab:: :fa:`brands fa-python` Python + + .. code-block:: python + + liblockdown = dyld_cache.find_lib_from_name("liblockdown.dylib") + + macho: lief.MachO.Binary = liblockdown.get() + macho.write("on-disk-liblockdown.dylib") + + .. tab:: :fa:`regular fa-file-code` C++ + + .. code-block:: cpp + + std::unique_ptr dyld_cache; + std::unique_ptr liblockdown = dyld_cache->find_lib_from_name("liblockdown.dylib"); + + std::unique_ptr macho = liblockdown.get(); + macho->write("on-disk-liblockdown.dylib"); + + .. tab:: :fa:`brands fa-rust` Rust + + .. code-block:: rust + + let liblockdown = dyld_cache.find_lib_from_name("liblockdown.dylib").unwrap(); + let macho = liblockdown.get().unwrap(); + + macho.write("on-disk-liblockdown.dylib"); + +.. warning:: + + By default, LIEF **does not** remove dyld shared cache optimizations. + To remove some of these optimizations, you can check the |lief-dsc-dylib-eopt| + structure. + +:fa:`solid fa-stopwatch` Performance Considerations +**************************************************** + +Dyld shared cache files are pretty large which means that they can't be +processed in the same way as other regular |lief-macho-binary| or +|lief-elf-binary| binaries. + +The dyld shared cache support in LIEF follows the principle: +*don't pay overhead for what you don't access*. This is the opposite of the +implementation of |lief-pe-parse|, |lief-macho-parse| and |lief-elf-parse|. + +.. note:: + + These functions parse all the format structures (with decent performances) + because: + + 1. Most of the binary's sizes are less than gigabytes. + 2. A complete representation is required for modifying binaries. + +From a technical perspective, LIEF is using a :cpp:class:`LIEF::FileStream` to +access (on-demand) dyld shared cache structures. Thus, the in-memory consumption +is limited to the size of the structures being accessed. The drawback of this +:cpp:class:`~LIEF::FileStream` is that since this is a file-based access, it takes +more time compared to a :cpp:class:`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*. + +For instance, |lief-dsc-dyldsharedcache-libraries| is returning an **iterator** +over the |lief-dsc-dylib|. Therefore, if you don't iterate, you don't pay for the +access and the parsing of the |lief-dsc-dylib| objects. + +When it is possible, LIEF implements the trait of a **random access** iterator [1]_ +so that we can programmatically do: + +.. tabs:: + + .. tab:: :fa:`brands fa-python` Python + + .. code-block:: python + + dyld_cache: lief.dsc.DyldSharedCache = ... + + # No cost + libraries = cache.libraries + + # O(1) cost + first_lib = libraries[0] + + # O(len(libraries)) cost + for lib in libraries: + print(lib.path) + + .. tab:: :fa:`regular fa-file-code` C++ + + .. code-block:: cpp + + std::unique_ptr dyld_cache; + + // No cost + auto libraries = dyld_cache.libraries(); + + // O(1) cost + std::unique_ptr first_lib = libraries[0]; + + // O(libraries.size()) cost + for (const auto& dylib : libraries) { + std::cout << dylib.path() << '\n'; + } + +When extracting a |lief-macho-binary| from a |lief-dsc-dylib| object with +|lief-dsc-dylib-get|, **the extraction can a substantial amount of time**, +especially if some deoptimizations are turned on (c.f. |lief-dsc-dylib-eopt|). + +For instance, |lief-dsc-dylib-eopt-fix_branches| could require to iterate +over the dyld shared cache's stub islands several times. To improve overall +performances, LIEF provides a cache-based optimization that can be enabled +and configured with: + +- |lief-dsc-enable_cache| +- |lief-dsc-dyldsharedcache-enable_caching| + +.. admonition:: When you should turn caching on? + :class: warning + + 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. + + For all other situations, you should turn on |lief-dsc-enable_cache|. + + **By default, the caching is not enabled.** + +.. [1] https://en.cppreference.com/w/cpp/iterator/random_access_iterator + + +:fa:`solid fa-book-open-reader` References +******************************************* + +- :github-ref:`arandomdev/DyldExtractor` +- :github-ref:`blacktop/ipsw` +- :github-ref:`apple-oss-distributions/dyld` +- https://www.romainthomas.fr/post/24-09-apple-lockdown-dbi-lifting/ + +:fa:`brands fa-python` :doc:`Python API ` + +:fa:`regular fa-file-code` :doc:`C++ API ` + +:fa:`brands fa-rust` Rust API: :rust:module:`lief::dsc` + +.. include:: ../../_cross_api.rst + diff --git a/doc/sphinx/extended/dsc/python.rst b/doc/sphinx/extended/dsc/python.rst new file mode 100644 index 0000000000..6a06c7e78b --- /dev/null +++ b/doc/sphinx/extended/dsc/python.rst @@ -0,0 +1,37 @@ +:fa:`brands fa-python` Python +------------------------------ + +.. autofunction:: lief.dsc.load + +Cache Processing +**************** + +.. warning:: + + If you aim at extracting several libraries from a dyld shared cache, it is + **highly** recommended to enable caching. Otherwise, performances can be + impacted. + +.. autofunction:: lief.dsc.enable_cache + +DyldSharedCache +*************** + +.. autoclass:: lief.dsc.DyldSharedCache + + +Dylib +***** + +.. autoclass:: lief.dsc.Dylib + +MappingInfo +*********** + +.. autoclass:: lief.dsc.MappingInfo + +SubCache +******** + +.. autoclass:: lief.dsc.SubCache + diff --git a/doc/sphinx/extended/dsc/rust.rst b/doc/sphinx/extended/dsc/rust.rst new file mode 100644 index 0000000000..d40edae58d --- /dev/null +++ b/doc/sphinx/extended/dsc/rust.rst @@ -0,0 +1,6 @@ +:fa:`brands fa-rust` Rust +--------------------------- + +.. note:: + + Please check: :rust:module:`lief::dsc` diff --git a/doc/sphinx/extended/intro.rst b/doc/sphinx/extended/intro.rst index ce3dcd2ca0..3a3af55f8c 100644 --- a/doc/sphinx/extended/intro.rst +++ b/doc/sphinx/extended/intro.rst @@ -3,8 +3,11 @@ :fa:`cubes` What is LIEF Extended? ---------------------------------- +Introduction +************ + *LIEF extended* is an enhanced version of LIEF that contains additional features -like the support of Objective-C metadata, PDB and DWARF. +like the support of Dyld shared cache, Objective-C metadata, PDB and DWARF. Whilst the main version of LIEF is focused on (only) providing the support for ELF, PE, and Mach-O, LIEF extended aims at providing other functionalities that @@ -12,29 +15,31 @@ were not originally designed to be integrated in LIEF. You can find the differences between both versions in this table: -+-------------------------------+-------------------+-------------------+------------------------------------------------------+ -| Module | Regular Version | Extended Version | Note | -+===============================+===================+===================+======================================================+ -| :ref:`ELF ` | :fa-check:`check` | :fa-check:`check` | | -+-------------------------------+-------------------+-------------------+------------------------------------------------------+ -| :ref:`PE ` | :fa-check:`check` | :fa-check:`check` | | -+-------------------------------+-------------------+-------------------+------------------------------------------------------+ -| :ref:`Mach-O ` | :fa-check:`check` | :fa-check:`check` | | -+-------------------------------+-------------------+-------------------+------------------------------------------------------+ -| :ref:`DEX ` | :fa-check:`check` | :fa-check:`check` | | -+-------------------------------+-------------------+-------------------+------------------------------------------------------+ -| :ref:`OAT ` | :fa-check:`check` | :fa-check:`check` | | -+-------------------------------+-------------------+-------------------+------------------------------------------------------+ -| :ref:`VDEX ` | :fa-check:`check` | :fa-check:`check` | | -+-------------------------------+-------------------+-------------------+------------------------------------------------------+ -| :ref:`ART ` | :fa-check:`check` | :fa-check:`check` | | -+-------------------------------+-------------------+-------------------+------------------------------------------------------+ -| :ref:`PDB ` | :xmark:`mark` | :fa-check:`check` | Support based on LLVM | -+-------------------------------+-------------------+-------------------+------------------------------------------------------+ -| :ref:`DWARF ` | :xmark:`mark` | :fa-check:`check` | Support based on LLVM | -+-------------------------------+-------------------+-------------------+------------------------------------------------------+ -| :ref:`ObjC ` | :xmark:`mark` | :fa-check:`check` | Support based on :github-ref:`romainthomas/iCDump` | -+-------------------------------+-------------------+-------------------+------------------------------------------------------+ ++-----------------------------------------+-------------------+-------------------+----------------------------------------------------+ +| Module | Regular Version | Extended Version | Note | ++=========================================+===================+===================+====================================================+ +| :ref:`ELF ` | :fa-check:`check` | :fa-check:`check` | | ++-----------------------------------------+-------------------+-------------------+----------------------------------------------------+ +| :ref:`PE ` | :fa-check:`check` | :fa-check:`check` | | ++-----------------------------------------+-------------------+-------------------+----------------------------------------------------+ +| :ref:`Mach-O ` | :fa-check:`check` | :fa-check:`check` | | ++-----------------------------------------+-------------------+-------------------+----------------------------------------------------+ +| :ref:`DEX ` | :fa-check:`check` | :fa-check:`check` | | ++-----------------------------------------+-------------------+-------------------+----------------------------------------------------+ +| :ref:`OAT ` | :fa-check:`check` | :fa-check:`check` | | ++-----------------------------------------+-------------------+-------------------+----------------------------------------------------+ +| :ref:`VDEX ` | :fa-check:`check` | :fa-check:`check` | | ++-----------------------------------------+-------------------+-------------------+----------------------------------------------------+ +| :ref:`ART ` | :fa-check:`check` | :fa-check:`check` | | ++-----------------------------------------+-------------------+-------------------+----------------------------------------------------+ +| :ref:`PDB ` | :xmark:`mark` | :fa-check:`check` | Support based on LLVM | ++-----------------------------------------+-------------------+-------------------+----------------------------------------------------+ +| :ref:`DWARF ` | :xmark:`mark` | :fa-check:`check` | Support based on LLVM | ++-----------------------------------------+-------------------+-------------------+----------------------------------------------------+ +| :ref:`ObjC ` | :xmark:`mark` | :fa-check:`check` | Support based on :github-ref:`romainthomas/iCDump` | ++-----------------------------------------+-------------------+-------------------+----------------------------------------------------+ +| :ref:`Dyld Shared Cache ` | :xmark:`mark` | :fa-check:`check` | | ++-----------------------------------------+-------------------+-------------------+----------------------------------------------------+ To access the extended version, you must oauth-login with GitHub here: |lief-extended-url|. diff --git a/doc/sphinx/extended/objc/index.rst b/doc/sphinx/extended/objc/index.rst index 352e89c1e2..ac0fa7e441 100644 --- a/doc/sphinx/extended/objc/index.rst +++ b/doc/sphinx/extended/objc/index.rst @@ -107,12 +107,6 @@ a header-like output of all the Objective-C metadata found in the binary. } println!("{}", metadata.to_decl()); - -This Objective-C support is based on iCDump which is detailed here: - -- https://www.romainthomas.fr/post/23-01-icdump/ -- https://github.com/romainthomas/iCDump - Class Dump ********** @@ -204,7 +198,13 @@ this option: for cls in metadata.classes: print(cls.to_decl(config)) ----- + +:fa:`solid fa-book-open-reader` References +******************************************* + +- :github-ref:`romainthomas/iCDump` +- :github-ref:`nygard/class-dump` +- https://www.romainthomas.fr/post/23-01-icdump/ API **** diff --git a/doc/sphinx/index.rst b/doc/sphinx/index.rst index 954e53266a..6db225184a 100644 --- a/doc/sphinx/index.rst +++ b/doc/sphinx/index.rst @@ -40,6 +40,7 @@ Welcome to LIEF's documentation! extended/dwarf/index extended/pdb/index extended/objc/index + extended/dsc/index .. toctree:: :caption:  Tutorials diff --git a/examples/cpp/CMakeLists.txt b/examples/cpp/CMakeLists.txt index ffd6939d85..ef13f78917 100644 --- a/examples/cpp/CMakeLists.txt +++ b/examples/cpp/CMakeLists.txt @@ -5,6 +5,7 @@ set(LIEF_CPP_EXAMPLES dwarf_inspect.cpp pdb_inspect.cpp objc_inspect.cpp + dyld_shared_cache_reader.cpp ) if (LIEF_ELF) diff --git a/examples/cpp/dyld_shared_cache_reader.cpp b/examples/cpp/dyld_shared_cache_reader.cpp new file mode 100644 index 0000000000..b63acce05e --- /dev/null +++ b/examples/cpp/dyld_shared_cache_reader.cpp @@ -0,0 +1,34 @@ +#include +#include +#include + +#include + +using namespace LIEF::logging; + +static constexpr auto LOG_LVL = LEVEL::INFO; + +int main(int argc, const char** argv) { + if (!LIEF::is_extended()) { + log(LEVEL::ERR, "This example requires the extended version of LIEF"); + return EXIT_FAILURE; + } + + if (argc != 2) { + log(LEVEL::ERR, "Usage: {} ", argv[0]); + return EXIT_FAILURE; + } + + set_level(LEVEL::INFO); + std::unique_ptr dyld_cache = LIEF::dsc::load(argv[1]); + if (dyld_cache == nullptr) { + log(LEVEL::ERR, "Can't read {} as a shared cache file", argv[0]); + return EXIT_FAILURE; + } + + for (std::unique_ptr lib : dyld_cache->libraries()) { + log(LOG_LVL, "{}: {}", std::to_string(lib->address()), lib->path()); + } + + return EXIT_SUCCESS; +} diff --git a/include/LIEF/BinaryStream/FileStream.hpp b/include/LIEF/BinaryStream/FileStream.hpp index 0f66e95724..b476153611 100644 --- a/include/LIEF/BinaryStream/FileStream.hpp +++ b/include/LIEF/BinaryStream/FileStream.hpp @@ -26,7 +26,7 @@ namespace LIEF { -//! Stream interface over a std::ifstream +//! Stream interface over a `std::ifstream` class LIEF_API FileStream : public BinaryStream { public: static result from_file(const std::string& file); diff --git a/include/LIEF/DyldSharedCache.hpp b/include/LIEF/DyldSharedCache.hpp new file mode 100644 index 0000000000..28f781bebb --- /dev/null +++ b/include/LIEF/DyldSharedCache.hpp @@ -0,0 +1,23 @@ +/* Copyright 2017 - 2024 R. Thomas + * Copyright 2017 - 2024 Quarkslab + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef LIEF_DYLD_SHARED_CACHE_H +#define LIEF_DYLD_SHARED_CACHE_H +#include +#include +#include +#include +#include +#endif diff --git a/include/LIEF/DyldSharedCache/DyldSharedCache.hpp b/include/LIEF/DyldSharedCache/DyldSharedCache.hpp new file mode 100644 index 0000000000..e920b3e378 --- /dev/null +++ b/include/LIEF/DyldSharedCache/DyldSharedCache.hpp @@ -0,0 +1,269 @@ +/* Copyright 2017 - 2024 R. Thomas + * Copyright 2017 - 2024 Quarkslab + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef LIEF_DSC_DYLD_SHARED_CACHE_H +#define LIEF_DSC_DYLD_SHARED_CACHE_H +#include "LIEF/visibility.h" +#include "LIEF/iterators.hpp" +#include "LIEF/DyldSharedCache/Dylib.hpp" +#include "LIEF/DyldSharedCache/MappingInfo.hpp" +#include "LIEF/DyldSharedCache/SubCache.hpp" + +#include +#include +#include + +namespace LIEF { +/// Namespace related to the dyld shared cache support +namespace dsc { +namespace details { +class DyldSharedCache; +} + +/// This class represents a dyld shared cache file. +class LIEF_API DyldSharedCache { + public: + + /// This enum wraps the dyld's git tags for which the structure of + /// dyld shared cache evolved + enum class VERSION : uint32_t { + UNKNOWN = 0, + + DYLD_95_3, ///< dyld-95.3 (2007-10-30) + DYLD_195_5, ///< dyld-195.5 (2011-07-13) + DYLD_239_3, ///< dyld-239.3 (2013-10-29) + DYLD_360_14, ///< dyld-360.14 (2015-09-04) + DYLD_421_1, ///< dyld-421.1 (2016-09-22) + DYLD_832_7_1, ///< dyld-832.7.1 (2020-11-19) + DYLD_940, ///< dyld-940 (2021-02-09) + DYLD_1042_1, ///< dyld-1042.1 (2022-10-19) + + /// This value is used for versions of dyld not publicly released or not yet + /// supported by LIEF + UNRELEASED, + }; + + /// Platforms supported by the dyld shared cache + enum class DYLD_TARGET_PLATFORM : uint32_t { + UNKNOWN = 0, + MACOS = 1, + IOS = 2, + TVOS = 3, + WATCHOS = 4, + BRIDGEOS = 5, + IOSMAC = 6, + IOS_SIMULATOR = 7, + TVOS_SIMULATOR = 8, + WATCHOS_SIMULATOR = 9, + DRIVERKIT = 10, + VISIONOS = 11, + VISIONOS_SIMULATOR = 12, + FIRMWARE = 13, + SEPOS = 14, + + ANY = 0xFFFFFFFF + }; + + /// Architecture supported by the dyld shared cache + enum class DYLD_TARGET_ARCH { + UNKNOWN = 0, + + I386, + + X86_64, + X86_64H, + + ARMV5, + ARMV6, + ARMV7, + + ARM64, + ARM64E, + }; + + /// Iterator over the libraries in the shared cache + using dylib_iterator = iterator_range; + + /// Iterator over the mapping info in the shared cache + using mapping_info_iterator = iterator_range; + + /// Iterator over the split/sub-cache in this **main** shared cache + using subcache_iterator = iterator_range; + + DyldSharedCache(std::unique_ptr impl); + ~DyldSharedCache(); + + /// See the \ref load functions for the details + static std::unique_ptr from_path(const std::string& path, + const std::string& arch = ""); + + /// See the \ref load functions for the details + static std::unique_ptr from_files(const std::vector& path); + + /// Filename of the dyld shared file associated with this object. + /// + /// For instance: `dyld_shared_cache_arm64e, dyld_shared_cache_arm64e.62.dyldlinkedit` + std::string filename() const; + + /// Version of dyld used by this cache + VERSION version() const; + + /// Full path to the original dyld shared cache file associated with object + /// (e.g. `/home/lief/downloads/visionos/dyld_shared_cache_arm64e.42`) + std::string filepath() const; + + /// Based address of this cache + uint64_t load_address() const; + + /// Name of the architecture targeted by this cache (`x86_64h`) + std::string arch_name() const; + + /// Platform targeted by this cache (e.g. vision-os) + DYLD_TARGET_PLATFORM platform() const; + + /// Architecture targeted by this cache + DYLD_TARGET_ARCH arch() const; + + /// Find the Dylib that encompasses the given virtual address. + /// It returns a nullptr if a Dylib can't be found. + std::unique_ptr find_lib_from_va(uint64_t va) const; + + /// Find the Dylib whose Dylib::path matches the provided path. + std::unique_ptr find_lib_from_path(const std::string& path) const; + + /// Find the Dylib whose filename of Dylib::path matches the provided name. + /// + /// If multiple libraries have the same name (but with a different path), + /// the **first one** matching the provided name is returned. + std::unique_ptr find_lib_from_name(const std::string& name) const; + + /// True if the subcaches are associated with this cache + bool has_subcaches() const; + + /// Return an interator over the libraries embedded in this dyld shared cache. + /// + /// This iterator implements the *random access* trait. Thus, one can use + /// iterator_range::size, iterator_range::at, iterator_range::operator[] to a + /// access Dylib at an arbitrary index: + /// + /// ```cpp + /// auto libraries = cache.libaries(); + /// for (size_t i = 0; i < libraries.size(); ++i) { + /// std::string path = libaries[i]->path(); + /// } + /// ``` + dylib_iterator libraries() const; + + /// Return an interator over the mapping information of this dyld shared cache. + /// + /// This iterator implements the *random access* trait. Thus, one can use + /// iterator_range::size, iterator_range::at, iterator_range::operator[] to + /// access a MappingInfo at an arbitrary index: + /// + /// ```cpp + /// auto mapping = cache.mapping_info(); + /// for (size_t i = 0; i < mapping.size(); ++i) { + /// const uint64_t addr = mapping[i]->address(); + /// } + /// ``` + mapping_info_iterator mapping_info() const; + + /// Return an interator over the subcaches associated with this (main) dyld shared + /// cache. + /// + /// This iterator implements the *random access* trait. Thus, one can use + /// iterator_range::size, iterator_range::at, iterator_range::operator[] to + /// access a SubCache at an arbitrary index: + /// + /// ```cpp + /// auto subcaches = cache.subcaches(); + /// for (size_t i = 0; i < subcaches.size(); ++i) { + /// std::unique_ptr impl = subcaches[i]->cache(); + /// } + /// ``` + subcache_iterator subcaches() const; + + /// When enabled, this function allows to record and to keep in *cache*, + /// dyld shared cache information that are costly to access. + /// + /// For instance, GOT symbols, rebases information, stub symbols, ... + /// + /// It is **highly** recommended to enable this function when processing + /// a dyld shared cache several times or when extracting a large number of + /// LIEF::dsc::Dylib with enhanced extraction options (e.g. Dylib::extract_opt_t::fix_branches) + /// + /// One can enable caching by calling this function: + /// + /// ```cpp + /// auto dyld_cache = LIEF::dsc::load("macos-15.0.1/"); + /// dyld_cache->enable_caching("/home/user/.cache/lief-dsc"); + /// ``` + /// + /// One can also enable this cache optimization **globally** using the + /// function: LIEF::dsc::enable_cache or by setting the environment variable + /// `DYLDSC_ENABLE_CACHE` to 1. + void enable_caching(const std::string& target_cache_dir) const; + + /// Flush internal information into the on-disk cache (see: enable_caching) + void flush_cache() const; + + private: + std::unique_ptr impl_; +}; + +/// Load a shared cache from a single file or from a directory specified +/// by the \p path parameter. +/// +/// In the case where multiple architectures are +/// available in the \p path directory, the \p arch parameter can be used to +/// define which architecture should be prefered. +/// +/// **Example:** +/// +/// ```cpp +/// // From a directory (split caches) +/// auto cache = LIEF::dsc::load("vision-pro-2.0/"); +/// +/// // From a single cache file +/// auto cache = LIEF::dsc::load("ios-14.2/dyld_shared_cache_arm64"); +/// +/// // From a directory with multiple architectures +/// auto cache = LIEF::dsc::load("macos-12.6/", /*arch=*/"x86_64h"); +/// ``` +inline std::unique_ptr load(const std::string& path, + const std::string& arch = "") +{ + return DyldSharedCache::from_path(path, arch); +} + +/// Load a shared cache from a list of files. +/// +/// ```cpp +/// std::vector files = { +/// "/tmp/dsc/dyld_shared_cache_arm64e", +/// "/tmp/dsc/dyld_shared_cache_arm64e.1" +/// }; +/// auto cache = LIEF::dsc::load(files); +/// ``` +inline std::unique_ptr load(const std::vector& files) +{ + return DyldSharedCache::from_files(files); +} + +} +} +#endif + + diff --git a/include/LIEF/DyldSharedCache/Dylib.hpp b/include/LIEF/DyldSharedCache/Dylib.hpp new file mode 100644 index 0000000000..284cf31e68 --- /dev/null +++ b/include/LIEF/DyldSharedCache/Dylib.hpp @@ -0,0 +1,158 @@ +/* Copyright 2017 - 2024 R. Thomas + * Copyright 2017 - 2024 Quarkslab + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef LIEF_DSC_DYLIB_H +#define LIEF_DSC_DYLIB_H +#include "LIEF/visibility.h" +#include "LIEF/iterators.hpp" +#include "LIEF/errors.hpp" + +#include +#include + +namespace LIEF { + +namespace MachO { +class Binary; +} + +namespace dsc { + +namespace details { +class Dylib; +class DylibIt; +} + +/// This class represents a library embedded in a dyld shared cache. +/// It mirrors the original `dyld_cache_image_info` structure. +class LIEF_API Dylib { + public: + /// Dylib Iterator + class LIEF_API Iterator : + public iterator_facade_base, std::ptrdiff_t, Dylib*, + std::unique_ptr> + { + public: + using implementation = details::DylibIt; + + Iterator(std::unique_ptr impl); + Iterator(const Iterator&); + Iterator& operator=(const Iterator&); + + Iterator(Iterator&&) noexcept; + Iterator& operator=(Iterator&&) noexcept; + + ~Iterator(); + bool operator<(const Iterator& rhs) const; + + std::ptrdiff_t operator-(const Iterator& R) const; + + Iterator& operator+=(std::ptrdiff_t n); + Iterator& operator-=(std::ptrdiff_t n); + + friend LIEF_API bool operator==(const Iterator& LHS, const Iterator& RHS); + + friend LIEF_API bool operator!=(const Iterator& LHS, const Iterator& RHS) { + return !(LHS == RHS); + } + + std::unique_ptr operator*() const; + + private: + std::unique_ptr impl_; + }; + public: + /// This structure is used to tweak the extraction process while calling + /// Dylib::get. These options allow to deoptimize the dylib and get an + /// accurate representation of the origin Mach-O binary. + struct LIEF_API extract_opt_t { + extract_opt_t(); + + /// Whether the segment's offsets should be packed to avoid + /// an in-memory size while writing back the binary. + /// + /// \note This option does not have an impact on the performances + bool pack = true; + + /// Fix call instructions that target addresses outside the current dylib + /// virtual space. + /// + /// \warning Enabling this option can have a significant impact on the + /// performances. Make sure to enable the internal cache mechanism: + /// LIEF::dsc::enable_cache or LIEF::dsc::DyldSharedCache::enable_caching + bool fix_branches = false; + + /// Fix memory accesses performed outside the dylib's virtual space + /// + /// \warning Enabling this option can have a significant impact on the + /// performances. Make sure to enable the internal cache mechanism: + /// LIEF::dsc::enable_cache or LIEF::dsc::DyldSharedCache::enable_caching + bool fix_memory = false; + + /// Recover and fix relocations + /// + /// \warning Enabling this option can have a significant impact on the + /// performances. Make sure to enable the internal cache mechanism: + /// LIEF::dsc::enable_cache or LIEF::dsc::DyldSharedCache::enable_caching + bool fix_relocations = false; + + /// Fix Objective-C information + bool fix_objc = false; + + /// Whether the `LC_DYLD_CHAINED_FIXUPS` command should be (re)created. + /// + /// If this value is not set, LIEF will add the command only if it's + /// meaningful regarding the other options + LIEF::result create_dyld_chained_fixup_cmd; + }; + + Dylib(std::unique_ptr impl); + ~Dylib(); + + /// Original path of the library (e.g. `/usr/lib/libcryptex.dylib`) + std::string path() const; + + /// In-memory address of the library + uint64_t address() const; + + /// Modification time of the library matching `stat.st_mtime`, or 0 + uint64_t modtime() const; + + /// File serial number matching `stat.st_ino` or 0 + /// + /// Note that for shared cache targeting iOS, this value can hold a hash of + /// the path (if modtime is set to 0) + uint64_t inode() const; + + /// Padding alignment value (should be 0) + uint64_t padding() const; + + /// Get a MachO::Binary representation for this Dylib. + /// + /// One can use this function to write back the Mach-O binary on the disk: + /// + /// ```cpp + /// dyld_cache->libraries()[12]->get()->write("liblockdown.dylib"); + /// ``` + std::unique_ptr get(const extract_opt_t& opt = extract_opt_t()) const; + + private: + std::unique_ptr impl_; +}; + +} +} +#endif diff --git a/include/LIEF/DyldSharedCache/MappingInfo.hpp b/include/LIEF/DyldSharedCache/MappingInfo.hpp new file mode 100644 index 0000000000..1548bf9e7b --- /dev/null +++ b/include/LIEF/DyldSharedCache/MappingInfo.hpp @@ -0,0 +1,103 @@ +/* Copyright 2017 - 2024 R. Thomas + * Copyright 2017 - 2024 Quarkslab + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef LIEF_DSC_MAPPING_INFO_H +#define LIEF_DSC_MAPPING_INFO_H +#include "LIEF/visibility.h" +#include "LIEF/iterators.hpp" + +#include +#include + +namespace LIEF { +namespace dsc { + +namespace details { +class MappingInfo; +class MappingInfoIt; +} + +/// This class represents a `dyld_cache_mapping_info` entry. +/// +/// It provides information about the relationshiop between on-disk shared cache +/// and in-memory shared cache. +class LIEF_API MappingInfo { + public: + /// MappingInfo Iterator + class LIEF_API Iterator : + public iterator_facade_base, std::ptrdiff_t, MappingInfo*, + std::unique_ptr> + { + public: + using implementation = details::MappingInfoIt; + + Iterator(std::unique_ptr impl); + Iterator(const Iterator&); + Iterator& operator=(const Iterator&); + + Iterator(Iterator&&) noexcept; + Iterator& operator=(Iterator&&) noexcept; + + ~Iterator(); + bool operator<(const Iterator& rhs) const; + + std::ptrdiff_t operator-(const Iterator& R) const; + + Iterator& operator+=(std::ptrdiff_t n); + Iterator& operator-=(std::ptrdiff_t n); + + friend LIEF_API bool operator==(const Iterator& LHS, const Iterator& RHS); + + friend LIEF_API bool operator!=(const Iterator& LHS, const Iterator& RHS) { + return !(LHS == RHS); + } + + std::unique_ptr operator*() const; + + private: + std::unique_ptr impl_; + }; + + MappingInfo(std::unique_ptr impl); + ~MappingInfo(); + + /// The in-memory address where this dyld shared cache region is mapped + uint64_t address() const; + + /// Size of the region being mapped + uint64_t size() const; + + /// End virtual address of the region + uint64_t end_address() const { + return address() + size(); + } + + /// On-disk file offset + uint64_t file_offset() const; + + /// Max memory protection + uint32_t max_prot() const; + + /// Initial memory protection + uint32_t init_prot() const; + + private: + std::unique_ptr impl_; +}; + +} +} +#endif diff --git a/include/LIEF/DyldSharedCache/SubCache.hpp b/include/LIEF/DyldSharedCache/SubCache.hpp new file mode 100644 index 0000000000..ce07265231 --- /dev/null +++ b/include/LIEF/DyldSharedCache/SubCache.hpp @@ -0,0 +1,103 @@ +/* Copyright 2017 - 2024 R. Thomas + * Copyright 2017 - 2024 Quarkslab + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef LIEF_DSC_SUBCACHE_H +#define LIEF_DSC_SUBCACHE_H +#include "LIEF/visibility.h" +#include "LIEF/iterators.hpp" +#include "LIEF/DyldSharedCache/uuid.hpp" + +#include +#include + +namespace LIEF { +namespace dsc { +class DyldSharedCache; + +namespace details { +class SubCache; +class SubCacheIt; +} + +/// This class represents a subcache in the case of large/split dyld shared +/// cache. +/// +/// It mirror (and abstracts) the original `dyld_subcache_entry` / `dyld_subcache_entry_v1` +class LIEF_API SubCache { + public: + /// SubCache Iterator + class LIEF_API Iterator : + public iterator_facade_base, std::ptrdiff_t, SubCache*, + std::unique_ptr + + > + { + public: + using implementation = details::SubCacheIt; + + Iterator(std::unique_ptr impl); + Iterator(const Iterator&); + Iterator& operator=(const Iterator&); + + Iterator(Iterator&&) noexcept; + Iterator& operator=(Iterator&&) noexcept; + + ~Iterator(); + bool operator<(const Iterator& rhs) const; + + std::ptrdiff_t operator-(const Iterator& R) const; + + Iterator& operator+=(std::ptrdiff_t n); + Iterator& operator-=(std::ptrdiff_t n); + + friend LIEF_API bool operator==(const Iterator& LHS, const Iterator& RHS); + + friend LIEF_API bool operator!=(const Iterator& LHS, const Iterator& RHS) { + return !(LHS == RHS); + } + + std::unique_ptr operator*() const; + + private: + std::unique_ptr impl_; + }; + + public: + SubCache(std::unique_ptr impl); + ~SubCache(); + + /// The uuid of the subcache file + sc_uuid_t uuid() const; + + /// The offset of this subcache from the main cache base address + uint64_t vm_offset() const; + + /// The file name suffix of the subCache file (e.g. `.25.data`, `.03.development`) + std::string suffix() const; + + /// The associated DyldSharedCache object for this subcache + std::unique_ptr cache() const; + + friend LIEF_API + std::ostream& operator<<(std::ostream& os, const SubCache& subcache); + + private: + std::unique_ptr impl_; +}; + +} +} +#endif diff --git a/include/LIEF/DyldSharedCache/caching.hpp b/include/LIEF/DyldSharedCache/caching.hpp new file mode 100644 index 0000000000..0b8f74c794 --- /dev/null +++ b/include/LIEF/DyldSharedCache/caching.hpp @@ -0,0 +1,53 @@ +/* Copyright 2017 - 2024 R. Thomas + * Copyright 2017 - 2024 Quarkslab + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef LIEF_DSC_CACHING_H +#define LIEF_DSC_CACHING_H +#include +namespace LIEF { +namespace dsc { + +/// Enable globally cache/memoization. One can also leverage this function +/// by setting the environment variable `DYLDSC_ENABLE_CACHE` to `1` +/// +/// By default, LIEF will use the directory specified by the environment +/// variable `DYLDSC_CACHE_DIR` as its cache-root directory: +/// +/// ```bash +/// DYLDSC_ENABLE_CACHE=1 DYLDSC_CACHE_DIR=/tmp/my_dir ./my-program +/// ``` +/// +/// Otherwise, if `DYLDSC_CACHE_DIR` is not set, LIEF will use the following +/// directory (in this priority): +/// +/// 1. System or user cache directory +/// - macOS: `DARWIN_USER_TEMP_DIR` / `DARWIN_USER_CACHE_DIR` + `/dyld_shared_cache` +/// - Linux: `${XDG_CACHE_HOME}/dyld_shared_cache` +/// - Windows: `%LOCALAPPDATA%\dyld_shared_cache` +/// 2. Home directory +/// - macOS/Linux: `$HOME/.dyld_shared_cache` +/// - Windows: `%USERPROFILE%\.dyld_shared_cache` +/// +/// +/// \see LIEF::dsc::DyldSharedCache::enable_caching for a finer granularity +bool enable_cache(); + +/// Same behavior as enable_cache() but with a +/// user-provided cache directory +bool enable_cache(const std::string& dir); +} +} +#endif + diff --git a/include/LIEF/DyldSharedCache/uuid.hpp b/include/LIEF/DyldSharedCache/uuid.hpp new file mode 100644 index 0000000000..a935085536 --- /dev/null +++ b/include/LIEF/DyldSharedCache/uuid.hpp @@ -0,0 +1,29 @@ +/* Copyright 2017 - 2024 R. Thomas + * Copyright 2017 - 2024 Quarkslab + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef LIEF_DSC_UUID_H +#define LIEF_DSC_UUID_H +#include +#include + +// NOTE(romain): Windows is #define-ing uuid_t so we can't use this alias + +namespace LIEF { +namespace dsc { +/// UUID used in different places of the shared cache +using sc_uuid_t = std::array; +} +} +#endif diff --git a/include/LIEF/LIEF.hpp b/include/LIEF/LIEF.hpp index ac244a88da..40b43459c2 100644 --- a/include/LIEF/LIEF.hpp +++ b/include/LIEF/LIEF.hpp @@ -29,6 +29,7 @@ #include #include #include +#include #include #include #include diff --git a/include/LIEF/config.h.in b/include/LIEF/config.h.in index d051434710..e38fb5ef5e 100644 --- a/include/LIEF/config.h.in +++ b/include/LIEF/config.h.in @@ -29,9 +29,10 @@ #cmakedefine LIEF_ART_SUPPORT @LIEF_ART_SUPPORT@ // Extended features -#cmakedefine LIEF_DEBUG_INFO @LIEF_DEBUG_INFO_SUPPORT@ -#cmakedefine LIEF_OBJC @LIEF_OBJC_SUPPORT@ -#cmakedefine LIEF_EXTENDED @LIEF_EXTENDED@ +#cmakedefine LIEF_DEBUG_INFO @LIEF_DEBUG_INFO_SUPPORT@ +#cmakedefine LIEF_OBJC @LIEF_OBJC_SUPPORT@ +#cmakedefine LIEF_DYLD_SHARED_CACHE @LIEF_DYLD_SHARED_CACHE_SUPPORT@ +#cmakedefine LIEF_EXTENDED @LIEF_EXTENDED@ // LIEF options #cmakedefine LIEF_JSON_SUPPORT @ENABLE_JSON_SUPPORT@ diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index a8bbff8ff8..3f5c00af75 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -59,3 +59,6 @@ if(NOT LIEF_OBJC_SUPPORT) add_subdirectory(ObjC) endif() +if(NOT LIEF_DYLD_SHARED_CACHE_SUPPORT) + add_subdirectory(dyld-shared-cache) +endif() diff --git a/src/dyld-shared-cache/CMakeLists.txt b/src/dyld-shared-cache/CMakeLists.txt new file mode 100644 index 0000000000..46f8e71690 --- /dev/null +++ b/src/dyld-shared-cache/CMakeLists.txt @@ -0,0 +1,3 @@ +target_sources(LIB_LIEF PRIVATE + dyldsc.cpp +) diff --git a/src/dyld-shared-cache/dyldsc.cpp b/src/dyld-shared-cache/dyldsc.cpp new file mode 100644 index 0000000000..1d9fa78b82 --- /dev/null +++ b/src/dyld-shared-cache/dyldsc.cpp @@ -0,0 +1,335 @@ +/* Copyright 2022 - 2024 R. Thomas + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "LIEF/DyldSharedCache/DyldSharedCache.hpp" +#include "LIEF/DyldSharedCache/Dylib.hpp" +#include "LIEF/DyldSharedCache/MappingInfo.hpp" +#include "LIEF/DyldSharedCache/SubCache.hpp" + +#include "LIEF/MachO/Binary.hpp" + +#include "logging.hpp" +#include "messages.hpp" +#include "internal_utils.hpp" + +namespace LIEF::dsc { +namespace details { +class DyldSharedCache {}; +class Dylib {}; +class DylibIt {}; + +class MappingInfo {}; +class MappingInfoIt {}; + +class SubCache {}; +class SubCacheIt {}; +} + +// ---------------------------------------------------------------------------- +// caching +// ---------------------------------------------------------------------------- +bool enable_cache() { + LIEF_ERR(DSC_NOT_SUPPORTED); + return false; +} + +bool enable_cache(const std::string&) { + LIEF_ERR(DSC_NOT_SUPPORTED); + return false; +} + +// ---------------------------------------------------------------------------- +// DyldSharedCache/DyldSharedCache.hpp +// ---------------------------------------------------------------------------- +DyldSharedCache::DyldSharedCache(std::unique_ptr) +{} + +DyldSharedCache::~DyldSharedCache() = default; + +std::unique_ptr + DyldSharedCache::from_path(const std::string&, const std::string&) +{ + LIEF_ERR(DSC_NOT_SUPPORTED); + return nullptr; +} + +std::unique_ptr + DyldSharedCache::from_files(const std::vector&) +{ + LIEF_ERR(DSC_NOT_SUPPORTED); + return nullptr; +} + +std::string DyldSharedCache::filename() const { + return ""; +} + +DyldSharedCache::VERSION DyldSharedCache::version() const { + return VERSION::UNKNOWN; +} + +std::string DyldSharedCache::filepath() const { + return ""; +} + +uint64_t DyldSharedCache::load_address() const { + return 0; +} + +std::string DyldSharedCache::arch_name() const { + return ""; +} + +DyldSharedCache::DYLD_TARGET_PLATFORM DyldSharedCache::platform() const { + return DYLD_TARGET_PLATFORM::UNKNOWN; +} + +DyldSharedCache::DYLD_TARGET_ARCH DyldSharedCache::arch() const { + return DYLD_TARGET_ARCH::UNKNOWN; +} + +std::unique_ptr DyldSharedCache::find_lib_from_va(uint64_t) const { + return nullptr; +} + +std::unique_ptr DyldSharedCache::find_lib_from_path(const std::string&) const { + return nullptr; +} + +std::unique_ptr DyldSharedCache::find_lib_from_name(const std::string&) const { + return nullptr; +} + +bool DyldSharedCache::has_subcaches() const { + return false; +} + +DyldSharedCache::dylib_iterator DyldSharedCache::libraries() const { + return make_empty_iterator(); +} + +DyldSharedCache::mapping_info_iterator DyldSharedCache::mapping_info() const { + return make_empty_iterator(); +} + +DyldSharedCache::subcache_iterator DyldSharedCache::subcaches() const { + return make_empty_iterator(); +} + +void DyldSharedCache::enable_caching(const std::string&) const {} +void DyldSharedCache::flush_cache() const {} + +// ---------------------------------------------------------------------------- +// DyldSharedCache/Dylib.hpp +// ---------------------------------------------------------------------------- + +Dylib::Dylib::extract_opt_t::extract_opt_t() = default; + +Dylib::Iterator::Iterator(std::unique_ptr) {} + +Dylib::Iterator::Iterator(Dylib::Iterator&&) noexcept {} + +Dylib::Iterator& Dylib::Iterator::operator=(Dylib::Iterator&&) noexcept { + return *this; +} + +Dylib::Iterator::Iterator(const Dylib::Iterator&) {} +Dylib::Iterator& Dylib::Iterator::operator=(const Dylib::Iterator&) { + return *this; +} + +Dylib::Iterator::~Iterator() = default; +bool Dylib::Iterator::operator<(const Dylib::Iterator&) const { + return false; +} + +std::ptrdiff_t Dylib::Iterator::operator-(const Iterator&) const { + return 0; +} + +Dylib::Iterator& Dylib::Iterator::operator+=(std::ptrdiff_t) { + return *this; +} + +Dylib::Iterator& Dylib::Iterator::operator-=(std::ptrdiff_t) { + return *this; +} + +bool operator==(const Dylib::Iterator&, const Dylib::Iterator&) { + return false; +} + +std::unique_ptr Dylib::Iterator::operator*() const { + return nullptr; +} + + +Dylib::Dylib(std::unique_ptr) {} + +Dylib::~Dylib() = default; + +std::unique_ptr Dylib::get(const Dylib::extract_opt_t&) const { + return nullptr; +} + +std::string Dylib::path() const { + return ""; +} + +uint64_t Dylib::address() const { + return 0; +} + +uint64_t Dylib::modtime() const { + return 0; +} + +uint64_t Dylib::inode() const { + return 0; +} + +uint64_t Dylib::padding() const { + return 0; +} + +// ---------------------------------------------------------------------------- +// DyldSharedCache/MappingInfo.hpp +// ---------------------------------------------------------------------------- +MappingInfo::Iterator::Iterator(std::unique_ptr) {} + +MappingInfo::Iterator::Iterator(MappingInfo::Iterator&&) noexcept {} + +MappingInfo::Iterator& MappingInfo::Iterator::operator=(MappingInfo::Iterator&&) noexcept { + return *this; +} + +MappingInfo::Iterator::Iterator(const MappingInfo::Iterator&) {} +MappingInfo::Iterator& MappingInfo::Iterator::operator=(const MappingInfo::Iterator&) { + return *this; +} + +MappingInfo::Iterator::~Iterator() = default; +bool MappingInfo::Iterator::operator<(const MappingInfo::Iterator&) const { + return false; +} + +std::ptrdiff_t MappingInfo::Iterator::operator-(const Iterator&) const { + return 0; +} + +MappingInfo::Iterator& MappingInfo::Iterator::operator+=(std::ptrdiff_t) { + return *this; +} + +MappingInfo::Iterator& MappingInfo::Iterator::operator-=(std::ptrdiff_t) { + return *this; +} + +bool operator==(const MappingInfo::Iterator&, const MappingInfo::Iterator&) { + return false; +} + +std::unique_ptr MappingInfo::Iterator::operator*() const { + return nullptr; +} + +MappingInfo::MappingInfo(std::unique_ptr) {} +MappingInfo::~MappingInfo() = default; + +uint64_t MappingInfo::address() const { + return 0; +} + +uint64_t MappingInfo::size() const { + return 0; +} + +uint64_t MappingInfo::file_offset() const { + return 0; +} + +uint32_t MappingInfo::max_prot() const { + return 0; +} + +uint32_t MappingInfo::init_prot() const { + return 0; +} + + +// ---------------------------------------------------------------------------- +// DyldSharedCache/SubCache.hpp +// ---------------------------------------------------------------------------- +SubCache::Iterator::Iterator(std::unique_ptr) {} + +SubCache::Iterator::Iterator(SubCache::Iterator&&) noexcept {} + +SubCache::Iterator& SubCache::Iterator::operator=(SubCache::Iterator&&) noexcept { + return *this; +} + +SubCache::Iterator::Iterator(const SubCache::Iterator&) {} +SubCache::Iterator& SubCache::Iterator::operator=(const SubCache::Iterator&) { + return *this; +} + +SubCache::Iterator::~Iterator() = default; +bool SubCache::Iterator::operator<(const SubCache::Iterator&) const { + return false; +} + +std::ptrdiff_t SubCache::Iterator::operator-(const Iterator&) const { + return 0; +} + +SubCache::Iterator& SubCache::Iterator::operator+=(std::ptrdiff_t) { + return *this; +} + +SubCache::Iterator& SubCache::Iterator::operator-=(std::ptrdiff_t) { + return *this; +} + +bool operator==(const SubCache::Iterator&, const SubCache::Iterator&) { + return false; +} + +std::unique_ptr SubCache::Iterator::operator*() const { + return nullptr; +} + +SubCache::SubCache(std::unique_ptr) {} +SubCache::~SubCache() = default; + +sc_uuid_t SubCache::uuid() const { + return {}; +} + +uint64_t SubCache::vm_offset() const { + return 0; +} + +std::string SubCache::suffix() const { + return ""; +} + +std::unique_ptr SubCache::cache() const { + return nullptr; +} + +std::ostream& operator<<(std::ostream& os, const SubCache&) { + return os; +} + +} diff --git a/src/messages.hpp b/src/messages.hpp index 9a11f325d1..e69ef86d29 100644 --- a/src/messages.hpp +++ b/src/messages.hpp @@ -36,6 +36,12 @@ "Please checkout " LIEF_DOC_PREFIX "/latest/extended/intro.html for the details" #endif +#if !defined(DSC_NOT_SUPPORTED) +#define DSC_NOT_SUPPORTED \ + "Dyld shared cache is not available for this build.\n" \ + "Please checkout " LIEF_DOC_PREFIX "/latest/extended/intro.html for the details" +#endif + #if !defined(NEEDS_EXTENDED_MSG) #define NEEDS_EXTENDED_MSG \ "This function requires the extended version of LIEF.\n" \ diff --git a/tests/dyld-shared-cache/test_dsc_misc.py b/tests/dyld-shared-cache/test_dsc_misc.py new file mode 100644 index 0000000000..c7f6d98e56 --- /dev/null +++ b/tests/dyld-shared-cache/test_dsc_misc.py @@ -0,0 +1,19 @@ +import lief +import pytest + +from utils import has_dyld_shared_cache_samples, get_dsc_sample + +if not lief.__extended__: + pytest.skip("skipping: extended version only", allow_module_level=True) + +if not has_dyld_shared_cache_samples(): + pytest.skip("skipping: missing dyld shared cache files", allow_module_level=True) + +def test_filename(): + dsc = lief.dsc.load(get_dsc_sample("ios-18.1")) + assert dsc is not None + assert dsc.filename == "dyld_shared_cache_arm64e" + + dsc = lief.dsc.load(get_dsc_sample("ios-18.1/dyld_shared_cache_arm64e.62.dyldlinkedit")) + assert dsc is not None + assert dsc.filename == "dyld_shared_cache_arm64e.62.dyldlinkedit" diff --git a/tests/dyld-shared-cache/test_ios.py b/tests/dyld-shared-cache/test_ios.py new file mode 100644 index 0000000000..727d4963ce --- /dev/null +++ b/tests/dyld-shared-cache/test_ios.py @@ -0,0 +1,65 @@ +import lief +import pytest +from pathlib import Path + +from utils import has_dyld_shared_cache_samples, get_dsc_sample + +if not lief.__extended__: + pytest.skip("skipping: extended version only", allow_module_level=True) + +if not has_dyld_shared_cache_samples(): + pytest.skip("skipping: missing dyld shared cache files", allow_module_level=True) + +def test_ios_18(): + dsc = lief.dsc.load(get_dsc_sample("ios-18.1")) + assert dsc is not None + assert dsc.filename == "dyld_shared_cache_arm64e" + assert dsc.version == lief.dsc.DyldSharedCache.VERSION.UNRELEASED + assert dsc.load_address == 0x180000000 + assert dsc.arch == lief.dsc.DyldSharedCache.ARCH.ARM64E + assert dsc.platform == lief.dsc.DyldSharedCache.PLATFORM.IOS + assert dsc.arch_name == "arm64e" + + assert dsc.find_lib_from_va(0) is None + assert dsc.find_lib_from_va(0x20d0a4010).path == "/System/Library/Frameworks/OpenGLES.framework/OpenGLES" + assert dsc.find_lib_from_path("/usr/lib/libobjc.A.dylib") is not None + assert dsc.find_lib_from_path("/usr/lib/libobjc.X.dylib") is None + + assert dsc.find_lib_from_name("liblockdown.dylib") is not None + assert dsc.find_lib_from_name("liblockdown.Y.dylib") is None + + assert Path(dsc.filepath).as_posix().endswith("ios-18.1/dyld_shared_cache_arm64e") + + libraries = dsc.libraries + + assert len(libraries) == 3756 + assert libraries[0].path == "/usr/lib/libobjc.A.dylib" + assert libraries[0].inode == 0 + assert libraries[0].address == 0x180100000 + assert libraries[0].modtime == 0 + assert libraries[0].padding == 0 + + assert libraries[900].path == "/System/Library/Frameworks/OpenGLES.framework/OpenGLES" + assert libraries[900].inode == 0 + assert libraries[900].address == 0x20d0a4000 + assert libraries[900].modtime == 0 + assert libraries[900].padding == 0 + + map_info = dsc.mapping_info + assert len(map_info) == 1 + assert map_info[0].address == 0x180000000 + assert map_info[0].size == 0x80000 + assert map_info[0].end_address == 0x180080000 + assert map_info[0].file_offset == 0 + assert map_info[0].max_prot == 5 + assert map_info[0].init_prot == 5 + + assert dsc.has_subcaches + subcaches = dsc.subcaches + + assert len(subcaches) == 62 + assert subcaches[0].suffix == ".01" + assert subcaches[0].vm_offset == 0x80000 + assert subcaches[0].cache.filename == "dyld_shared_cache_arm64e.01" + assert bytes(subcaches[0].uuid).hex(":") == "9e:52:2b:55:1d:d4:33:c3:b1:b1:2b:88:a8:a3:99:28" + assert str(subcaches[0]) diff --git a/tests/run_pytest.py b/tests/run_pytest.py index 220852db2f..1bca36e13a 100644 --- a/tests/run_pytest.py +++ b/tests/run_pytest.py @@ -17,6 +17,7 @@ (CWD / "pdb"), (CWD / "dwarf"), (CWD / "objc"), + (CWD / "dyld-shared-cache"), (CWD / "abstract"), "--verbose" ]) diff --git a/tests/utils.py b/tests/utils.py index b00e24f33c..24cf43ea57 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -112,6 +112,27 @@ def is_server_ci() -> bool: def has_private_samples() -> bool: return (Path(lief_samples_dir()) / "private").is_dir() +def has_dyld_shared_cache_samples(): + if (Path(lief_samples_dir()) / "dyld_shared_cache").is_dir(): + return True + + dsc_samples_dir = os.getenv("LIEF_DSC_SAMPLES_DIR", None) + if dsc_samples_dir is None: + return False + + return Path(dsc_samples_dir).is_dir() + +def get_dsc_sample(suffix: str) -> Path: + dir1 = Path(lief_samples_dir()) / "dyld_shared_cache" + if dir1.is_dir(): + return dir1 / suffix + + dsc_samples_dir = os.environ["LIEF_DSC_SAMPLES_DIR"] + if dsc_samples_dir is None: + raise RuntimeError("Missing 'LIEF_DSC_SAMPLES_DIR'") + + return Path(dsc_samples_dir).resolve().absolute() / suffix + def _win_gui_exec_server(executable: Path, timeout: int = 60) -> Optional[Tuple[int, str]]: si = subprocess.STARTUPINFO() # type: ignore[attr-defined] si.dwFlags = subprocess.STARTF_USESTDHANDLES | subprocess.STARTF_USESHOWWINDOW # type: ignore[attr-defined]