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..0a31d37 100644 --- a/maxima-lib/src/core/background_service_win.rs +++ b/maxima-lib/src/core/background_service_win.rs @@ -1,9 +1,9 @@ -use dll_syringe::{process::OwnedProcess, Syringe}; use log::debug; 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; @@ -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..9401c67 100644 --- a/maxima-lib/src/core/error.rs +++ b/maxima-lib/src/core/error.rs @@ -4,9 +4,6 @@ use thiserror::Error; 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 +12,9 @@ pub enum BackgroundServiceClientError { Reqwest(#[from] reqwest::Error), #[error(transparent)] Registry(#[from] crate::util::registry::RegistryError), + #[cfg(windows)] + #[error(transparent)] + Injection(#[from] crate::util::dll_injector::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..7d78532 --- /dev/null +++ b/maxima-lib/src/util/dll_injector.rs @@ -0,0 +1,149 @@ +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); + } + } + } +} diff --git a/maxima-lib/src/util/mod.rs b/maxima-lib/src/util/mod.rs index 701fbfd..6e49d96 100644 --- a/maxima-lib/src/util/mod.rs +++ b/maxima-lib/src/util/mod.rs @@ -7,6 +7,9 @@ pub mod simple_crypto; pub mod system_profiler_utils; pub mod wmi_utils; +#[cfg(windows)] +pub mod dll_injector; + #[derive(thiserror::Error, Debug)] pub enum BackgroundServiceControlError { #[error(transparent)] 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..673002b 100644 --- a/maxima-service/src/service/error.rs +++ b/maxima-service/src/service/error.rs @@ -1,12 +1,11 @@ use actix_web::{error, http::header::ContentType, HttpResponse}; use reqwest::StatusCode; use thiserror::Error; -use windows_service::service; #[derive(Error, Debug)] pub enum ServerError { #[error(transparent)] - Inject(#[from] dll_syringe::error::InjectError), + Injection(#[from] maxima::util::dll_injector::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..f8bfb68 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; @@ -23,6 +21,7 @@ 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::dll_injector::{DllInjector, InjectionError}; use maxima::util::native::SafeParent; pub(crate) mod error; @@ -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