# noinspection PyPep8Naming
# This file takes some liberties to match the naming from the dyld implementation
import ctypes
import enum
from ctypes import Structure, c_uint16, c_uint32, c_uint64
# Some type aliases
FilePointer = int # Offset into a raw binary file
FileOffset = int # Offset to another FilePointer
MemoryPointer = int # Offset into the mapped memory space
# All of this is based on the dyld-852.2 source code
[docs]
class HelperStruct(Structure):
"""
Subclass of ctypes.Structure that adds a helpful repr method for debugging
"""
def __repr__(self):
r = [f"<struct {self.__class__.__name__}"]
for name, *_ in self._fields_:
r.append(f"{name}={hex(getattr(self, name))}")
r.append(">")
return " ".join(r)
# noinspection PyPep8Naming
[docs]
class dyld_chained_ptr_arm64e_auth_rebase(HelperStruct):
"""
https://github.com/apple-opensource/dyld/blob/852.2/include/mach-o/fixup-chains.h#L128-L138
"""
_fields_ = [
("target", c_uint64, 32),
("diversity", c_uint64, 16),
("addrDiv", c_uint64, 1),
("key", c_uint64, 2),
("next", c_uint64, 11),
("bind", c_uint64, 1),
("auth", c_uint64, 1),
]
# noinspection PyPep8Naming
[docs]
class dyld_chained_ptr_arm64e_auth_bind(HelperStruct):
"""
https://github.com/apple-opensource/dyld/blob/852.2/include/mach-o/fixup-chains.h#L140-L151
"""
_fields_ = [
("ordinal", c_uint64, 16),
("zero", c_uint64, 16),
("diversity", c_uint64, 16),
("addrDiv", c_uint64, 1),
("key", c_uint64, 2),
("next", c_uint64, 11),
("bind", c_uint64, 1),
("auth", c_uint64, 1),
]
# noinspection PyPep8Naming
[docs]
class dyld_chained_ptr_arm64e_rebase(HelperStruct):
"""
https://github.com/apple-opensource/dyld/blob/852.2/include/mach-o/fixup-chains.h#L107-L115
"""
_fields_ = [
("target", c_uint64, 43),
("high8", c_uint64, 8),
("next", c_uint64, 11),
("bind", c_uint64, 1),
("auth", c_uint64, 1),
]
# noinspection PyPep8Naming
[docs]
class dyld_chained_ptr_arm64e_bind(HelperStruct):
"""
https://github.com/apple-opensource/dyld/blob/852.2/include/mach-o/fixup-chains.h#L117-L126
"""
_fields_ = [
("ordinal", c_uint64, 16),
("zero", c_uint64, 16),
("addend", c_uint64, 19),
("next", c_uint64, 11),
("bind", c_uint64, 1),
("auth", c_uint64, 1),
]
# noinspection PyPep8Naming
[docs]
class dyld_chained_ptr_arm64e_bind24(HelperStruct):
"""
https://github.com/apple-opensource/dyld/blob/852.2/include/mach-o/fixup-chains.h#L164-L173
"""
# noinspection PyPep8Naming
[docs]
class dyld_chained_ptr_arm64e_auth_bind24(HelperStruct):
"""
https://github.com/apple-opensource/dyld/blob/852.2/include/mach-o/fixup-chains.h#L175-L186
"""
_fields_ = [
("ordinal", c_uint64, 24),
("zero", c_uint64, 8),
("diversity", c_uint64, 16),
("addrDiv", c_uint64, 1),
("key", c_uint64, 2),
("next", c_uint64, 11),
("bind", c_uint64, 1),
("auth", c_uint64, 1),
]
[docs]
class Arm64e(ctypes.Union):
"""
named after the Union `Arm64e` from dyld MachOLoaded.h
https://github.com/apple-opensource/dyld/blob/852.2/dyld3/MachOLoaded.h#L89-L103
"""
authRebase: dyld_chained_ptr_arm64e_auth_rebase
authBind: dyld_chained_ptr_arm64e_auth_bind
rebase: dyld_chained_ptr_arm64e_rebase
bind: dyld_chained_ptr_arm64e_bind
bind24: dyld_chained_ptr_arm64e_bind24
authBind24: dyld_chained_ptr_arm64e_auth_bind24
_fields_ = [
("authRebase", dyld_chained_ptr_arm64e_auth_rebase),
("authBind", dyld_chained_ptr_arm64e_auth_bind),
("rebase", dyld_chained_ptr_arm64e_rebase),
("bind", dyld_chained_ptr_arm64e_bind),
("bind24", dyld_chained_ptr_arm64e_bind24),
("authBind24", dyld_chained_ptr_arm64e_auth_bind24),
]
@property
def sign_extended_addend(self):
assert self.authBind.bind == 1
assert self.authBind.auth == 0
addend19 = self.bind.addend
if addend19 & 0x40000:
return addend19 | 0xFFFFFFFFFFFC0000
else:
return addend19
@property
def unpack_target(self):
assert self.authBind.bind == 0
assert self.authBind.auth == 0
return self.rebase.high8 << 56 | self.rebase.target
# noinspection PyPep8Naming
[docs]
class dyld_chained_ptr_64_rebase(HelperStruct):
"""
https://github.com/apple-opensource/dyld/blob/852.2/include/mach-o/fixup-chains.h#L153-L161
"""
target: FilePointer | FileOffset
high8: int
next: int
bind: int
_fields_ = [
("target", c_uint64, 36),
("high8", c_uint64, 8),
("_reserved", c_uint64, 7),
("next", c_uint64, 12),
("bind", c_uint64, 1),
]
@property
def unpackedTarget(self):
return self.high8 << 56 | self.target
# noinspection PyPep8Naming
[docs]
class dyld_chained_ptr_64_bind(HelperStruct):
"""
https://github.com/apple-opensource/dyld/blob/852.2/include/mach-o/fixup-chains.h#L189-L197
"""
ordinal: int
addend: int
next: int
bind: int
_fields_ = [
("ordinal", c_uint64, 24),
("addend", c_uint64, 8),
("_reserved", c_uint64, 19),
("next", c_uint64, 12),
("bind", c_uint64, 1),
]
[docs]
class Generic64(ctypes.Union):
"""
named after the Union `Generic64` from dyld MachOLoaded.h
https://github.com/apple-opensource/dyld/blob/852.2/dyld3/MachOLoaded.h#L105-L111
"""
rebase: dyld_chained_ptr_64_rebase
bind: dyld_chained_ptr_64_bind
_fields_ = [
("rebase", dyld_chained_ptr_64_rebase),
("bind", dyld_chained_ptr_64_bind),
]
# noinspection PyPep8Naming
[docs]
class ChainedFixupPointerOnDisk(ctypes.Union):
"""
the ChainedFixupPointerOnDisk union from dyld MachOLoaded.h
https://github.com/apple-opensource/dyld/blob/852.2/dyld3/MachOLoaded.h#L87-L141
"""
generic64: Generic64
arm64e: Arm64e
_fields_ = [("generic64", Generic64), ("arm64e", Arm64e)]
[docs]
def isBind(self, pointer_format: DyldChainedPtrFormats) -> tuple[int, int] | None:
"""
Port of ChainedFixupPointerOnDisk::isBind(uint16_t pointerFormat, uint32_t& bindOrdinal, int64_t& addend)
https://github.com/apple-opensource/dyld/blob/852.2/dyld3/MachOLoaded.cpp#L1098-L1147
Returns None if not a bind (so `if struct.isBind()` works),
:return:
"""
# pylint: disable=no-else-raise
if Arm64e.check_valid_pointer_format(pointer_format):
# https://github.com/apple-opensource/dyld/blob/852.2/dyld3/MachOLoaded.cpp#L1107-L1124
if self.arm64e.authBind.bind:
if self.arm64e.authBind.auth:
if pointer_format == DyldChainedPtrFormats.DYLD_CHAINED_PTR_ARM64E_USERLAND24:
return self.arm64e.authBind24.ordinal, 0
else:
return self.arm64e.authBind.ordinal, 0
else:
if pointer_format == DyldChainedPtrFormats.DYLD_CHAINED_PTR_ARM64E_USERLAND24:
return self.arm64e.bind24.ordinal, self.arm64e.sign_extended_addend
else:
return self.arm64e.bind.ordinal, self.arm64e.sign_extended_addend
else:
return None
elif Generic64.check_valid_pointer_format(pointer_format):
# https://github.com/apple-opensource/dyld/blob/852.2/dyld3/MachOLoaded.cpp#L1126-L1132
if self.generic64.bind.bind:
return self.generic64.bind.ordinal, self.generic64.bind.addend
else:
return None
else:
raise NotImplementedError(f"Not yet supported pointer format {pointer_format}")
[docs]
def isRebase(
self, pointer_format: DyldChainedPtrFormats, preferredLoadAddress: MemoryPointer
) -> MemoryPointer | None:
"""
port of ChainedFixupPointerOnDisk::isRebase(
uint16_t pointerFormat, uint64_t preferedLoadAddress, uint64_t& targetRuntimeOffset)
https://github.com/apple-opensource/dyld/blob/852.2/dyld3/MachOLoaded.cpp#L1046-L1096
:param pointer_format:
:param preferredLoadAddress: I think that's just the requested base address
:return:
"""
# pylint: disable=no-else-raise
if Arm64e.check_valid_pointer_format(pointer_format):
# https://github.com/apple-opensource/dyld/blob/852.2/dyld3/MachOLoaded.cpp#L1049-L1067
if self.arm64e.bind.bind:
return False
else:
if self.arm64e.authRebase.auth:
return self.arm64e.authRebase.target
else:
targetRuntimeOffset = self.arm64e.unpack_target
if pointer_format in [
DyldChainedPtrFormats.DYLD_CHAINED_PTR_ARM64E,
DyldChainedPtrFormats.DYLD_CHAINED_PTR_ARM64E_FIRMWARE,
]:
targetRuntimeOffset -= preferredLoadAddress
return targetRuntimeOffset
elif Generic64.check_valid_pointer_format(pointer_format):
# https://github.com/apple-opensource/dyld/blob/852.2/dyld3/MachOLoaded.cpp#L1068-L1076
rebase = self.generic64.rebase
if rebase.bind:
# Then this wasn't actually a rebase
return False
else:
targetRuntimeOffset = rebase.unpackedTarget
if pointer_format == DyldChainedPtrFormats.DYLD_CHAINED_PTR_64:
targetRuntimeOffset -= preferredLoadAddress
return targetRuntimeOffset
else:
raise NotImplementedError(f"Not yet supported pointer format {pointer_format}")
# DYLD_CHAINED_PTR_BASE: Dict[DyldChainedPtrFormats, Type[ChainedFixupPointerOnDisk]] = {
# DyldChainedPtrFormats.DYLD_CHAINED_PTR_ARM64E: Arm64e,
# DyldChainedPtrFormats.DYLD_CHAINED_PTR_ARM64E_USERLAND24: Arm64e,
# DyldChainedPtrFormats.DYLD_CHAINED_PTR_64: Generic64,
# DyldChainedPtrFormats.DYLD_CHAINED_PTR_64_OFFSET: Generic64
#
# }
[docs]
class DyldImportStruct(HelperStruct):
"""
Meta Struct for the different kind of import structs and the fields they are all guaranteed to have
"""
lib_ordinal: int
weak_import: bool
name_offset: FileOffset
[docs]
@staticmethod
def get_struct(pointer: DyldImportFormats) -> type["DyldImportStruct"]:
return {
DyldImportFormats.DYLD_CHAINED_IMPORT: dyld_chained_import,
DyldImportFormats.DYLD_CHAINED_IMPORT_ADDEND64: dyld_chained_import_addend64,
DyldImportFormats.DYLD_CHAINED_IMPORT_ADDEND: dyld_chained_import_addend,
}[pointer]
# noinspection PyPep8Naming
[docs]
class dyld_chained_import(DyldImportStruct):
"""
Struct for symbol format DYLD_CHAINED_IMPORT
"""
_fields_ = [
("lib_ordinal", c_uint32, 8),
("weak_import", c_uint32, 1),
("name_offset", c_uint32, 23),
]
# noinspection PyPep8Naming
[docs]
class dyld_chained_import_addend(DyldImportStruct):
"""
https://github.com/apple-opensource/dyld/blob/852.2/include/mach-o/fixup-chains.h#L264-L271
"""
addend: int
_fields_ = [
("lib_ordinal", c_uint32, 8),
("weak_import", c_uint32, 1),
("name_offset", c_uint32, 23),
("addend", c_uint32),
]
# DYLD_CHAINED_IMPORT_ADDEND64
# noinspection PyPep8Naming
[docs]
class dyld_chained_import_addend64(DyldImportStruct):
"""
https://github.com/apple-opensource/dyld/blob/852.2/include/mach-o/fixup-chains.h#L273-L281
"""
addend: int
_fields_ = [
("lib_ordinal", c_uint64, 16),
("weak_import", c_uint64, 1),
("reserved", c_uint64, 15),
("name_offset", c_uint64, 32),
("addend", c_uint64),
]
# noinspection PyPep8Naming
# noinspection PyPep8Naming
[docs]
class dyld_chained_starts_in_image(ctypes.Structure):
"""
https://github.com/apple-opensource/dyld/blob/852.2/include/mach-o/fixup-chains.h#L48-L54
"""
seg_count: int
seg_info_offset: ctypes.Array
_fields_ = [("seg_count", c_uint32), ("seg_info_offset", c_uint32 * 1)]
DYLD_CHAINED_PTR_START_NONE = 0xFFFF # used in page_start[] to denote a page with no fixups
DYLD_CHAINED_PTR_START_MULTI = 0x8000 # used in page_start[] to denote a page which has multiple starts
DYLD_CHAINED_PTR_START_LAST = 0x8000 # used in chain_starts[] to denote last start in list for page
# noinspection PyPep8Naming
[docs]
class dyld_chained_starts_in_segment(HelperStruct):
"""
https://github.com/apple-opensource/dyld/blob/852.2/include/mach-o/fixup-chains.h#L56-L72
"""
# size: int
page_size: int
_pointer_format: int
segment_offset: int
max_valid_pointer: int
page_count: int
page_start: int
_fields_ = [
# size of this (amount kernel needs to copy)
("_size", c_uint32),
# 0x1000 or 0x4000
("page_size", c_uint16),
# DYLD_CHAINED_PTR_*
("_pointer_format", c_uint16),
# offset in memory to start of segment
("segment_offset", c_uint64),
("max_valid_pointer", c_uint32),
("page_count", c_uint16),
("page_start", c_uint16),
]
@property
def pointer_format(self) -> DyldChainedPtrFormats:
return DyldChainedPtrFormats(self._pointer_format)
BIND_SPECIAL_DYLIB_WEAK_LOOKUP = -3