diff --git a/README.md b/README.md index 211a8a3..725ea70 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,11 @@ A windows dll injection library written in Rust. ## Supported scenarios + +> [!WARNING] +> Although tests currently pass, some uses of 64-bit into 32-bit may potentially fail on +> Linux (WINE) due to [Wine Bug #56362](https://bugs.winehq.org/show_bug.cgi?id=56362). + | Injector Process | Target Process | Supported? | | ---------------- | -------------- | ------------------------------------------ | | 32-bit | 32-bit | Yes | diff --git a/src/rpc/rpc_core.rs b/src/rpc/rpc_core.rs index 3d33e67..d58ac8e 100644 --- a/src/rpc/rpc_core.rs +++ b/src/rpc/rpc_core.rs @@ -1,5 +1,4 @@ -use iced_x86::{code_asm::*, IcedError}; - +use iced_x86::code_asm::*; use std::{ ffi::CString, mem, diff --git a/src/syringe.rs b/src/syringe.rs index 4351c01..6a4d5b5 100644 --- a/src/syringe.rs +++ b/src/syringe.rs @@ -116,6 +116,28 @@ impl Syringe { } } + /// Creates a new syringe for the given suspended target process. + pub fn for_suspended_process(process: OwnedProcess) -> Result { + let syringe = Self::for_process(process); + + // If we are injecting into a 'suspended' process, then said process is said to not be fully + // initialized. This means: + // - We can't use `EnumProcessModulesEx` and friends. + // - So we can't locate Kernel32.dll in 32-bit process (from 64-bit process) + // - And therefore calling LoadLibrary is not possible. + + // Thankfully we can 'initialize' this suspended process without running any end user logic + // (e.g. a game's entry point) by creating a dummy method and invoking it. + let ret: u8 = 0xC3; + let bx = syringe.remote_allocator.alloc_and_copy(&ret)?; + syringe.process().run_remote_thread( + unsafe { mem::transmute(bx.as_raw_ptr()) }, + std::ptr::null::() as *mut u8, + )?; + + Ok(syringe) + } + /// Returns the target process for this syringe. pub fn process(&self) -> BorrowedProcess<'_> { self.remote_allocator.process() diff --git a/tests/common/mod.rs b/tests/common/mod.rs index 98f691c..657172c 100644 --- a/tests/common/mod.rs +++ b/tests/common/mod.rs @@ -1,3 +1,14 @@ +use core::iter::once; +use dll_syringe::process::OwnedProcess; +use dll_syringe::process::Process; +use std::ffi::CString; +use std::ffi::OsStr; +use std::mem::zeroed; +use std::os::windows::io::FromRawHandle; +use std::os::windows::io::OwnedHandle; +use std::os::windows::prelude::OsStrExt; +use std::path::Path; +use std::ptr::null_mut; use std::{ env::{current_dir, var}, error::Error, @@ -8,6 +19,8 @@ use std::{ str::FromStr, sync::Mutex, }; +use winapi::um::processthreadsapi::{CreateProcessW, PROCESS_INFORMATION, STARTUPINFOW}; +use winapi::um::winbase::CREATE_SUSPENDED; pub fn build_test_payload_x86() -> Result> { build_helper_crate("test_payload", &find_x86_variant_of_target(), false, "dll") @@ -90,6 +103,37 @@ fn is_cross() -> bool { var("CROSS_SYSROOT").is_ok() } +pub(crate) fn start_suspended_process(path: &Path) -> OwnedProcess { + unsafe { + let mut startup_info: STARTUPINFOW = zeroed(); + let mut process_info: PROCESS_INFORMATION = zeroed(); + startup_info.cb = std::mem::size_of::() as u32; + + let target_path_wide: Vec = OsStr::new(path.to_str().unwrap()) + .encode_wide() + .chain(once(0)) // null terminator + .collect(); + + if CreateProcessW( + target_path_wide.as_ptr(), + null_mut(), // Command line + null_mut(), // Process security attributes + null_mut(), // Thread security attributes + 0, // Inherit handles + CREATE_SUSPENDED, // Creation flags + null_mut(), // Environment + null_mut(), // Current directory + &mut startup_info, + &mut process_info, + ) == 0 + { + panic!("Failed to create suspended process"); + } else { + OwnedProcess::from_handle_unchecked(OwnedHandle::from_raw_handle(process_info.hProcess)) + } + } +} + #[macro_export] macro_rules! syringe_test { (fn $test_name:ident ($process:ident : OwnedProcess, $payload_path:ident : &Path $(,)?) $body:block) => { @@ -98,8 +142,8 @@ macro_rules! syringe_test { use dll_syringe::process::OwnedProcess; use std::{ path::Path, - process::{Command, Stdio}, }; + use $crate::common::start_suspended_process; #[test] #[cfg(any( @@ -126,21 +170,59 @@ macro_rules! syringe_test { payload_path: impl AsRef, target_path: impl AsRef, ) { - let dummy_process: OwnedProcess = Command::new(target_path.as_ref()) - .stdin(Stdio::null()) - .stdout(Stdio::null()) - .stderr(Stdio::null()) - .spawn().unwrap() - .into(); + let proc = start_suspended_process(target_path.as_ref()); + let _guard = proc.try_clone().unwrap().kill_on_drop(); + test(proc, payload_path.as_ref()); + } - let _guard = dummy_process.try_clone().unwrap().kill_on_drop(); + fn test( + $process : OwnedProcess, + $payload_path : &Path, + ) $body + } + }; +} + +#[macro_export] +macro_rules! suspended_process_test { + (fn $test_name:ident ($process:ident : OwnedProcess $(,)?) $body:block) => { + mod $test_name { + use super::*; + use dll_syringe::process::OwnedProcess; + use std::{ + path::Path + }; + use $crate::common::start_suspended_process; + + #[test] + #[cfg(any( + target_arch = "x86", + all(target_arch = "x86_64", feature = "into-x86-from-x64") + ))] + fn x86() { + test_with_setup( + common::build_test_target_x86().unwrap(), + ) + } - test(dummy_process, payload_path.as_ref()) + #[test] + #[cfg(target_arch = "x86_64")] + fn x86_64() { + test_with_setup( + common::build_test_target_x64().unwrap(), + ) + } + + fn test_with_setup( + target_path: impl AsRef, + ) { + let proc = start_suspended_process(target_path.as_ref()); + let _guard = proc.try_clone().unwrap().kill_on_drop(); + test(proc) } fn test( $process : OwnedProcess, - $payload_path : &Path, ) $body } }; @@ -154,7 +236,8 @@ macro_rules! process_test { use dll_syringe::process::OwnedProcess; use std::{ path::Path, - process::{Command, Stdio}, + process::Command, + process::Stdio, }; #[test] @@ -188,7 +271,6 @@ macro_rules! process_test { .into(); let _guard = dummy_process.try_clone().unwrap().kill_on_drop(); - test(dummy_process) } diff --git a/tests/eject.rs b/tests/eject.rs index e3f8ffd..5e7f3b5 100644 --- a/tests/eject.rs +++ b/tests/eject.rs @@ -10,7 +10,7 @@ syringe_test! { process: OwnedProcess, payload_path: &Path, ) { - let syringe = Syringe::for_process(process); + let syringe = Syringe::for_suspended_process(process).unwrap(); let module = syringe.inject(payload_path).unwrap(); syringe.eject(module).unwrap(); } @@ -21,7 +21,7 @@ syringe_test! { process: OwnedProcess, payload_path: &Path, ) { - let syringe = Syringe::for_process(process); + let syringe = Syringe::for_suspended_process(process).unwrap(); let module = syringe.inject(payload_path).unwrap(); syringe.process().kill().unwrap(); diff --git a/tests/inject.rs b/tests/inject.rs index 3ad6b69..86db93b 100644 --- a/tests/inject.rs +++ b/tests/inject.rs @@ -10,16 +10,16 @@ syringe_test! { process: OwnedProcess, payload_path: &Path, ) { - let syringe = Syringe::for_process(process); + let syringe = Syringe::for_suspended_process(process).unwrap(); syringe.inject(payload_path).unwrap(); } } -process_test! { +suspended_process_test! { fn inject_with_invalid_path_fails_with_remote_io( process: OwnedProcess, ) { - let syringe = Syringe::for_process(process); + let syringe = Syringe::for_suspended_process(process).unwrap(); let result = syringe.inject("invalid path"); assert!(result.is_err()); let err = result.unwrap_err(); @@ -37,7 +37,7 @@ syringe_test! { process: OwnedProcess, payload_path: &Path, ) { - let syringe = Syringe::for_process(process); + let syringe = Syringe::for_suspended_process(process).unwrap(); syringe.process().kill().unwrap(); let result = syringe.inject(payload_path); diff --git a/tests/module.rs b/tests/module.rs index a424263..190a0e1 100644 --- a/tests/module.rs +++ b/tests/module.rs @@ -33,7 +33,7 @@ syringe_test! { process: OwnedProcess, payload_path: &Path, ) { - let syringe = Syringe::for_process(process); + let syringe = Syringe::for_suspended_process(process).unwrap(); let module = syringe.inject(payload_path).unwrap(); assert!(module.guess_is_loaded()); } @@ -45,7 +45,7 @@ syringe_test! { process: OwnedProcess, payload_path: &Path, ) { - let syringe = Syringe::for_process(process); + let syringe = Syringe::for_suspended_process(process).unwrap(); let module = syringe.inject(payload_path).unwrap(); syringe.eject(module).unwrap(); assert!(!module.try_guess_is_loaded().unwrap()); @@ -58,7 +58,7 @@ syringe_test! { process: OwnedProcess, payload_path: &Path, ) { - let syringe = Syringe::for_process(process); + let syringe = Syringe::for_suspended_process(process).unwrap(); let module = syringe.inject(payload_path).unwrap(); syringe.process().kill().unwrap(); assert!(!module.try_guess_is_loaded().unwrap()); diff --git a/tests/process.rs b/tests/process.rs index 7e7eccf..9264d88 100644 --- a/tests/process.rs +++ b/tests/process.rs @@ -1,6 +1,6 @@ -use core::mem::zeroed; +use core::mem::{size_of, zeroed}; use dll_syringe::process::{BorrowedProcess, OwnedProcess, Process}; -use std::{ffi::CString, fs, mem, mem::size_of, time::Duration}; +use std::{ffi::CString, fs, mem, mem::size_of, time::Duration, process::Command}; use winapi::um::{ libloaderapi::{GetProcAddress, LoadLibraryA}, winnt::OSVERSIONINFOW, @@ -33,7 +33,7 @@ process_test! { } } -process_test! { +suspended_process_test! { fn list_module_handles_on_crashed_does_not_hang( process: OwnedProcess ) { @@ -43,7 +43,7 @@ process_test! { } } -process_test! { +suspended_process_test! { fn is_alive_is_true_for_running( process: OwnedProcess ) { @@ -53,7 +53,7 @@ process_test! { } } -process_test! { +suspended_process_test! { fn is_alive_is_false_for_killed( process: OwnedProcess ) { @@ -81,7 +81,7 @@ process_test! { } } -process_test! { +suspended_process_test! { fn kill_guard_kills_process_on_drop( process: OwnedProcess ) { @@ -92,7 +92,7 @@ process_test! { } } -process_test! { +suspended_process_test! { fn long_process_paths_are_supported( process: OwnedProcess ) { diff --git a/tests/rpc.rs b/tests/rpc.rs index d96b026..f7dd569 100644 --- a/tests/rpc.rs +++ b/tests/rpc.rs @@ -9,11 +9,11 @@ mod core { pub use super::*; use std::time::Duration; - process_test! { + suspended_process_test! { fn get_procedure_address_of_win32_fn( process: OwnedProcess, ) { - let syringe = Syringe::for_process(process); + let syringe = Syringe::for_suspended_process(process).unwrap(); let module = syringe.process().wait_for_module_by_name("kernel32.dll", Duration::from_secs(1)).unwrap().unwrap(); let open_process = syringe.get_procedure_address( @@ -29,7 +29,7 @@ mod core { process: OwnedProcess, payload_path: &Path, ) { - let syringe = Syringe::for_process(process); + let syringe = Syringe::for_suspended_process(process).unwrap(); let module = syringe.inject(payload_path).unwrap(); let dll_main = syringe.get_procedure_address(module, "DllMain").unwrap(); @@ -37,11 +37,11 @@ mod core { } } - process_test! { + suspended_process_test! { fn get_procedure_address_of_invalid( process: OwnedProcess, ) { - let syringe = Syringe::for_process(process); + let syringe = Syringe::for_suspended_process(process).unwrap(); let module = syringe.process().wait_for_module_by_name("kernel32.dll", Duration::from_secs(1)).unwrap().unwrap(); let invalid = syringe.get_procedure_address(module, "ProcedureThatDoesNotExist").unwrap(); assert!(invalid.is_none()); @@ -59,7 +59,7 @@ mod payload { process: OwnedProcess, payload_path: &Path, ) { - let syringe = Syringe::for_process(process); + let syringe = Syringe::for_suspended_process(process).unwrap(); let module = syringe.inject(payload_path).unwrap(); let remote_add = unsafe { syringe.get_payload_procedure:: u32>(module, "add") }.unwrap().unwrap(); @@ -73,7 +73,7 @@ mod payload { process: OwnedProcess, payload_path: &Path, ) { - let syringe = Syringe::for_process(process); + let syringe = Syringe::for_suspended_process(process).unwrap(); let module = syringe.inject(payload_path).unwrap(); let remote_sum = unsafe { syringe.get_payload_procedure::) -> u64>(module, "sum") }.unwrap().unwrap(); @@ -87,7 +87,7 @@ mod payload { process: OwnedProcess, payload_path: &Path, ) { - let syringe = Syringe::for_process(process); + let syringe = Syringe::for_suspended_process(process).unwrap(); let module = syringe.inject(payload_path).unwrap(); let remote_does_panic = unsafe { syringe.get_payload_procedure::(module, "does_panic") }.unwrap().unwrap(); @@ -114,7 +114,7 @@ mod raw { process: OwnedProcess, payload_path: &Path, ) { - let syringe = Syringe::for_process(process); + let syringe = Syringe::for_suspended_process(process).unwrap(); let module = syringe.inject(payload_path).unwrap(); let remote_add = unsafe { syringe.get_raw_procedure:: u32>(module, "add_raw") }.unwrap().unwrap(); @@ -128,7 +128,7 @@ mod raw { process: OwnedProcess, payload_path: &Path, ) { - let syringe = Syringe::for_process(process); + let syringe = Syringe::for_suspended_process(process).unwrap(); let module = syringe.inject(payload_path).unwrap(); let remote_sub = unsafe { syringe.get_raw_procedure:: u32>(module, "sub_raw") }.unwrap().unwrap(); @@ -142,7 +142,7 @@ mod raw { process: OwnedProcess, payload_path: &Path, ) { - let syringe = Syringe::for_process(process); + let syringe = Syringe::for_suspended_process(process).unwrap(); let module = syringe.inject(payload_path).unwrap(); let remote_add = unsafe { syringe.get_raw_procedure:: u16>(module, "add_smol_raw") }.unwrap().unwrap(); @@ -156,7 +156,7 @@ mod raw { process: OwnedProcess, payload_path: &Path, ) { - let syringe = Syringe::for_process(process); + let syringe = Syringe::for_suspended_process(process).unwrap(); let module = syringe.inject(payload_path).unwrap(); let remote_sum = unsafe { syringe.get_raw_procedure:: u32>(module, "sum_5_raw") }.unwrap().unwrap(); @@ -170,7 +170,7 @@ mod raw { process: OwnedProcess, payload_path: &Path, ) { - let syringe = Syringe::for_process(process); + let syringe = Syringe::for_suspended_process(process).unwrap(); let module = syringe.inject(payload_path).unwrap(); let remote_sum = unsafe { syringe.get_raw_procedure:: u32>(module, "sum_10_raw") }.unwrap().unwrap(); @@ -184,7 +184,7 @@ mod raw { process: OwnedProcess, payload_path: &Path, ) { - let syringe = Syringe::for_process(process); + let syringe = Syringe::for_suspended_process(process).unwrap(); let module = syringe.inject(payload_path).unwrap(); let remote_sub = unsafe { syringe.get_raw_procedure:: f32>(module, "sub_float_raw") }.unwrap().unwrap(); @@ -198,7 +198,7 @@ mod raw { process: OwnedProcess, payload_path: &Path, ) { - let syringe = Syringe::for_process(process); + let syringe = Syringe::for_suspended_process(process).unwrap(); let module = syringe.inject(payload_path).unwrap(); let remote_add = unsafe { syringe.get_raw_procedure:: u32>(module, "add_raw_c") }.unwrap().unwrap(); @@ -212,7 +212,7 @@ mod raw { process: OwnedProcess, payload_path: &Path, ) { - let syringe = Syringe::for_process(process); + let syringe = Syringe::for_suspended_process(process).unwrap(); let module = syringe.inject(payload_path).unwrap(); let remote_sum = unsafe { syringe.get_raw_procedure:: u32>(module, "sum_10_raw_c") }.unwrap().unwrap(); @@ -226,7 +226,7 @@ mod raw { process: OwnedProcess, payload_path: &Path, ) { - let syringe = Syringe::for_process(process); + let syringe = Syringe::for_suspended_process(process).unwrap(); let module = syringe.inject(payload_path).unwrap(); let remote_add = unsafe { syringe.get_raw_procedure:: u32>(module, "add_raw_c") }.unwrap().unwrap(); syringe.eject(module).unwrap(); @@ -240,7 +240,7 @@ mod raw { process: OwnedProcess, payload_path: &Path, ) { - let syringe = Syringe::for_process(process); + let syringe = Syringe::for_suspended_process(process).unwrap(); let module = syringe.inject(payload_path).unwrap(); let remote_add = unsafe { syringe.get_raw_procedure:: u32>(module, "add_raw_c") }.unwrap().unwrap(); syringe.process().kill().unwrap(); @@ -254,7 +254,7 @@ mod raw { process: OwnedProcess, payload_path: &Path, ) { - let syringe = Syringe::for_process(process); + let syringe = Syringe::for_suspended_process(process).unwrap(); let module = syringe.inject(payload_path).unwrap(); let remote_add = unsafe { syringe.get_raw_procedure::(module, "crash") }.unwrap().unwrap(); let add_err = remote_add.call().unwrap_err();