Skip to content

Commit c00c3dd

Browse files
authored
replace dll-syringe with native winapi injection (#36)
1 parent ebd07ff commit c00c3dd

9 files changed

Lines changed: 166 additions & 17 deletions

File tree

Cargo.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,3 @@ strip = true
1616
[patch.crates-io]
1717
flate2 = { git = 'https://github.com/ArmchairDevelopers/flate2-rs.git' }
1818
async-compression = { git = 'https://github.com/ArmchairDevelopers/async-compression.git' }
19-
dll-syringe = { git = "https://github.com/fry/dll-syringe", rev = "0a8b18e" }

maxima-lib/Cargo.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,6 @@ winreg = "0.51.0"
9696
windows-service = "0.6.0"
9797
is_elevated = "0.1.2"
9898
widestring = "1.0.2"
99-
dll-syringe = "0.15.2"
10099
wmi = "0.13.1"
101100

102101
[target.'cfg(target_os = "macos")'.dependencies]

maxima-lib/src/core/background_service_win.rs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
use dll_syringe::{process::OwnedProcess, Syringe};
21
use log::debug;
32
use reqwest::StatusCode;
43
use serde::{Deserialize, Serialize};
54

65
use crate::core::error::BackgroundServiceClientError;
6+
use crate::util::dll_injector::DllInjector;
77
use crate::util::native::NativeError;
88
use crate::util::registry::{set_up_registry, RegistryError};
99
use is_elevated::is_elevated;
@@ -23,9 +23,8 @@ pub async fn request_library_injection(
2323
debug!("Injecting {}", path);
2424

2525
if is_elevated() {
26-
let process = OwnedProcess::from_pid(pid)?;
27-
let syringe = Syringe::for_process(process);
28-
syringe.inject(path)?;
26+
let injector = DllInjector::new(pid);
27+
injector.inject(path)?;
2928
return Ok(());
3029
}
3130

maxima-lib/src/core/error.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,6 @@ use thiserror::Error;
44
pub enum BackgroundServiceClientError {
55
#[error(transparent)]
66
Native(#[from] crate::util::native::NativeError),
7-
#[cfg(windows)]
8-
#[error(transparent)]
9-
Inject(#[from] dll_syringe::error::InjectError),
107
#[error(transparent)]
118
Io(#[from] std::io::Error),
129
#[error(transparent)]
@@ -15,6 +12,9 @@ pub enum BackgroundServiceClientError {
1512
Reqwest(#[from] reqwest::Error),
1613
#[error(transparent)]
1714
Registry(#[from] crate::util::registry::RegistryError),
15+
#[cfg(windows)]
16+
#[error(transparent)]
17+
Injection(#[from] crate::util::dll_injector::InjectionError),
1818

1919
#[error("request failed: `{0}`")]
2020
Request(String),
Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
use std::ffi::CString;
2+
use std::mem;
3+
use std::ptr;
4+
use thiserror::Error;
5+
use winapi::shared::minwindef::LPVOID;
6+
use winapi::um::errhandlingapi::GetLastError;
7+
use winapi::um::handleapi::CloseHandle;
8+
use winapi::um::libloaderapi::{GetModuleHandleA, GetProcAddress};
9+
use winapi::um::memoryapi::{VirtualAllocEx, VirtualFreeEx, WriteProcessMemory};
10+
use winapi::um::processthreadsapi::{CreateRemoteThread, OpenProcess};
11+
use winapi::um::synchapi::WaitForSingleObject;
12+
use winapi::um::winbase::INFINITE;
13+
use winapi::um::winnt::{
14+
HANDLE, MEM_COMMIT, MEM_RELEASE, MEM_RESERVE, PAGE_READWRITE, PROCESS_ALL_ACCESS,
15+
};
16+
17+
#[derive(Debug, Error)]
18+
pub enum InjectionError {
19+
#[error("failed to create remote thread, error code: {0}")]
20+
CreateRemoteThreadFailed(u32),
21+
#[error("failed to get kernel32 handle, error code: {0}")]
22+
GetModuleHandleFailed(u32),
23+
#[error("failed to get LoadLibraryA address, error code: {0}")]
24+
GetProcAddressFailed(u32),
25+
#[error("invalid DLL path")]
26+
InvalidPath,
27+
#[error("failed to open process, error code: {0}")]
28+
OpenProcessFailed(u32),
29+
#[error("process not found")]
30+
ProcessNotFound,
31+
#[error("failed to write process memory, error code: {0}")]
32+
WriteProcessMemoryFailed(u32),
33+
#[error("failed to allocate memory, error code: {0}")]
34+
VirtualAllocFailed(u32),
35+
}
36+
37+
pub struct DllInjector {
38+
target_pid: u32,
39+
}
40+
41+
impl DllInjector {
42+
pub fn new(pid: u32) -> Self {
43+
Self { target_pid: pid }
44+
}
45+
46+
pub fn inject(&self, dll_path: &str) -> Result<(), InjectionError> {
47+
unsafe {
48+
let process_handle = OpenProcess(PROCESS_ALL_ACCESS, 0, self.target_pid);
49+
if process_handle.is_null() {
50+
return Err(InjectionError::OpenProcessFailed(GetLastError()));
51+
}
52+
53+
let _process_guard = ProcessHandleGuard(process_handle);
54+
let dll_path_cstring =
55+
CString::new(dll_path).map_err(|_| InjectionError::InvalidPath)?;
56+
let dll_path_bytes = dll_path_cstring.as_bytes_with_nul();
57+
let dll_path_size = dll_path_bytes.len();
58+
59+
let remote_memory = VirtualAllocEx(
60+
process_handle,
61+
ptr::null_mut(),
62+
dll_path_size,
63+
MEM_COMMIT | MEM_RESERVE,
64+
PAGE_READWRITE,
65+
);
66+
67+
if remote_memory.is_null() {
68+
return Err(InjectionError::VirtualAllocFailed(GetLastError()));
69+
}
70+
71+
let _memory_guard = RemoteMemoryGuard {
72+
process_handle,
73+
address: remote_memory,
74+
};
75+
76+
let mut bytes_written: usize = 0;
77+
let result = WriteProcessMemory(
78+
process_handle,
79+
remote_memory,
80+
dll_path_bytes.as_ptr() as LPVOID,
81+
dll_path_size,
82+
&mut bytes_written as *mut usize,
83+
);
84+
85+
if result == 0 {
86+
return Err(InjectionError::WriteProcessMemoryFailed(GetLastError()));
87+
}
88+
89+
let kernel32_cstring = CString::new("kernel32.dll").unwrap();
90+
let kernel32_handle = GetModuleHandleA(kernel32_cstring.as_ptr());
91+
if kernel32_handle.is_null() {
92+
return Err(InjectionError::GetModuleHandleFailed(GetLastError()));
93+
}
94+
95+
let load_library_cstring = CString::new("LoadLibraryA").unwrap();
96+
let load_library_addr = GetProcAddress(kernel32_handle, load_library_cstring.as_ptr());
97+
98+
if load_library_addr.is_null() {
99+
return Err(InjectionError::GetProcAddressFailed(GetLastError()));
100+
}
101+
102+
let thread_handle = CreateRemoteThread(
103+
process_handle,
104+
ptr::null_mut(),
105+
0,
106+
Some(mem::transmute(load_library_addr)),
107+
remote_memory,
108+
0,
109+
ptr::null_mut(),
110+
);
111+
112+
if thread_handle.is_null() {
113+
return Err(InjectionError::CreateRemoteThreadFailed(GetLastError()));
114+
}
115+
116+
WaitForSingleObject(thread_handle, INFINITE);
117+
CloseHandle(thread_handle);
118+
119+
Ok(())
120+
}
121+
}
122+
}
123+
124+
struct ProcessHandleGuard(HANDLE);
125+
126+
impl Drop for ProcessHandleGuard {
127+
fn drop(&mut self) {
128+
unsafe {
129+
if !self.0.is_null() {
130+
CloseHandle(self.0);
131+
}
132+
}
133+
}
134+
}
135+
136+
struct RemoteMemoryGuard {
137+
process_handle: HANDLE,
138+
address: LPVOID,
139+
}
140+
141+
impl Drop for RemoteMemoryGuard {
142+
fn drop(&mut self) {
143+
unsafe {
144+
if !self.address.is_null() {
145+
VirtualFreeEx(self.process_handle, self.address, 0, MEM_RELEASE);
146+
}
147+
}
148+
}
149+
}

maxima-lib/src/util/mod.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ pub mod simple_crypto;
77
pub mod system_profiler_utils;
88
pub mod wmi_utils;
99

10+
#[cfg(windows)]
11+
pub mod dll_injector;
12+
1013
#[derive(thiserror::Error, Debug)]
1114
pub enum BackgroundServiceControlError {
1215
#[error(transparent)]

maxima-service/Cargo.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@ thiserror = "2.0.12"
2626
winapi = { version = "0.3.9", features = [ "memoryapi", "handleapi", "synchapi", "wincon", "consoleapi" ] }
2727
winreg = "0.50.0"
2828
windows-service = "0.6.0"
29-
dll-syringe = "0.15.2"
3029

3130
[build-dependencies]
3231
maxima-resources = { path = "../maxima-resources" }

maxima-service/src/service/error.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
11
use actix_web::{error, http::header::ContentType, HttpResponse};
22
use reqwest::StatusCode;
33
use thiserror::Error;
4-
use windows_service::service;
54

65
#[derive(Error, Debug)]
76
pub enum ServerError {
87
#[error(transparent)]
9-
Inject(#[from] dll_syringe::error::InjectError),
8+
Injection(#[from] maxima::util::dll_injector::InjectionError),
109
#[error(transparent)]
1110
Io(#[from] std::io::Error),
1211
#[error(transparent)]

maxima-service/src/service/mod.rs

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
use std::fs::File;
22

33
use actix_web::{get, post, web, HttpResponse, Responder};
4-
use dll_syringe::process::OwnedProcess;
5-
use dll_syringe::Syringe;
64
use log::info;
75
use maxima::util::registry::set_up_registry;
86
use maxima::util::service::SERVICE_NAME;
@@ -23,6 +21,7 @@ use windows_service::{
2321
use crate::service::error::ServerError;
2422
use crate::service::hash::get_sha256_hash_of_pid;
2523
use maxima::core::background_service::{ServiceLibraryInjectionRequest, BACKGROUND_SERVICE_PORT};
24+
use maxima::util::dll_injector::{DllInjector, InjectionError};
2625
use maxima::util::native::SafeParent;
2726

2827
pub(crate) mod error;
@@ -89,6 +88,11 @@ async fn req_set_up_registry() -> impl Responder {
8988
format!("Done")
9089
}
9190

91+
pub fn inject_dll(pid: u32, dll_path: &str) -> Result<(), InjectionError> {
92+
let injector = DllInjector::new(pid);
93+
injector.inject(dll_path)
94+
}
95+
9296
// This is for KYBER. Ideally this would be moved to a separate Kyber service,
9397
// but it isn't a great user experience to have to install two windows services.
9498
// 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<HttpResponse, self::Serv
97101
info!("Injecting...");
98102

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

102105
let hash_result = get_sha256_hash_of_pid(req.pid)?;
103106

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

113-
let syringe = Syringe::for_process(process);
114-
syringe.inject(req.path)?;
116+
inject_dll(req.pid, &req.path)?;
115117

116118
Ok(HttpResponse::Ok().body("Injected"))
117119
}

0 commit comments

Comments
 (0)