1use std::ffi::CString;
4use std::path::Path;
5
6use windows::Win32::Foundation::{CloseHandle, HANDLE, INVALID_HANDLE_VALUE, WAIT_OBJECT_0};
7use windows::Win32::System::Diagnostics::Debug::WriteProcessMemory;
8use windows::Win32::System::Diagnostics::ToolHelp::{
9 CreateToolhelp32Snapshot, MODULEENTRY32W, Module32FirstW, Module32NextW, TH32CS_SNAPMODULE,
10 TH32CS_SNAPMODULE32,
11};
12use windows::Win32::System::LibraryLoader::{GetModuleHandleA, GetProcAddress};
13use windows::Win32::System::Memory::{
14 MEM_COMMIT, MEM_RELEASE, MEM_RESERVE, PAGE_READWRITE, VirtualAllocEx, VirtualFreeEx,
15};
16use windows::Win32::System::Threading::{
17 CreateRemoteThread, OpenProcess, PROCESS_CREATE_THREAD, PROCESS_DUP_HANDLE,
18 PROCESS_VM_OPERATION, PROCESS_VM_WRITE, WaitForSingleObject,
19};
20
21use super::processes::Process;
22use crate::error::{
23 AlreadyInjectedError, Error, InjectionFailedError, InvalidParameterError, ProcessError, Result,
24};
25
26const INJECT_TIMEOUT_MS: u32 = 5000;
28
29impl Process {
30 pub fn inject_dll(&self, dll_path: &Path) -> Result<()> {
57 let path_str = dll_path.to_str().ok_or_else(|| {
58 Error::InvalidParameter(InvalidParameterError::new(
59 "dll_path",
60 "DLL path must be valid UTF-8",
61 ))
62 })?;
63
64 let dll_path_cstr = CString::new(path_str).map_err(|_| {
65 Error::InvalidParameter(InvalidParameterError::new(
66 "dll_path",
67 "DLL path must not contain embedded null characters",
68 ))
69 })?;
70
71 let dll_filename = dll_path
73 .file_name()
74 .and_then(|n| n.to_str())
75 .unwrap_or(path_str)
76 .to_ascii_lowercase();
77
78 if self.is_dll_loaded(&dll_filename)? {
79 return Err(Error::Process(ProcessError::AlreadyInjected(
80 AlreadyInjectedError::new(self.pid().as_u32(), dll_filename),
81 )));
82 }
83
84 let pid = self.pid().as_u32();
86 let process_handle = unsafe {
87 OpenProcess(
88 PROCESS_CREATE_THREAD
89 | PROCESS_VM_WRITE
90 | PROCESS_VM_OPERATION
91 | PROCESS_DUP_HANDLE,
92 false,
93 pid,
94 )
95 }
96 .map_err(|e| {
97 Error::Process(ProcessError::InjectionFailed(
98 InjectionFailedError::with_code(
99 pid,
100 "Failed to open process with injection rights",
101 e.code().0,
102 ),
103 ))
104 })?;
105
106 let result = inject_impl(process_handle, pid, &dll_path_cstr);
108 unsafe {
109 let _ = CloseHandle(process_handle);
110 }
111 result
112 }
113
114 pub fn is_dll_loaded(&self, dll_name: &str) -> Result<bool> {
130 let pid = self.pid().as_u32();
131 let dll_name_lower = dll_name.to_ascii_lowercase();
132
133 let snapshot =
135 unsafe { CreateToolhelp32Snapshot(TH32CS_SNAPMODULE | TH32CS_SNAPMODULE32, pid) }
136 .map_err(|e| {
137 Error::Process(ProcessError::InjectionFailed(
138 InjectionFailedError::with_code(
139 pid,
140 "Failed to snapshot process modules",
141 e.code().0,
142 ),
143 ))
144 })?;
145
146 let mut entry = MODULEENTRY32W {
147 dwSize: std::mem::size_of::<MODULEENTRY32W>() as u32,
148 ..Default::default()
149 };
150
151 let found = unsafe {
152 if Module32FirstW(snapshot, &mut entry).is_err() {
153 let _ = CloseHandle(snapshot);
154 return Ok(false);
155 }
156 let mut result = false;
157 loop {
158 let name_end = entry
160 .szModule
161 .iter()
162 .position(|&c| c == 0)
163 .unwrap_or(entry.szModule.len());
164 let module_name =
165 String::from_utf16_lossy(&entry.szModule[..name_end]).to_ascii_lowercase();
166 if module_name == dll_name_lower {
167 result = true;
168 break;
169 }
170 if Module32NextW(snapshot, &mut entry).is_err() {
171 break;
172 }
173 }
174 result
175 };
176
177 unsafe {
178 let _ = CloseHandle(snapshot);
179 }
180 Ok(found)
181 }
182
183 fn pid(&self) -> super::types::ProcessId {
185 self.id()
186 }
187}
188
189fn inject_impl(process_handle: HANDLE, pid: u32, dll_path: &CString) -> Result<()> {
192 let path_bytes = dll_path.as_bytes_with_nul();
193
194 let remote_mem = unsafe {
196 VirtualAllocEx(
197 process_handle,
198 None,
199 path_bytes.len(),
200 MEM_RESERVE | MEM_COMMIT,
201 PAGE_READWRITE,
202 )
203 };
204 if remote_mem.is_null() {
205 return Err(Error::Process(ProcessError::InjectionFailed(
206 InjectionFailedError::new(pid, "Failed to allocate memory in target process"),
207 )));
208 }
209
210 let write_result = unsafe {
212 WriteProcessMemory(
213 process_handle,
214 remote_mem,
215 path_bytes.as_ptr() as *const _,
216 path_bytes.len(),
217 None,
218 )
219 };
220 if let Err(e) = write_result {
221 unsafe {
222 let _ = VirtualFreeEx(process_handle, remote_mem, 0, MEM_RELEASE);
223 }
224 return Err(Error::Process(ProcessError::InjectionFailed(
225 InjectionFailedError::with_code(
226 pid,
227 "Failed to write DLL path into target process",
228 e.code().0,
229 ),
230 )));
231 }
232
233 let load_library_addr = unsafe {
235 let k32 = GetModuleHandleA(windows::core::s!("kernel32.dll")).map_err(
236 |e: windows::core::Error| {
237 let _ = VirtualFreeEx(process_handle, remote_mem, 0, MEM_RELEASE);
238 Error::Process(ProcessError::InjectionFailed(
239 InjectionFailedError::with_code(
240 pid,
241 "Failed to get kernel32.dll handle",
242 e.code().0,
243 ),
244 ))
245 },
246 )?;
247 GetProcAddress(k32, windows::core::s!("LoadLibraryA")).ok_or_else(|| {
248 let _ = VirtualFreeEx(process_handle, remote_mem, 0, MEM_RELEASE);
249 Error::Process(ProcessError::InjectionFailed(InjectionFailedError::new(
250 pid,
251 "Failed to resolve LoadLibraryA",
252 )))
253 })?
254 };
255
256 let start_routine: unsafe extern "system" fn(*mut std::ffi::c_void) -> u32 =
258 unsafe { std::mem::transmute(load_library_addr) };
259
260 let remote_thread = unsafe {
261 CreateRemoteThread(
262 process_handle,
263 None,
264 0,
265 Some(start_routine),
266 Some(remote_mem),
267 0,
268 None,
269 )
270 }
271 .map_err(|e| {
272 unsafe {
273 let _ = VirtualFreeEx(process_handle, remote_mem, 0, MEM_RELEASE);
274 }
275 Error::Process(ProcessError::InjectionFailed(
276 InjectionFailedError::with_code(
277 pid,
278 "Failed to create remote thread in target process",
279 e.code().0,
280 ),
281 ))
282 })?;
283
284 if remote_thread == INVALID_HANDLE_VALUE {
285 unsafe {
286 let _ = VirtualFreeEx(process_handle, remote_mem, 0, MEM_RELEASE);
287 }
288 return Err(Error::Process(ProcessError::InjectionFailed(
289 InjectionFailedError::new(pid, "CreateRemoteThread returned invalid handle"),
290 )));
291 }
292
293 let wait_result = unsafe { WaitForSingleObject(remote_thread, INJECT_TIMEOUT_MS) };
295
296 if wait_result == WAIT_OBJECT_0 {
297 let mut exit_code: u32 = 0;
301 if unsafe {
302 windows::Win32::System::Threading::GetExitCodeThread(remote_thread, &mut exit_code)
303 .is_ok()
304 } && exit_code == 0
305 {
306 unsafe {
308 let _ = CloseHandle(remote_thread);
309 let _ = VirtualFreeEx(process_handle, remote_mem, 0, MEM_RELEASE);
310 }
311 return Err(Error::Process(ProcessError::InjectionFailed(
312 InjectionFailedError::new(pid, "LoadLibraryA returned NULL in target process"),
313 )));
314 }
315 }
316 unsafe {
320 let _ = CloseHandle(remote_thread);
321 let _ = VirtualFreeEx(process_handle, remote_mem, 0, MEM_RELEASE);
322 }
323 Ok(())
324}