Skip to content

Support Mac for Unity / Mono #53

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 8 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ libm = { version = "0.2.7", optional = true }
wasi = { version = "0.11.0+wasi-snapshot-preview1", default-features = false }

[features]
std = ["alloc"]
alloc = []
derive = ["asr-derive"]
flags = ["bitflags"]
Expand Down
123 changes: 123 additions & 0 deletions src/file_format/macho.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
//! Support for parsing MachO files

use crate::{Address, PointerSize, Process};

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<Address> {
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::<u32>(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 pointer_size(process: &Process, address: Address) -> Option<PointerSize> {
let magic: u32 = process.read(address).ok()?;
match magic {
MH_MAGIC_64 | MH_CIGAM_64 => Some(PointerSize::Bit64),
MH_MAGIC_32 | MH_CIGAM_32 => Some(PointerSize::Bit32),
_ => 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<Address> {
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<u32> {
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<T: bytemuck::CheckedBitPattern>(slice: &[u8], address: usize) -> Result<T, bytemuck::checked::CheckedCastError> {
let size = mem::size_of::<T>();
let slice_src = &slice[address..(address + size)];
bytemuck::checked::try_from_bytes(slice_src).cloned()
}
1 change: 1 addition & 0 deletions src/file_format/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@

pub mod elf;
pub mod pe;
pub mod macho;
Loading