decant-inject 0.1.0

The injection contract: an Injector trait plus a method-agnostic load request, and the standard public-exports-only injector. Methods are selected by config or supplied as plugins.
Documentation
use std::ffi::c_void;

use crate::InjectError;

pub mod raw {
    use std::ffi::c_void;

    #[link(name = "kernel32")]
    unsafe extern "system" {
        pub fn VirtualAllocEx(
            process: *mut c_void,
            address: *mut c_void,
            size: usize,
            allocation_type: u32,
            protect: u32,
        ) -> *mut c_void;
        pub fn VirtualFreeEx(
            process: *mut c_void,
            address: *mut c_void,
            size: usize,
            free_type: u32,
        ) -> i32;
        pub fn VirtualProtectEx(
            process: *mut c_void,
            address: *mut c_void,
            size: usize,
            new_protect: u32,
            old_protect: *mut u32,
        ) -> i32;
        pub fn WriteProcessMemory(
            process: *mut c_void,
            base_address: *mut c_void,
            buffer: *const c_void,
            size: usize,
            written: *mut usize,
        ) -> i32;
        pub fn ReadProcessMemory(
            process: *mut c_void,
            base_address: *const c_void,
            buffer: *mut c_void,
            size: usize,
            read: *mut usize,
        ) -> i32;
        pub fn CreateRemoteThread(
            process: *mut c_void,
            thread_attributes: *const c_void,
            stack_size: usize,
            start_address: *mut c_void,
            parameter: *mut c_void,
            creation_flags: u32,
            thread_id: *mut u32,
        ) -> *mut c_void;
        pub fn GetThreadContext(thread: *mut c_void, context: *mut c_void) -> i32;
        pub fn SetThreadContext(thread: *mut c_void, context: *const c_void) -> i32;
        pub fn GetModuleHandleA(module_name: *const u8) -> *mut c_void;
        pub fn GetProcAddress(module: *mut c_void, proc_name: *const u8) -> *mut c_void;
        pub fn LoadLibraryA(file_name: *const u8) -> *mut c_void;
        pub fn WaitForSingleObject(handle: *mut c_void, milliseconds: u32) -> u32;
        pub fn CloseHandle(object: *mut c_void) -> i32;
        pub fn GetLastError() -> u32;
    }
}

pub const MEM_COMMIT_RESERVE: u32 = 0x0000_1000 | 0x0000_2000;
pub const MEM_RELEASE: u32 = 0x0000_8000;
pub const PAGE_NOACCESS: u32 = 0x01;
pub const PAGE_READONLY: u32 = 0x02;
pub const PAGE_READWRITE: u32 = 0x04;
pub const PAGE_EXECUTE_READ: u32 = 0x20;
pub const PAGE_EXECUTE_READWRITE: u32 = 0x40;
pub const INFINITE: u32 = 0xFFFF_FFFF;

pub unsafe fn alloc(
    process: *mut c_void,
    size: usize,
    protect: u32,
) -> Result<*mut c_void, InjectError> {
    unsafe { alloc_at(process, std::ptr::null_mut(), size, protect) }
}

pub unsafe fn alloc_at(
    process: *mut c_void,
    address: *mut c_void,
    size: usize,
    protect: u32,
) -> Result<*mut c_void, InjectError> {
    let remote =
        unsafe { raw::VirtualAllocEx(process, address, size, MEM_COMMIT_RESERVE, protect) };
    match remote.is_null() {
        true => Err(InjectError::RemoteAlloc(unsafe { raw::GetLastError() })),
        false => Ok(remote),
    }
}

pub unsafe fn free(process: *mut c_void, address: *mut c_void) {
    let _ = unsafe { raw::VirtualFreeEx(process, address, 0, MEM_RELEASE) };
}

pub unsafe fn write(
    process: *mut c_void,
    remote: *mut c_void,
    bytes: &[u8],
) -> Result<(), InjectError> {
    let mut written = 0usize;
    let ok = unsafe {
        raw::WriteProcessMemory(
            process,
            remote,
            bytes.as_ptr() as *const c_void,
            bytes.len(),
            &mut written,
        )
    };
    match ok != 0 && written == bytes.len() {
        true => Ok(()),
        false => Err(InjectError::RemoteWrite(unsafe { raw::GetLastError() })),
    }
}

pub unsafe fn read_usize(
    process: *mut c_void,
    remote: *const c_void,
) -> Result<usize, InjectError> {
    let mut out = 0usize;
    let mut read = 0usize;
    let ok = unsafe {
        raw::ReadProcessMemory(
            process,
            remote,
            &mut out as *mut usize as *mut c_void,
            std::mem::size_of::<usize>(),
            &mut read,
        )
    };
    match ok != 0 && read == std::mem::size_of::<usize>() {
        true => Ok(out),
        false => Err(InjectError::RemoteRead(unsafe { raw::GetLastError() })),
    }
}

pub unsafe fn protect(
    process: *mut c_void,
    remote: *mut c_void,
    size: usize,
    protect: u32,
) -> Result<(), InjectError> {
    let mut old = 0u32;
    match unsafe { raw::VirtualProtectEx(process, remote, size, protect, &mut old) } {
        0 => Err(InjectError::RemoteProtect(unsafe { raw::GetLastError() })),
        _ => Ok(()),
    }
}

