diff --git a/src/file_format/macho.rs b/src/file_format/macho.rs new file mode 100644 index 0000000..1061956 --- /dev/null +++ b/src/file_format/macho.rs @@ -0,0 +1,123 @@ +//! Support for parsing MachO files + +use crate::{Process, Address}; + +use core::mem; + +// Magic mach-o header constants from: +// https://opensource.apple.com/source/xnu/xnu-4570.71.2/EXTERNAL_HEADERS/mach-o/loader.h.auto.html +const MH_MAGIC_32: u32 = 0xfeedface; +const MH_CIGAM_32: u32 = 0xcefaedfe; +const MH_MAGIC_64: u32 = 0xfeedfacf; +const MH_CIGAM_64: u32 = 0xcffaedfe; + +struct MachOFormatOffsets { + number_of_commands: usize, + load_commands: usize, + command_size: usize, + symbol_table_offset: usize, + number_of_symbols: usize, + string_table_offset: usize, + nlist_value: usize, + size_of_nlist_item: usize, +} + +impl MachOFormatOffsets { + const fn new() -> Self { + // offsets taken from: + // - https://github.com/hackf5/unityspy/blob/master/src/HackF5.UnitySpy/Offsets/MachOFormatOffsets.cs + // - https://opensource.apple.com/source/xnu/xnu-4570.71.2/EXTERNAL_HEADERS/mach-o/loader.h.auto.html + MachOFormatOffsets { + number_of_commands: 0x10, + load_commands: 0x20, + command_size: 0x04, + symbol_table_offset: 0x08, + number_of_symbols: 0x0c, + string_table_offset: 0x10, + nlist_value: 0x08, + size_of_nlist_item: 0x10, + } + } +} + +/// Scans the range for a page that begins with MachO Magic +pub fn scan_macho_page(process: &Process, range: (Address, u64)) -> Option
{ + const PAGE_SIZE: u64 = 0x1000; + let (addr, len) = range; + // negation mod PAGE_SIZE + let distance_to_page = (PAGE_SIZE - (addr.value() % PAGE_SIZE)) % PAGE_SIZE; + // round up to the next multiple of PAGE_SIZE + let first_page = addr + distance_to_page; + for i in 0..((len - distance_to_page) / PAGE_SIZE) { + let a = first_page + (i * PAGE_SIZE); + match process.read::(a) { + Ok(MH_MAGIC_64 | MH_CIGAM_64 | MH_MAGIC_32 | MH_CIGAM_32) => { + return Some(a); + } + _ => () + } + } + None +} + +/// Determines whether a MachO header at the address is 64-bit or 32-bit +pub fn is_64_bit(process: &Process, address: Address) -> Option { + let magic: u32 = process.read(address).ok()?; + match magic { + MH_MAGIC_64 | MH_CIGAM_64 => Some(true), + MH_MAGIC_32 | MH_CIGAM_32 => Some(false), + _ => None + } +} + +/// Finds the address of a function from a MachO module range and file contents. +pub fn get_function_address(process: &Process, range: (Address, u64), macho_bytes: &[u8], function_name: &[u8]) -> Option
{ + let function_offset: u32 = get_function_offset(&macho_bytes, function_name)?; + let function_address = scan_macho_page(process, range)? + function_offset; + let actual: [u8; 0x100] = process.read(function_address).ok()?; + let expected: [u8; 0x100] = slice_read(&macho_bytes, function_offset as usize).ok()?; + if actual != expected { return None; } + Some(function_address) +} + +/// Finds the offset of a function in the bytes of a MachO file. +pub fn get_function_offset(macho_bytes: &[u8], function_name: &[u8]) -> Option { + let macho_offsets = MachOFormatOffsets::new(); + let number_of_commands: u32 = slice_read(macho_bytes, macho_offsets.number_of_commands).ok()?; + let function_name_len = function_name.len(); + + let mut offset_to_next_command: usize = macho_offsets.load_commands as usize; + for _i in 0..number_of_commands { + // Check if load command is LC_SYMTAB + let next_command: i32 = slice_read(macho_bytes, offset_to_next_command).ok()?; + if next_command == 2 { + let symbol_table_offset: u32 = slice_read(macho_bytes, offset_to_next_command + macho_offsets.symbol_table_offset).ok()?; + let number_of_symbols: u32 = slice_read(macho_bytes, offset_to_next_command + macho_offsets.number_of_symbols).ok()?; + let string_table_offset: u32 = slice_read(macho_bytes, offset_to_next_command + macho_offsets.string_table_offset).ok()?; + + for j in 0..(number_of_symbols as usize) { + let symbol_name_offset: u32 = slice_read(macho_bytes, symbol_table_offset as usize + (j * macho_offsets.size_of_nlist_item)).ok()?; + let string_offset = string_table_offset as usize + symbol_name_offset as usize; + let symbol_name: &[u8] = &macho_bytes[string_offset..(string_offset + function_name_len + 1)]; + + if symbol_name[function_name_len] == 0 && symbol_name.starts_with(function_name) { + return Some(slice_read(macho_bytes, symbol_table_offset as usize + (j * macho_offsets.size_of_nlist_item) + macho_offsets.nlist_value).ok()?); + } + } + + break; + } else { + let command_size: u32 = slice_read(macho_bytes, offset_to_next_command + macho_offsets.command_size).ok()?; + offset_to_next_command += command_size as usize; + } + } + None +} + +/// Reads a value of the type specified from the slice at the address +/// given. +pub fn slice_read(slice: &[u8], address: usize) -> Result { + let size = mem::size_of::(); + let slice_src = &slice[address..(address + size)]; + bytemuck::checked::try_from_bytes(slice_src).cloned() +} diff --git a/src/file_format/mod.rs b/src/file_format/mod.rs index 14b8a83..f26597d 100644 --- a/src/file_format/mod.rs +++ b/src/file_format/mod.rs @@ -2,3 +2,4 @@ pub mod elf; pub mod pe; +pub mod macho;