yai/
lib.rs

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
48/// Injects the payload pointed to by `payload_location` into `pid`.
49pub 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                // Transmute from 'fn (*const u8) -> isize' to 'fn(*mut c_void) -> u32'.
107                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}