pub unsafe fn create_remote_thread_and_wait(
    process: *mut c_void,
    start: *mut c_void,
    param: *mut c_void,
) -> Result<(), InjectError> {
    let thread = unsafe {
        raw::CreateRemoteThread(
            process,
            std::ptr::null(),
            0,
            start,
            param,
            0,
            std::ptr::null_mut(),
        )
    };
    match thread.is_null() {
        true => Err(InjectError::RemoteThread(unsafe { raw::GetLastError() })),
        false => {
            unsafe {
                raw::WaitForSingleObject(thread, INFINITE);
                raw::CloseHandle(thread);
            }
            Ok(())
        }
    }
}

pub unsafe fn remote_call3(
    process: *mut c_void,
    function: usize,
    a0: usize,
    a1: usize,
    a2: usize,
) -> Result<usize, InjectError> {
    let block_size = 96usize;
    let result_offset = 80usize;
    let block = unsafe { alloc(process, block_size, PAGE_EXECUTE_READWRITE)? };
    let result = (block as usize + result_offset) as u64;
    let mut code = Vec::with_capacity(result_offset);
    code.extend_from_slice(&[0x48, 0xB9]);
    code.extend_from_slice(&(a0 as u64).to_le_bytes());
    code.extend_from_slice(&[0x48, 0xBA]);
    code.extend_from_slice(&(a1 as u64).to_le_bytes());
    code.extend_from_slice(&[0x49, 0xB8]);
    code.extend_from_slice(&(a2 as u64).to_le_bytes());
    code.extend_from_slice(&[0x48, 0xB8]);
    code.extend_from_slice(&(function as u64).to_le_bytes());
    code.extend_from_slice(&[0x48, 0x83, 0xEC, 0x28, 0xFF, 0xD0, 0x48, 0xA3]);
    code.extend_from_slice(&result.to_le_bytes());
    code.extend_from_slice(&[0x48, 0x83, 0xC4, 0x28, 0xC3]);
    code.resize(result_offset, 0);
    unsafe {
        write(process, block, &code)?;
        create_remote_thread_and_wait(process, block, std::ptr::null_mut())?;
    }
    let out = unsafe { read_usize(process, result as *const c_void) };
    unsafe { free(process, block) };
    out
}

pub unsafe fn remote_load_library(
    process: *mut c_void,
    module: &[u8],
) -> Result<usize, InjectError> {
    let kernel32 = unsafe { raw::GetModuleHandleA(b"kernel32.dll\0".as_ptr()) };
    let load_library = unsafe { raw::GetProcAddress(kernel32, b"LoadLibraryA\0".as_ptr()) };
    match load_library.is_null() {
        true => Err(InjectError::ResolveLoadLibrary),
        false => {
            let remote_name = unsafe { remote_cstr(process, module)? };
            let out =
                unsafe { remote_call3(process, load_library as usize, remote_name as usize, 0, 0) };
            unsafe { free(process, remote_name) };
            match out? {
                0 => Err(InjectError::ManualMap(format!(
                    "LoadLibraryA({}) returned NULL",
                    String::from_utf8_lossy(module)
                ))),
                base => Ok(base),
            }
        }
    }
}

pub unsafe fn remote_get_proc_address_name(
    process: *mut c_void,
    module: usize,
    name: &[u8],
) -> Result<usize, InjectError> {
    let kernel32 = unsafe { raw::GetModuleHandleA(b"kernel32.dll\0".as_ptr()) };
    let get_proc = unsafe { raw::GetProcAddress(kernel32, b"GetProcAddress\0".as_ptr()) };
    match get_proc.is_null() {
        true => Err(InjectError::ResolveLoadLibrary),
        false => {
            let remote_name = unsafe { remote_cstr(process, name)? };
            let out = unsafe {
                remote_call3(process, get_proc as usize, module, remote_name as usize, 0)
            };
            unsafe { free(process, remote_name) };
            match out? {
                0 => Err(InjectError::ManualMap(format!(
                    "GetProcAddress({}) returned NULL",
                    String::from_utf8_lossy(name)
                ))),
                proc => Ok(proc),
            }
        }
    }
}

pub unsafe fn remote_get_proc_address_ordinal(
    process: *mut c_void,
    module: usize,
    ordinal: u16,
) -> Result<usize, InjectError> {
    let kernel32 = unsafe { raw::GetModuleHandleA(b"kernel32.dll\0".as_ptr()) };
    let get_proc = unsafe { raw::GetProcAddress(kernel32, b"GetProcAddress\0".as_ptr()) };
    match get_proc.is_null() {
        true => Err(InjectError::ResolveLoadLibrary),
        false => {
            match unsafe { remote_call3(process, get_proc as usize, module, ordinal as usize, 0)? }
            {
                0 => Err(InjectError::ManualMap(format!(
                    "GetProcAddress ordinal {ordinal} returned NULL"
                ))),
                proc => Ok(proc),
            }
        }
    }
}

unsafe fn remote_cstr(process: *mut c_void, bytes: &[u8]) -> Result<*mut c_void, InjectError> {
    let mut z = bytes.to_vec();
    z.push(0);
    let remote = unsafe { alloc(process, z.len(), PAGE_READWRITE)? };
    unsafe { write(process, remote, &z)? };
    Ok(remote)
}