From d9923394606330f839476f7fb90056f311f0d8bc Mon Sep 17 00:00:00 2001 From: Andrew Palm Date: Sun, 27 Feb 2022 18:08:39 -0500 Subject: [PATCH 1/2] mach.parse: Handle DyldExportsTrie Fixes missing exports. --- src/mach/exports.rs | 46 ++++++++++++++++++++++++++++++++++++++++ src/mach/load_command.rs | 2 +- src/mach/mod.rs | 5 +++++ 3 files changed, 52 insertions(+), 1 deletion(-) diff --git a/src/mach/exports.rs b/src/mach/exports.rs index 4342bc245..8b27d9b3e 100644 --- a/src/mach/exports.rs +++ b/src/mach/exports.rs @@ -294,6 +294,30 @@ impl<'a> ExportTrie<'a> { location, } } + + /// Create a new, lazy, zero-copy export trie from the `LinkeditDataCommand` `command` + pub fn new_from_linkedit_data_command( + bytes: &'a [u8], + command: &load_command::LinkeditDataCommand, + ) -> Self { + let start = command.dataoff as usize; + // FIXME: Ideally, this should validate `command`, but the best we can + // do for now is return an empty `Range`. + let location = match start + .checked_add(command.datasize as usize) + .and_then(|end| bytes.get(start..end).map(|_| start..end)) + { + Some(location) => location, + None => { + log::warn!("Invalid `DyldInfo` `command`."); + 0..0 + } + }; + ExportTrie { + data: bytes, + location, + } + } } impl<'a> Debug for ExportTrie<'a> { @@ -331,6 +355,28 @@ mod tests { assert_eq!(exports.len() as usize, 3usize) } + #[test] + fn export_trie_linkedit_data() { + const EXPORTS: [u8; 64] = [ + 0x00, 0x01, 0x5f, 0x00, 0x05, 0x00, 0x02, 0x5f, 0x6d, 0x68, 0x5f, 0x65, 0x78, 0x65, + 0x63, 0x75, 0x74, 0x65, 0x5f, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x00, 0x1f, 0x6d, + 0x61, 0x00, 0x23, 0x02, 0x00, 0x00, 0x00, 0x00, 0x02, 0x78, 0x69, 0x6d, 0x75, 0x6d, + 0x00, 0x30, 0x69, 0x6e, 0x00, 0x35, 0x03, 0x00, 0xc0, 0x1e, 0x00, 0x03, 0x00, 0xd0, + 0x1e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + ]; + let exports = &EXPORTS[..]; + let libs = vec!["/usr/lib/libderp.so", "/usr/lib/libthuglife.so"]; + let command = load_command::LinkeditDataCommand { + datasize: exports.len() as u32, + ..Default::default() + }; + let trie = ExportTrie::new_from_linkedit_data_command(exports, &command); + println!("trie: {:#?}", &trie); + let exports = trie.exports(&libs).unwrap(); + println!("len: {} exports: {:#?}", exports.len(), &exports); + assert_eq!(exports.len() as usize, 3usize); + } + #[test] fn invalid_range() { let mut command = load_command::DyldInfoCommand::default(); diff --git a/src/mach/load_command.rs b/src/mach/load_command.rs index 063c2c19c..f549cc0b0 100644 --- a/src/mach/load_command.rs +++ b/src/mach/load_command.rs @@ -947,7 +947,7 @@ pub const SIZEOF_RPATH_COMMAND: usize = 12; /// The linkedit_data_command contains the offsets and sizes of a blob /// of data in the __LINKEDIT segment. #[repr(C)] -#[derive(Debug, Clone, Copy, Pread, Pwrite, IOread, IOwrite, SizeWith)] +#[derive(Default, Debug, Clone, Copy, Pread, Pwrite, IOread, IOwrite, SizeWith)] pub struct LinkeditDataCommand { /// LC_CODE_SIGNATURE, LC_SEGMENT_SPLIT_INFO, LC_FUNCTION_STARTS, LC_DATA_IN_CODE, /// LC_DYLIB_CODE_SIGN_DRS, LC_LINKER_OPTIMIZATION_HINT, LC_DYLD_EXPORTS_TRIE, or LC_DYLD_CHAINED_FIXUPS. diff --git a/src/mach/mod.rs b/src/mach/mod.rs index f1984cbd9..48830e023 100644 --- a/src/mach/mod.rs +++ b/src/mach/mod.rs @@ -210,6 +210,11 @@ impl<'a> MachO<'a> { export_trie = Some(exports::ExportTrie::new(bytes, &command)); bind_interpreter = Some(imports::BindInterpreter::new(bytes, &command)); } + load_command::CommandVariant::DyldExportsTrie(command) => { + export_trie = Some(exports::ExportTrie::new_from_linkedit_data_command( + bytes, &command, + )); + } load_command::CommandVariant::Unixthread(command) => { // dyld cares only about the first LC_UNIXTHREAD if unixthread_entry_address.is_none() { From 2a3d7bdd25e823d856ee6f6e0311b6b873797a32 Mon Sep 17 00:00:00 2001 From: Andrew Palm Date: Tue, 22 Mar 2022 11:53:37 -0400 Subject: [PATCH 2/2] [mach] Add `new_impl` to `ExportTrie` --- src/.DS_Store | Bin 0 -> 6148 bytes src/mach/exports.rs | 51 ++++++++++++++++++-------------------------- 2 files changed, 21 insertions(+), 30 deletions(-) create mode 100644 src/.DS_Store diff --git a/src/.DS_Store b/src/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..210ce8679d22bd25bb7829b26b220c2e08e53e3e GIT binary patch literal 6148 zcmeHKy-EW?5T3mpm|&Aeu#&U1%p08HVrw8Tpo!542^R##@}9%iN^ER=0d z#?!OTV3gLf&G8o%;Cr`0$8

