diff --git a/src/macho.rs b/src/macho.rs index 1c6032db..48e3bd49 100644 --- a/src/macho.rs +++ b/src/macho.rs @@ -500,6 +500,17 @@ pub struct DyldCacheMappingAndSlideInfo { pub init_prot: U32, } +/// Corresponds to struct dyld_cache_image_info from dyld_cache_format.h. +#[derive(Debug, Clone, Copy)] +#[repr(C)] +pub struct DyldCacheImageInfo { + pub address: U64, + pub mod_time: U64, + pub inode: U64, + pub path_file_offset: U32, + pub pad: U32, +} + /// Corresponds to struct dyld_cache_slide_info5 from dyld_cache_format.h. #[derive(Debug, Clone, Copy)] #[repr(C)] @@ -513,17 +524,6 @@ pub struct DyldCacheSlideInfo5 { 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)] -pub struct DyldCacheImageInfo { - pub address: U64, - pub mod_time: U64, - pub inode: U64, - pub path_file_offset: U32, - pub pad: U32, -} - /// Corresponds to struct dyld_cache_slide_pointer5 from dyld_cache_format.h. #[derive(Debug, Clone, Copy)] pub struct DyldCacheSlidePointer5(pub u64); diff --git a/src/read/macho/dyld_cache.rs b/src/read/macho/dyld_cache.rs index fef1e8e0..35972e6b 100644 --- a/src/read/macho/dyld_cache.rs +++ b/src/read/macho/dyld_cache.rs @@ -32,378 +32,223 @@ where 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, - }, +/// A slice of structs describing each subcache. The struct gained +/// an additional field (the file suffix) in dyld-1042.1 (macOS 13 / iOS 16), +/// so this is an enum of the two possible slice types. +#[derive(Debug, Clone, Copy)] +#[non_exhaustive] +pub enum DyldSubCacheSlice<'data, E: Endian> { + /// V1, used between dyld-940 and dyld-1042.1. + V1(&'data [macho::DyldSubCacheEntryV1]), + /// V2, used since dyld-1042.1. + V2(&'data [macho::DyldSubCacheEntryV2]), } -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() - } -} +// This is the offset of the end of the images_across_all_subcaches_count field. +const MIN_HEADER_SIZE_SUBCACHES_V1: u32 = 0x1c8; -impl<'data, E, R> DyldCacheMapping<'data, E, R> +// This is the offset of the end of the cacheSubType field. +// This field comes right after the images_across_all_subcaches_count field, +// and we don't currently have it in our definition of the DyldCacheHeader type. +const MIN_HEADER_SIZE_SUBCACHES_V2: u32 = 0x1d0; + +impl<'data, E, R> DyldCache<'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), + /// Parse the raw dyld shared cache data. + /// + /// For shared caches from macOS 12 / iOS 15 and above, the subcache files need to be + /// supplied as well, in the correct order, with the `.symbols` subcache last (if present). + /// For example, `data` would be the data for `dyld_shared_cache_x86_64`, + /// and `subcache_data` would be the data for `[dyld_shared_cache_x86_64.1, dyld_shared_cache_x86_64.2, ...]`. + pub fn parse(data: R, subcache_data: &[R]) -> Result { + let header = macho::DyldCacheHeader::parse(data)?; + let (arch, endian) = header.parse_magic()?; + let mappings = header.mappings(endian, data)?; + + let symbols_subcache_uuid = header.symbols_subcache_uuid(endian); + let subcaches_info = header.subcaches(endian, data)?; + let subcaches_count = match subcaches_info { + Some(DyldSubCacheSlice::V1(subcaches)) => subcaches.len(), + Some(DyldSubCacheSlice::V2(subcaches)) => subcaches.len(), + None => 0, + }; + if subcache_data.len() != subcaches_count + symbols_subcache_uuid.is_some() as usize { + return Err(Error("Incorrect number of SubCaches")); } - } - /// 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), + // Split out the .symbols subcache data from the other subcaches. + let (symbols_subcache_data_and_uuid, subcache_data) = + if let Some(symbols_uuid) = symbols_subcache_uuid { + let (sym_data, rest_data) = subcache_data.split_last().unwrap(); + (Some((*sym_data, symbols_uuid)), rest_data) + } else { + (None, subcache_data) + }; + + // Read the regular SubCaches, if present. + let mut subcaches = Vec::new(); + if let Some(subcaches_info) = subcaches_info { + let (v1, v2) = match subcaches_info { + DyldSubCacheSlice::V1(s) => (s, &[][..]), + DyldSubCacheSlice::V2(s) => (&[][..], s), + }; + 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 header = macho::DyldCacheHeader::::parse(data)?; + if &header.uuid != uuid { + return Err(Error("Unexpected SubCache UUID")); + } + let mappings = header.mappings(endian, data)?; + subcaches.push(DyldSubCache { data, mappings }); + } } + + // Read the .symbols SubCache, if present. + // Other than the UUID verification, the symbols SubCache is currently unused. + let _symbols_subcache = match symbols_subcache_data_and_uuid { + Some((data, uuid)) => { + let header = macho::DyldCacheHeader::::parse(data)?; + if header.uuid != uuid { + return Err(Error("Unexpected .symbols SubCache UUID")); + } + let mappings = header.mappings(endian, data)?; + Some(DyldSubCache { data, mappings }) + } + None => None, + }; + + let images = header.images(endian, data)?; + Ok(DyldCache { + endian, + data, + subcaches, + mappings, + images, + arch, + }) } - /// 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), - } + /// Get the architecture type of the file. + pub fn architecture(&self) -> Architecture { + self.arch } - /// 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), + /// Get the endianness of the file. + #[inline] + pub fn endianness(&self) -> Endianness { + if self.is_little_endian() { + Endianness::Little + } else { + Endianness::Big } } - /// 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), - } + /// Return true if the file is little endian, false if it is big endian. + pub fn is_little_endian(&self) -> bool { + self.endian.is_little_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"), + /// Iterate over the images in this cache. + pub fn images<'cache>(&'cache self) -> DyldCacheImageIterator<'data, 'cache, E, R> { + DyldCacheImageIterator { + cache: self, + iter: self.images.iter(), } } - /// 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()) - } + /// 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) = self.mappings.address_to_file_offset(address) { + return Some((self.data, file_offset)); + } + for subcache in &self.subcaches { + if let Some(file_offset) = subcache.mappings.address_to_file_offset(address) { + return Some((subcache.data, file_offset)); } } + None } } -/// An iterator over relocations in a mapping +/// An iterator over all the images (dylibs) in the dyld shared cache. #[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> +pub struct DyldCacheImageIterator<'data, 'cache, E = Endianness, R = &'data [u8]> 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 - } + cache: &'cache DyldCache<'data, E, R>, + iter: slice::Iter<'data, macho::DyldCacheImageInfo>, } -impl<'data, E, R> Iterator for DyldCacheRelocationMappingIterator<'data, E, R> +impl<'data, 'cache, E, R> Iterator for DyldCacheImageIterator<'data, 'cache, 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; - } + type Item = DyldCacheImage<'data, 'cache, E, R>; - *page_index += 1; - } else { - return None; - } - } - } - }, - } + fn next(&mut self) -> Option> { + let image_info = self.iter.next()?; + Some(DyldCacheImage { + cache: self.cache, + image_info, + }) } } -/// A versioned iterator over relocations in a page +/// One image (dylib) from inside the dyld shared cache. #[derive(Debug)] -pub enum DyldCacheRelocationPageIterator<'data, E = Endianness, R = &'data [u8]> +pub struct DyldCacheImage<'data, 'cache, 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, - }, + pub(crate) cache: &'cache DyldCache<'data, E, R>, + image_info: &'data macho::DyldCacheImageInfo, } -impl<'data, E, R> Iterator for DyldCacheRelocationPageIterator<'data, E, R> +impl<'data, 'cache, E, R> DyldCacheImage<'data, 'cache, 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 - } - } - } + /// The file system path of this image. + pub fn path(&self) -> Result<&'data str> { + let path = self.image_info.path(self.cache.endian, self.cache.data)?; + // The path should always be ascii, so from_utf8 should always succeed. + let path = core::str::from_utf8(path).map_err(|_| Error("Path string not valid utf-8"))?; + Ok(path) } -} - -/// 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() + /// The subcache data which contains the Mach-O header for this image, + /// together with the file offset at which this image starts. + pub fn image_data_and_offset(&self) -> Result<(R, u64)> { + let address = self.image_info.address.get(self.cache.endian); + self.cache + .data_and_offset_for_address(address) + .ok_or(Error("Address not found in any mapping")) } -} -/// A slice of structs describing each subcache. The struct gained -/// an additional field (the file suffix) in dyld-1042.1 (macOS 13 / iOS 16), -/// so this is an enum of the two possible slice types. -#[derive(Debug, Clone, Copy)] -#[non_exhaustive] -pub enum DyldSubCacheSlice<'data, E: Endian> { - /// V1, used between dyld-940 and dyld-1042.1. - V1(&'data [macho::DyldSubCacheEntryV1]), - /// V2, used since dyld-1042.1. - V2(&'data [macho::DyldSubCacheEntryV2]), + /// Parse this image into an Object. + pub fn parse_object(&self) -> Result> { + File::parse_dyld_cache_image(self) + } } /// An enum of arrays containing dyld cache mappings @@ -524,270 +369,387 @@ where } } -/// An enum of arrays containing dyld cache mappings +/// Information about a mapping. #[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; - -// This is the offset of the end of the cacheSubType field. -// This field comes right after the images_across_all_subcaches_count field, -// and we don't currently have it in our definition of the DyldCacheHeader type. -const MIN_HEADER_SIZE_SUBCACHES_V2: u32 = 0x1d0; - -impl<'data, E, R> DyldCache<'data, E, R> +pub enum DyldCacheMapping<'data, E = Endianness, R = &'data [u8]> where E: Endian, R: ReadRef<'data>, { - /// Parse the raw dyld shared cache data. - /// - /// For shared caches from macOS 12 / iOS 15 and above, the subcache files need to be - /// supplied as well, in the correct order, with the `.symbols` subcache last (if present). - /// For example, `data` would be the data for `dyld_shared_cache_x86_64`, - /// and `subcache_data` would be the data for `[dyld_shared_cache_x86_64.1, dyld_shared_cache_x86_64.2, ...]`. - pub fn parse(data: R, subcache_data: &[R]) -> Result { - let header = macho::DyldCacheHeader::parse(data)?; - let (arch, endian) = header.parse_magic()?; - let mappings = header.mappings(endian, data)?; - - let symbols_subcache_uuid = header.symbols_subcache_uuid(endian); - let subcaches_info = header.subcaches(endian, data)?; - let subcaches_count = match subcaches_info { - Some(DyldSubCacheSlice::V1(subcaches)) => subcaches.len(), - Some(DyldSubCacheSlice::V2(subcaches)) => subcaches.len(), - None => 0, - }; - if subcache_data.len() != subcaches_count + symbols_subcache_uuid.is_some() as usize { - return Err(Error("Incorrect number of SubCaches")); - } + /// 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, + }, +} - // Split out the .symbols subcache data from the other subcaches. - let (symbols_subcache_data_and_uuid, subcache_data) = - if let Some(symbols_uuid) = symbols_subcache_uuid { - let (sym_data, rest_data) = subcache_data.split_last().unwrap(); - (Some((*sym_data, symbols_uuid)), rest_data) - } else { - (None, subcache_data) - }; +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() + } +} - // Read the regular SubCaches, if present. - let mut subcaches = Vec::new(); - if let Some(subcaches_info) = subcaches_info { - let (v1, v2) = match subcaches_info { - DyldSubCacheSlice::V1(s) => (s, &[][..]), - DyldSubCacheSlice::V2(s) => (&[][..], s), - }; - 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 header = macho::DyldCacheHeader::::parse(data)?; - if &header.uuid != uuid { - return Err(Error("Unexpected SubCache UUID")); - } - let mappings = header.mappings(endian, data)?; - subcaches.push(DyldSubCache { data, mappings }); - } +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), } - - // Read the .symbols SubCache, if present. - // Other than the UUID verification, the symbols SubCache is currently unused. - let _symbols_subcache = match symbols_subcache_data_and_uuid { - Some((data, uuid)) => { - let header = macho::DyldCacheHeader::::parse(data)?; - if header.uuid != uuid { - return Err(Error("Unexpected .symbols SubCache UUID")); - } - let mappings = header.mappings(endian, data)?; - Some(DyldSubCache { data, mappings }) - } - None => None, - }; - - let images = header.images(endian, data)?; - Ok(DyldCache { - endian, - data, - subcaches, - mappings, - images, - arch, - }) } - /// Get the architecture type of the file. - pub fn architecture(&self) -> Architecture { - self.arch + /// 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), + } } - /// Get the endianness of the file. - #[inline] - pub fn endianness(&self) -> Endianness { - if self.is_little_endian() { - Endianness::Little - } else { - Endianness::Big + /// 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), } } - /// Return true if the file is little endian, false if it is big endian. - pub fn is_little_endian(&self) -> bool { - self.endian.is_little_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), + } } - /// Iterate over the images in this cache. - pub fn images<'cache>(&'cache self) -> DyldCacheImageIterator<'data, 'cache, E, R> { - DyldCacheImageIterator { - cache: self, - iter: self.images.iter(), + /// 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), } } - /// 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()), - ) + /// 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"), + } } - /// 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) = self.mappings.address_to_file_offset(address) { - return Some((self.data, file_offset)); - } - for subcache in &self.subcaches { - if let Some(file_offset) = subcache.mappings.address_to_file_offset(address) { - return Some((subcache.data, file_offset)); + /// 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()) + } } } - None } } -/// An iterator over all the images (dylibs) in the dyld shared cache. -#[derive(Debug)] -pub struct DyldCacheImageIterator<'data, 'cache, E = Endianness, R = &'data [u8]> +/// 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>, { - cache: &'cache DyldCache<'data, E, R>, - iter: slice::Iter<'data, macho::DyldCacheImageInfo>, + 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; + } + } + } + }, + } + } } -impl<'data, 'cache, E, R> Iterator for DyldCacheImageIterator<'data, 'cache, E, R> -where - E: Endian, - R: ReadRef<'data>, -{ - type Item = DyldCacheImage<'data, 'cache, E, R>; +/// 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]), +} - fn next(&mut self) -> Option> { - let image_info = self.iter.next()?; - Some(DyldCacheImage { - cache: self.cache, - image_info, - }) +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(), + } } } -/// One image (dylib) from inside the dyld shared cache. +/// A versioned iterator over relocations in a page #[derive(Debug)] -pub struct DyldCacheImage<'data, 'cache, E = Endianness, R = &'data [u8]> +pub enum DyldCacheRelocationPageIterator<'data, E = Endianness, R = &'data [u8]> where E: Endian, R: ReadRef<'data>, { - pub(crate) cache: &'cache DyldCache<'data, E, R>, - image_info: &'data macho::DyldCacheImageInfo, + /// 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, 'cache, E, R> DyldCacheImage<'data, 'cache, E, R> +impl<'data, E, R> Iterator for DyldCacheRelocationPageIterator<'data, E, R> where E: Endian, R: ReadRef<'data>, { - /// The file system path of this image. - pub fn path(&self) -> Result<&'data str> { - let path = self.image_info.path(self.cache.endian, self.cache.data)?; - // The path should always be ascii, so from_utf8 should always succeed. - let path = core::str::from_utf8(path).map_err(|_| Error("Path string not valid utf-8"))?; - Ok(path) - } - - /// The subcache data which contains the Mach-O header for this image, - /// together with the file offset at which this image starts. - pub fn image_data_and_offset(&self) -> Result<(R, u64)> { - let address = self.image_info.address.get(self.cache.endian); - self.cache - .data_and_offset_for_address(address) - .ok_or(Error("Address not found in any mapping")) - } + type Item = Result; - /// Parse this image into an Object. - pub fn parse_object(&self) -> Result> { - File::parse_dyld_cache_image(self) - } -} + 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); -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))) + let next = pointer.next(); + if next == 0 { + *page_offset = None; + } else { + *page_offset = Some(offset + (next * 8)); } - _ => Err(Error("Unsupported dyld_cache_slide_info version")), + + 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() + } +} + impl macho::DyldCacheHeader { /// Read the dyld cache header. pub fn parse<'data, R: ReadRef<'data>>(data: R) -> Result<&'data Self> { @@ -925,3 +887,41 @@ impl macho::DyldCacheImageInfo { .read_error("Invalid dyld cache image address") } } + +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")), + } + } + } + } +}