cradle-hooks 0.1.3

The cradle workspace's function hooking logic
Documentation
use cradle_shared::{CradleError, CradleResult};
use std::ffi::c_void;
use std::ptr::null_mut;
use windows_sys::Win32::Foundation::{GetLastError, HANDLE};
use windows_sys::Win32::System::Diagnostics::Debug::{
    CONTEXT, CONTEXT_ALL_AMD64, GetThreadContext, ReadProcessMemory, SetThreadContext,
    WriteProcessMemory,
};

pub unsafe fn set_thread_context(thread: HANDLE, ctx: &CONTEXT) -> CradleResult {
    unsafe {
        let ok = SetThreadContext(thread, ctx) != 0;
        if !ok {
            return Err(CradleError::WindowsError(GetLastError() as i32));
        }
        Ok(())
    }
}

pub unsafe fn get_thread_context(thread: HANDLE) -> CradleResult<CONTEXT> {
    unsafe {
        let mut ctx: CONTEXT = std::mem::zeroed();
        ctx.ContextFlags = CONTEXT_ALL_AMD64;
        if GetThreadContext(thread, &mut ctx) == 0 {
            return Err(CradleError::WindowsError(GetLastError() as i32));
        }
        Ok(ctx)
    }
}

pub unsafe fn write_single_byte(proc: HANDLE, addr: usize, byte: &[u8; 1]) -> CradleResult {
    unsafe {
        let ok = WriteProcessMemory(
            proc,
            addr as *const c_void,
            byte.as_ptr() as _,
            1,
            null_mut(),
        ) != 0;
        if !ok {
            return Err(CradleError::WindowsError(GetLastError() as i32));
        }
    }
    Ok(())
}

pub unsafe fn write_process_memory(proc: HANDLE, addr: usize, byte: &[u8]) -> CradleResult {
    unsafe {
        let ok = WriteProcessMemory(
            proc,
            addr as *const c_void,
            byte.as_ptr() as _,
            byte.len(),
            null_mut(),
        ) != 0;
        if !ok {
            return Err(CradleError::WindowsError(GetLastError() as i32));
        }
    }
    Ok(())
}

pub unsafe fn read_single_byte(proc: HANDLE, addr: usize, buf: &mut [u8; 1]) -> CradleResult {
    unsafe {
        let ok = ReadProcessMemory(
            proc,
            addr as *const c_void,
            buf.as_mut_ptr() as _,
            1,
            null_mut(),
        ) != 0;
        if !ok {
            return Err(CradleError::WindowsError(GetLastError() as i32));
        }
    }
    Ok(())
}

pub unsafe fn read_process_memory(
    proc: HANDLE,
    addr: usize,
    buf: &mut [u8],
    len: usize,
) -> CradleResult {
    unsafe {
        let ok = ReadProcessMemory(
            proc,
            addr as *const c_void,
            buf.as_mut_ptr() as _,
            len,
            null_mut(),
        ) != 0;
        if !ok {
            return Err(CradleError::WindowsError(GetLastError() as i32));
        }
    }
    Ok(())
}

pub fn read_remote_or<T: Copy>(proc: HANDLE, addr: usize, default: T) -> T {
    read_remote::<T>(proc, addr).unwrap_or(default)
}

#[cfg(target_os = "linux")]
pub fn resolve_export(_: *mut c_void, _: usize, _: &str) -> CradleResult<usize> {
    Ok(0)
}
#[cfg(target_os = "windows")]
pub fn resolve_export(
    proc: std::os::windows::raw::HANDLE,
    base: usize,
    name: &str,
) -> CradleResult<usize> {
    unsafe {
        let e_lfanew: i32 = read_remote(proc, base + 0x3C)?;
        let nt_headers = base + e_lfanew as usize;
        let export_rva: u32 = read_remote(proc, nt_headers + 0x18 + 0x70)?;
        if export_rva == 0 {
            return Err(CradleError::InvalidValue("no export directory".into()));
        }
        let export_dir = base + export_rva as usize;
        let number_of_names: u32 = read_remote(proc, export_dir + 0x18)?;
        let addr_of_functions: u32 = read_remote(proc, export_dir + 0x1C)?;
        let addr_of_names: u32 = read_remote(proc, export_dir + 0x20)?;
        let addr_of_ordinals: u32 = read_remote(proc, export_dir + 0x24)?;

        let names_table = base + addr_of_names as usize;
        let ordinals_table = base + addr_of_ordinals as usize;
        let functions_table = base + addr_of_functions as usize;
        for i in 0..number_of_names as usize {
            let name_rva: u32 = read_remote(proc, names_table + i * 4)?;
            let name_addr = base + name_rva as usize;
            let mut buf = [0u8; 64];
            ReadProcessMemory(
                proc,
                name_addr as *const c_void,
                buf.as_mut_ptr() as _,
                buf.len(),
                null_mut(),
            );

            let end = buf.iter().position(|&b| b == 0).unwrap_or(buf.len());
            if &buf[..end] == name.as_bytes() {
                let ordinal: u16 = read_remote(proc, ordinals_table + i * 2)?;
                let func_rva: u32 = read_remote(proc, functions_table + ordinal as usize * 4)?;
                return Ok(base + func_rva as usize);
            }
        }

        Err(CradleError::InvalidValue(format!(
            "export '{name}' not found"
        )))
    }
}

pub fn read_remote<T>(proc: HANDLE, addr: usize) -> CradleResult<T> {
    unsafe {
        let mut val: T = std::mem::zeroed();
        let ok = ReadProcessMemory(
            proc,
            addr as *const c_void,
            &mut val as *mut T as _,
            size_of::<T>(),
            null_mut(),
        );
        if ok == 0 {
            return Err(CradleError::WindowsError(GetLastError() as i32));
        }
        Ok(val)
    }
}

pub fn normalize_name(name: &str) -> String {
    let leaf = name
        .rsplit('\\')
        .next()
        .unwrap_or(name)
        .rsplit('/')
        .next()
        .unwrap_or(name)
        .trim_end_matches('\0');
    let lower = leaf.to_ascii_lowercase();
    lower.strip_suffix(".dll").unwrap_or(&lower).to_string()
}