Skip to content

Commit

Permalink
Initial support for Apple's dyld shared cache
Browse files Browse the repository at this point in the history
  • Loading branch information
romainthomas committed Oct 19, 2024
1 parent 91a4710 commit 442fb46
Show file tree
Hide file tree
Showing 71 changed files with 3,698 additions and 48 deletions.
12 changes: 9 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
1 change: 1 addition & 0 deletions api/python/README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
40 changes: 40 additions & 0 deletions api/python/examples/dyld_shared_cache_reader.py
Original file line number Diff line number Diff line change
@@ -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())
2 changes: 1 addition & 1 deletion api/python/lief/__init__.pyi
Original file line number Diff line number Diff line change
@@ -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
Expand Down
167 changes: 167 additions & 0 deletions api/python/lief/dsc.pyi
Original file line number Diff line number Diff line change
@@ -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]: ...
1 change: 1 addition & 0 deletions api/python/src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
8 changes: 8 additions & 0 deletions api/python/src/DyldSharedCache/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
target_sources(pyLIEF PRIVATE
init.cpp
pyDyldSharedCache.cpp
pyDylib.cpp
pyMappingInfo.cpp
pySubCache.cpp
)

61 changes: 61 additions & 0 deletions api/python/src/DyldSharedCache/init.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
#include "DyldSharedCache/init.hpp"
#include "DyldSharedCache/pyDyldSharedCache.hpp"
#include "LIEF/DyldSharedCache/caching.hpp"

#include <nanobind/stl/string.h>
#include <nanobind/stl/unique_ptr.h>

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<const std::string&>(&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<LIEF::dsc::DyldSharedCache>(mod);
create<LIEF::dsc::Dylib>(mod);
create<LIEF::dsc::SubCache>(mod);
create<LIEF::dsc::MappingInfo>(mod);
}
}
8 changes: 8 additions & 0 deletions api/python/src/DyldSharedCache/init.hpp
Original file line number Diff line number Diff line change
@@ -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
Loading

0 comments on commit 442fb46

Please sign in to comment.