diff --git a/intel_mac.rs b/intel_mac.rs index d43465c..f3141b0 100644 --- a/intel_mac.rs +++ b/intel_mac.rs @@ -77,15 +77,24 @@ fn patch_command(cmd_type: u32, buf: &mut [u8], file_len: usize) { } } -/// Find section data in an Intel Mac Mach-O executable +/// Find section data in an Intel Mac Mach-O executable. /// -/// Searches for the sentinel "<~sui-data~>" from the end of the executable, -/// reads the data length, and returns a reference to the section data. -/// -/// # Returns -/// -/// Returns `Some(&[u8])` if section data is found, `None` otherwise +/// Searches the current executable for the `<~sui-data~>` sentinel and +/// returns the embedded payload, or `None` if no payload is present. pub fn find_section() -> std::io::Result> { + let exe = std::env::current_exe()?; + find_section_in_file(&exe) +} + +/// Find section data in an Intel Mac Mach-O image on disk. +/// +/// Identical to [`find_section`] but reads from `path` instead of the current +/// executable, so it can also recover the payload from a dylib loaded into +/// the process. Used by `find_section_in_current_image` on x86_64 macOS, +/// where the payload is appended past the Mach-O sections (rather than +/// written as a real named section) and so cannot be read with +/// `getsectiondata`. +pub fn find_section_in_file(path: &std::path::Path) -> std::io::Result> { use std::io::{Read, Seek, SeekFrom}; // Construct sentinel from reversed string to prevent it from existing as contiguous @@ -100,8 +109,7 @@ pub fn find_section() -> std::io::Result> { sentinel.extend_from_slice(&magic); let sentinel = sentinel.as_slice(); - let exe = std::env::current_exe()?; - let mut file = std::fs::File::open(exe)?; + let mut file = std::fs::File::open(path)?; // Get file size let file_size = file.seek(SeekFrom::End(0))?; diff --git a/lib.rs b/lib.rs index 9aca935..084eb0a 100644 --- a/lib.rs +++ b/lib.rs @@ -992,8 +992,46 @@ mod macho { ) -> std::io::Result> { #[cfg(target_arch = "x86_64")] { - // TODO: Intel Mac dylib support - Ok(None) + // Intel Mach-O embedding does not write a real named section + // (see `Macho::build` for x86_64): the payload is appended past + // __LINKEDIT and located at runtime by scanning for the + // `<~sui-data~>` sentinel. `getsectiondata` therefore cannot + // find it. Resolve the file backing the current image with + // `dladdr` and run the sentinel scan against that file instead. + use std::ffi::CStr; + use std::os::raw::c_char; + use std::path::PathBuf; + + #[repr(C)] + #[allow(non_camel_case_types)] + struct Dl_info { + dli_fname: *const c_char, + dli_fbase: *mut std::ffi::c_void, + dli_sname: *const c_char, + dli_saddr: *mut std::ffi::c_void, + } + + extern "C" { + fn dladdr(addr: *const std::ffi::c_void, info: *mut Dl_info) -> std::ffi::c_int; + } + + let mut info: Dl_info = unsafe { std::mem::zeroed() }; + let self_addr = find_section_in_current_image as *const std::ffi::c_void; + let ret = unsafe { dladdr(self_addr, &mut info) }; + if ret == 0 || info.dli_fname.is_null() { + return Ok(None); + } + + // SAFETY: `dladdr` succeeded with a non-null `dli_fname`, which + // points at a NUL-terminated path owned by dyld for the lifetime + // of the loaded image. We copy it out immediately. + let fname = unsafe { CStr::from_ptr(info.dli_fname) }; + let Ok(fname) = fname.to_str() else { + return Ok(None); + }; + let path = PathBuf::from(fname); + + super::intel_mac::find_section_in_file(&path) } #[cfg(not(target_arch = "x86_64"))]