From e832a52ce8e542715465735d661a225a39456957 Mon Sep 17 00:00:00 2001 From: 7reax Date: Sun, 29 Jun 2025 23:48:04 +0200 Subject: [PATCH 1/8] replace dll-syringe with custom dll injector (using winapi) --- Cargo.toml | 1 - maxima-lib/Cargo.toml | 1 - maxima-lib/src/core/background_service_win.rs | 7 +- maxima-lib/src/core/error.rs | 6 +- maxima-lib/src/util/dll_injector.rs | 152 ++++++++++++++++++ maxima-lib/src/util/mod.rs | 1 + maxima-service/Cargo.toml | 1 - maxima-service/src/service/error.rs | 4 +- maxima-service/src/service/mod.rs | 12 +- 9 files changed, 168 insertions(+), 17 deletions(-) create mode 100644 maxima-lib/src/util/dll_injector.rs diff --git a/Cargo.toml b/Cargo.toml index b573978..491cbaf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,4 +16,3 @@ strip = true [patch.crates-io] flate2 = { git = 'https://github.com/ArmchairDevelopers/flate2-rs.git' } async-compression = { git = 'https://github.com/ArmchairDevelopers/async-compression.git' } -dll-syringe = { git = "https://github.com/fry/dll-syringe", rev = "0a8b18e" } diff --git a/maxima-lib/Cargo.toml b/maxima-lib/Cargo.toml index 3eca4a4..b034b66 100644 --- a/maxima-lib/Cargo.toml +++ b/maxima-lib/Cargo.toml @@ -96,7 +96,6 @@ winreg = "0.51.0" windows-service = "0.6.0" is_elevated = "0.1.2" widestring = "1.0.2" -dll-syringe = "0.15.2" wmi = "0.13.1" [target.'cfg(target_os = "macos")'.dependencies] diff --git a/maxima-lib/src/core/background_service_win.rs b/maxima-lib/src/core/background_service_win.rs index 8eae5b5..89b59bc 100644 --- a/maxima-lib/src/core/background_service_win.rs +++ b/maxima-lib/src/core/background_service_win.rs @@ -1,4 +1,3 @@ -use dll_syringe::{process::OwnedProcess, Syringe}; use log::debug; use reqwest::StatusCode; use serde::{Deserialize, Serialize}; @@ -7,6 +6,7 @@ use crate::core::error::BackgroundServiceClientError; use crate::util::native::NativeError; use crate::util::registry::{set_up_registry, RegistryError}; use is_elevated::is_elevated; +use crate::util::dll_injector::DllInjector; pub const BACKGROUND_SERVICE_PORT: u16 = 13021; @@ -23,9 +23,8 @@ pub async fn request_library_injection( debug!("Injecting {}", path); if is_elevated() { - let process = OwnedProcess::from_pid(pid)?; - let syringe = Syringe::for_process(process); - syringe.inject(path)?; + let injector = DllInjector::new(pid); + injector.inject(path)?; return Ok(()); } diff --git a/maxima-lib/src/core/error.rs b/maxima-lib/src/core/error.rs index c522a66..d6c21dc 100644 --- a/maxima-lib/src/core/error.rs +++ b/maxima-lib/src/core/error.rs @@ -1,12 +1,10 @@ use thiserror::Error; +use crate::util::dll_injector::InjectionError; #[derive(Error, Debug)] pub enum BackgroundServiceClientError { #[error(transparent)] Native(#[from] crate::util::native::NativeError), - #[cfg(windows)] - #[error(transparent)] - Inject(#[from] dll_syringe::error::InjectError), #[error(transparent)] Io(#[from] std::io::Error), #[error(transparent)] @@ -15,6 +13,8 @@ pub enum BackgroundServiceClientError { Reqwest(#[from] reqwest::Error), #[error(transparent)] Registry(#[from] crate::util::registry::RegistryError), + #[error(transparent)] + Injection(#[from] InjectionError), #[error("request failed: `{0}`")] Request(String), diff --git a/maxima-lib/src/util/dll_injector.rs b/maxima-lib/src/util/dll_injector.rs new file mode 100644 index 0000000..2812224 --- /dev/null +++ b/maxima-lib/src/util/dll_injector.rs @@ -0,0 +1,152 @@ +use std::ffi::CString; +use std::mem; +use std::ptr; +use thiserror::Error; +use winapi::shared::minwindef::{LPVOID}; +use winapi::um::errhandlingapi::GetLastError; +use winapi::um::handleapi::CloseHandle; +use winapi::um::libloaderapi::{GetModuleHandleA, GetProcAddress}; +use winapi::um::memoryapi::{VirtualAllocEx, VirtualFreeEx, WriteProcessMemory}; +use winapi::um::processthreadsapi::{CreateRemoteThread, OpenProcess}; +use winapi::um::synchapi::WaitForSingleObject; +use winapi::um::winbase::INFINITE; +use winapi::um::winnt::{ + HANDLE, MEM_COMMIT, MEM_RELEASE, MEM_RESERVE, PAGE_READWRITE, PROCESS_ALL_ACCESS, +}; + +#[derive(Debug, Error)] +pub enum InjectionError { + #[error("failed to create remote thread, error code: {0}")] + CreateRemoteThreadFailed(u32), + #[error("failed to get kernel32 handle, error code: {0}")] + GetModuleHandleFailed(u32), + #[error("failed to get LoadLibraryA address, error code: {0}")] + GetProcAddressFailed(u32), + #[error("invalid DLL path")] + InvalidPath, + #[error("failed to open process, error code: {0}")] + OpenProcessFailed(u32), + #[error("process not found")] + ProcessNotFound, + #[error("failed to write process memory, error code: {0}")] + WriteProcessMemoryFailed(u32), + #[error("failed to allocate memory, error code: {0}")] + VirtualAllocFailed(u32), +} + +pub struct DllInjector { + target_pid: u32, +} + +impl DllInjector { + pub fn new(pid: u32) -> Self { + Self { target_pid: pid } + } + + pub fn inject(&self, dll_path: &str) -> Result<(), InjectionError> { + unsafe { + let process_handle = OpenProcess(PROCESS_ALL_ACCESS, 0, self.target_pid); + if process_handle.is_null() { + return Err(InjectionError::OpenProcessFailed(GetLastError())); + } + + let _process_guard = ProcessHandleGuard(process_handle); + let dll_path_cstring = CString::new(dll_path) + .map_err(|_| InjectionError::InvalidPath)?; + let dll_path_bytes = dll_path_cstring.as_bytes_with_nul(); + let dll_path_size = dll_path_bytes.len(); + + let remote_memory = VirtualAllocEx( + process_handle, + ptr::null_mut(), + dll_path_size, + MEM_COMMIT | MEM_RESERVE, + PAGE_READWRITE, + ); + + if remote_memory.is_null() { + return Err(InjectionError::VirtualAllocFailed(GetLastError())); + } + + let _memory_guard = RemoteMemoryGuard { + process_handle, + address: remote_memory, + }; + + let mut bytes_written: usize = 0; + let result = WriteProcessMemory( + process_handle, + remote_memory, + dll_path_bytes.as_ptr() as LPVOID, + dll_path_size, + &mut bytes_written as *mut usize, + ); + + if result == 0 { + return Err(InjectionError::WriteProcessMemoryFailed(GetLastError())); + } + + let kernel32_cstring = CString::new("kernel32.dll").unwrap(); + let kernel32_handle = GetModuleHandleA(kernel32_cstring.as_ptr()); + if kernel32_handle.is_null() { + return Err(InjectionError::GetModuleHandleFailed(GetLastError())); + } + + let load_library_cstring = CString::new("LoadLibraryA").unwrap(); + let load_library_addr = GetProcAddress( + kernel32_handle, + load_library_cstring.as_ptr(), + ); + + if load_library_addr.is_null() { + return Err(InjectionError::GetProcAddressFailed(GetLastError())); + } + + let thread_handle = CreateRemoteThread( + process_handle, + ptr::null_mut(), + 0, + Some(mem::transmute(load_library_addr)), + remote_memory, + 0, + ptr::null_mut(), + ); + + if thread_handle.is_null() { + return Err(InjectionError::CreateRemoteThreadFailed(GetLastError())); + } + + WaitForSingleObject(thread_handle, INFINITE); + CloseHandle(thread_handle); + + Ok(()) + } + } +} + +struct ProcessHandleGuard(HANDLE); + +impl Drop for ProcessHandleGuard { + fn drop(&mut self) { + unsafe { + if !self.0.is_null() { + CloseHandle(self.0); + } + } + } +} + +struct RemoteMemoryGuard { + process_handle: HANDLE, + address: LPVOID, +} + +impl Drop for RemoteMemoryGuard { + fn drop(&mut self) { + unsafe { + if !self.address.is_null() { + VirtualFreeEx(self.process_handle, self.address, 0, MEM_RELEASE); + } + } + } +} \ No newline at end of file diff --git a/maxima-lib/src/util/mod.rs b/maxima-lib/src/util/mod.rs index 701fbfd..efa8391 100644 --- a/maxima-lib/src/util/mod.rs +++ b/maxima-lib/src/util/mod.rs @@ -6,6 +6,7 @@ pub mod registry; pub mod simple_crypto; pub mod system_profiler_utils; pub mod wmi_utils; +pub mod dll_injector; #[derive(thiserror::Error, Debug)] pub enum BackgroundServiceControlError { diff --git a/maxima-service/Cargo.toml b/maxima-service/Cargo.toml index 1d7e43e..aa68d90 100644 --- a/maxima-service/Cargo.toml +++ b/maxima-service/Cargo.toml @@ -26,7 +26,6 @@ thiserror = "2.0.12" winapi = { version = "0.3.9", features = [ "memoryapi", "handleapi", "synchapi", "wincon", "consoleapi" ] } winreg = "0.50.0" windows-service = "0.6.0" -dll-syringe = "0.15.2" [build-dependencies] maxima-resources = { path = "../maxima-resources" } diff --git a/maxima-service/src/service/error.rs b/maxima-service/src/service/error.rs index dd1bf6a..4933af3 100644 --- a/maxima-service/src/service/error.rs +++ b/maxima-service/src/service/error.rs @@ -1,12 +1,12 @@ use actix_web::{error, http::header::ContentType, HttpResponse}; use reqwest::StatusCode; use thiserror::Error; -use windows_service::service; +use maxima::util::dll_injector::InjectionError; #[derive(Error, Debug)] pub enum ServerError { #[error(transparent)] - Inject(#[from] dll_syringe::error::InjectError), + Injection(#[from] InjectionError), #[error(transparent)] Io(#[from] std::io::Error), #[error(transparent)] diff --git a/maxima-service/src/service/mod.rs b/maxima-service/src/service/mod.rs index 0f758ac..0350f2f 100644 --- a/maxima-service/src/service/mod.rs +++ b/maxima-service/src/service/mod.rs @@ -1,8 +1,6 @@ use std::fs::File; use actix_web::{get, post, web, HttpResponse, Responder}; -use dll_syringe::process::OwnedProcess; -use dll_syringe::Syringe; use log::info; use maxima::util::registry::set_up_registry; use maxima::util::service::SERVICE_NAME; @@ -24,6 +22,7 @@ use crate::service::error::ServerError; use crate::service::hash::get_sha256_hash_of_pid; use maxima::core::background_service::{ServiceLibraryInjectionRequest, BACKGROUND_SERVICE_PORT}; use maxima::util::native::SafeParent; +use maxima::util::dll_injector::{DllInjector, InjectionError}; pub(crate) mod error; mod hash; @@ -89,6 +88,11 @@ async fn req_set_up_registry() -> impl Responder { format!("Done") } +pub fn inject_dll(pid: u32, dll_path: &str) -> Result<(), InjectionError> { + let injector = DllInjector::new(pid); + injector.inject(dll_path) +} + // This is for KYBER. Ideally this would be moved to a separate Kyber service, // but it isn't a great user experience to have to install two windows services. // We'll eventually find a better workaround and move this somewhere else. @@ -97,7 +101,6 @@ async fn req_inject_library(body: web::Bytes) -> Result Result Date: Mon, 30 Jun 2025 00:04:44 +0200 Subject: [PATCH 2/8] fix linux build --- maxima-service/src/service/error.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/maxima-service/src/service/error.rs b/maxima-service/src/service/error.rs index 4933af3..ac2e61b 100644 --- a/maxima-service/src/service/error.rs +++ b/maxima-service/src/service/error.rs @@ -1,10 +1,13 @@ use actix_web::{error, http::header::ContentType, HttpResponse}; use reqwest::StatusCode; use thiserror::Error; + +#[cfg(target_os = "windows")] use maxima::util::dll_injector::InjectionError; #[derive(Error, Debug)] pub enum ServerError { + #[cfg(target_os = "windows")] #[error(transparent)] Injection(#[from] InjectionError), #[error(transparent)] From 74879afefebd9573a35029748ed93fe67672456d Mon Sep 17 00:00:00 2001 From: 7reax Date: Mon, 30 Jun 2025 00:16:09 +0200 Subject: [PATCH 3/8] inline cfg --- maxima-service/src/service/error.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/maxima-service/src/service/error.rs b/maxima-service/src/service/error.rs index ac2e61b..4075e88 100644 --- a/maxima-service/src/service/error.rs +++ b/maxima-service/src/service/error.rs @@ -2,14 +2,11 @@ use actix_web::{error, http::header::ContentType, HttpResponse}; use reqwest::StatusCode; use thiserror::Error; -#[cfg(target_os = "windows")] -use maxima::util::dll_injector::InjectionError; - #[derive(Error, Debug)] pub enum ServerError { #[cfg(target_os = "windows")] #[error(transparent)] - Injection(#[from] InjectionError), + Injection(#[from] maxima::util::dll_injector::InjectionError), #[error(transparent)] Io(#[from] std::io::Error), #[error(transparent)] From 63be5d5f8a397fde2fa2faf04f68964ce1a306ee Mon Sep 17 00:00:00 2001 From: 7reax Date: Mon, 30 Jun 2025 00:16:19 +0200 Subject: [PATCH 4/8] add cfg for dll_injector --- maxima-lib/src/util/mod.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/maxima-lib/src/util/mod.rs b/maxima-lib/src/util/mod.rs index efa8391..6e49d96 100644 --- a/maxima-lib/src/util/mod.rs +++ b/maxima-lib/src/util/mod.rs @@ -6,6 +6,8 @@ pub mod registry; pub mod simple_crypto; pub mod system_profiler_utils; pub mod wmi_utils; + +#[cfg(windows)] pub mod dll_injector; #[derive(thiserror::Error, Debug)] From 82f71a29b57086fb1edd0157d8ae707a50e1d1a2 Mon Sep 17 00:00:00 2001 From: 7reax Date: Mon, 30 Jun 2025 00:20:11 +0200 Subject: [PATCH 5/8] oups --- maxima-service/src/service/error.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/maxima-service/src/service/error.rs b/maxima-service/src/service/error.rs index 4075e88..673002b 100644 --- a/maxima-service/src/service/error.rs +++ b/maxima-service/src/service/error.rs @@ -4,7 +4,6 @@ use thiserror::Error; #[derive(Error, Debug)] pub enum ServerError { - #[cfg(target_os = "windows")] #[error(transparent)] Injection(#[from] maxima::util::dll_injector::InjectionError), #[error(transparent)] From de83f13821d902f57323d9ee3042d531b5eedffb Mon Sep 17 00:00:00 2001 From: 7reax Date: Mon, 30 Jun 2025 01:29:50 +0200 Subject: [PATCH 6/8] oups --- maxima-lib/src/core/error.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/maxima-lib/src/core/error.rs b/maxima-lib/src/core/error.rs index d6c21dc..9401c67 100644 --- a/maxima-lib/src/core/error.rs +++ b/maxima-lib/src/core/error.rs @@ -1,5 +1,4 @@ use thiserror::Error; -use crate::util::dll_injector::InjectionError; #[derive(Error, Debug)] pub enum BackgroundServiceClientError { @@ -13,8 +12,9 @@ pub enum BackgroundServiceClientError { Reqwest(#[from] reqwest::Error), #[error(transparent)] Registry(#[from] crate::util::registry::RegistryError), + #[cfg(windows)] #[error(transparent)] - Injection(#[from] InjectionError), + Injection(#[from] crate::util::dll_injector::InjectionError), #[error("request failed: `{0}`")] Request(String), From 99f54ec68615a4d06b9cdb3bc0b9a261d6758241 Mon Sep 17 00:00:00 2001 From: liam Date: Tue, 1 Jul 2025 01:50:46 +0200 Subject: [PATCH 7/8] fix formatting in dll_injector.rs --- maxima-lib/src/util/dll_injector.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/maxima-lib/src/util/dll_injector.rs b/maxima-lib/src/util/dll_injector.rs index 2812224..f1b724c 100644 --- a/maxima-lib/src/util/dll_injector.rs +++ b/maxima-lib/src/util/dll_injector.rs @@ -2,7 +2,7 @@ use std::ffi::CString; use std::mem; use std::ptr; use thiserror::Error; -use winapi::shared::minwindef::{LPVOID}; +use winapi::shared::minwindef::LPVOID; use winapi::um::errhandlingapi::GetLastError; use winapi::um::handleapi::CloseHandle; use winapi::um::libloaderapi::{GetModuleHandleA, GetProcAddress}; From fc3beb8671666c1c2b7632474cd8b03cc9813e3b Mon Sep 17 00:00:00 2001 From: liam Date: Tue, 1 Jul 2025 02:05:36 +0200 Subject: [PATCH 8/8] rustfmt --- maxima-lib/src/core/background_service_win.rs | 2 +- maxima-lib/src/util/dll_injector.rs | 11 ++++------- maxima-service/src/service/mod.rs | 2 +- 3 files changed, 6 insertions(+), 9 deletions(-) diff --git a/maxima-lib/src/core/background_service_win.rs b/maxima-lib/src/core/background_service_win.rs index 89b59bc..0a31d37 100644 --- a/maxima-lib/src/core/background_service_win.rs +++ b/maxima-lib/src/core/background_service_win.rs @@ -3,10 +3,10 @@ use reqwest::StatusCode; use serde::{Deserialize, Serialize}; use crate::core::error::BackgroundServiceClientError; +use crate::util::dll_injector::DllInjector; use crate::util::native::NativeError; use crate::util::registry::{set_up_registry, RegistryError}; use is_elevated::is_elevated; -use crate::util::dll_injector::DllInjector; pub const BACKGROUND_SERVICE_PORT: u16 = 13021; diff --git a/maxima-lib/src/util/dll_injector.rs b/maxima-lib/src/util/dll_injector.rs index f1b724c..7d78532 100644 --- a/maxima-lib/src/util/dll_injector.rs +++ b/maxima-lib/src/util/dll_injector.rs @@ -51,8 +51,8 @@ impl DllInjector { } let _process_guard = ProcessHandleGuard(process_handle); - let dll_path_cstring = CString::new(dll_path) - .map_err(|_| InjectionError::InvalidPath)?; + let dll_path_cstring = + CString::new(dll_path).map_err(|_| InjectionError::InvalidPath)?; let dll_path_bytes = dll_path_cstring.as_bytes_with_nul(); let dll_path_size = dll_path_bytes.len(); @@ -93,10 +93,7 @@ impl DllInjector { } let load_library_cstring = CString::new("LoadLibraryA").unwrap(); - let load_library_addr = GetProcAddress( - kernel32_handle, - load_library_cstring.as_ptr(), - ); + let load_library_addr = GetProcAddress(kernel32_handle, load_library_cstring.as_ptr()); if load_library_addr.is_null() { return Err(InjectionError::GetProcAddressFailed(GetLastError())); @@ -149,4 +146,4 @@ impl Drop for RemoteMemoryGuard { } } } -} \ No newline at end of file +} diff --git a/maxima-service/src/service/mod.rs b/maxima-service/src/service/mod.rs index 0350f2f..f8bfb68 100644 --- a/maxima-service/src/service/mod.rs +++ b/maxima-service/src/service/mod.rs @@ -21,8 +21,8 @@ use windows_service::{ use crate::service::error::ServerError; use crate::service::hash::get_sha256_hash_of_pid; use maxima::core::background_service::{ServiceLibraryInjectionRequest, BACKGROUND_SERVICE_PORT}; -use maxima::util::native::SafeParent; use maxima::util::dll_injector::{DllInjector, InjectionError}; +use maxima::util::native::SafeParent; pub(crate) mod error; mod hash;