no#Nf&F1oRN7wa?S(+rnX*xut`Fe2pxb^Y0zb@1V0w;rwVg%6mNLYhGVtiQt8Xf4XI zGUxsK?#qs>xu~6`OV)Mq3U8y&yaCPgw?$VO+}!2^y(wS{m;!|Y{C$Yf7-Pju(0@A6`6B=@gxed&Z5{w4#aJ;DL*pdxE9_ literal 0 HcmV?d00001 diff --git a/src/mach/exports.rs b/src/mach/exports.rs index 8b27d9b3e..2ce4f89e0 100644 --- a/src/mach/exports.rs +++ b/src/mach/exports.rs @@ -265,22 +265,11 @@ impl<'a> ExportTrie<'a> { } } - /// Walk the export trie for symbols exported by this binary, using the provided `libs` to resolve re-exports - pub fn exports(&self, libs: &[&'a str]) -> error::Result>> { - let offset = self.location.start; - let current_symbol = String::new(); - let mut exports = Vec::new(); - self.walk_trie(libs, current_symbol, offset, &mut exports)?; - Ok(exports) - } - - /// Create a new, lazy, zero-copy export trie from the `DyldInfo` `command` - pub fn new(bytes: &'a [u8], command: &load_command::DyldInfoCommand) -> Self { - let start = command.export_off as usize; + fn new_impl(bytes: &'a [u8], start: usize, size: usize) -> Self { // FIXME: Ideally, this should validate `command`, but the best we can // do for now is return an empty `Range`. let location = match start - .checked_add(command.export_size as usize) + .checked_add(size) .and_then(|end| bytes.get(start..end).map(|_| start..end)) { Some(location) => location, @@ -295,28 +284,30 @@ impl<'a> ExportTrie<'a> { } } + /// Walk the export trie for symbols exported by this binary, using the provided `libs` to resolve re-exports + pub fn exports(&self, libs: &[&'a str]) -> error::Result>> { + let offset = self.location.start; + let current_symbol = String::new(); + let mut exports = Vec::new(); + self.walk_trie(libs, current_symbol, offset, &mut exports)?; + Ok(exports) + } + + /// Create a new, lazy, zero-copy export trie from the `DyldInfo` `command` + pub fn new(bytes: &'a [u8], command: &load_command::DyldInfoCommand) -> Self { + Self::new_impl( + bytes, + command.export_off as usize, + command.export_size as usize, + ) + } + /// Create a new, lazy, zero-copy export trie from the `LinkeditDataCommand` `command` pub fn new_from_linkedit_data_command( bytes: &'a [u8], command: &load_command::LinkeditDataCommand, ) -> Self { - let start = command.dataoff as usize; - // FIXME: Ideally, this should validate `command`, but the best we can - // do for now is return an empty `Range`. - let location = match start - .checked_add(command.datasize as usize) - .and_then(|end| bytes.get(start..end).map(|_| start..end)) - { - Some(location) => location, - None => { - log::warn!("Invalid `DyldInfo` `command`."); - 0..0 - } - }; - ExportTrie { - data: bytes, - location, - } + Self::new_impl(bytes, command.dataoff as usize, command.datasize as usize) } }