use iced_x86::{code_asm::*, IcedError};
use std::{
ffi::{c_void, CString},
lazy::OnceCell,
marker::PhantomData,
mem::{self},
ptr::{self, NonNull},
};
use winapi::shared::minwindef::FARPROC;
use crate::{
error::SyringeError,
process_memory::{RemoteBox, RemoteBoxAllocator},
ProcessModule, ProcessRef, Syringe,
};
type RemoteProcedurePtr = NonNull<c_void>;
#[cfg_attr(feature = "doc_cfg", doc(cfg(feature = "remote_procedure")))]
impl<'a> Syringe<'a> {
pub fn get_procedure<T, R, N: AsRef<str>>(
&mut self,
module: ProcessModule<'_>,
name: N,
) -> Result<Option<RemoteProcedure<'a, T, R>>, SyringeError> {
match self.get_procedure_address(module, name) {
Ok(Some(procedure)) => Ok(Some(RemoteProcedure::new(
procedure,
self.remote_allocator.clone(),
))),
Ok(None) => Ok(None),
Err(e) => Err(e),
}
}
pub fn get_procedure_address(
&mut self,
module: ProcessModule<'_>,
name: impl AsRef<str>,
) -> Result<Option<RemoteProcedurePtr>, SyringeError> {
assert!(
module.process() == self.process,
"trying to load a procedure from a module of a different process"
);
self.build_get_proc_address_stub()?;
let stub = self.get_proc_address_stub.get_mut().unwrap();
let name = name.as_ref();
let mut name = self
.remote_allocator
.alloc_and_copy(CString::new(name).unwrap().as_bytes_with_nul())?;
stub.parameter.write(&GetProcAddressParams {
module_handle: module.handle() as u64,
name: name.as_ptr() as u64,
})?;
stub.result.write(&ptr::null_mut())?;
let exit_code = self.process.run_remote_thread(
unsafe { mem::transmute(stub.code.as_mut_ptr()) },
stub.parameter.as_mut_ptr().cast(),
)?;
Syringe::remote_exit_code_to_exception(exit_code)?;
Ok(RemoteProcedurePtr::new(stub.result.read()?.cast()))
}
fn build_get_proc_address_stub(&mut self) -> Result<(), SyringeError> {
self.get_proc_address_stub
.get_or_try_init::<_, SyringeError>(|| {
let inject_data = self
.inject_help_data
.get_or_try_init(|| Self::load_inject_help_data_for_process(self.process))?;
let remote_get_proc_address = inject_data.get_proc_address_fn_ptr();
let parameter = self
.remote_allocator
.alloc_uninit::<GetProcAddressParams>()?;
let mut result = self.remote_allocator.alloc_uninit::<FARPROC>()?;
let code = if self.process.is_x86()? {
Syringe::build_get_proc_address_x86(
remote_get_proc_address as *const _,
result.as_mut_ptr().cast(),
)
.unwrap()
} else {
Syringe::build_get_proc_address_x64(
remote_get_proc_address as *const _,
result.as_mut_ptr().cast(),
)
.unwrap()
};
let function_stub = self.remote_allocator.alloc_and_copy(code.as_slice())?;
function_stub.memory().flush_instruction_cache()?;
Ok(RemoteProcedureStub {
code: function_stub,
parameter,
result,
})
})?;
Ok(())
}
fn build_call_procedure_x86(
procedure: *const c_void,
return_buffer: *mut c_void,
) -> Result<Vec<u8>, IcedError> {
assert!(!procedure.is_null());
assert!(!return_buffer.is_null());
assert_eq!(procedure as u32 as usize, procedure as usize);
assert_eq!(return_buffer as u32 as usize,return_buffer as usize);
let mut asm = CodeAssembler::new(32)?;
asm.mov(eax, esp + 4)?; asm.push(return_buffer as u32)?; asm.push(eax)?; asm.mov(eax, procedure as u32)?; asm.call(eax)?; asm.mov(eax, 0)?; asm.ret_1(4)?;
let code = asm.assemble(0x1234_5678)?;
debug_assert_eq!(code, asm.assemble(0x1111_2222)?, "CallProcedure x86 stub is not location independent");
Ok(code)
}
fn build_call_procedure_x64(
procedure: *const c_void,
return_buffer: *mut c_void,
) -> Result<Vec<u8>, IcedError> {
assert!(!procedure.is_null());
assert!(!return_buffer.is_null());
let mut asm = CodeAssembler::new(64)?;
asm.sub(rsp, 40)?; asm.mov(rdx, return_buffer as u64)?; asm.mov(rcx, rcx)?; asm.mov(rax, procedure as u64)?;
asm.call(rax)?;
asm.mov(rax, 0u64)?; asm.add(rsp, 40)?; asm.mov(rax, 0u64)?; asm.ret()?;
let code = asm.assemble(0x1234_5678)?;
debug_assert_eq!(code, asm.assemble(0x1111_2222)?, "CallProcedure x64 stub is not location independent");
Ok(code)
}
fn build_get_proc_address_x86(
get_proc_address: *const c_void,
return_buffer: *mut c_void,
) -> Result<Vec<u8>, IcedError> {
assert!(!get_proc_address.is_null());
assert!(!return_buffer.is_null());
assert_eq!(get_proc_address as u32 as usize, get_proc_address as usize);
assert_eq!(return_buffer as u32 as usize,return_buffer as usize);
let mut asm = CodeAssembler::new(32)?;
asm.mov(eax, esp + 4)?; asm.push(dword_ptr(eax + 8))?; asm.push(dword_ptr(eax + 0))?; asm.mov(eax, get_proc_address as u32)?;
asm.call(eax)?;
asm.mov(dword_ptr(return_buffer as u32), eax)?;
asm.mov(eax, 0)?; asm.ret_1(4)?;
let code = asm.assemble(0x1234_5678)?;
debug_assert_eq!(code, asm.assemble(0x1111_2222)?, "GetProcAddress x86 stub is not location independent");
Ok(code)
}
fn build_get_proc_address_x64(
get_proc_address: *const c_void,
return_buffer: *mut c_void,
) -> Result<Vec<u8>, IcedError> {
assert!(!get_proc_address.is_null());
assert!(!return_buffer.is_null());
let mut asm = CodeAssembler::new(64).unwrap();
asm.sub(rsp, 40)?; asm.mov(rdx, qword_ptr(rcx + 8))?; asm.mov(rcx, qword_ptr(rcx + 0))?; asm.mov(rax, get_proc_address as u64)?;
asm.call(rax)?;
asm.mov(qword_ptr(return_buffer as u64), rax)?;
asm.mov(rax, 0u64)?; asm.add(rsp, 40)?; asm.ret()?;
let code = asm.assemble(0x1234_5678)?;
debug_assert_eq!(code, asm.assemble(0x1111_2222)?, "GetProcAddress x64 stub is not location independent");
Ok(code)
}
}
#[cfg_attr(feature = "doc_cfg", doc(cfg(feature = "remote_procedure")))]
#[derive(Debug)]
pub struct RemoteProcedure<'a, T, R> {
ptr: RemoteProcedurePtr,
stub: OnceCell<RemoteProcedureStub<'a, T, R>>,
remote_allocator: RemoteBoxAllocator<'a>,
phantom: PhantomData<fn(T) -> R>,
}
impl<'a, T, R> RemoteProcedure<'a, T, R> {
fn new(ptr: RemoteProcedurePtr, remote_allocator: RemoteBoxAllocator<'a>) -> Self {
Self {
ptr,
remote_allocator,
stub: OnceCell::new(),
phantom: PhantomData,
}
}
pub fn process(&self) -> ProcessRef<'a> {
self.remote_allocator.process()
}
pub const fn as_ptr(&self) -> *const c_void {
self.ptr.as_ptr()
}
pub fn call(&mut self, arg: &T) -> Result<R, SyringeError> {
self.stub
.get_or_try_init(|| Self::build_stub(self.ptr.as_ptr(), &mut self.remote_allocator))?;
let stub = self.stub.get_mut().unwrap();
stub.call(arg)
}
fn build_stub(
procedure: *const c_void,
remote_allocator: &mut RemoteBoxAllocator<'a>,
) -> Result<RemoteProcedureStub<'a, T, R>, SyringeError> {
let parameter = remote_allocator.alloc_uninit::<T>()?;
let mut result = remote_allocator.alloc_uninit::<R>()?;
let code = if remote_allocator.process().is_x86()? {
Syringe::build_call_procedure_x86(procedure, result.as_mut_ptr().cast()).unwrap()
} else {
Syringe::build_call_procedure_x64(procedure, result.as_mut_ptr().cast()).unwrap()
};
let code = remote_allocator.alloc_and_copy(code.as_slice())?;
code.memory().flush_instruction_cache()?;
Ok(RemoteProcedureStub {
code,
parameter,
result,
})
}
}
#[derive(Debug)]
#[repr(C)]
pub(crate) struct GetProcAddressParams {
module_handle: u64,
name: u64,
}
#[derive(Debug)]
pub(crate) struct RemoteProcedureStub<'a, T, R> {
pub code: RemoteBox<'a, [u8]>,
pub parameter: RemoteBox<'a, T>,
pub result: RemoteBox<'a, R>,
}
impl<'a, T, R> RemoteProcedureStub<'a, T, R> {
pub fn call(&mut self, arg: &T) -> Result<R, SyringeError> {
self.parameter.write(arg)?;
let exit_code = self.code.memory().process().run_remote_thread(
unsafe { mem::transmute(self.code.as_ptr()) },
self.parameter.as_mut_ptr().cast(),
)?;
Syringe::remote_exit_code_to_exception(exit_code)?;
Ok(self.result.read()?)
}
}