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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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" }
1 change: 0 additions & 1 deletion maxima-lib/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
7 changes: 3 additions & 4 deletions maxima-lib/src/core/background_service_win.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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(());
}

Expand Down
6 changes: 3 additions & 3 deletions maxima-lib/src/core/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand All @@ -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),
Expand Down
149 changes: 149 additions & 0 deletions maxima-lib/src/util/dll_injector.rs
Original file line number Diff line number Diff line change
@@ -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);
}
}
}
}
3 changes: 3 additions & 0 deletions maxima-lib/src/util/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand Down
1 change: 0 additions & 1 deletion maxima-service/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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" }
Expand Down
3 changes: 1 addition & 2 deletions maxima-service/src/service/error.rs
Original file line number Diff line number Diff line change
@@ -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)]
Expand Down
12 changes: 7 additions & 5 deletions maxima-service/src/service/mod.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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.
Expand All @@ -97,7 +101,6 @@ async fn req_inject_library(body: web::Bytes) -> Result<HttpResponse, self::Serv
info!("Injecting...");

let req: ServiceLibraryInjectionRequest = serde_json::from_slice(&body)?;
let process = OwnedProcess::from_pid(req.pid)?;

let hash_result = get_sha256_hash_of_pid(req.pid)?;

Expand All @@ -110,8 +113,7 @@ async fn req_inject_library(body: web::Bytes) -> Result<HttpResponse, self::Serv
return Err(self::ServerError::InvalidInjectionTarget);
}

let syringe = Syringe::for_process(process);
syringe.inject(req.path)?;
inject_dll(req.pid, &req.path)?;

Ok(HttpResponse::Ok().body("Injected"))
}
Expand Down
Loading