Skip to content

Skip Flushing Instruction Cache on x86 #21

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 13 commits into
base: master
Choose a base branch
from
Open
2 changes: 2 additions & 0 deletions .cargo/config.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[build]
target = ['i686-pc-windows-msvc','x86_64-pc-windows-msvc']
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,2 @@
/target
Cargo.lock
.vscode
11 changes: 11 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"rust-analyzer.checkOnSave.command": "clippy",
"[rust]": {
"editor.formatOnSave": true
},
"rust-analyzer.linkedProjects": [
"Cargo.toml",
"tests/helpers/test_payload/Cargo.toml",
"tests/helpers/test_target/Cargo.toml",
]
}
23 changes: 23 additions & 0 deletions .vscode/tasks.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"version": "2.0.0",
"tasks": [
{
"label": "Test on Windows",
"type": "shell",
"command": "pwsh ./scripts/test.ps1",
"problemMatcher": []
},
{
"label": "Clean",
"type": "shell",
"command": "pwsh ./scripts/clean.ps1",
"problemMatcher": []
},
{
"label": "Test on Wine (See Readme)",
"type": "shell",
"command": "pwsh ./scripts/test-wine.ps1",
"problemMatcher": []
},
]
}
47 changes: 41 additions & 6 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 Expand Up @@ -42,11 +47,11 @@ syringe.eject(injected_payload).unwrap();
## Remote Procedure Calls (RPC)
This crate supports two mechanisms for rpc. Both only work one-way for calling exported functions in the target process and are only intended for one-time initialization usage. For extended communication a dedicated rpc library should be used.

| | `RemotePayloadProcedure` | `RemoteRawProcedure` |
| ---------------- | ------------------------------ | ------------------------------------------ |
| Feature | `rpc-payload` | `rpc-raw` |
| Argument and Return Requirements | `Serialize + DeserializeOwned` | `Copy`, Argument size has to be smaller than `usize` in target process |
| Function Definition | Using macro `payload_procedure!` | Any `extern "system"` or `extern "C"` with `#[no_mangle]` |
| | `RemotePayloadProcedure` | `RemoteRawProcedure` |
| -------------------------------- | -------------------------------- | ---------------------------------------------------------------------- |
| Feature | `rpc-payload` | `rpc-raw` |
| Argument and Return Requirements | `Serialize + DeserializeOwned` | `Copy`, Argument size has to be smaller than `usize` in target process |
| Function Definition | Using macro `payload_procedure!` | Any `extern "system"` or `extern "C"` with `#[no_mangle]` |

### RemotePayloadProcedure
A rpc mechanism based on [`bincode`](https://crates.io/crates/bincode).
Expand Down Expand Up @@ -119,6 +124,36 @@ syringe.eject(injected_payload).unwrap();
## License
Licensed under MIT license ([LICENSE](https://github.com/OpenByteDev/dll-syringe/blob/master/LICENSE) or http://opensource.org/licenses/MIT)

## Instructions for Contributors

### Prerequisites

You will need the nightly toolchains of Rust and Cargo to build/test this project.

```
rustup target add x86_64-pc-windows-msvc --toolchain nightly
rustup target add i686-pc-windows-msvc --toolchain nightly
```

> [!NOTE]
> Also applies to developing on Linux, you'll need it for your IDE (i.e. rust-analyzer or RustRover) to work properly.

### Run Tests

Run the `./scripts/test.ps1` script from PowerShell.

### Running Tests on Linux

You'll need `cargo xwin` to build the MSVC targets on Linux:

```
cargo install cargo-xwin
```

After that, you can run the tests with `./scripts/test-wine.ps1` PowerShell script.
(As opposed to `./scripts/test.ps1`)

Make sure you have Wine installed!

## Attribution
Inspired by [Reloaded.Injector](https://github.com/Reloaded-Project/Reloaded.Injector) from [Sewer](https://github.com/Sewer56).

9 changes: 0 additions & 9 deletions clean.ps1

This file was deleted.

2 changes: 2 additions & 0 deletions rust-toolchain.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[toolchain]
channel = "nightly"
11 changes: 11 additions & 0 deletions scripts/clean.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Navigate up one folder from the current script location
Set-Location "$PSScriptRoot\.."
cargo clean

Set-Location "./tests/helpers/test_payload"
cargo clean
Set-Location "../../.."

Set-Location "./tests/helpers/test_target"
cargo clean
Set-Location "../../.."
17 changes: 17 additions & 0 deletions scripts/test-wine.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Navigate up one folder from the current script location
Set-Location "$PSScriptRoot\.."

# Testing
$env:CROSS_SYSROOT = "." # pretend we cross

# Prebuild dummy projects.
cargo +nightly xwin rustc --manifest-path "tests/helpers/test_target/Cargo.toml" --target i686-pc-windows-msvc --xwin-arch x86 --xwin-cache-dir "target/cache/x86"
cargo +nightly xwin rustc --manifest-path "tests/helpers/test_payload/Cargo.toml" --target i686-pc-windows-msvc --xwin-arch x86 --xwin-cache-dir "target/cache/x86"
cargo +nightly xwin rustc --manifest-path "tests/helpers/test_target/Cargo.toml" --target x86_64-pc-windows-msvc --xwin-arch x86_64 --xwin-cache-dir "target/cache/x64"
cargo +nightly xwin rustc --manifest-path "tests/helpers/test_payload/Cargo.toml" --target x86_64-pc-windows-msvc --xwin-arch x86_64 --xwin-cache-dir "target/cache/x64"

# Windows/MSVC x86
cargo +nightly xwin test --target i686-pc-windows-msvc --xwin-arch x86 --xwin-cache-dir "target/cache/x86"

# Windows/MSVC x64
cargo +nightly xwin test --target x86_64-pc-windows-msvc --xwin-arch x86_64 --xwin-cache-dir "target/cache/x64"
8 changes: 8 additions & 0 deletions scripts/test.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Navigate up one folder from the current script location
Set-Location "$PSScriptRoot\.."

# Windows/MSVC x86
cargo test --target i686-pc-windows-msvc -- --nocapture

# Windows/MSVC x64
cargo test --target x86_64-pc-windows-msvc -- --nocapture
6 changes: 6 additions & 0 deletions src/process/memory/buffer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -338,6 +338,12 @@ impl<'a> ProcessMemorySlice<'a> {
}

let mut bytes_written = 0;
if buf.is_empty() {
// This works around a discrepancy between Wine and actual Windows.
// On Wine, a 0 sized write fails, on Windows this suceeds. Will file as bug soon.
return Ok(());
}

let result = unsafe {
WriteProcessMemory(
self.process.as_raw_handle(),
Expand Down
1 change: 1 addition & 0 deletions src/rpc/raw.rs
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,7 @@ where
Self::build_call_stub_x64(self.ptr, result.as_ptr().as_ptr(), float_mask).unwrap()
};
let code = self.remote_allocator.alloc_and_copy_buf(code.as_slice())?;
#[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))]
code.memory().flush_instruction_cache()?;

Ok(RemoteRawProcedureStub {
Expand Down
4 changes: 2 additions & 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 Expand Up @@ -83,6 +82,7 @@ impl Syringe {
.unwrap()
};
let function_stub = self.remote_allocator.alloc_and_copy_buf(code.as_slice())?;
#[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))]
function_stub.memory().flush_instruction_cache()?;

Ok(RemoteProcedureStub {
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
2 changes: 0 additions & 2 deletions test.ps1

This file was deleted.

Loading