fishhook 0.2.0

A library that enables dynamically rebinding symbols in Mach-O binaries at runtime
Documentation
use super::Rebinding;
use crate::arch::platform::arch::{MachHeaderT, NlistT, SectionT, SegmentCommandT};
use goblin::mach::constants::{
    SECTION_TYPE, SEG_DATA, S_LAZY_SYMBOL_POINTERS, S_NON_LAZY_SYMBOL_POINTERS,
};
use goblin::mach::load_command::{DysymtabCommand, SymtabCommand, LC_DYSYMTAB, LC_SYMTAB};
use mach2::kern_return::KERN_SUCCESS;
use mach2::traps::mach_task_self;
use mach2::vm::mach_vm_protect;
use mach2::vm_prot::{VM_PROT_COPY, VM_PROT_READ, VM_PROT_WRITE};
use mach2::vm_types::{mach_vm_address_t, mach_vm_size_t};
use std::ffi::{c_char, c_int, c_void, CStr};
use std::sync::Mutex;

static BINDINGS: Mutex<Vec<Rebinding>> = Mutex::new(Vec::new());

const SEG_DATA_CONST: &str = "__DATA_CONST";

#[cfg(target_pointer_width = "64")]
mod arch {
    use goblin::mach::header::Header64;
    use goblin::mach::load_command::{Section64, SegmentCommand64, LC_SEGMENT_64};
    use goblin::mach::symbols::Nlist64;

    pub const LC_SEGMENT_ARCH_DEPENDENT: u32 = LC_SEGMENT_64;
    pub type NlistT = Nlist64;
    pub type SectionT = Section64;
    pub type SegmentCommandT = SegmentCommand64;
    pub type MachHeaderT = Header64;
}

#[cfg(target_pointer_width = "32")]
mod arch {
    use goblin::mach::header::Header32;
    use goblin::mach::load_command::{Section32, SegmentCommand32, LC_SEGMENT, LC_SEGMENT_64};
    use goblin::mach::symbols::Nlist32;

    pub const LC_SEGMENT_ARCH_DEPENDENT: u32 = LC_SEGMENT;
    pub type NlistT = Nlist32;
    pub type SectionT = Section32;
    pub type SegmentCommandT = SegmentCommand32;
    pub type MachHeaderT = Header32;
}

extern "C" {
    fn _dyld_register_func_for_add_image(callback: extern "C" fn(*const c_void, c_int));
}

pub unsafe fn register(bindings: Vec<Rebinding>) {
    let mut locked = BINDINGS.lock().unwrap();
    *locked = bindings;

    _dyld_register_func_for_add_image(add_image);
}

extern "C" fn add_image(header: *const c_void, slide: c_int) {
    unsafe {
        rebind_for_image(header, slide);
    }
}

unsafe fn rebind_for_image(header: *const c_void, slide: c_int) {
    let mut symtab_cmd = None;
    let mut dynsymtab_cmd = None;

    let header = header as *const MachHeaderT;

    let mut segment_cmd = header.byte_add(size_of::<MachHeaderT>()) as *const SegmentCommandT;

    for _ in 0..(*header).ncmds {
        match (*segment_cmd).cmd {
            LC_SYMTAB => {
                symtab_cmd = Some(segment_cmd as *const SymtabCommand);
            }
            LC_DYSYMTAB => {
                dynsymtab_cmd = Some(segment_cmd as *const DysymtabCommand);
            }
            _ => {}
        }

        segment_cmd = segment_cmd.byte_add((*segment_cmd).cmdsize as usize);
    }

    let (Some(symtab_cmd), Some(dynsymtab_cmd)) = (symtab_cmd, dynsymtab_cmd) else {
        return;
    };

    if (*dynsymtab_cmd).nindirectsyms == 0 {
        return;
    }

    let mut segment_cmd = header.byte_add(size_of::<MachHeaderT>()) as *const SegmentCommandT;

    for _ in 0..(*header).ncmds {
        if (*segment_cmd).cmd == arch::LC_SEGMENT_ARCH_DEPENDENT
            && (equal_str((*segment_cmd).segname, SEG_DATA)
                || equal_str((*segment_cmd).segname, SEG_DATA_CONST))
        {
            bind_symbols(header, segment_cmd, symtab_cmd, dynsymtab_cmd, slide);
        }

        segment_cmd = segment_cmd.byte_add((*segment_cmd).cmdsize as usize);
    }
}

unsafe fn bind_symbols(
    header: *const MachHeaderT,
    segment_cmd: *const SegmentCommandT,
    symtab_cmd: *const SymtabCommand,
    dynsymtab_cmd: *const DysymtabCommand,
    slide: i32,
) {
    let symbol_table = header.byte_add((*symtab_cmd).symoff as usize) as *const NlistT;
    let indirect_symbol_table =
        header.byte_add((*dynsymtab_cmd).indirectsymoff as usize) as *const u32;

    for j in 0..(*segment_cmd).nsects {
        let sect = segment_cmd
            .byte_add(size_of::<SegmentCommandT>() + j as usize * size_of::<SectionT>())
            as *const SectionT;

        if !matches!(
            (*sect).flags & SECTION_TYPE,
            S_NON_LAZY_SYMBOL_POINTERS | S_LAZY_SYMBOL_POINTERS
        ) {
            continue;
        }

        let indirect_bindings = ((*sect).addr as usize + slide as usize) as *mut *const c_void;

        let len_sect = (*sect).size as usize / size_of::<*const c_void>();
        'symbol_loop: for k in 0..len_sect {
            let symbol_index = *indirect_symbol_table.add(k);

            if symbol_index >= (*symtab_cmd).nsyms {
                continue;
            }

            let symbol = symbol_table.add(symbol_index as usize) as *const NlistT;

            let string_table_end = (*symtab_cmd).stroff as usize + (*symtab_cmd).strsize as usize;
            let offset = (*symtab_cmd).stroff as usize + (*symbol).n_strx as usize;
            if offset >= string_table_end {
                continue;
            }

            let symbol_name = header
                .byte_add((*symtab_cmd).stroff as usize + (*symbol).n_strx as usize)
                as *const c_char;

            let Ok(name) = CStr::from_ptr(symbol_name).to_str() else {
                continue;
            };

            if name.is_empty() {
                continue;
            }

            let indirect_binding = indirect_bindings.wrapping_add(k as usize) as *const c_void;

            let mut bindings = BINDINGS.lock().unwrap();

            for binding in &mut *bindings {
                if name[1..] == binding.name {
                    let fn_ptr = binding.function as *const c_void;

                    if fn_ptr.is_null() || indirect_binding == fn_ptr {
                        continue;
                    }

                    let result = mach_vm_protect(
                        mach_task_self(),
                        indirect_bindings as mach_vm_address_t,
                        (*sect).size as mach_vm_size_t,
                        0,
                        VM_PROT_READ | VM_PROT_WRITE | VM_PROT_COPY,
                    );
                    if result == KERN_SUCCESS {
                        *indirect_bindings.wrapping_add(k as usize) = fn_ptr;
                    }
                    continue 'symbol_loop;
                }
            }
        }
    }
}

#[inline]
fn equal_str(a: impl IntoIterator<Item = u8>, b: &str) -> bool {
    a.into_iter().zip(b.as_bytes()).all(|(a, &b)| a == b)
}