1use log::{error, info, trace};
2use std::{ffi::c_void, path::Path};
3use sysinfo::{Pid, PidExt};
4use thiserror::Error;
5use windows_sys::{
6 core::PCSTR,
7 s,
8 Win32::{
9 Foundation::{CloseHandle, FALSE, HANDLE, HMODULE},
10 System::{
11 Diagnostics::Debug::WriteProcessMemory,
12 LibraryLoader::{GetModuleHandleA, GetProcAddress},
13 Memory::{VirtualAllocEx, VirtualFreeEx, MEM_COMMIT, MEM_DECOMMIT, PAGE_READWRITE},
14 Threading::{
15 CreateRemoteThread, OpenProcess, WaitForSingleObject, INFINITE,
16 PROCESS_CREATE_THREAD, PROCESS_VM_OPERATION, PROCESS_VM_WRITE,
17 },
18 },
19 },
20};
21
22type LoadLibraryA = unsafe extern "system" fn(lplibfilename: PCSTR) -> HMODULE;
23
24#[derive(Error, Debug)]
25pub enum InjectorError {
26 #[error("Payload does not exist: `{0}`")]
27 PayloadMissing(String),
28 #[error("Payload location unable to be initialized as a CString: `{0}`")]
29 PayloadCString(#[from] std::ffi::NulError),
30 #[error("Payload location unable to be canonicalized: `{0}`")]
31 PayloadCanonicalization(#[from] std::io::Error),
32 #[error("Process is not active: `{0}`")]
33 ProcessNotActive(String),
34 #[error("Unable to obtain handle to Kernel32 Module")]
35 KernelModule(),
36 #[error("Unable to obtain handle to LoadLibrary Proc")]
37 LoadLibraryProc(),
38 #[error("Unable to open process")]
39 ProcessOpen(),
40 #[error("Unable to allocate memory in target process")]
41 AllocationFailure(),
42 #[error("Unable to write specified memory")]
43 WriteFailure(),
44 #[error("Unable to spawn remote thread")]
45 RemoteThread(),
46}
47
48pub fn inject_into(
50 payload_location: impl AsRef<Path>,
51 pid: impl Into<Pid>,
52) -> Result<(), InjectorError> {
53 let payload_location = match std::fs::canonicalize(payload_location) {
54 Ok(p) => p.to_str().unwrap().replace("\\\\?\\", ""),
55 Err(e) => return Err(InjectorError::PayloadCanonicalization(e)),
56 };
57 let pid = pid.into();
58
59 info!(
60 "Injecting Payload: {:#?} into Pid: {}",
61 payload_location, pid
62 );
63
64 let kernel_module = get_kernel_module()?;
65 info!("Identified kernel module: {:#?}", kernel_module);
66
67 let load_library_proc = get_load_library_proc(kernel_module)?;
68 info!(
69 "Identified load library proc: {:#?}",
70 load_library_proc as *const usize
71 );
72
73 let raw_process = RawProcess::open(pid)?;
74 let write_size = payload_location.len() + 1;
75 let raw_allocation = raw_process.allocate(write_size, MEM_COMMIT, PAGE_READWRITE)?;
76
77 let payload_cstring = match std::ffi::CString::new(payload_location) {
78 Ok(cstring) => cstring,
79 Err(err) => {
80 error!("Unable to create CString from payload absolute path");
81 return Err(InjectorError::PayloadCString(err));
82 }
83 };
84 raw_allocation.write(payload_cstring.as_ptr() as *mut c_void)?;
85 raw_allocation.spawn_thread_with_args(load_library_proc)?;
86
87 Ok(())
88}
89
90struct ContextedRemoteThread<'process> {
91 _process: &'process RawProcess,
92 thread: HANDLE,
93}
94
95impl<'process> ContextedRemoteThread<'process> {
96 fn spawn_with_args(
97 process: &'process RawProcess,
98 allocation: &'process RawAllocation,
99 entry_function: LoadLibraryA,
100 ) -> Result<Self, InjectorError> {
101 let thread = unsafe {
102 CreateRemoteThread(
103 process.inner(),
104 std::ptr::null_mut(),
105 0,
106 Some(std::mem::transmute(entry_function)),
108 allocation.inner(),
109 0,
110 std::ptr::null_mut(),
111 )
112 };
113
114 if thread == 0 {
115 return Err(InjectorError::RemoteThread());
116 }
117
118 Ok(ContextedRemoteThread {
119 _process: process,
120 thread,
121 })
122 }
123}
124
125impl<'process> Drop for ContextedRemoteThread<'process> {
126 fn drop(&mut self) {
127 trace!("Closing thread handle");
128 unsafe {
129 WaitForSingleObject(self.thread, INFINITE);
130 CloseHandle(self.thread);
131 };
132 }
133}
134
135struct RawAllocation<'process> {
136 process: &'process RawProcess,
137 allocation: *mut c_void,
138 size: usize,
139}
140
141impl<'process> RawAllocation<'process> {
142 fn allocate(
143 process: &'process RawProcess,
144 size: usize,
145 allocation_flags: u32,
146 protection_flags: u32,
147 ) -> Result<Self, InjectorError> {
148 let allocation = unsafe {
149 VirtualAllocEx(
150 process.inner(),
151 std::ptr::null_mut(),
152 size,
153 allocation_flags,
154 protection_flags,
155 )
156 };
157
158 if allocation.is_null() {
159 return Err(InjectorError::AllocationFailure());
160 }
161
162 trace!(
163 "Allocated n bytes: {}, with allocation_flags: {}, and protection_flags: {}",
164 size,
165 allocation_flags,
166 protection_flags
167 );
168
169 Ok(RawAllocation {
170 process,
171 allocation,
172 size,
173 })
174 }
175
176 fn spawn_thread_with_args(
177 &self,
178 entry_function: LoadLibraryA,
179 ) -> Result<ContextedRemoteThread, InjectorError> {
180 ContextedRemoteThread::spawn_with_args(self.process, self, entry_function)
181 }
182
183 fn inner(&self) -> *mut c_void {
184 self.allocation
185 }
186
187 fn write(&self, buffer: *mut c_void) -> Result<usize, InjectorError> {
188 let mut bytes_written: usize = 0;
189
190 let write_result = unsafe {
191 WriteProcessMemory(
192 self.process.inner(),
193 self.allocation,
194 buffer,
195 self.size,
196 &mut bytes_written,
197 )
198 };
199
200 if write_result == 0 || bytes_written == 0 {
201 return Err(InjectorError::WriteFailure());
202 }
203
204 trace!(
205 "Wrote n bytes: {} for allocation of size: {}",
206 bytes_written,
207 self.size
208 );
209
210 Ok(bytes_written)
211 }
212}
213
214impl<'process> Drop for RawAllocation<'process> {
215 fn drop(&mut self) {
216 trace!("Dropping allocated data");
217 unsafe {
218 VirtualFreeEx(
219 self.process.inner(),
220 self.allocation,
221 self.size,
222 MEM_DECOMMIT,
223 );
224 }
225 }
226}
227
228struct RawProcess {
229 handle: HANDLE,
230}
231
232impl RawProcess {
233 fn open(pid: Pid) -> Result<Self, InjectorError> {
234 let handle = unsafe {
235 OpenProcess(
236 PROCESS_CREATE_THREAD | PROCESS_VM_WRITE | PROCESS_VM_OPERATION,
237 FALSE,
238 pid.as_u32(),
239 )
240 };
241
242 if handle == 0 {
243 return Err(InjectorError::ProcessOpen());
244 }
245
246 Ok(Self { handle })
247 }
248
249 fn allocate(
250 &self,
251 size: usize,
252 allocation_flags: u32,
253 protection_flags: u32,
254 ) -> Result<RawAllocation, InjectorError> {
255 RawAllocation::allocate(self, size, allocation_flags, protection_flags)
256 }
257
258 fn inner(&self) -> HANDLE {
259 self.handle
260 }
261}
262
263impl Drop for RawProcess {
264 fn drop(&mut self) {
265 trace!("Dropping Process Handle");
266 unsafe {
267 CloseHandle(self.handle as HANDLE);
268 }
269 }
270}
271
272fn get_kernel_module() -> Result<HMODULE, InjectorError> {
273 let kernel_module = unsafe { GetModuleHandleA(s!("kernel32.dll")) };
274
275 if kernel_module == 0 {
276 return Err(InjectorError::KernelModule());
277 }
278
279 Ok(kernel_module)
280}
281
282fn get_load_library_proc(kernel_module: HMODULE) -> Result<LoadLibraryA, InjectorError> {
283 let load_library_proc = unsafe { GetProcAddress(kernel_module, s!("LoadLibraryA")) }
284 .ok_or(InjectorError::LoadLibraryProc())?;
285
286 let load_library_proc: LoadLibraryA = unsafe { std::mem::transmute(load_library_proc) };
287
288 Ok(load_library_proc)
289}