diff --git a/.cargo/config.toml b/.cargo/config.toml
new file mode 100644
index 0000000..6f9aacc
--- /dev/null
+++ b/.cargo/config.toml
@@ -0,0 +1,2 @@
+[build]
+target = ['i686-pc-windows-msvc','x86_64-pc-windows-msvc']
\ No newline at end of file
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index c676424..3fef1bc 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -2,9 +2,10 @@ name: CI
on:
push:
- branches: [ master ]
+ branches: [ master,add-c-exports ]
pull_request:
branches: [ master ]
+ workflow_dispatch:
env:
CARGO_TERM_COLOR: always
@@ -17,49 +18,134 @@ jobs:
strategy:
fail-fast: false
matrix:
- toolchain: ["nightly"]
os: ["windows-latest"]
target: ["x86_64-pc-windows-msvc", "i686-pc-windows-msvc"]
- include:
- - target: x86_64-pc-windows-msvc
- target32: i686-pc-windows-msvc
steps:
- - uses: actions/checkout@v2
-
- - name: Install latest rust ${{ matrix.toolchain }} for ${{ matrix.target }}
- uses: actions-rs/toolchain@v1
+ - uses: actions/checkout@v4
+
+ - name: Install Nightly Rust
+ uses: dtolnay/rust-toolchain@nightly
with:
- target: ${{ matrix.target }}
- toolchain: ${{ matrix.toolchain }}
- override: true
-
- - name: Install latest rust ${{ matrix.toolchain }} for x86 version of target
- if: matrix.target32
- uses: actions-rs/toolchain@v1
- with:
- target: ${{ matrix.target32 }}
- toolchain: ${{ matrix.toolchain }}
- override: true
-
- - name: Build
+ targets: ${{ matrix.target }}
+
+ # We need this because we also text x64-to-x86 injection.
+ - name: Add i686 Rust Target
+ run: rustup target add i686-pc-windows-msvc --toolchain nightly
+
+ - name: Can Build
run: cargo build --target ${{ matrix.target }}
- - name: Build test target
- run: cargo build --target ${{ matrix.target }} --manifest-path "tests/helpers/test_target/Cargo.toml" --all-targets
- - name: Build test payload
- run: cargo build --target ${{ matrix.target }} --manifest-path "tests/helpers/test_payload/Cargo.toml" --all-targets
+ - name: Can Build test target
+ run: cargo build --target ${{ matrix.target }} --manifest-path "tests/helpers/test_target/Cargo.toml"
+ - name: Can Build test payload
+ run: cargo build --target ${{ matrix.target }} --manifest-path "tests/helpers/test_payload/Cargo.toml"
- name: Test
- run: cargo test --target ${{ matrix.target }} --all-targets --all-features -- --nocapture
+ run: cargo test --target ${{ matrix.target }} --all-features -- --nocapture
- - name: Build (default features)
- run: cargo build --target ${{ matrix.target }} --all-targets
- - name: Build (no features)
- run: cargo build --target ${{ matrix.target }} --no-default-features --all-targets
- - name: Build (feature syringe)
- run: cargo build --target ${{ matrix.target }} --features syringe --all-targets
- - name: Build (feature rpc)
- run: cargo build --target ${{ matrix.target }} --features rpc --all-targets
+ - name: Can Build (no features)
+ run: cargo build --target ${{ matrix.target }} --no-default-features
+ - name: Can Build (feature syringe)
+ run: cargo build --target ${{ matrix.target }} --no-default-features --features syringe
+ - name: Can Build (feature rpc)
+ run: cargo build --target ${{ matrix.target }} --no-default-features --features rpc
+
+ test-on-wine:
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ submodules: recursive
+
+ # Install Rust nightly toolchain
+ - name: Install Nightly Rust
+ uses: dtolnay/rust-toolchain@nightly
+
+ # Add Windows MSVC targets
+ - name: Add Rust targets
+ run: |
+ rustup target add x86_64-pc-windows-msvc --toolchain nightly
+ rustup target add i686-pc-windows-msvc --toolchain nightly
+
+ # Install cargo-xwin
+ - name: Install cargo-xwin
+ run: cargo install cargo-xwin
+
+ # We can't use 'cross' due to using xwin, so we have to manually install Wine
+ - name: Install Wine
+ run: |
+ sudo apt-get install ppa-purge && sudo ppa-purge -y ppa:ubuntu-toolchain-r/test
+ sudo dpkg --add-architecture i386
+ sudo mkdir -pm755 /etc/apt/keyrings && sudo wget -O /etc/apt/keyrings/winehq-archive.key https://dl.winehq.org/wine-builds/winehq.key
+ sudo wget -NP /etc/apt/sources.list.d/ https://dl.winehq.org/wine-builds/ubuntu/dists/jammy/winehq-jammy.sources
+ sudo apt-get update
+ sudo apt install --install-recommends winehq-stable
+
+ # Run tests with Wine
+ - name: Run tests on Wine
+ run: ./scripts/test-wine.ps1
+ shell: pwsh
+
+ build-c-libs:
+ strategy:
+ matrix:
+ include:
+ - os: windows-latest
+ target: x86_64-pc-windows-msvc
+ use_cross: false
+ features: "c-exports,into-x86-from-x64"
+ - os: windows-latest
+ target: i686-pc-windows-msvc
+ use_cross: false
+ features: "c-exports"
+ runs-on: ${{ matrix.os }}
+
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ submodules: recursive
+
+ - id: build-libs
+ uses: Reloaded-Project/reloaded-project-configurations-rust/.github/actions/build-c-library@v1 # upgrade if needed
+ with:
+ target: ${{ matrix.target }}
+ use_pgo: false
+ use_cross: ${{ matrix.use_cross }}
+ features: ${{ matrix.features }}
+ no_default_features: true
+
+ build-c-headers:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ submodules: recursive
+
+ - name: Generate C++ bindings
+ uses: Reloaded-Project/reloaded-project-configurations-rust/.github/actions/generate-bindings@v1
+ with:
+ config_file: cbindgen_cpp.toml
+ header_file: bindings_cpp.hpp
+
+ - name: Generate C bindings
+ uses: Reloaded-Project/reloaded-project-configurations-rust/.github/actions/generate-bindings@v1
+ with:
+ config_file: cbindgen_c.toml
+ header_file: bindings_c.h
+
+ build-dotnet-library:
+ needs: build-c-libs
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v4
+
+ - name: Build and Package .NET Library
+ uses: Reloaded-Project/reloaded-project-configurations-rust/.github/actions/build-dotnet-library@dotnet-bindings
+ with:
+ targets: 'x86_64-pc-windows-msvc,i686-pc-windows-msvc'
documentation:
runs-on: ${{ matrix.os }}
@@ -67,19 +153,25 @@ jobs:
matrix:
os: ["windows-latest"]
steps:
- - uses: actions/checkout@v2
- - name: Install latest nightly
- uses: actions-rs/toolchain@v1
+ - uses: actions/checkout@v4
with:
- profile: minimal
- toolchain: nightly
- override: true
+ submodules: recursive
+
+ - name: Install Nightly Rust
+ uses: dtolnay/rust-toolchain@nightly
+
+ # Add Windows MSVC targets
+ - name: Add Rust targets
+ run: |
+ rustup target add x86_64-pc-windows-msvc --toolchain nightly
+ rustup target add i686-pc-windows-msvc --toolchain nightly
+
- name: Generate documentation
- run: cargo doc --all-features
+ run: cargo +nightly doc --all-features
- name: Install cargo-deadlinks
- run: cargo install cargo-deadlinks
+ run: cargo +nightly install cargo-deadlinks
- name: Check dead links in doc
- run: cargo deadlinks
+ run: cargo +nightly deadlinks
clippy:
runs-on: ${{ matrix.os }}
@@ -87,13 +179,81 @@ jobs:
matrix:
os: ["windows-latest"]
steps:
- - uses: actions/checkout@v1
- - uses: actions-rs/toolchain@v1
+ - uses: actions/checkout@v4
+ with:
+ submodules: recursive
+
+ - name: Install Nightly Rust
+ uses: dtolnay/rust-toolchain@nightly
+ with:
+ components: clippy
+
+ - uses: giraffate/clippy-action@v1
with:
- toolchain: nightly
- components: clippy
- override: true
- - uses: actions-rs/clippy-check@v1
+ reporter: 'github-pr-review'
+ github_token: ${{ secrets.GITHUB_TOKEN }}
+
+ publish-crate:
+ permissions:
+ contents: write
+
+ needs: [test,build-c-libs,build-c-headers,test-on-wine,build-dotnet-library]
+ # Publish only on tags
+ if: startsWith(github.ref, 'refs/tags/')
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ submodules: recursive
+
+ - name: ↙️ Download Artifacts
+ uses: actions/download-artifact@v4
+ with:
+ path: artifacts
+
+ - name: Upload to NuGet
+ shell: pwsh
+ run: |
+ $items = Get-ChildItem -Path "artifacts/**.nupkg" -Recurse
+ Foreach ($item in $items)
+ {
+ Write-Host "Pushing $item"
+ dotnet nuget push "$item" -k "${{ secrets.NUGET_KEY }}" -s "https://api.nuget.org/v3/index.json" --skip-duplicate
+ }
+
+ $items = Get-ChildItem -Path "artifacts/**.snupkg" -Recurse
+ Foreach ($item in $items)
+ {
+ Write-Host "Pushing Symbol Package $item"
+ dotnet nuget push "$item" -k "${{ secrets.NUGET_KEY }}" -s "https://api.nuget.org/v3/index.json" --skip-duplicate
+ }
+
+ - name: Compress Artifacts
+ shell: bash
+ run: |
+ dir="artifacts"
+ if [ ! -d "$dir" ]; then
+ echo "Directory $dir does not exist. No artifacts found."
+ exit 0
+ fi
+
+ for subdir in "$dir"/*; do
+ if [ -d "$subdir" ]; then
+ base=$(basename "$subdir")
+ zip -r "$dir/$base.zip" "$subdir"
+ rm -r "$subdir"
+ fi
+ done
+ ls -A ./artifacts
+
+ - name: GitHub Release Artifacts
+ uses: softprops/action-gh-release@v1
+ with:
+ files: |
+ artifacts/*
+
+
+ - name: Publish to crates.io
+ uses: Reloaded-Project/reloaded-project-configurations-rust/.github/actions/publish-crate@v1
with:
- token: ${{ secrets.GITHUB_TOKEN }}
- args: --all-features
+ token: ${{ secrets.CRATES_IO_TOKEN }}
diff --git a/.gitignore b/.gitignore
index c640ca5..96ef6c0 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,2 @@
/target
Cargo.lock
-.vscode
diff --git a/.vscode/settings.json b/.vscode/settings.json
new file mode 100644
index 0000000..9c2c2b4
--- /dev/null
+++ b/.vscode/settings.json
@@ -0,0 +1,12 @@
+{
+ "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",
+ ],
+ "rust-analyzer.cargo.features": "all"
+}
diff --git a/.vscode/tasks.json b/.vscode/tasks.json
new file mode 100644
index 0000000..5b576a4
--- /dev/null
+++ b/.vscode/tasks.json
@@ -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": []
+ },
+ ]
+}
\ No newline at end of file
diff --git a/Cargo.toml b/Cargo.toml
index 700730b..a9c6688 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -29,6 +29,10 @@ iced-x86 = { version = "1.19", features = ["std", "code_asm"], default-features
bincode = { version = "1.3", default-features = false, optional = true }
serde = { version = "1.0", default-features = false, optional = true }
+# C# Bindings
+[build-dependencies]
+csbindgen = "1.9.0"
+
[target.'cfg(target_arch = "x86")'.dependencies]
[target.'cfg(target_arch = "x86_64")'.dependencies]
@@ -50,7 +54,23 @@ payload-utils = ["bincode", "serde"]
syringe = ["iced-x86"]
full = ["into-x86-from-x64", "rpc", "process-memory", "payload-utils"]
doc-cfg = ["full"]
+c-exports = ["syringe"]
[package.metadata.docs.rs]
targets = ["x86_64-pc-windows-msvc", "i686-pc-windows-msvc"]
features = ["doc-cfg"]
+
+# Profile Build
+[profile.profile]
+inherits = "release"
+debug = true
+codegen-units = 1
+lto = true
+strip = false # No stripping!!
+
+# Optimized Release Build
+[profile.release]
+codegen-units = 1
+lto = true
+strip = true # Automatically strip symbols from the binary.
+panic = "abort"
\ No newline at end of file
diff --git a/README.md b/README.md
index b233341..031e2ee 100644
--- a/README.md
+++ b/README.md
@@ -9,6 +9,7 @@
A windows dll injection library written in Rust.
## Supported scenarios
+
| Injector Process | Target Process | Supported? |
| ---------------- | -------------- | ------------------------------------------ |
| 32-bit | 32-bit | Yes |
@@ -42,11 +43,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).
@@ -119,6 +120,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).
-
diff --git a/bindings/csharp/Init.cs b/bindings/csharp/Init.cs
new file mode 100644
index 0000000..d7b2313
--- /dev/null
+++ b/bindings/csharp/Init.cs
@@ -0,0 +1,13 @@
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using dll_syringe.Net.Sys;
+
+// ReSharper disable once CheckNamespace
+class Init
+{
+ [ModuleInitializer]
+ internal static void RegisterImportResolver()
+ {
+ NativeLibrary.SetDllImportResolver(typeof(NativeMethods).Assembly, NativeMethods.DllImportResolver);
+ }
+}
\ No newline at end of file
diff --git a/bindings/csharp/NativeMethods.cs b/bindings/csharp/NativeMethods.cs
new file mode 100644
index 0000000..4675da8
--- /dev/null
+++ b/bindings/csharp/NativeMethods.cs
@@ -0,0 +1,70 @@
+using System;
+using System.IO;
+using System.Reflection;
+using System.Runtime.InteropServices;
+
+namespace dll_syringe.Net.Sys;
+
+public static unsafe partial class NativeMethods
+{
+ // https://docs.microsoft.com/en-us/dotnet/standard/native-interop/cross-platform
+ // Library path will search
+ // win => __DllName, __DllName.dll
+ // linux, osx => __DllName.so, __DllName.dylib
+ internal static IntPtr DllImportResolver(string libraryName, Assembly assembly, DllImportSearchPath? searchPath)
+ {
+ if (libraryName == __DllName)
+ {
+ var dllName = __DllName;
+ var path = "runtimes/";
+ var extension = "";
+
+ if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
+ {
+ path += "win-";
+ extension = ".dll";
+ }
+ else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
+ {
+ path += "osx-";
+ extension = ".dylib";
+ dllName = "lib" + dllName;
+ }
+ else
+ {
+ path += "linux-";
+ extension = ".so";
+ dllName = "lib" + dllName;
+ }
+
+ if (RuntimeInformation.ProcessArchitecture == Architecture.X86)
+ {
+ path += "x86";
+ }
+ else if (RuntimeInformation.ProcessArchitecture == Architecture.X64)
+ {
+ path += "x64";
+ }
+ else if (RuntimeInformation.ProcessArchitecture == Architecture.Arm)
+ {
+ path += "arm";
+ }
+ else if (RuntimeInformation.ProcessArchitecture == Architecture.Arm64)
+ {
+ path += "arm64";
+ }
+
+ path += "/native/" + dllName + extension;
+ try
+ {
+ return NativeLibrary.Load(Path.Combine(AppContext.BaseDirectory, path), assembly, searchPath);
+ }
+ catch (DllNotFoundException)
+ {
+ return NativeLibrary.Load(Path.Combine(AppContext.BaseDirectory, dllName + extension));
+ }
+ }
+
+ return IntPtr.Zero;
+ }
+}
\ No newline at end of file
diff --git a/bindings/csharp/NativeMethods.g.cs b/bindings/csharp/NativeMethods.g.cs
new file mode 100644
index 0000000..f4b2d8e
--- /dev/null
+++ b/bindings/csharp/NativeMethods.g.cs
@@ -0,0 +1,65 @@
+//
+// This code is generated by csbindgen.
+// DON'T CHANGE THIS DIRECTLY.
+//
+#pragma warning disable CS8500
+#pragma warning disable CS8981
+using System;
+using System.Runtime.InteropServices;
+
+
+namespace dll_syringe.Net.Sys
+{
+ public static unsafe partial class NativeMethods
+ {
+ const string __DllName = "dll_syringe";
+
+
+
+ /// Creates a new `Syringe` instance for a process identified by PID. # Arguments * `pid` - The PID of the target process. # Returns A pointer to a `CSyringe` instance, or null if the process could not be opened.
+ [DllImport(__DllName, EntryPoint = "syringe_for_process", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
+ public static extern CSyringe* syringe_for_process(uint pid);
+
+ /// Creates a new `Syringe` instance for a suspended process identified by PID. # Arguments * `pid` - The PID of the target suspended process. # Returns A pointer to a `CSyringe` instance, or null if the process could not be opened or initialized.
+ [DllImport(__DllName, EntryPoint = "syringe_for_suspended_process", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
+ public static extern CSyringe* syringe_for_suspended_process(uint pid);
+
+ /// Injects a DLL into the target process associated with the given `Syringe`. # Safety This function is unsafe because it dereferences raw pointers. # Arguments * `c_syringe` - A pointer to the `CSyringe` instance. * `dll_path` - A C string path to the DLL to be injected. # Returns `true` if injection succeeded, otherwise `false`.
+ [DllImport(__DllName, EntryPoint = "syringe_inject", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
+ [return: MarshalAs(UnmanagedType.U1)]
+ public static extern bool syringe_inject(CSyringe* c_syringe, byte* dll_path);
+
+ /// Finds or injects a DLL into the target process. If the DLL is already present in the target process, it returns the existing module. Otherwise, it injects the DLL. # Safety This function is unsafe because it dereferences raw pointers. # Arguments * `c_syringe` - A pointer to the `CSyringe` instance. * `dll_path` - A C string path to the DLL to be injected. # Returns A pointer to a `CProcessModule`, or null if the operation failed.
+ [DllImport(__DllName, EntryPoint = "syringe_find_or_inject", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
+ public static extern CProcessModule* syringe_find_or_inject(CSyringe* c_syringe, byte* dll_path);
+
+ /// Ejects a module from the target process. # Arguments * `c_syringe` - A pointer to the `CSyringe` instance. * `c_module` - A pointer to the `CProcessModule` to be ejected. # Returns `true` if ejection succeeded, otherwise `false`. # Safety This is safe as long as it has a valid pointer to a Syringe and Module.
+ [DllImport(__DllName, EntryPoint = "syringe_eject", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
+ [return: MarshalAs(UnmanagedType.U1)]
+ public static extern bool syringe_eject(CSyringe* c_syringe, CProcessModule* c_module);
+
+ /// Frees a `CSyringe` instance. # Arguments * `c_syringe` - A pointer to the `CSyringe` instance to be freed. # Safety This is safe as long as it has a valid pointer to a Syringe instance.
+ [DllImport(__DllName, EntryPoint = "syringe_free", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
+ public static extern void syringe_free(CSyringe* c_syringe);
+
+ /// Frees a `CProcessModule` instance. # Arguments * `c_module` - A pointer to the `CProcessModule` to be freed. # Safety This is safe as long as it has a valid pointer to a module created by this Syringe instance.
+ [DllImport(__DllName, EntryPoint = "syringe_module_free", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)]
+ public static extern void syringe_module_free(CProcessModule* c_module);
+
+
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ public unsafe partial struct CSyringe
+ {
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ public unsafe partial struct CProcessModule
+ {
+ }
+
+
+
+}
+
\ No newline at end of file
diff --git a/bindings/csharp/csharp.csproj b/bindings/csharp/csharp.csproj
new file mode 100644
index 0000000..6a2290b
--- /dev/null
+++ b/bindings/csharp/csharp.csproj
@@ -0,0 +1,42 @@
+
+
+
+ net5.0
+ dll_syringe.Net.Sys
+ dll_syringe.Net.Sys
+ https://github.com/OpenByteDev/dll-syringe
+ A windows dll injection library written in Rust. (Raw C# Bindings).
+ 0.16.0
+
+ dll-syringe
+
+
+ preview
+ true
+ true
+ snupkg
+ true
+ true
+ true
+
+ LICENSE
+ true
+ true
+ true
+
+
+
+
+ True
+ /
+
+
+
+
+
+
diff --git a/build.rs b/build.rs
new file mode 100644
index 0000000..bffac40
--- /dev/null
+++ b/build.rs
@@ -0,0 +1,9 @@
+fn main() {
+ csbindgen::Builder::default()
+ .input_extern_file("src/c_exports.rs")
+ .csharp_dll_name("dll_syringe")
+ .csharp_class_accessibility("public")
+ .csharp_namespace("dll_syringe.Net.Sys")
+ .generate_csharp_file("bindings/csharp/NativeMethods.g.cs")
+ .unwrap();
+}
diff --git a/cbindgen_c.toml b/cbindgen_c.toml
new file mode 100644
index 0000000..d76b608
--- /dev/null
+++ b/cbindgen_c.toml
@@ -0,0 +1,486 @@
+# The language to output bindings in
+#
+# possible values: "C", "C++"
+#
+# default: "C++"
+language = "C"
+
+# Options for wrapping the contents of the header:
+
+# An optional string of text to output at the beginning of the generated file
+# default: doesn't emit anything
+header = """
+#ifdef _MSC_VER
+#define PACKED
+ #pragma pack(push, 1)
+#else
+ #define PACKED __attribute__((packed))
+#endif
+"""
+
+# An optional string of text to output at the end of the generated file
+# default: doesn't emit anything
+trailer = "/* Text to put at the end of the generated file */"
+
+# An optional name to use as an include guard
+# default: doesn't emit an include guard
+include_guard = "dll_syringe"
+
+# An optional string of text to output between major sections of the generated
+# file as a warning against manual editing
+#
+# default: doesn't emit anything
+autogen_warning = "/* Warning, this file is autogenerated by cbindgen. Don't modify this manually. */"
+
+# Whether to include a comment with the version of cbindgen used to generate the file
+# default: false
+include_version = true
+
+# An optional namespace to output around the generated bindings
+# default: doesn't emit a namespace
+namespace = "dll_syringe"
+
+# An optional list of namespaces to output around the generated bindings
+# default: []
+namespaces = [ ]
+
+# An optional list of namespaces to declare as using with "using namespace"
+# default: []
+using_namespaces = [ ]
+
+# A list of sys headers to #include (with angle brackets)
+# default: []
+sys_includes = []
+
+# A list of headers to #include (with quotes)
+# default: []
+includes = []
+
+# Whether cbindgen's default C/C++ standard imports should be suppressed. These
+# imports are included by default because our generated headers tend to require
+# them (e.g. for uint32_t). Currently, the generated imports are:
+#
+# * for C: , , , ,
+#
+# * for C++: , , , , (depending on config)
+#
+# default: false
+no_includes = false
+
+# Code Style Options
+
+# The style to use for curly braces
+#
+# possible values: "SameLine", "NextLine"
+#
+# default: "SameLine"
+braces = "SameLine"
+
+# The desired length of a line to use when formatting lines
+# default: 100
+line_length = 100
+
+# The amount of spaces to indent by
+# default: 2
+tab_width = 2
+
+# How the generated documentation should be commented.
+#
+# possible values:
+# * "c": /* like this */
+# * "c99": // like this
+# * "c++": /// like this
+# * "doxy": like C, but with leading *'s on each line
+# * "auto": "c++" if that's the language, "doxy" otherwise
+#
+# default: "auto"
+documentation_style = "auto"
+
+# Codegen Options
+
+# When generating a C header, the kind of declaration style to use for structs
+# or enums.
+#
+# possible values:
+# * "type": typedef struct { ... } MyType;
+# * "tag": struct MyType { ... };
+# * "both": typedef struct MyType { ... } MyType;
+#
+# default: "both"
+style = "both"
+
+# A list of substitutions for converting cfg's to ifdefs. cfgs which aren't
+# defined here will just be discarded.
+#
+# e.g.
+# `#[cfg(target = "freebsd")] ...`
+# becomes
+# `#if defined(DEFINE_FREEBSD) ... #endif`
+[defines]
+#"target_os = freebsd" = "DEFINE_FREEBSD"
+#"feature = serde" = "DEFINE_SERDE"
+
+[export]
+# A list of additional items to always include in the generated bindings if they're
+# found but otherwise don't appear to be used by the public API.
+#
+# default: []
+include = []
+
+# A list of items to not include in the generated bindings
+# default: []
+exclude = []
+
+# A prefix to add before the name of every item
+# default: no prefix is added
+prefix = ""
+
+# Types of items that we'll generate. If empty, then all types of item are emitted.
+#
+# possible items: (TODO: explain these in detail)
+# * "constants":
+# * "globals":
+# * "enums":
+# * "structs":
+# * "unions":
+# * "typedefs":
+# * "opaque":
+# * "functions":
+#
+# default: []
+item_types = []
+
+# Whether applying rules in export.rename prevents export.prefix from applying.
+#
+# e.g. given this toml:
+#
+# [export]
+# prefix = "capi_"
+# [export.rename]
+# "MyType" = "my_cool_type"
+#
+# You get the following results:
+#
+# renaming_overrides_prefixing = true:
+# "MyType" => "my_cool_type"
+#
+# renaming_overrides_prefixing = false:
+# "MyType => capi_my_cool_type"
+#
+# default: false
+renaming_overrides_prefixing = true
+
+# Table of name conversions to apply to item names (lhs becomes rhs)
+[export.rename]
+"AtomicI32" = "int"
+
+# Table of things to add to the body of any struct, union, or enum that has the
+# given name. This can be used to add things like methods which don't change ABI.
+[export.body]
+#"MyType" = """
+# void cppMethod() const;
+#"""
+
+[layout]
+# A string that should come before the name of any type which has been marked
+# as `#[repr(packed)]`. For instance, "__attribute__((packed))" would be a
+# reasonable value if targeting gcc/clang. A more portable solution would
+# involve emitting the name of a macro which you define in a platform-specific
+# way. e.g. "PACKED"
+#
+# default: `#[repr(packed)]` types will be treated as opaque, since it would
+# be unsafe for C callers to use a incorrectly laid-out union.
+packed = "PACKED"
+
+# A string that should come before the name of any type which has been marked
+# as `#[repr(align(n))]`. This string must be a function-like macro which takes
+# a single argument (the requested alignment, `n`). For instance, a macro
+# `#define`d as `ALIGNED(n)` in `header` which translates to
+# `__attribute__((aligned(n)))` would be a reasonable value if targeting
+# gcc/clang.
+#
+# default: `#[repr(align(n))]` types will be treated as opaque, since it
+# could be unsafe for C callers to use a incorrectly-aligned union.
+aligned_n = "ALIGNED"
+
+
+[fn]
+# An optional prefix to put before every function declaration
+# default: no prefix added
+prefix = ""
+
+# An optional postfix to put after any function declaration
+# default: no postix added
+postfix = ""
+
+# How to format function arguments
+#
+# possible values:
+# * "horizontal": place all arguments on the same line
+# * "vertical": place each argument on its own line
+# * "auto": only use vertical if horizontal would exceed line_length
+#
+# default: "auto"
+args = "horizontal"
+
+# An optional string that should prefix function declarations which have been
+# marked as `#[must_use]`. For instance, "__attribute__((warn_unused_result))"
+# would be a reasonable value if targeting gcc/clang. A more portable solution
+# would involve emitting the name of a macro which you define in a
+# platform-specific way. e.g. "MUST_USE_FUNC"
+# default: nothing is emitted for must_use functions
+must_use = "MUST_USE_FUNC"
+
+# A rule to use to rename function argument names. The renaming assumes the input
+# is the Rust standard snake_case, however it accepts all the different rename_args
+# inputs. This means many options here are no-ops or redundant.
+#
+# possible values (that actually do something):
+# * "CamelCase": my_arg => myArg
+# * "PascalCase": my_arg => MyArg
+# * "GeckoCase": my_arg => aMyArg
+# * "ScreamingSnakeCase": my_arg => MY_ARG
+# * "None": apply no renaming
+#
+# technically possible values (that shouldn't have a purpose here):
+# * "SnakeCase": apply no renaming
+# * "LowerCase": apply no renaming (actually applies to_lowercase, is this bug?)
+# * "UpperCase": same as ScreamingSnakeCase in this context
+# * "QualifiedScreamingSnakeCase" => same as ScreamingSnakeCase in this context
+#
+# default: "None"
+rename_args = "CamelCase"
+
+[struct]
+# A rule to use to rename struct field names. The renaming assumes the input is
+# the Rust standard snake_case, however it acccepts all the different rename_args
+# inputs. This means many options here are no-ops or redundant.
+#
+# possible values (that actually do something):
+# * "CamelCase": my_arg => myArg
+# * "PascalCase": my_arg => MyArg
+# * "GeckoCase": my_arg => mMyArg
+# * "ScreamingSnakeCase": my_arg => MY_ARG
+# * "None": apply no renaming
+#
+# technically possible values (that shouldn't have a purpose here):
+# * "SnakeCase": apply no renaming
+# * "LowerCase": apply no renaming (actually applies to_lowercase, is this bug?)
+# * "UpperCase": same as ScreamingSnakeCase in this context
+# * "QualifiedScreamingSnakeCase" => same as ScreamingSnakeCase in this context
+#
+# default: "None"
+rename_fields = "PascalCase"
+
+# An optional string that should come before the name of any struct which has been
+# marked as `#[must_use]`. For instance, "__attribute__((warn_unused))"
+# would be a reasonable value if targeting gcc/clang. A more portable solution
+# would involve emitting the name of a macro which you define in a
+# platform-specific way. e.g. "MUST_USE_STRUCT"
+#
+# default: nothing is emitted for must_use structs
+must_use = "MUST_USE_STRUCT"
+
+# Whether a Rust type with associated consts should emit those consts inside the
+# type's body. Otherwise they will be emitted trailing and with the type's name
+# prefixed. This does nothing if the target is C, or if
+# [const]allow_static_const = false
+#
+# default: false
+# associated_constants_in_body: false
+
+# Whether to derive a simple constructor that takes a value for every field.
+# default: false
+derive_constructor = true
+
+# Whether to derive an operator== for all structs
+# default: false
+derive_eq = false
+
+# Whether to derive an operator!= for all structs
+# default: false
+derive_neq = false
+
+# Whether to derive an operator< for all structs
+# default: false
+derive_lt = false
+
+# Whether to derive an operator<= for all structs
+# default: false
+derive_lte = false
+
+# Whether to derive an operator> for all structs
+# default: false
+derive_gt = false
+
+# Whether to derive an operator>= for all structs
+# default: false
+derive_gte = false
+
+[enum]
+# A rule to use to rename enum variants, and the names of any fields those
+# variants have. This should probably be split up into two separate options, but
+# for now, they're the same! See the documentation for `[struct]rename_fields`
+# for how this applies to fields. Renaming of the variant assumes that the input
+# is the Rust standard PascalCase. In the case of QualifiedScreamingSnakeCase,
+# it also assumed that the enum's name is PascalCase.
+#
+# possible values (that actually do something):
+# * "CamelCase": MyVariant => myVariant
+# * "SnakeCase": MyVariant => my_variant
+# * "ScreamingSnakeCase": MyVariant => MY_VARIANT
+# * "QualifiedScreamingSnakeCase": MyVariant => ENUM_NAME_MY_VARIANT
+# * "LowerCase": MyVariant => myvariant
+# * "UpperCase": MyVariant => MYVARIANT
+# * "None": apply no renaming
+#
+# technically possible values (that shouldn't have a purpose for the variants):
+# * "PascalCase": apply no renaming
+# * "GeckoCase": apply no renaming
+#
+# default: "None"
+rename_variants = "None"
+
+# Whether an extra "sentinel" enum variant should be added to all generated enums.
+# Firefox uses this for their IPC serialization library.
+#
+# WARNING: if the sentinel is ever passed into Rust, behaviour will be Undefined.
+# Rust does not know about this value, and will assume it cannot happen.
+#
+# default: false
+add_sentinel = false
+
+# Whether enum variant names should be prefixed with the name of the enum.
+# default: false
+prefix_with_name = false
+
+# Whether to generate static `::MyVariant(..)` constructors and `bool IsMyVariant()`
+# methods for enums with fields.
+#
+# default: false
+derive_helper_methods = false
+
+# Whether to generate `const MyVariant& AsMyVariant() const` methods for enums with fields.
+# default: false
+derive_const_casts = false
+
+# Whether to generate `MyVariant& AsMyVariant()` methods for enums with fields
+# default: false
+derive_mut_casts = false
+
+# The name of the macro/function to use for asserting `IsMyVariant()` in the body of
+# derived `AsMyVariant()` cast methods.
+#
+# default: "assert" (but also causes `` to be included by default)
+cast_assert_name = "MOZ_RELEASE_ASSERT"
+
+# An optional string that should come before the name of any enum which has been
+# marked as `#[must_use]`. For instance, "__attribute__((warn_unused))"
+# would be a reasonable value if targeting gcc/clang. A more portable solution
+# would involve emitting the name of a macro which you define in a
+# platform-specific way. e.g. "MUST_USE_ENUM"
+#
+# Note that this refers to the *output* type. That means this will not apply to an enum
+# with fields, as it will be emitted as a struct. `[struct]must_use` will apply there.
+#
+# default: nothing is emitted for must_use enums
+must_use = "MUST_USE_ENUM"
+
+# Whether enums with fields should generate destructors. This exists so that generic
+# enums can be properly instantiated with payloads that are C++ types with
+# destructors. This isn't necessary for structs because C++ has rules to
+# automatically derive the correct constructors and destructors for those types.
+#
+# Care should be taken with this option, as Rust and C++ cannot
+# properly interoperate with eachother's notions of destructors. Also, this may
+# change the ABI for the type. Either your destructor-full enums must live
+# exclusively within C++, or they must only be passed by-reference between
+# C++ and Rust.
+#
+# default: false
+derive_tagged_enum_destructor = false
+
+# Whether enums with fields should generate copy-constructor. See the discussion on
+# derive_tagged_enum_destructor for why this is both useful and very dangerous.
+#
+# default: false
+derive_tagged_enum_copy_constructor = false
+
+# Whether enums with fields should generate an empty, private destructor.
+# This allows the auto-generated constructor functions to compile, if there are
+# non-trivially constructible members. This falls in the same family of
+# dangerousness as `derive_tagged_enum_copy_constructor` and co.
+#
+# default: false
+private_default_tagged_enum_constructor = false
+
+[const]
+# Whether a generated constant can be a static const in C++ mode. I have no
+# idea why you would turn this off.
+#
+# default: true
+allow_static_const = true
+
+[macro_expansion]
+# Whether bindings should be generated for instances of the bitflags! macro.
+# default: false
+bitflags = false
+
+# Options for how your Rust library should be parsed
+
+[parse]
+# Whether to parse dependent crates and include their types in the output
+# default: false
+parse_deps = false
+
+# A white list of crate names that are allowed to be parsed. If this is defined,
+# only crates found in this list will ever be parsed.
+#
+# default: there is no whitelist (NOTE: this is the opposite of [])
+include = []
+
+# A black list of crate names that are not allowed to be parsed.
+# default: []
+exclude = []
+
+# Whether to use a new temporary target directory when running `rustc --pretty=expanded`.
+# This may be required for some build processes.
+#
+# default: false
+clean = false
+
+# Which crates other than the top-level binding crate we should generate
+# bindings for.
+#
+# default: []
+extra_bindings = []
+
+[parse.expand]
+# A list of crate names that should be run through `cargo expand` before
+# parsing to expand any macros. Note that if a crate is named here, it
+# will always be parsed, even if the blacklist/whitelist says it shouldn't be.
+#
+# default: []
+crates = []
+
+# If enabled, use the `--all-features` option when expanding. Ignored when
+# `features` is set. For backwards-compatibility, this is forced on if
+# `expand = ["euclid"]` shorthand is used.
+#
+# default: false
+all_features = false
+
+# When `all_features` is disabled and this is also disabled, use the
+# `--no-default-features` option when expanding.
+#
+# default: true
+default_features = true
+
+# A list of feature names that should be used when running `cargo expand`. This
+# combines with `default_features` like in your `Cargo.toml`. Note that the features
+# listed here are features for the current crate being built, *not* the crates
+# being expanded. The crate's `Cargo.toml` must take care of enabling the
+# appropriate features in its dependencies
+#
+# default: []
+features = ["cbindgen"]
\ No newline at end of file
diff --git a/cbindgen_cpp.toml b/cbindgen_cpp.toml
new file mode 100644
index 0000000..3864569
--- /dev/null
+++ b/cbindgen_cpp.toml
@@ -0,0 +1,486 @@
+# The language to output bindings in
+#
+# possible values: "C", "C++"
+#
+# default: "C++"
+language = "C++"
+
+# Options for wrapping the contents of the header:
+
+# An optional string of text to output at the beginning of the generated file
+# default: doesn't emit anything
+header = """
+#ifdef _MSC_VER
+#define PACKED
+ #pragma pack(push, 1)
+#else
+ #define PACKED __attribute__((packed))
+#endif
+"""
+
+# An optional string of text to output at the end of the generated file
+# default: doesn't emit anything
+trailer = "/* Text to put at the end of the generated file */"
+
+# An optional name to use as an include guard
+# default: doesn't emit an include guard
+include_guard = "dll_syringe"
+
+# An optional string of text to output between major sections of the generated
+# file as a warning against manual editing
+#
+# default: doesn't emit anything
+autogen_warning = "/* Warning, this file is autogenerated by cbindgen. Don't modify this manually. */"
+
+# Whether to include a comment with the version of cbindgen used to generate the file
+# default: false
+include_version = true
+
+# An optional namespace to output around the generated bindings
+# default: doesn't emit a namespace
+namespace = "dll_syringe"
+
+# An optional list of namespaces to output around the generated bindings
+# default: []
+namespaces = [ ]
+
+# An optional list of namespaces to declare as using with "using namespace"
+# default: []
+using_namespaces = [ ]
+
+# A list of sys headers to #include (with angle brackets)
+# default: []
+sys_includes = []
+
+# A list of headers to #include (with quotes)
+# default: []
+includes = []
+
+# Whether cbindgen's default C/C++ standard imports should be suppressed. These
+# imports are included by default because our generated headers tend to require
+# them (e.g. for uint32_t). Currently, the generated imports are:
+#
+# * for C: , , , ,
+#
+# * for C++: , , , , (depending on config)
+#
+# default: false
+no_includes = false
+
+# Code Style Options
+
+# The style to use for curly braces
+#
+# possible values: "SameLine", "NextLine"
+#
+# default: "SameLine"
+braces = "SameLine"
+
+# The desired length of a line to use when formatting lines
+# default: 100
+line_length = 100
+
+# The amount of spaces to indent by
+# default: 2
+tab_width = 2
+
+# How the generated documentation should be commented.
+#
+# possible values:
+# * "c": /* like this */
+# * "c99": // like this
+# * "c++": /// like this
+# * "doxy": like C, but with leading *'s on each line
+# * "auto": "c++" if that's the language, "doxy" otherwise
+#
+# default: "auto"
+documentation_style = "auto"
+
+# Codegen Options
+
+# When generating a C header, the kind of declaration style to use for structs
+# or enums.
+#
+# possible values:
+# * "type": typedef struct { ... } MyType;
+# * "tag": struct MyType { ... };
+# * "both": typedef struct MyType { ... } MyType;
+#
+# default: "both"
+style = "both"
+
+# A list of substitutions for converting cfg's to ifdefs. cfgs which aren't
+# defined here will just be discarded.
+#
+# e.g.
+# `#[cfg(target = "freebsd")] ...`
+# becomes
+# `#if defined(DEFINE_FREEBSD) ... #endif`
+[defines]
+#"target_os = freebsd" = "DEFINE_FREEBSD"
+#"feature = serde" = "DEFINE_SERDE"
+
+[export]
+# A list of additional items to always include in the generated bindings if they're
+# found but otherwise don't appear to be used by the public API.
+#
+# default: []
+include = []
+
+# A list of items to not include in the generated bindings
+# default: []
+exclude = []
+
+# A prefix to add before the name of every item
+# default: no prefix is added
+prefix = ""
+
+# Types of items that we'll generate. If empty, then all types of item are emitted.
+#
+# possible items: (TODO: explain these in detail)
+# * "constants":
+# * "globals":
+# * "enums":
+# * "structs":
+# * "unions":
+# * "typedefs":
+# * "opaque":
+# * "functions":
+#
+# default: []
+item_types = []
+
+# Whether applying rules in export.rename prevents export.prefix from applying.
+#
+# e.g. given this toml:
+#
+# [export]
+# prefix = "capi_"
+# [export.rename]
+# "MyType" = "my_cool_type"
+#
+# You get the following results:
+#
+# renaming_overrides_prefixing = true:
+# "MyType" => "my_cool_type"
+#
+# renaming_overrides_prefixing = false:
+# "MyType => capi_my_cool_type"
+#
+# default: false
+renaming_overrides_prefixing = true
+
+# Table of name conversions to apply to item names (lhs becomes rhs)
+[export.rename]
+"AtomicI32" = "int"
+
+# Table of things to add to the body of any struct, union, or enum that has the
+# given name. This can be used to add things like methods which don't change ABI.
+[export.body]
+#"MyType" = """
+# void cppMethod() const;
+#"""
+
+[layout]
+# A string that should come before the name of any type which has been marked
+# as `#[repr(packed)]`. For instance, "__attribute__((packed))" would be a
+# reasonable value if targeting gcc/clang. A more portable solution would
+# involve emitting the name of a macro which you define in a platform-specific
+# way. e.g. "PACKED"
+#
+# default: `#[repr(packed)]` types will be treated as opaque, since it would
+# be unsafe for C callers to use a incorrectly laid-out union.
+packed = "PACKED"
+
+# A string that should come before the name of any type which has been marked
+# as `#[repr(align(n))]`. This string must be a function-like macro which takes
+# a single argument (the requested alignment, `n`). For instance, a macro
+# `#define`d as `ALIGNED(n)` in `header` which translates to
+# `__attribute__((aligned(n)))` would be a reasonable value if targeting
+# gcc/clang.
+#
+# default: `#[repr(align(n))]` types will be treated as opaque, since it
+# could be unsafe for C callers to use a incorrectly-aligned union.
+aligned_n = "ALIGNED"
+
+
+[fn]
+# An optional prefix to put before every function declaration
+# default: no prefix added
+prefix = ""
+
+# An optional postfix to put after any function declaration
+# default: no postix added
+postfix = ""
+
+# How to format function arguments
+#
+# possible values:
+# * "horizontal": place all arguments on the same line
+# * "vertical": place each argument on its own line
+# * "auto": only use vertical if horizontal would exceed line_length
+#
+# default: "auto"
+args = "horizontal"
+
+# An optional string that should prefix function declarations which have been
+# marked as `#[must_use]`. For instance, "__attribute__((warn_unused_result))"
+# would be a reasonable value if targeting gcc/clang. A more portable solution
+# would involve emitting the name of a macro which you define in a
+# platform-specific way. e.g. "MUST_USE_FUNC"
+# default: nothing is emitted for must_use functions
+must_use = "MUST_USE_FUNC"
+
+# A rule to use to rename function argument names. The renaming assumes the input
+# is the Rust standard snake_case, however it accepts all the different rename_args
+# inputs. This means many options here are no-ops or redundant.
+#
+# possible values (that actually do something):
+# * "CamelCase": my_arg => myArg
+# * "PascalCase": my_arg => MyArg
+# * "GeckoCase": my_arg => aMyArg
+# * "ScreamingSnakeCase": my_arg => MY_ARG
+# * "None": apply no renaming
+#
+# technically possible values (that shouldn't have a purpose here):
+# * "SnakeCase": apply no renaming
+# * "LowerCase": apply no renaming (actually applies to_lowercase, is this bug?)
+# * "UpperCase": same as ScreamingSnakeCase in this context
+# * "QualifiedScreamingSnakeCase" => same as ScreamingSnakeCase in this context
+#
+# default: "None"
+rename_args = "CamelCase"
+
+[struct]
+# A rule to use to rename struct field names. The renaming assumes the input is
+# the Rust standard snake_case, however it acccepts all the different rename_args
+# inputs. This means many options here are no-ops or redundant.
+#
+# possible values (that actually do something):
+# * "CamelCase": my_arg => myArg
+# * "PascalCase": my_arg => MyArg
+# * "GeckoCase": my_arg => mMyArg
+# * "ScreamingSnakeCase": my_arg => MY_ARG
+# * "None": apply no renaming
+#
+# technically possible values (that shouldn't have a purpose here):
+# * "SnakeCase": apply no renaming
+# * "LowerCase": apply no renaming (actually applies to_lowercase, is this bug?)
+# * "UpperCase": same as ScreamingSnakeCase in this context
+# * "QualifiedScreamingSnakeCase" => same as ScreamingSnakeCase in this context
+#
+# default: "None"
+rename_fields = "PascalCase"
+
+# An optional string that should come before the name of any struct which has been
+# marked as `#[must_use]`. For instance, "__attribute__((warn_unused))"
+# would be a reasonable value if targeting gcc/clang. A more portable solution
+# would involve emitting the name of a macro which you define in a
+# platform-specific way. e.g. "MUST_USE_STRUCT"
+#
+# default: nothing is emitted for must_use structs
+must_use = "MUST_USE_STRUCT"
+
+# Whether a Rust type with associated consts should emit those consts inside the
+# type's body. Otherwise they will be emitted trailing and with the type's name
+# prefixed. This does nothing if the target is C, or if
+# [const]allow_static_const = false
+#
+# default: false
+# associated_constants_in_body: false
+
+# Whether to derive a simple constructor that takes a value for every field.
+# default: false
+derive_constructor = true
+
+# Whether to derive an operator== for all structs
+# default: false
+derive_eq = false
+
+# Whether to derive an operator!= for all structs
+# default: false
+derive_neq = false
+
+# Whether to derive an operator< for all structs
+# default: false
+derive_lt = false
+
+# Whether to derive an operator<= for all structs
+# default: false
+derive_lte = false
+
+# Whether to derive an operator> for all structs
+# default: false
+derive_gt = false
+
+# Whether to derive an operator>= for all structs
+# default: false
+derive_gte = false
+
+[enum]
+# A rule to use to rename enum variants, and the names of any fields those
+# variants have. This should probably be split up into two separate options, but
+# for now, they're the same! See the documentation for `[struct]rename_fields`
+# for how this applies to fields. Renaming of the variant assumes that the input
+# is the Rust standard PascalCase. In the case of QualifiedScreamingSnakeCase,
+# it also assumed that the enum's name is PascalCase.
+#
+# possible values (that actually do something):
+# * "CamelCase": MyVariant => myVariant
+# * "SnakeCase": MyVariant => my_variant
+# * "ScreamingSnakeCase": MyVariant => MY_VARIANT
+# * "QualifiedScreamingSnakeCase": MyVariant => ENUM_NAME_MY_VARIANT
+# * "LowerCase": MyVariant => myvariant
+# * "UpperCase": MyVariant => MYVARIANT
+# * "None": apply no renaming
+#
+# technically possible values (that shouldn't have a purpose for the variants):
+# * "PascalCase": apply no renaming
+# * "GeckoCase": apply no renaming
+#
+# default: "None"
+rename_variants = "None"
+
+# Whether an extra "sentinel" enum variant should be added to all generated enums.
+# Firefox uses this for their IPC serialization library.
+#
+# WARNING: if the sentinel is ever passed into Rust, behaviour will be Undefined.
+# Rust does not know about this value, and will assume it cannot happen.
+#
+# default: false
+add_sentinel = false
+
+# Whether enum variant names should be prefixed with the name of the enum.
+# default: false
+prefix_with_name = false
+
+# Whether to generate static `::MyVariant(..)` constructors and `bool IsMyVariant()`
+# methods for enums with fields.
+#
+# default: false
+derive_helper_methods = false
+
+# Whether to generate `const MyVariant& AsMyVariant() const` methods for enums with fields.
+# default: false
+derive_const_casts = false
+
+# Whether to generate `MyVariant& AsMyVariant()` methods for enums with fields
+# default: false
+derive_mut_casts = false
+
+# The name of the macro/function to use for asserting `IsMyVariant()` in the body of
+# derived `AsMyVariant()` cast methods.
+#
+# default: "assert" (but also causes `` to be included by default)
+cast_assert_name = "MOZ_RELEASE_ASSERT"
+
+# An optional string that should come before the name of any enum which has been
+# marked as `#[must_use]`. For instance, "__attribute__((warn_unused))"
+# would be a reasonable value if targeting gcc/clang. A more portable solution
+# would involve emitting the name of a macro which you define in a
+# platform-specific way. e.g. "MUST_USE_ENUM"
+#
+# Note that this refers to the *output* type. That means this will not apply to an enum
+# with fields, as it will be emitted as a struct. `[struct]must_use` will apply there.
+#
+# default: nothing is emitted for must_use enums
+must_use = "MUST_USE_ENUM"
+
+# Whether enums with fields should generate destructors. This exists so that generic
+# enums can be properly instantiated with payloads that are C++ types with
+# destructors. This isn't necessary for structs because C++ has rules to
+# automatically derive the correct constructors and destructors for those types.
+#
+# Care should be taken with this option, as Rust and C++ cannot
+# properly interoperate with eachother's notions of destructors. Also, this may
+# change the ABI for the type. Either your destructor-full enums must live
+# exclusively within C++, or they must only be passed by-reference between
+# C++ and Rust.
+#
+# default: false
+derive_tagged_enum_destructor = false
+
+# Whether enums with fields should generate copy-constructor. See the discussion on
+# derive_tagged_enum_destructor for why this is both useful and very dangerous.
+#
+# default: false
+derive_tagged_enum_copy_constructor = false
+
+# Whether enums with fields should generate an empty, private destructor.
+# This allows the auto-generated constructor functions to compile, if there are
+# non-trivially constructible members. This falls in the same family of
+# dangerousness as `derive_tagged_enum_copy_constructor` and co.
+#
+# default: false
+private_default_tagged_enum_constructor = false
+
+[const]
+# Whether a generated constant can be a static const in C++ mode. I have no
+# idea why you would turn this off.
+#
+# default: true
+allow_static_const = true
+
+[macro_expansion]
+# Whether bindings should be generated for instances of the bitflags! macro.
+# default: false
+bitflags = false
+
+# Options for how your Rust library should be parsed
+
+[parse]
+# Whether to parse dependent crates and include their types in the output
+# default: false
+parse_deps = false
+
+# A white list of crate names that are allowed to be parsed. If this is defined,
+# only crates found in this list will ever be parsed.
+#
+# default: there is no whitelist (NOTE: this is the opposite of [])
+include = []
+
+# A black list of crate names that are not allowed to be parsed.
+# default: []
+exclude = []
+
+# Whether to use a new temporary target directory when running `rustc --pretty=expanded`.
+# This may be required for some build processes.
+#
+# default: false
+clean = false
+
+# Which crates other than the top-level binding crate we should generate
+# bindings for.
+#
+# default: []
+extra_bindings = []
+
+[parse.expand]
+# A list of crate names that should be run through `cargo expand` before
+# parsing to expand any macros. Note that if a crate is named here, it
+# will always be parsed, even if the blacklist/whitelist says it shouldn't be.
+#
+# default: []
+crates = []
+
+# If enabled, use the `--all-features` option when expanding. Ignored when
+# `features` is set. For backwards-compatibility, this is forced on if
+# `expand = ["euclid"]` shorthand is used.
+#
+# default: false
+all_features = false
+
+# When `all_features` is disabled and this is also disabled, use the
+# `--no-default-features` option when expanding.
+#
+# default: true
+default_features = true
+
+# A list of feature names that should be used when running `cargo expand`. This
+# combines with `default_features` like in your `Cargo.toml`. Note that the features
+# listed here are features for the current crate being built, *not* the crates
+# being expanded. The crate's `Cargo.toml` must take care of enabling the
+# appropriate features in its dependencies
+#
+# default: []
+features = ["cbindgen"]
\ No newline at end of file
diff --git a/clean.ps1 b/clean.ps1
deleted file mode 100644
index b5e7270..0000000
--- a/clean.ps1
+++ /dev/null
@@ -1,9 +0,0 @@
-cargo clean
-
-Set-Location .\tests\helpers\test_payload
-cargo clean
-Set-Location ..\..\..
-
-Set-Location .\tests\helpers\test_target
-cargo clean
-Set-Location ..\..\..
diff --git a/dll-syringe.sln b/dll-syringe.sln
new file mode 100644
index 0000000..b910aba
--- /dev/null
+++ b/dll-syringe.sln
@@ -0,0 +1,25 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.5.002.0
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "csharp", "csharp\csharp.csproj", "{F984ECFF-FE18-456A-9F11-789454DFCB87}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {F984ECFF-FE18-456A-9F11-789454DFCB87}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {F984ECFF-FE18-456A-9F11-789454DFCB87}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {F984ECFF-FE18-456A-9F11-789454DFCB87}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {F984ECFF-FE18-456A-9F11-789454DFCB87}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {C0C6BF2E-7E97-457D-8547-69772834230A}
+ EndGlobalSection
+EndGlobal
diff --git a/rust-toolchain.toml b/rust-toolchain.toml
new file mode 100644
index 0000000..271800c
--- /dev/null
+++ b/rust-toolchain.toml
@@ -0,0 +1,2 @@
+[toolchain]
+channel = "nightly"
\ No newline at end of file
diff --git a/scripts/clean.ps1 b/scripts/clean.ps1
new file mode 100644
index 0000000..4c6df25
--- /dev/null
+++ b/scripts/clean.ps1
@@ -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 "../../.."
diff --git a/scripts/test-wine.ps1 b/scripts/test-wine.ps1
new file mode 100644
index 0000000..2518492
--- /dev/null
+++ b/scripts/test-wine.ps1
@@ -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"
\ No newline at end of file
diff --git a/scripts/test.ps1 b/scripts/test.ps1
new file mode 100644
index 0000000..a6c1e6c
--- /dev/null
+++ b/scripts/test.ps1
@@ -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
\ No newline at end of file
diff --git a/src/c_exports.rs b/src/c_exports.rs
new file mode 100644
index 0000000..0a8deaf
--- /dev/null
+++ b/src/c_exports.rs
@@ -0,0 +1,178 @@
+use crate::process::{BorrowedProcessModule, OwnedProcess};
+use crate::Syringe;
+use std::ffi::CStr;
+use std::os::raw::c_char;
+use std::path::Path;
+use std::ptr::null_mut;
+
+// Note: Don't specify [repr(C)] for these structs, as they are not passed to C code.
+// The C code only interacts with pointers to these structs.
+
+/// Represents an instance of a Syringe for a target process.
+#[derive(Debug)]
+pub struct CSyringe {
+ syringe: Syringe,
+}
+
+/// Represents a module within a process, allowing for ejection of a previously injected module.
+#[derive(Debug)]
+pub struct CProcessModule<'a> {
+ module: BorrowedProcessModule<'a>,
+}
+
+/// Creates a new `Syringe` instance for a process identified by PID.
+///
+/// # Arguments
+///
+/// * `pid` - The PID of the target process.
+///
+/// # Returns
+///
+/// A pointer to a `CSyringe` instance, or null if the process could not be opened.
+#[no_mangle]
+pub extern "C" fn syringe_for_process(pid: u32) -> *mut CSyringe {
+ let process = match OwnedProcess::from_pid(pid) {
+ Ok(process) => process,
+ Err(_) => return null_mut(),
+ };
+ let syringe = Syringe::for_process(process);
+ let boxed = Box::new(CSyringe { syringe });
+ Box::into_raw(boxed) // Directly return a pointer to the boxed CSyringe
+}
+
+/// Creates a new `Syringe` instance for a suspended process identified by PID.
+///
+/// # Arguments
+///
+/// * `pid` - The PID of the target suspended process.
+///
+/// # Returns
+///
+/// A pointer to a `CSyringe` instance, or null if the process could not be opened or initialized.
+#[no_mangle]
+pub extern "C" fn syringe_for_suspended_process(pid: u32) -> *mut CSyringe {
+ let process = match OwnedProcess::from_pid(pid) {
+ Ok(process) => process,
+ Err(_) => return null_mut(),
+ };
+
+ match Syringe::for_suspended_process(process) {
+ Ok(syringe) => {
+ let boxed = Box::new(CSyringe { syringe });
+ Box::into_raw(boxed) // Return a pointer to the boxed CSyringe
+ }
+ Err(_) => null_mut(),
+ }
+}
+
+/// Injects a DLL into the target process associated with the given `Syringe`.
+///
+/// # Safety
+///
+/// This function is unsafe because it dereferences raw pointers.
+///
+/// # Arguments
+///
+/// * `c_syringe` - A pointer to the `CSyringe` instance.
+/// * `dll_path` - A C string path to the DLL to be injected.
+///
+/// # Returns
+///
+/// `true` if injection succeeded, otherwise `false`.
+#[no_mangle]
+pub unsafe extern "C" fn syringe_inject(c_syringe: *mut CSyringe, dll_path: *const c_char) -> bool {
+ let c_syringe = unsafe { &mut *c_syringe };
+ let path_str = unsafe { CStr::from_ptr(dll_path).to_str().unwrap() };
+ c_syringe.syringe.inject(Path::new(path_str)).is_ok()
+}
+
+/// Finds or injects a DLL into the target process.
+///
+/// If the DLL is already present in the target process, it returns the existing module.
+/// Otherwise, it injects the DLL.
+///
+/// # Safety
+///
+/// This function is unsafe because it dereferences raw pointers.
+///
+/// # Arguments
+///
+/// * `c_syringe` - A pointer to the `CSyringe` instance.
+/// * `dll_path` - A C string path to the DLL to be injected.
+///
+/// # Returns
+///
+/// A pointer to a `CProcessModule`, or null if the operation failed.
+#[no_mangle]
+pub unsafe extern "C" fn syringe_find_or_inject<'a>(
+ c_syringe: *mut CSyringe,
+ dll_path: *const c_char,
+) -> *mut CProcessModule<'a> {
+ let syringe = unsafe { &mut (*c_syringe).syringe };
+ let path_str = unsafe { CStr::from_ptr(dll_path).to_str().unwrap() };
+ match syringe.find_or_inject(Path::new(path_str)) {
+ Ok(module) => {
+ let c_module = Box::new(CProcessModule { module });
+ Box::into_raw(c_module)
+ }
+ Err(_) => null_mut(),
+ }
+}
+
+/// Ejects a module from the target process.
+///
+/// # Arguments
+///
+/// * `c_syringe` - A pointer to the `CSyringe` instance.
+/// * `c_module` - A pointer to the `CProcessModule` to be ejected.
+///
+/// # Returns
+///
+/// `true` if ejection succeeded, otherwise `false`.
+///
+/// # Safety
+/// This is safe as long as it has a valid pointer to a Syringe and Module.
+#[no_mangle]
+pub unsafe extern "C" fn syringe_eject(
+ c_syringe: *mut CSyringe,
+ c_module: *mut CProcessModule<'_>,
+) -> bool {
+ let syringe = unsafe { &mut (*c_syringe).syringe };
+ let module = unsafe { &mut (*c_module).module };
+ syringe.eject(*module).is_ok()
+}
+
+/// Frees a `CSyringe` instance.
+///
+/// # Arguments
+///
+/// * `c_syringe` - A pointer to the `CSyringe` instance to be freed.
+///
+/// # Safety
+/// This is safe as long as it has a valid pointer to a Syringe instance.
+#[no_mangle]
+pub unsafe extern "C" fn syringe_free(c_syringe: *mut CSyringe) {
+ if !c_syringe.is_null() {
+ unsafe {
+ let _ = Box::from_raw(c_syringe); // drop
+ }
+ }
+}
+
+/// Frees a `CProcessModule` instance.
+///
+/// # Arguments
+///
+/// * `c_module` - A pointer to the `CProcessModule` to be freed.
+///
+/// # Safety
+/// This is safe as long as it has a valid pointer to a module
+/// created by this Syringe instance.
+#[no_mangle]
+pub unsafe extern "C" fn syringe_module_free(c_module: *mut CProcessModule<'_>) {
+ if !c_module.is_null() {
+ unsafe {
+ let _ = Box::from_raw(c_module); // drop
+ }
+ }
+}
diff --git a/src/lib.rs b/src/lib.rs
index 8aaa914..b697940 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -48,6 +48,10 @@ pub mod error;
/// Module containing traits and types for working with function pointers.
pub mod function;
+/// C exports for the library.
+#[cfg(feature = "c-exports")]
+pub mod c_exports;
+
#[cfg(feature = "payload-utils")]
#[doc(hidden)]
pub mod payload_utils;
diff --git a/src/process/memory/buffer.rs b/src/process/memory/buffer.rs
index 5ebb334..be746f8 100644
--- a/src/process/memory/buffer.rs
+++ b/src/process/memory/buffer.rs
@@ -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(),
diff --git a/src/process/memory/mod.rs b/src/process/memory/mod.rs
index b7c91c3..77f7d2b 100644
--- a/src/process/memory/mod.rs
+++ b/src/process/memory/mod.rs
@@ -1,4 +1,5 @@
mod buffer;
+#[allow(unused_imports)]
pub use buffer::*;
#[cfg(feature = "syringe")]
diff --git a/src/process/memory/raw_allocator.rs b/src/process/memory/raw_allocator.rs
index e274472..797876a 100644
--- a/src/process/memory/raw_allocator.rs
+++ b/src/process/memory/raw_allocator.rs
@@ -1,6 +1,5 @@
-use std::{collections::LinkedList, io, mem, ptr::NonNull};
-
use crate::process::{memory::ProcessMemoryBuffer, BorrowedProcess, Process};
+use std::{collections::LinkedList, io, mem, ptr::NonNull};
pub trait RawAllocator {
type Error;
@@ -224,12 +223,17 @@ pub enum AllocError {
}
#[cfg(test)]
-mod tests {
- use std::mem;
-
- use crate::process::memory::ProcessMemorySlice;
+struct AlignTestStruct {
+ _a: u8,
+ _b: u16,
+ _c: u32,
+ _d: u64,
+}
+#[cfg(test)]
+mod tests {
use super::*;
+ use crate::process::memory::ProcessMemorySlice;
#[test]
fn single_alloc() {
@@ -367,11 +371,3 @@ mod tests {
assert!(alloc.len > page_size);
}
}
-
-#[cfg(test)]
-struct AlignTestStruct {
- _a: u8,
- _b: u16,
- _c: u32,
- _d: u64,
-}
diff --git a/src/rpc/raw.rs b/src/rpc/raw.rs
index b6abdff..767ab97 100644
--- a/src/rpc/raw.rs
+++ b/src/rpc/raw.rs
@@ -1,5 +1,4 @@
-use iced_x86::{code_asm::*, IcedError};
-
+use iced_x86::code_asm::*;
use std::{
any::{self, TypeId},
cell::OnceCell,
@@ -165,6 +164,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 {
diff --git a/src/rpc/rpc_core.rs b/src/rpc/rpc_core.rs
index 3d33e67..f1cf76f 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,
@@ -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 {
diff --git a/src/syringe.rs b/src/syringe.rs
index 4351c01..d713a4e 100644
--- a/src/syringe.rs
+++ b/src/syringe.rs
@@ -27,7 +27,7 @@ use crate::{
#[cfg(all(target_arch = "x86_64", feature = "into-x86-from-x64"))]
use {
goblin::pe::PE,
- std::{convert::TryInto, fs, mem::MaybeUninit, path::PathBuf, time::Duration},
+ std::{fs, mem::MaybeUninit, path::PathBuf, time::Duration},
widestring::U16Str,
winapi::{shared::minwindef::MAX_PATH, um::wow64apiset::GetSystemWow64DirectoryW},
};
@@ -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/test.ps1 b/test.ps1
deleted file mode 100644
index bf8ab5c..0000000
--- a/test.ps1
+++ /dev/null
@@ -1,2 +0,0 @@
-cargo test --target i686-pc-windows-msvc -- --nocapture
-cargo test --target x86_64-pc-windows-msvc -- --nocapture
diff --git a/tests/common/mod.rs b/tests/common/mod.rs
index d666d51..657172c 100644
--- a/tests/common/mod.rs
+++ b/tests/common/mod.rs
@@ -1,44 +1,41 @@
+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,
+ fs::{canonicalize, remove_file, File},
+ io::{copy, ErrorKind},
path::PathBuf,
process::{Command, Stdio},
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",
- Some(&find_x86_variant_of_target()),
- false,
- "dll",
- )
+ build_helper_crate("test_payload", &find_x86_variant_of_target(), false, "dll")
}
pub fn build_test_target_x86() -> Result> {
- build_helper_crate(
- "test_target",
- Some(&find_x86_variant_of_target()),
- false,
- "exe",
- )
+ build_helper_crate("test_target", &find_x86_variant_of_target(), false, "exe")
}
pub fn build_test_payload_x64() -> Result> {
- build_helper_crate(
- "test_payload",
- Some(&find_x64_variant_of_target()),
- false,
- "dll",
- )
+ build_helper_crate("test_payload", &find_x64_variant_of_target(), false, "dll")
}
pub fn build_test_target_x64() -> Result> {
- build_helper_crate(
- "test_target",
- Some(&find_x64_variant_of_target()),
- false,
- "exe",
- )
+ build_helper_crate("test_target", &find_x64_variant_of_target(), false, "exe")
}
fn find_x64_variant_of_target() -> String {
@@ -51,7 +48,7 @@ fn find_x86_variant_of_target() -> String {
pub fn build_helper_crate(
crate_name: &str,
- target: Option<&str>,
+ target: &str,
release: bool,
ext: &str,
) -> Result> {
@@ -59,37 +56,84 @@ pub fn build_helper_crate(
.join(crate_name)
.canonicalize()?;
- let mut command = Command::new("cargo");
- command
- .arg("build")
- .stdin(Stdio::null())
- .stdout(Stdio::null())
- .stderr(Stdio::null());
- if let Some(target) = target {
- command.arg("--target").arg(target);
+ // For cross/wine testing, we precompile in external script.
+ if !is_cross() {
+ let mut command = Command::new("cargo");
+ command
+ .arg("build")
+ .arg("--target")
+ .arg(target)
+ .stdin(Stdio::null())
+ .stdout(Stdio::null())
+ .stderr(Stdio::null());
+
+ let exit_code = command.current_dir(&payload_crate_path).spawn()?.wait()?;
+ assert!(
+ exit_code.success(),
+ "Failed to build helper crate {} for target {}",
+ crate_name,
+ target
+ );
}
- let exit_code = command.current_dir(&payload_crate_path).spawn()?.wait()?;
- assert!(
- exit_code.success(),
- "Failed to build helper crate {} for target {}",
- crate_name,
- target.unwrap_or("default")
- );
let mut payload_artifact_path = payload_crate_path;
payload_artifact_path.push("target");
-
- if let Some(target) = target {
- payload_artifact_path.push(target);
- }
-
+ payload_artifact_path.push(target);
payload_artifact_path.push(if release { "release" } else { "debug" });
payload_artifact_path.push(format!("{crate_name}.{ext}"));
- assert!(&payload_artifact_path.exists());
+ assert!(
+ &payload_artifact_path.exists(),
+ "Artifact doesn't exist! {:?}",
+ &payload_artifact_path
+ );
Ok(payload_artifact_path)
}
+/// Detects cross-rs.
+///
+/// Remarks:
+///
+/// I wish I could install Rust itself via `Rustup` here, but the Ubuntu image that ships with
+/// `cross` doesn't have the right packages to support encryption, thus we can't download toolchains
+/// (I tried). And I also didn't have good luck with pre-build step and downloading extra packages.
+///
+/// So as a compromise, we build the test binaries outside for testing from Linux.
+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(dummy_process, payload_path.as_ref())
+ #[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]
+ #[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/exports.rs b/tests/exports.rs
new file mode 100644
index 0000000..dadab16
--- /dev/null
+++ b/tests/exports.rs
@@ -0,0 +1,98 @@
+#![cfg(feature = "c-exports")]
+#![cfg(feature = "syringe")]
+
+#[allow(unused)]
+mod common;
+
+use dll_syringe::c_exports::*;
+use std::ffi::CString;
+use std::os::windows::io::IntoRawHandle;
+use winapi::um::processthreadsapi::GetProcessId;
+
+syringe_test! {
+ fn test_csyringe_for_process(
+ process: OwnedProcess,
+ _payload_path: &Path,
+ ) {
+ let handle = process.into_raw_handle();
+ let pid = unsafe { GetProcessId(handle) };
+ let c_syringe = syringe_for_suspended_process(pid);
+ assert!(!c_syringe.is_null());
+
+ unsafe { syringe_free(c_syringe) };
+ }
+}
+
+suspended_process_test! {
+ fn test_csyringe_for_suspended_process(
+ process: OwnedProcess,
+ ) {
+ let handle = process.into_raw_handle();
+ let pid = unsafe { GetProcessId(handle) };
+ let c_syringe = syringe_for_suspended_process(pid);
+ assert!(!c_syringe.is_null());
+
+ unsafe { syringe_free(c_syringe) };
+ }
+}
+
+syringe_test! {
+ fn test_csyringe_inject(
+ process: OwnedProcess,
+ payload_path: &Path,
+ ) {
+ let handle = process.into_raw_handle();
+ let pid = unsafe { GetProcessId(handle) };
+ let c_syringe = syringe_for_suspended_process(pid);
+ assert!(!c_syringe.is_null());
+
+ let dll_path = CString::new(payload_path.to_str().unwrap()).unwrap();
+ let injected = unsafe { syringe_inject(c_syringe, dll_path.as_ptr()) };
+ assert!(injected);
+
+ unsafe { syringe_free(c_syringe) };
+ }
+}
+
+syringe_test! {
+ fn test_csyringe_find_or_inject(
+ process: OwnedProcess,
+ payload_path: &Path,
+ ) {
+ let handle = process.into_raw_handle();
+ let pid = unsafe { GetProcessId(handle) };
+ let c_syringe = syringe_for_suspended_process(pid);
+ assert!(!c_syringe.is_null());
+
+ let dll_path = CString::new(payload_path.to_str().unwrap()).unwrap();
+ let c_module = unsafe { syringe_find_or_inject(c_syringe, dll_path.as_ptr()) };
+ assert!(!c_module.is_null());
+
+ unsafe {
+ syringe_module_free(c_module);
+ syringe_free(c_syringe);
+ }
+ }
+}
+
+syringe_test! {
+ fn test_csyringe_eject(
+ process: OwnedProcess,
+ payload_path: &Path,
+ ) {
+ let handle = process.into_raw_handle();
+ let pid = unsafe { GetProcessId(handle) };
+ let c_syringe = syringe_for_suspended_process(pid);
+ assert!(!c_syringe.is_null());
+
+ let dll_path = CString::new(payload_path.to_str().unwrap()).unwrap();
+ let c_module = unsafe { syringe_find_or_inject(c_syringe, dll_path.as_ptr()) };
+ assert!(!c_module.is_null());
+
+ let ejected = unsafe { syringe_eject(c_syringe, c_module) };
+ assert!(ejected);
+
+ unsafe { syringe_module_free(c_module); }
+ unsafe { syringe_free(c_syringe) };
+ }
+}
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 a190905..abf7858 100644
--- a/tests/process.rs
+++ b/tests/process.rs
@@ -1,5 +1,10 @@
+use core::mem::{size_of, zeroed};
use dll_syringe::process::{BorrowedProcess, OwnedProcess, Process};
-use std::{fs, mem, time::Duration};
+use std::{ffi::CString, fs, mem, process::Command, time::Duration};
+use winapi::um::{
+ libloaderapi::{GetProcAddress, LoadLibraryA},
+ winnt::OSVERSIONINFOW,
+};
#[allow(unused)]
mod common;
@@ -28,7 +33,7 @@ process_test! {
}
}
-process_test! {
+suspended_process_test! {
fn list_module_handles_on_crashed_does_not_hang(
process: OwnedProcess
) {
@@ -38,7 +43,7 @@ process_test! {
}
}
-process_test! {
+suspended_process_test! {
fn is_alive_is_true_for_running(
process: OwnedProcess
) {
@@ -48,7 +53,7 @@ process_test! {
}
}
-process_test! {
+suspended_process_test! {
fn is_alive_is_false_for_killed(
process: OwnedProcess
) {
@@ -76,7 +81,7 @@ process_test! {
}
}
-process_test! {
+suspended_process_test! {
fn kill_guard_kills_process_on_drop(
process: OwnedProcess
) {
@@ -87,10 +92,15 @@ process_test! {
}
}
-process_test! {
+suspended_process_test! {
fn long_process_paths_are_supported(
process: OwnedProcess
) {
+ if is_running_under_wine() || is_older_than_windows_10() {
+ println!("Test skipped due to running under an environment with unsupported long paths. (Wine or older than Windows 10).");
+ return;
+ }
+
let process_path = process.path().unwrap();
process.kill().unwrap();
@@ -138,3 +148,32 @@ fn current_pseudo_process_eq_current_process() {
assert_eq!(pseudo.try_to_owned().unwrap(), normal);
assert_eq!(pseudo, normal.try_clone().unwrap());
}
+
+fn is_running_under_wine() -> bool {
+ unsafe {
+ let ntdll = CString::new("ntdll.dll").unwrap();
+ let lib = LoadLibraryA(ntdll.as_ptr());
+ if !lib.is_null() {
+ let func_name = CString::new("wine_get_version").unwrap();
+ let func = GetProcAddress(lib, func_name.as_ptr());
+ !func.is_null()
+ } else {
+ false
+ }
+ }
+}
+
+// winapi crate doesn't have this.
+// This is in ntdll, so already loaded for every Windows process.
+extern "system" {
+ fn RtlGetVersion(lpVersionInformation: &mut OSVERSIONINFOW) -> u32;
+}
+
+fn is_older_than_windows_10() -> bool {
+ unsafe {
+ let mut os_info: OSVERSIONINFOW = zeroed();
+ os_info.dwOSVersionInfoSize = size_of::() as u32;
+ RtlGetVersion(&mut os_info);
+ os_info.dwMajorVersion < 10
+ }
+}
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();