Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 |
Expand Down
3 changes: 1 addition & 2 deletions src/rpc/rpc_core.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
use iced_x86::{code_asm::*, IcedError};

use iced_x86::code_asm::*;
use std::{
ffi::CString,
mem,
Expand Down
22 changes: 22 additions & 0 deletions src/syringe.rs
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,28 @@ impl Syringe {
}
}

/// Creates a new syringe for the given suspended target process.
pub fn for_suspended_process(process: OwnedProcess) -> Result<Self, io::Error> {
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::<u8>() as *mut u8,
)?;

Ok(syringe)
}

/// Returns the target process for this syringe.
pub fn process(&self) -> BorrowedProcess<'_> {
self.remote_allocator.process()
Expand Down
106 changes: 94 additions & 12 deletions tests/common/mod.rs
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -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<PathBuf, Box<dyn Error>> {
build_helper_crate("test_payload", &find_x86_variant_of_target(), false, "dll")
Expand Down Expand Up @@ -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::<STARTUPINFOW>() as u32;

let target_path_wide: Vec<u16> = 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) => {
Expand All @@ -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(
Expand All @@ -126,21 +170,59 @@ macro_rules! syringe_test {
payload_path: impl AsRef<Path>,
target_path: impl AsRef<Path>,
) {
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<Path>,
) {
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
}
};
Expand All @@ -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]
Expand Down Expand Up @@ -188,7 +271,6 @@ macro_rules! process_test {
.into();

let _guard = dummy_process.try_clone().unwrap().kill_on_drop();

test(dummy_process)
}

Expand Down
4 changes: 2 additions & 2 deletions tests/eject.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
Expand All @@ -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();
Expand Down
8 changes: 4 additions & 4 deletions tests/inject.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -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);
Expand Down
6 changes: 3 additions & 3 deletions tests/module.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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());
}
Expand All @@ -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());
Expand All @@ -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());
Expand Down
14 changes: 7 additions & 7 deletions tests/process.rs
Original file line number Diff line number Diff line change
@@ -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,
Expand Down Expand Up @@ -33,7 +33,7 @@ process_test! {
}
}

process_test! {
suspended_process_test! {
fn list_module_handles_on_crashed_does_not_hang(
process: OwnedProcess
) {
Expand All @@ -43,7 +43,7 @@ process_test! {
}
}

process_test! {
suspended_process_test! {
fn is_alive_is_true_for_running(
process: OwnedProcess
) {
Expand All @@ -53,7 +53,7 @@ process_test! {
}
}

process_test! {
suspended_process_test! {
fn is_alive_is_false_for_killed(
process: OwnedProcess
) {
Expand Down Expand Up @@ -81,7 +81,7 @@ process_test! {
}
}

process_test! {
suspended_process_test! {
fn kill_guard_kills_process_on_drop(
process: OwnedProcess
) {
Expand All @@ -92,7 +92,7 @@ process_test! {
}
}

process_test! {
suspended_process_test! {
fn long_process_paths_are_supported(
process: OwnedProcess
) {
Expand Down
Loading
Loading