diff --git a/Cargo.toml b/Cargo.toml old mode 100755 new mode 100644 diff --git a/crates/examples/src/readobj/macho.rs b/crates/examples/src/readobj/macho.rs index 456b77ea..11672427 100644 --- a/crates/examples/src/readobj/macho.rs +++ b/crates/examples/src/readobj/macho.rs @@ -9,7 +9,7 @@ pub(super) fn print_dyld_cache(p: &mut Printer<'_>, data: &[u8]) { print_dyld_cache_header(p, endian, header); let mappings = header.mappings(endian, data).print_err(p); if let Some(mappings) = mappings { - print_dyld_cache_mappings(p, endian, mappings); + print_dyld_cache_mappings(p, mappings); } if let Some(images) = header.images(endian, data).print_err(p) { print_dyld_cache_images(p, endian, data, mappings, images); @@ -36,24 +36,60 @@ pub(super) fn print_dyld_cache_header( }); } -pub(super) fn print_dyld_cache_mappings( - p: &mut Printer<'_>, - endian: Endianness, - mappings: &[DyldCacheMappingInfo], -) { +pub(super) fn print_dyld_cache_mappings(p: &mut Printer<'_>, mappings: DyldCacheMappingSlice) { if !p.options.file { return; } - for mapping in mappings { - p.group("DyldCacheMappingInfo", |p| { - p.field_hex("Address", mapping.address.get(endian)); - p.field_hex("Size", mapping.size.get(endian)); - p.field_hex("FileOffset", mapping.file_offset.get(endian)); - p.field_hex("MaxProt", mapping.max_prot.get(endian)); - p.flags(mapping.max_prot.get(endian), 0, FLAGS_VM); - p.field_hex("InitProt", mapping.init_prot.get(endian)); - p.flags(mapping.init_prot.get(endian), 0, FLAGS_VM); - }); + + match mappings { + DyldCacheMappingSlice::V1 { + endian, + data: _, + info, + } => { + for mapping in info.iter() { + p.group("DyldCacheMappingInfo", |p| { + p.field_hex("Address", mapping.address.get(endian)); + p.field_hex("Size", mapping.size.get(endian)); + p.field_hex("FileOffset", mapping.file_offset.get(endian)); + p.field_hex("MaxProt", mapping.max_prot.get(endian)); + p.flags(mapping.max_prot.get(endian), 0, FLAGS_VM); + p.field_hex("InitProt", mapping.init_prot.get(endian)); + p.flags(mapping.init_prot.get(endian), 0, FLAGS_VM); + }); + } + } + DyldCacheMappingSlice::V2 { + endian, + data: _, + info, + } => { + for mapping in info.iter() { + p.group("DyldCacheMappingAndSlideInfo", |p| { + p.field_hex("Address", mapping.address.get(endian)); + p.field_hex("Size", mapping.size.get(endian)); + p.field_hex("FileOffset", mapping.file_offset.get(endian)); + p.field_hex( + "SlideInfoFileOffset", + mapping.slide_info_file_offset.get(endian), + ); + p.field_hex( + "SlideInfoFileSize", + mapping.slide_info_file_size.get(endian), + ); + p.field_hex("Flags", mapping.flags.get(endian)); + p.flags(mapping.flags.get(endian), 0, FLAGS_DYLD_CACHE_MAPPING); + p.field_hex("MaxProt", mapping.max_prot.get(endian)); + p.flags(mapping.max_prot.get(endian), 0, FLAGS_VM); + p.field_hex("InitProt", mapping.init_prot.get(endian)); + p.flags(mapping.init_prot.get(endian), 0, FLAGS_VM); + }); + } + } + _ => panic!( + "If this case is hit, it means that someone added a variant to the (non-exhaustive) \ + DyldCacheMappingSlice enum and forgot to update this example" + ), } } @@ -61,7 +97,7 @@ pub(super) fn print_dyld_cache_images( p: &mut Printer<'_>, endian: Endianness, data: &[u8], - mappings: Option<&[DyldCacheMappingInfo]>, + mappings: Option, images: &[DyldCacheImageInfo], ) { for image in images { @@ -78,8 +114,9 @@ pub(super) fn print_dyld_cache_images( p.field_hex("Pad", image.pad.get(endian)); }); } - if let Some(offset) = - mappings.and_then(|mappings| image.file_offset(endian, mappings).print_err(p)) + if let Some(offset) = mappings + .as_ref() + .and_then(|mappings| image.file_offset(endian, mappings).print_err(p)) { if p.options.file { p.blank(); @@ -931,6 +968,13 @@ const FLAGS_CPU_SUBTYPE_ARM64: &[Flag] = &flags!( ); const FLAGS_CPU_SUBTYPE_ARM64_32: &[Flag] = &flags!(CPU_SUBTYPE_ARM64_32_ALL, CPU_SUBTYPE_ARM64_32_V8); +const FLAGS_DYLD_CACHE_MAPPING: &[Flag] = &flags!( + DYLD_CACHE_MAPPING_AUTH_DATA, + DYLD_CACHE_MAPPING_DIRTY_DATA, + DYLD_CACHE_MAPPING_CONST_DATA, + DYLD_CACHE_MAPPING_TEXT_STUBS, + DYLD_CACHE_DYNAMIC_CONFIG_DATA, +); const FLAGS_MH_FILETYPE: &[Flag] = &flags!( MH_OBJECT, MH_EXECUTE, diff --git a/src/macho.rs b/src/macho.rs index 88919d62..1c6032db 100644 --- a/src/macho.rs +++ b/src/macho.rs @@ -9,6 +9,7 @@ use crate::endian::{BigEndian, Endian, U64Bytes, U16, U32, U64}; use crate::pod::Pod; +use core::fmt::{self, Debug}; // Definitions from "/usr/include/mach/machine.h". @@ -282,6 +283,39 @@ pub const VM_PROT_WRITE: u32 = 0x02; /// execute permission pub const VM_PROT_EXECUTE: u32 = 0x04; +// Definitions from ptrauth.h + +/// ptrauth_key enum +#[repr(u8)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum PtrauthKey { + /// ptrauth_key_asia + IA = 0, + /// ptrauth_key_asib + IB = 1, + /// ptrauth_key_asda + DA = 2, + /// ptrauth_key_asdb + DB = 3, +} + +/// Pointer auth data +pub struct Ptrauth { + pub key: PtrauthKey, + pub diversity: u16, + pub addr_div: bool, +} + +impl Debug for Ptrauth { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("Ptrauth") + .field("key", &self.key) + .field("diversity", &format_args!("{:#x}", self.diversity)) + .field("addr_div", &self.addr_div) + .finish() + } +} + // Definitions from https://opensource.apple.com/source/dyld/dyld-210.2.3/launch-cache/dyld_cache_format.h.auto.html /// The dyld cache header. @@ -296,44 +330,142 @@ pub struct DyldCacheHeader { /// e.g. "dyld_v0 i386" pub magic: [u8; 16], /// file offset to first dyld_cache_mapping_info - pub mapping_offset: U32, // offset: 0x10 + pub mapping_offset: U32, /// number of dyld_cache_mapping_info entries - pub mapping_count: U32, // offset: 0x14 - /// file offset to first dyld_cache_image_info - pub images_offset: U32, // offset: 0x18 - /// number of dyld_cache_image_info entries - pub images_count: U32, // offset: 0x1c + pub mapping_count: U32, + /// UNUSED: moved to imagesOffset to prevent older dsc_extarctors from crashing + pub images_offset_old: U32, + /// UNUSED: moved to imagesCount to prevent older dsc_extarctors from crashing + pub images_count_old: U32, /// base address of dyld when cache was built - pub dyld_base_address: U64, // offset: 0x20 - reserved1: [u8; 32], // offset: 0x28 + pub dyld_base_address: U64, + /// file offset of code signature blob + pub code_signature_offset: U64, + /// size of code signature blob (zero means to end of file) + pub code_signature_size: U64, + /// unused. Used to be file offset of kernel slid info + pub slide_info_offset_unused: U64, + /// unused. Used to be size of kernel slid info + pub slide_info_size_unused: U64, /// file offset of where local symbols are stored - pub local_symbols_offset: U64, // offset: 0x48 + pub local_symbols_offset: U64, /// size of local symbols information - pub local_symbols_size: U64, // offset: 0x50 + pub local_symbols_size: U64, /// unique value for each shared cache file - pub uuid: [u8; 16], // offset: 0x58 - reserved2: [u8; 32], // offset: 0x68 - reserved3: [u8; 32], // offset: 0x88 - reserved4: [u8; 32], // offset: 0xa8 - reserved5: [u8; 32], // offset: 0xc8 - reserved6: [u8; 32], // offset: 0xe8 - reserved7: [u8; 32], // offset: 0x108 - reserved8: [u8; 32], // offset: 0x128 - reserved9: [u8; 32], // offset: 0x148 - reserved10: [u8; 32], // offset: 0x168 - /// file offset to first dyld_subcache_info - pub subcaches_offset: U32, // offset: 0x188 - /// number of dyld_subcache_info entries - pub subcaches_count: U32, // offset: 0x18c - /// the UUID of the .symbols subcache - pub symbols_subcache_uuid: [u8; 16], // offset: 0x190 - reserved11: [u8; 32], // offset: 0x1a0 + pub uuid: [u8; 16], + /// 0 for development, 1 for production, 2 for multi-cache + pub cache_type: U64, + /// file offset to table of uint64_t pool addresses + pub branch_pools_offset: U32, + /// number of uint64_t entries + pub branch_pools_count: U32, + /// (unslid) address of mach_header of dyld in cache + pub dyld_in_cache_mh: U64, + /// (unslid) address of entry point (_dyld_start) of dyld in cache + pub dyld_in_cache_entry: U64, + /// file offset to first dyld_cache_image_text_info + pub images_text_offset: U64, + /// number of dyld_cache_image_text_info entries + pub images_text_count: U64, + /// (unslid) address of dyld_cache_patch_info + pub patch_info_addr: U64, + /// Size of all of the patch information pointed to via the dyld_cache_patch_info + pub patch_info_size: U64, + /// unused + pub other_image_group_addr_unused: U64, + /// unused + pub other_image_group_size_unused: U64, + /// (unslid) address of list of program launch closures + pub prog_closures_addr: U64, + /// size of list of program launch closures + pub prog_closures_size: U64, + /// (unslid) address of trie of indexes into program launch closures + pub prog_closures_trie_addr: U64, + /// size of trie of indexes into program launch closures + pub prog_closures_trie_size: U64, + /// platform number (macOS=1, etc) + pub platform: U32, + // bitfield of values + pub flags: U32, + /// base load address of cache if not slid + pub shared_region_start: U64, + /// overall size required to map the cache and all subCaches, if any + pub shared_region_size: U64, + /// runtime slide of cache can be between zero and this value + pub max_slide: U64, + /// (unslid) address of ImageArray for dylibs in this cache + pub dylibs_image_array_addr: U64, + /// size of ImageArray for dylibs in this cache + pub dylibs_image_array_size: U64, + /// (unslid) address of trie of indexes of all cached dylibs + pub dylibs_trie_addr: U64, + /// size of trie of cached dylib paths + pub dylibs_trie_size: U64, + /// (unslid) address of ImageArray for dylibs and bundles with dlopen closures + pub other_image_array_addr: U64, + /// size of ImageArray for dylibs and bundles with dlopen closures + pub other_image_array_size: U64, + /// (unslid) address of trie of indexes of all dylibs and bundles with dlopen closures + pub other_trie_addr: U64, + /// size of trie of dylibs and bundles with dlopen closures + pub other_trie_size: U64, + /// file offset to first dyld_cache_mapping_and_slide_info + pub mapping_with_slide_offset: U32, + /// number of dyld_cache_mapping_and_slide_info entries + pub mapping_with_slide_count: U32, + /// unused + pub dylibs_pbl_state_array_addr_unused: U64, + /// (unslid) address of PrebuiltLoaderSet of all cached dylibs + pub dylibs_pbl_set_addr: U64, + /// (unslid) address of pool of PrebuiltLoaderSet for each program + pub programs_pbl_set_pool_addr: U64, + /// size of pool of PrebuiltLoaderSet for each program + pub programs_pbl_set_pool_size: U64, + /// (unslid) address of trie mapping program path to PrebuiltLoaderSet + pub program_trie_addr: U64, + /// OS Version of dylibs in this cache for the main platform + pub os_version: U32, + /// e.g. iOSMac on macOS + pub alt_platform: U32, + /// e.g. 14.0 for iOSMac + pub alt_os_version: U32, + reserved1: [u8; 4], + /// VM offset from cache_header* to Swift optimizations header + pub swift_opts_offset: U64, + /// size of Swift optimizations header + pub swift_opts_size: U64, + /// file offset to first dyld_subcache_entry + pub sub_cache_array_offset: U32, + /// number of subCache entries + pub sub_cache_array_count: U32, + /// unique value for the shared cache file containing unmapped local symbols + pub symbol_file_uuid: [u8; 16], + /// (unslid) address of the start of where Rosetta can add read-only/executable data + pub rosetta_read_only_addr: U64, + /// maximum size of the Rosetta read-only/executable region + pub rosetta_read_only_size: U64, + /// (unslid) address of the start of where Rosetta can add read-write data + pub rosetta_read_write_addr: U64, + /// maximum size of the Rosetta read-write region + pub rosetta_read_write_size: U64, /// file offset to first dyld_cache_image_info - /// Use this instead of images_offset if mapping_offset is at least 0x1c4. - pub images_across_all_subcaches_offset: U32, // offset: 0x1c0 + pub images_offset: U32, /// number of dyld_cache_image_info entries - /// Use this instead of images_count if mapping_offset is at least 0x1c4. - pub images_across_all_subcaches_count: U32, // offset: 0x1c4 + pub images_count: U32, + /// 0 for development, 1 for production, when cacheType is multi-cache(2) + pub cache_sub_type: U32, + /// VM offset from cache_header* to ObjC optimizations header + pub objc_opts_offset: U64, + /// size of ObjC optimizations header + pub objc_opts_size: U64, + /// VM offset from cache_header* to embedded cache atlas for process introspection + pub cache_atlas_offset: U64, + /// size of embedded cache atlas + pub cache_atlas_size: U64, + /// VM offset from cache_header* to the location of dyld_cache_dynamic_data_header + pub dynamic_data_offset: U64, + /// maximum size of space reserved from dynamic data + pub dynamic_data_max_size: U64, } /// Corresponds to struct dyld_cache_mapping_info from dyld_cache_format.h. @@ -347,6 +479,40 @@ pub struct DyldCacheMappingInfo { pub init_prot: U32, } +// Contains the flags for the dyld_cache_mapping_and_slide_info flags field +pub const DYLD_CACHE_MAPPING_AUTH_DATA: u64 = 1 << 0; +pub const DYLD_CACHE_MAPPING_DIRTY_DATA: u64 = 1 << 1; +pub const DYLD_CACHE_MAPPING_CONST_DATA: u64 = 1 << 2; +pub const DYLD_CACHE_MAPPING_TEXT_STUBS: u64 = 1 << 3; +pub const DYLD_CACHE_DYNAMIC_CONFIG_DATA: u64 = 1 << 4; + +/// Corresponds to struct dyld_cache_mapping_and_slide_info from dyld_cache_format.h. +#[derive(Debug, Clone, Copy)] +#[repr(C)] +pub struct DyldCacheMappingAndSlideInfo { + pub address: U64, + pub size: U64, + pub file_offset: U64, + pub slide_info_file_offset: U64, + pub slide_info_file_size: U64, + pub flags: U64, + pub max_prot: U32, + pub init_prot: U32, +} + +/// Corresponds to struct dyld_cache_slide_info5 from dyld_cache_format.h. +#[derive(Debug, Clone, Copy)] +#[repr(C)] +pub struct DyldCacheSlideInfo5 { + pub version: U32, // currently 5 + pub page_size: U32, // currently 4096 (may also be 16384) + pub page_starts_count: U32, + reserved1: [u8; 4], + pub value_add: U64, +} + +pub const DYLD_CACHE_SLIDE_V5_PAGE_ATTR_NO_REBASE: u16 = 0xFFFF; // page has no rebasing + /// Corresponds to struct dyld_cache_image_info from dyld_cache_format.h. #[derive(Debug, Clone, Copy)] #[repr(C)] @@ -358,6 +524,54 @@ pub struct DyldCacheImageInfo { pub pad: U32, } +/// Corresponds to struct dyld_cache_slide_pointer5 from dyld_cache_format.h. +#[derive(Debug, Clone, Copy)] +pub struct DyldCacheSlidePointer5(pub u64); + +impl DyldCacheSlidePointer5 { + fn is_auth(&self) -> bool { + (self.0 & 0x8000_0000_0000_0000) != 0 + } + + pub fn value(&self, value_add: u64) -> u64 { + let runtime_offset: u64 = self.0 & 0x3_ffff_ffff; + + if self.is_auth() { + runtime_offset + value_add + } else { + let high8: u64 = (self.0 >> 34) & 0xff; + + high8 << 56 | (runtime_offset + value_add) + } + } + + pub fn auth(&self) -> Option { + if self.is_auth() { + let diversity: u16 = ((self.0 >> 34) & 0xffff) as u16; + let addr_div: bool = ((self.0 >> 50) & 0x1) != 0; + + let key_is_data: bool = ((self.0 >> 51) & 0x1) != 0; + let key = if key_is_data { + PtrauthKey::DA + } else { + PtrauthKey::IA + }; + + Some(Ptrauth { + key, + diversity, + addr_div, + }) + } else { + None + } + } + + pub fn next(&self) -> u64 { + (self.0 >> 52) & 0x7ff + } +} + /// Added in dyld-940, which shipped with macOS 12 / iOS 15. /// Originally called `dyld_subcache_entry`, renamed to `dyld_subcache_entry_v1` /// in dyld-1042.1. @@ -3245,7 +3459,9 @@ unsafe_impl_pod!(FatHeader, FatArch32, FatArch64,); unsafe_impl_endian_pod!( DyldCacheHeader, DyldCacheMappingInfo, + DyldCacheMappingAndSlideInfo, DyldCacheImageInfo, + DyldCacheSlideInfo5, DyldSubCacheEntryV1, DyldSubCacheEntryV2, MachHeader32, diff --git a/src/read/macho/dyld_cache.rs b/src/read/macho/dyld_cache.rs index 6375a369..fef1e8e0 100644 --- a/src/read/macho/dyld_cache.rs +++ b/src/read/macho/dyld_cache.rs @@ -1,7 +1,8 @@ use alloc::vec::Vec; -use core::slice; +use core::fmt::{self, Debug}; +use core::{mem, slice}; -use crate::endian::{Endian, Endianness}; +use crate::endian::{Endian, Endianness, U16, U32, U64}; use crate::macho; use crate::read::{Architecture, Error, File, ReadError, ReadRef, Result}; @@ -15,7 +16,7 @@ where endian: E, data: R, subcaches: Vec>, - mappings: &'data [macho::DyldCacheMappingInfo], + mappings: DyldCacheMappingSlice<'data, E, R>, images: &'data [macho::DyldCacheImageInfo], arch: Architecture, } @@ -28,7 +29,369 @@ where R: ReadRef<'data>, { data: R, - mappings: &'data [macho::DyldCacheMappingInfo], + mappings: DyldCacheMappingSlice<'data, E, R>, +} + +/// Information about a mapping. +#[derive(Clone, Copy)] +pub enum DyldCacheMapping<'data, E = Endianness, R = &'data [u8]> +where + E: Endian, + R: ReadRef<'data>, +{ + /// Corresponds to struct dyld_cache_mapping_info from dyld_cache_format.h. + V1 { + /// The mapping endianness + endian: E, + /// The mapping data + data: R, + /// The mapping information + info: &'data macho::DyldCacheMappingInfo, + }, + /// Corresponds to struct dyld_cache_mapping_and_slide_info from dyld_cache_format.h. + V2 { + /// The mapping endianness + endian: E, + /// The mapping data + data: R, + /// The mapping information + info: &'data macho::DyldCacheMappingAndSlideInfo, + }, +} + +impl<'data, E, R> Debug for DyldCacheMapping<'data, E, R> +where + E: Endian, + R: ReadRef<'data>, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("DyldCacheMapping") + .field("address", &format_args!("{:#x}", self.address())) + .field("size", &format_args!("{:#x}", self.size())) + .field("file_offset", &format_args!("{:#x}", self.file_offset())) + .field("max_prot", &format_args!("{:#x}", self.max_prot())) + .field("init_prot", &format_args!("{:#x}", self.init_prot())) + .finish() + } +} + +impl<'data, E, R> DyldCacheMapping<'data, E, R> +where + E: Endian, + R: ReadRef<'data>, +{ + /// The mapping address + pub fn address(&self) -> u64 { + match self { + Self::V1 { + endian, + data: _, + info, + } => info.address.get(*endian), + Self::V2 { + endian, + data: _, + info, + } => info.address.get(*endian), + } + } + + /// The mapping size + pub fn size(&self) -> u64 { + match self { + Self::V1 { + endian, + data: _, + info, + } => info.size.get(*endian), + Self::V2 { + endian, + data: _, + info, + } => info.size.get(*endian), + } + } + + /// The mapping file offset + pub fn file_offset(&self) -> u64 { + match self { + Self::V1 { + endian, + data: _, + info, + } => info.file_offset.get(*endian), + Self::V2 { + endian, + data: _, + info, + } => info.file_offset.get(*endian), + } + } + + /// The mapping maximum protection + pub fn max_prot(&self) -> u32 { + match self { + Self::V1 { + endian, + data: _, + info, + } => info.max_prot.get(*endian), + Self::V2 { + endian, + data: _, + info, + } => info.max_prot.get(*endian), + } + } + + /// The mapping initial protection + pub fn init_prot(&self) -> u32 { + match self { + Self::V1 { + endian, + data: _, + info, + } => info.init_prot.get(*endian), + Self::V2 { + endian, + data: _, + info, + } => info.init_prot.get(*endian), + } + } + + /// The mapping data + pub fn data(&self) -> Result<&'data [u8]> { + match self { + Self::V1 { endian, data, info } => data + .read_bytes_at(info.file_offset.get(*endian), info.size.get(*endian)) + .read_error("Failed to read bytes for mapping"), + Self::V2 { endian, data, info } => data + .read_bytes_at(info.file_offset.get(*endian), info.size.get(*endian)) + .read_error("Failed to read bytes for mapping"), + } + } + + /// Relocations for the mapping + pub fn relocations(self) -> Result> { + match self { + Self::V1 { .. } => Ok(DyldCacheRelocationMappingIterator::empty()), + Self::V2 { endian, data, info } => { + if let Some(slide) = info.slide(endian, data)? { + Ok(DyldCacheRelocationMappingIterator::slide( + data, endian, info, slide, + )) + } else { + Ok(DyldCacheRelocationMappingIterator::empty()) + } + } + } + } +} + +/// An iterator over relocations in a mapping +#[derive(Debug)] +pub enum DyldCacheRelocationMappingIterator<'data, E = Endianness, R = &'data [u8]> +where + E: Endian, + R: ReadRef<'data>, +{ + /// Empty + Empty, + /// Slide + Slide { + /// The mapping data + data: R, + /// Endian + endian: E, + /// The mapping information + info: &'data macho::DyldCacheMappingAndSlideInfo, + /// The mapping slide information + slide: DyldCacheSlideInfoSlice<'data, E>, + /// Page starts + page_index: u64, + /// Page iterator + iter: Option>, + }, +} + +impl<'data, E, R> DyldCacheRelocationMappingIterator<'data, E, R> +where + E: Endian, + R: ReadRef<'data>, +{ + /// Slide iterator + pub fn slide( + data: R, + endian: E, + info: &'data macho::DyldCacheMappingAndSlideInfo, + slide: DyldCacheSlideInfoSlice<'data, E>, + ) -> Self { + Self::Slide { + data, + endian, + info, + slide, + page_index: 0, + iter: None, + } + } + + /// Empty iterator + pub fn empty() -> Self { + Self::Empty + } +} + +impl<'data, E, R> Iterator for DyldCacheRelocationMappingIterator<'data, E, R> +where + E: Endian, + R: ReadRef<'data>, +{ + type Item = Result; + + fn next(&mut self) -> Option { + match self { + Self::Empty => None, + Self::Slide { + data, + endian, + info, + slide, + page_index, + iter, + } => loop { + if let Some(reloc) = iter.as_mut().and_then(|iter| iter.next()) { + return Some(reloc); + } + + match slide { + DyldCacheSlideInfoSlice::V5(slide, page_starts) => { + if *page_index < slide.page_starts_count.get(*endian).into() { + let page_start: u16 = page_starts[*page_index as usize].get(*endian); + + if page_start != macho::DYLD_CACHE_SLIDE_V5_PAGE_ATTR_NO_REBASE { + *iter = Some(DyldCacheRelocationPageIterator::V5 { + data: *data, + endian: *endian, + info: *info, + slide: *slide, + page_index: *page_index, + page_offset: Some(page_start.into()), + }); + } else { + *iter = None; + } + + *page_index += 1; + } else { + return None; + } + } + } + }, + } + } +} + +/// A versioned iterator over relocations in a page +#[derive(Debug)] +pub enum DyldCacheRelocationPageIterator<'data, E = Endianness, R = &'data [u8]> +where + E: Endian, + R: ReadRef<'data>, +{ + /// Corresponds to struct dyld_cache_slide_info5 from dyld_cache_format.h. + V5 { + /// The mapping data + data: R, + /// Endian + endian: E, + /// The mapping information + info: &'data macho::DyldCacheMappingAndSlideInfo, + /// The mapping slide information + slide: &'data macho::DyldCacheSlideInfo5, + /// Mapping page index + page_index: u64, + /// The current offset into the page + page_offset: Option, + }, +} + +impl<'data, E, R> Iterator for DyldCacheRelocationPageIterator<'data, E, R> +where + E: Endian, + R: ReadRef<'data>, +{ + type Item = Result; + + fn next(&mut self) -> Option { + match self { + Self::V5 { + data, + endian, + info, + slide, + page_index, + page_offset, + } => { + if let Some(offset) = *page_offset { + let mapping_offset: u64 = *page_index * slide.page_size.get(*endian) as u64; + let file_offset: u64 = info.file_offset.get(*endian) + mapping_offset + offset; + let pointer = match data.read_at::>(file_offset) { + Ok(pointer) => pointer.get(*endian), + Err(_) => { + return Some(Err(Error("Failed to read file offset"))); + } + }; + let pointer = macho::DyldCacheSlidePointer5(pointer); + + let next = pointer.next(); + if next == 0 { + *page_offset = None; + } else { + *page_offset = Some(offset + (next * 8)); + } + + let address = info.address.get(*endian) + mapping_offset + offset; + let value_add = slide.value_add.get(*endian); + let value = pointer.value(value_add); + let auth = pointer.auth(); + Some(Ok(DyldRelocation { + address, + file_offset, + value, + auth, + })) + } else { + None + } + } + } + } +} + +/// A cache mapping relocation. +pub struct DyldRelocation { + /// The address of the relocation + pub address: u64, + /// The offset of the relocation within the mapping + pub file_offset: u64, + /// The relocation value + pub value: u64, + /// The value auth context + pub auth: Option, +} + +impl Debug for DyldRelocation { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("DyldRelocation") + .field("address", &format_args!("{:#x}", self.address)) + .field("file_offset", &format_args!("{:#x}", self.file_offset)) + .field("value", &format_args!("{:#x}", self.value)) + .field("auth", &self.auth) + .finish() + } } /// A slice of structs describing each subcache. The struct gained @@ -43,6 +406,143 @@ pub enum DyldSubCacheSlice<'data, E: Endian> { V2(&'data [macho::DyldSubCacheEntryV2]), } +/// An enum of arrays containing dyld cache mappings +#[derive(Debug, Clone, Copy)] +#[non_exhaustive] +pub enum DyldCacheMappingSlice<'data, E = Endianness, R = &'data [u8]> +where + E: Endian, + R: ReadRef<'data>, +{ + /// Corresponds to an array of struct dyld_cache_mapping_info + V1 { + /// The mapping endianness + endian: E, + /// The mapping data + data: R, + /// The slice of mapping info + info: &'data [macho::DyldCacheMappingInfo], + }, + /// Corresponds to an array of struct dyld_cache_mapping_and_slide_info + V2 { + /// The mapping endianness + endian: E, + /// The mapping data + data: R, + /// The slice of mapping info + info: &'data [macho::DyldCacheMappingAndSlideInfo], + }, +} + +impl<'data, E, R> DyldCacheMappingSlice<'data, E, R> +where + E: Endian, + R: ReadRef<'data>, +{ + /// Return a slice iterator + pub fn iter(self) -> DyldCacheMappingIterator<'data, E, R> { + match self { + Self::V1 { endian, data, info } => DyldCacheMappingIterator::V1 { + endian, + data, + iter: info.iter(), + }, + Self::V2 { endian, data, info } => DyldCacheMappingIterator::V2 { + endian, + data, + iter: info.iter(), + }, + } + } + + /// Find the file offset of the image by looking up its address in the mappings. + pub fn address_to_file_offset(&self, address: u64) -> Option { + for mapping in self.iter() { + let mapping_address = mapping.address(); + if address >= mapping_address && address < mapping_address.wrapping_add(mapping.size()) + { + return Some(address - mapping_address + mapping.file_offset()); + } + } + None + } +} + +/// An iterator over all the mappings in a dyld shared cache. +#[derive(Debug)] +pub enum DyldCacheMappingIterator<'data, E = Endianness, R = &'data [u8]> +where + E: Endian, + R: ReadRef<'data>, +{ + /// Corresponds to struct dyld_cache_mapping_info from dyld_cache_format.h. + V1 { + /// The mapping endianness + endian: E, + /// The mapping data + data: R, + /// The mapping info iterator + iter: slice::Iter<'data, macho::DyldCacheMappingInfo>, + }, + /// Corresponds to struct dyld_cache_mapping_and_slide_info from dyld_cache_format.h. + V2 { + /// The mapping endianness + endian: E, + /// The mapping data + data: R, + /// The mapping info iterator + iter: slice::Iter<'data, macho::DyldCacheMappingAndSlideInfo>, + }, +} + +impl<'data, E, R> Iterator for DyldCacheMappingIterator<'data, E, R> +where + E: Endian, + R: ReadRef<'data>, +{ + type Item = DyldCacheMapping<'data, E, R>; + + fn next(&mut self) -> Option { + match self { + Self::V1 { endian, data, iter } => { + let info = iter.next()?; + Some(DyldCacheMapping::V1 { + endian: *endian, + data: *data, + info, + }) + } + Self::V2 { endian, data, iter } => { + let info = iter.next()?; + Some(DyldCacheMapping::V2 { + endian: *endian, + data: *data, + info, + }) + } + } + } +} + +/// An enum of arrays containing dyld cache mappings +#[derive(Clone, Copy)] +#[non_exhaustive] +pub enum DyldCacheSlideInfoSlice<'data, E: Endian> { + /// Corresponds to struct dyld_cache_slide_info5 from dyld_cache_format.h. + V5(&'data macho::DyldCacheSlideInfo5, &'data [U16]), +} + +impl Debug for DyldCacheSlideInfoSlice<'_, E> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::V5(info, _) => f + .debug_struct("DyldCacheSlideInfoSlice::V5") + .field("info", info) + .finish(), + } + } +} + // This is the offset of the end of the images_across_all_subcaches_count field. const MIN_HEADER_SIZE_SUBCACHES_V1: u32 = 0x1c8; @@ -96,11 +596,11 @@ where }; let uuids = v1.iter().map(|e| &e.uuid).chain(v2.iter().map(|e| &e.uuid)); for (&data, uuid) in subcache_data.iter().zip(uuids) { - let sc_header = macho::DyldCacheHeader::::parse(data)?; - if &sc_header.uuid != uuid { + let header = macho::DyldCacheHeader::::parse(data)?; + if &header.uuid != uuid { return Err(Error("Unexpected SubCache UUID")); } - let mappings = sc_header.mappings(endian, data)?; + let mappings = header.mappings(endian, data)?; subcaches.push(DyldSubCache { data, mappings }); } } @@ -109,11 +609,11 @@ where // Other than the UUID verification, the symbols SubCache is currently unused. let _symbols_subcache = match symbols_subcache_data_and_uuid { Some((data, uuid)) => { - let sc_header = macho::DyldCacheHeader::::parse(data)?; - if sc_header.uuid != uuid { + let header = macho::DyldCacheHeader::::parse(data)?; + if header.uuid != uuid { return Err(Error("Unexpected .symbols SubCache UUID")); } - let mappings = sc_header.mappings(endian, data)?; + let mappings = header.mappings(endian, data)?; Some(DyldSubCache { data, mappings }) } None => None, @@ -158,16 +658,25 @@ where } } + /// Return all the mappings in this cache. + pub fn mappings<'cache>( + &'cache self, + ) -> impl Iterator> + 'cache { + self.mappings.iter().chain( + self.subcaches + .iter() + .flat_map(|subcache| subcache.mappings.iter()), + ) + } + /// Find the address in a mapping and return the cache or subcache data it was found in, /// together with the translated file offset. pub fn data_and_offset_for_address(&self, address: u64) -> Option<(R, u64)> { - if let Some(file_offset) = address_to_file_offset(address, self.endian, self.mappings) { + if let Some(file_offset) = self.mappings.address_to_file_offset(address) { return Some((self.data, file_offset)); } for subcache in &self.subcaches { - if let Some(file_offset) = - address_to_file_offset(address, self.endian, subcache.mappings) - { + if let Some(file_offset) = subcache.mappings.address_to_file_offset(address) { return Some((subcache.data, file_offset)); } } @@ -241,6 +750,44 @@ where } } +impl macho::DyldCacheMappingAndSlideInfo { + /// Return the (optional) array of slide information structs + pub fn slide<'data, R: ReadRef<'data>>( + &self, + endian: E, + data: R, + ) -> Result>> { + match self.slide_info_file_size.get(endian) { + 0 => Ok(None), + _ => { + let slide_info_file_offset = self.slide_info_file_offset.get(endian); + let version = data + .read_at::>(slide_info_file_offset) + .read_error("Invalid slide info file offset size or alignment")? + .get(endian); + match version { + 5 => { + let slide = data + .read_at::>(slide_info_file_offset) + .read_error("Invalid dyld cache slide info size or alignment")?; + let page_starts_offset = slide_info_file_offset + .checked_add(mem::size_of::>() as u64) + .read_error("Page starts overflow")?; + let page_starts = data + .read_slice_at::>( + page_starts_offset, + slide.page_starts_count.get(endian) as usize, + ) + .read_error("Invalid page starts size or alignment")?; + Ok(Some(DyldCacheSlideInfoSlice::V5(slide, page_starts))) + } + _ => Err(Error("Unsupported dyld_cache_slide_info version")), + } + } + } + } +} + impl macho::DyldCacheHeader { /// Read the dyld cache header. pub fn parse<'data, R: ReadRef<'data>>(data: R) -> Result<&'data Self> { @@ -274,12 +821,24 @@ impl macho::DyldCacheHeader { &self, endian: E, data: R, - ) -> Result<&'data [macho::DyldCacheMappingInfo]> { - data.read_slice_at::>( - self.mapping_offset.get(endian).into(), - self.mapping_count.get(endian) as usize, - ) - .read_error("Invalid dyld cache mapping size or alignment") + ) -> Result> { + if self.mapping_with_slide_offset.get(endian) != 0 { + let info = data + .read_slice_at::>( + self.mapping_with_slide_offset.get(endian).into(), + self.mapping_with_slide_count.get(endian) as usize, + ) + .read_error("Invalid dyld cache mapping size or alignment")?; + Ok(DyldCacheMappingSlice::V2 { endian, data, info }) + } else { + let info = data + .read_slice_at::>( + self.mapping_offset.get(endian).into(), + self.mapping_count.get(endian) as usize, + ) + .read_error("Invalid dyld cache mapping size or alignment")?; + Ok(DyldCacheMappingSlice::V1 { endian, data, info }) + } } /// Return the information about subcaches, if present. @@ -294,16 +853,16 @@ impl macho::DyldCacheHeader { if header_size >= MIN_HEADER_SIZE_SUBCACHES_V2 { let subcaches = data .read_slice_at::>( - self.subcaches_offset.get(endian).into(), - self.subcaches_count.get(endian) as usize, + self.sub_cache_array_offset.get(endian).into(), + self.sub_cache_array_count.get(endian) as usize, ) .read_error("Invalid dyld subcaches size or alignment")?; Ok(Some(DyldSubCacheSlice::V2(subcaches))) } else if header_size >= MIN_HEADER_SIZE_SUBCACHES_V1 { let subcaches = data .read_slice_at::>( - self.subcaches_offset.get(endian).into(), - self.subcaches_count.get(endian) as usize, + self.sub_cache_array_offset.get(endian).into(), + self.sub_cache_array_count.get(endian) as usize, ) .read_error("Invalid dyld subcaches size or alignment")?; Ok(Some(DyldSubCacheSlice::V1(subcaches))) @@ -315,7 +874,7 @@ impl macho::DyldCacheHeader { /// Return the UUID for the .symbols subcache, if present. pub fn symbols_subcache_uuid(&self, endian: E) -> Option<[u8; 16]> { if self.mapping_offset.get(endian) >= MIN_HEADER_SIZE_SUBCACHES_V1 { - let uuid = self.symbols_subcache_uuid; + let uuid = self.symbol_file_uuid; if uuid != [0; 16] { return Some(uuid); } @@ -331,14 +890,14 @@ impl macho::DyldCacheHeader { ) -> Result<&'data [macho::DyldCacheImageInfo]> { if self.mapping_offset.get(endian) >= MIN_HEADER_SIZE_SUBCACHES_V1 { data.read_slice_at::>( - self.images_across_all_subcaches_offset.get(endian).into(), - self.images_across_all_subcaches_count.get(endian) as usize, + self.images_offset.get(endian).into(), + self.images_count.get(endian) as usize, ) .read_error("Invalid dyld cache image size or alignment") } else { data.read_slice_at::>( - self.images_offset.get(endian).into(), - self.images_count.get(endian) as usize, + self.images_offset_old.get(endian).into(), + self.images_count_old.get(endian) as usize, ) .read_error("Invalid dyld cache image size or alignment") } @@ -355,30 +914,14 @@ impl macho::DyldCacheImageInfo { } /// Find the file offset of the image by looking up its address in the mappings. - pub fn file_offset( + pub fn file_offset<'data, R: ReadRef<'data>>( &self, endian: E, - mappings: &[macho::DyldCacheMappingInfo], + mappings: &DyldCacheMappingSlice<'data, E, R>, ) -> Result { let address = self.address.get(endian); - address_to_file_offset(address, endian, mappings) + mappings + .address_to_file_offset(address) .read_error("Invalid dyld cache image address") } } - -/// Find the file offset of the image by looking up its address in the mappings. -pub fn address_to_file_offset( - address: u64, - endian: E, - mappings: &[macho::DyldCacheMappingInfo], -) -> Option { - for mapping in mappings { - let mapping_address = mapping.address.get(endian); - if address >= mapping_address - && address < mapping_address.wrapping_add(mapping.size.get(endian)) - { - return Some(address - mapping_address + mapping.file_offset.get(endian)); - } - } - None -}