1#![cfg_attr(not(feature = "std"), no_std)]
2
3use core::{
4 cell::UnsafeCell,
5 marker::PhantomData,
6 mem::MaybeUninit,
7 ops::{Deref, DerefMut},
8 sync::atomic::AtomicBool,
9};
10
11use arrayvec::ArrayVec;
12use hooker::{gen_hook_info, HookInfo, JumperBytes, MAX_JUMPER_LEN};
13use thiserror_no_std::Error;
14use windows_sys::{
15 core::PCSTR,
16 Win32::{
17 Foundation::{FreeLibrary, GetLastError, HMODULE},
18 System::{
19 Diagnostics::Debug::WriteProcessMemory,
20 LibraryLoader::{GetProcAddress, LoadLibraryA},
21 Memory::{
22 VirtualAlloc, VirtualFree, VirtualProtect, MEM_COMMIT, MEM_RELEASE, PAGE_EXECUTE,
23 PAGE_PROTECTION_FLAGS, PAGE_READWRITE,
24 },
25 ProcessStatus::{GetModuleInformation, MODULEINFO},
26 Threading::GetCurrentProcess,
27 },
28 },
29};
30
31struct SingleLock<T> {
33 value: UnsafeCell<T>,
34 is_locked: AtomicBool,
35}
36impl<T> SingleLock<T> {
37 const fn new(value: T) -> Self {
39 Self {
40 value: UnsafeCell::new(value),
41 is_locked: AtomicBool::new(false),
42 }
43 }
44 fn lock(&self) -> Option<SingleLockGuard<T>> {
46 if self
47 .is_locked
48 .swap(true, core::sync::atomic::Ordering::AcqRel)
49 {
50 return None;
51 }
52 Some(SingleLockGuard { lock: self })
53 }
54}
55unsafe impl<T> Send for SingleLock<T> {}
56unsafe impl<T> Sync for SingleLock<T> {}
57
58struct SingleLockGuard<'a, T> {
60 lock: &'a SingleLock<T>,
61}
62impl<'a, T> Drop for SingleLockGuard<'a, T> {
63 fn drop(&mut self) {
64 self.lock
65 .is_locked
66 .store(false, core::sync::atomic::Ordering::Release)
67 }
68}
69impl<'a, T> Deref for SingleLockGuard<'a, T> {
70 type Target = T;
71
72 fn deref(&self) -> &Self::Target {
73 unsafe { &*self.lock.value.get() }
74 }
75}
76impl<'a, T> DerefMut for SingleLockGuard<'a, T> {
77 fn deref_mut(&mut self) -> &mut Self::Target {
78 unsafe { &mut *self.lock.value.get() }
79 }
80}
81
82pub struct StaticHook<F: Copy> {
85 hook: SingleLock<Option<Hook>>,
86 phantom: PhantomData<F>,
87}
88impl<F: Copy> StaticHook<F> {
89 const HOOK_USED_MULTIPLE_TIMES_ERR_MSG: &'static str = "static hook used multiple times";
90 const HOOK_USED_BEFORE_HOOKING_ERR_MSG: &'static str =
91 "static hook used before hooking any function";
92
93 pub const fn new() -> Self {
95 Self {
96 hook: SingleLock::new(None),
97 phantom: PhantomData,
98 }
99 }
100 fn lock_hook(&self) -> SingleLockGuard<Option<Hook>> {
106 self.hook
107 .lock()
108 .expect(Self::HOOK_USED_MULTIPLE_TIMES_ERR_MSG)
109 }
110
111 fn lock_hook_and_assert_empty(&self) -> SingleLockGuard<Option<Hook>> {
117 let hook = self.lock_hook();
118 if hook.is_some() {
119 panic!("{}", Self::HOOK_USED_MULTIPLE_TIMES_ERR_MSG)
120 }
121 hook
122 }
123
124 pub unsafe fn hook_function(
135 &self,
136 module: HMODULE,
137 fn_addr: usize,
138 hook_to_addr: usize,
139 ) -> Result<()> {
140 let mut hook = self.lock_hook_and_assert_empty();
141 let created_hook = hook_function(module, fn_addr, hook_to_addr)?;
142 *hook = Some(created_hook);
143 Ok(())
144 }
145
146 pub fn hook_function_by_name(
157 &self,
158 library_name: PCSTR,
159 fn_name: PCSTR,
160 hook_to_addr: usize,
161 ) -> Result<()> {
162 let mut hook = self.lock_hook_and_assert_empty();
163 let created_hook = hook_function_by_name(library_name, fn_name, hook_to_addr)?;
164 *hook = Some(created_hook);
165 Ok(())
166 }
167
168 pub fn get_hook(&self) -> &Hook {
174 let hook_guard = self.lock_hook();
175 let hook_opt = unsafe { &*(hook_guard.deref() as *const Option<Hook>) };
176 hook_opt
177 .as_ref()
178 .expect(Self::HOOK_USED_BEFORE_HOOKING_ERR_MSG)
179 }
180
181 pub fn original(&self) -> F {
187 let hook = self.get_hook();
188 unsafe { hook.original() }
189 }
190
191 pub fn remove(&self) {
193 let mut hook_guard = self.lock_hook();
194 let hook_opt = unsafe { &mut *(hook_guard.deref_mut() as *mut Option<Hook>) };
195 if let Some(hook) = hook_opt.take() {
196 hook.remove()
197 }
198 }
199}
200
201struct ModuleHandleGuard(HMODULE);
203impl Drop for ModuleHandleGuard {
204 fn drop(&mut self) {
205 let _ = unsafe { FreeLibrary(self.0) };
206 }
207}
208
209pub fn hook_function_by_name(
212 library_name: PCSTR,
213 fn_name: PCSTR,
214 hook_to_addr: usize,
215) -> Result<Hook> {
216 let load_library_res = unsafe { LoadLibraryA(library_name) };
217 if load_library_res == 0 {
218 return Err(Error::FailedToLoadLibrary(WinapiError::last_error()));
219 }
220 let module_guard = ModuleHandleGuard(load_library_res);
221 let fn_addr =
222 unsafe { GetProcAddress(module_guard.0, fn_name).ok_or(Error::NoFunctionWithThatName)? };
223 hook_function(module_guard.0, fn_addr as usize, hook_to_addr)
224}
225
226pub unsafe fn module_get_fn_possible_content(
232 module: HMODULE,
233 fn_addr: usize,
234) -> Result<&'static [u8]> {
235 let mut module_info_uninit: MaybeUninit<MODULEINFO> = MaybeUninit::uninit();
236 let res = unsafe {
237 GetModuleInformation(
238 GetCurrentProcess(),
239 module,
240 module_info_uninit.as_mut_ptr(),
241 core::mem::size_of::<MODULEINFO>() as u32,
242 )
243 };
244 if res == 0 {
245 return Err(Error::FailedToGetModuleInformation(
246 WinapiError::last_error(),
247 ));
248 }
249 let module_info = unsafe { module_info_uninit.assume_init() };
250 let module_end_addr = module_info.lpBaseOfDll as usize + module_info.SizeOfImage as usize;
251 let fn_max_possible_size = module_end_addr - fn_addr;
252
253 Ok(unsafe { core::slice::from_raw_parts(fn_addr as *const u8, fn_max_possible_size) })
254}
255
256pub fn alloc_and_build_trampoline(hook_info: &HookInfo) -> Allocation {
258 let mut trampiline_alloc = Allocation::new(hook_info.trampoline_size());
260
261 let trampoline_code = hook_info.build_trampoline(trampiline_alloc.ptr as u64);
263 let trampoline_alloc_slice = unsafe { trampiline_alloc.as_mut_slice() };
264 trampoline_alloc_slice[..trampoline_code.len()].copy_from_slice(&trampoline_code);
265
266 trampiline_alloc.make_executable_and_read_only();
268
269 trampiline_alloc
270}
271
272pub fn prepare_hook(module: HMODULE, fn_addr: usize, hook_to_addr: usize) -> Result<PreparedHook> {
274 let fn_possible_content = unsafe { module_get_fn_possible_content(module, fn_addr) }?;
275 let hook_info = gen_hook_info(fn_possible_content, fn_addr as u64, hook_to_addr as u64)?;
276
277 let trampiline_alloc = alloc_and_build_trampoline(&hook_info);
279
280 let overwritten_bytes = fn_possible_content[..hook_info.jumper().len()]
282 .try_into()
283 .unwrap();
284
285 Ok(PreparedHook {
286 trampoline: trampiline_alloc,
287 fn_addr,
288 hook_to_addr,
289 overwritten_bytes,
290 jumper_code: hook_info.jumper().clone(),
291 })
292}
293
294pub fn hook_function(module: HMODULE, fn_addr: usize, hook_to_addr: usize) -> Result<Hook> {
297 let prepared_hook = prepare_hook(module, fn_addr, hook_to_addr)?;
298 Ok(prepared_hook.hook())
299}
300
301fn write_current_process_memory(addr: usize, bytes: &[u8]) {
303 let mut bytes_written = 0;
304 let res = unsafe {
305 WriteProcessMemory(
306 GetCurrentProcess(),
307 addr as *const _,
308 bytes.as_ptr().cast(),
309 bytes.len(),
310 &mut bytes_written,
311 )
312 };
313 assert_ne!(
314 res,
315 0,
316 "failed to write {} bytes to address 0x{:x}",
317 bytes.len(),
318 addr
319 );
320 assert_eq!(
322 bytes_written,
323 bytes.len(),
324 "only {} out of {} bytes were written when trying to write to address 0x{:x}",
325 bytes_written,
326 bytes.len(),
327 addr
328 );
329}
330
331pub struct Allocation {
333 ptr: *mut u8,
334 size: usize,
335}
336impl Allocation {
337 pub fn new(size: usize) -> Self {
338 let ptr = unsafe { VirtualAlloc(core::ptr::null(), size, MEM_COMMIT, PAGE_READWRITE) };
339 if ptr.is_null() {
340 panic!("failed to allocate read-write memory using VirtualAlloc");
342 }
343 Self {
344 ptr: ptr.cast(),
345 size,
346 }
347 }
348 pub fn make_executable_and_read_only(&mut self) {
349 let mut old_prot: MaybeUninit<PAGE_PROTECTION_FLAGS> = MaybeUninit::uninit();
350 let res = unsafe {
351 VirtualProtect(
352 self.ptr.cast(),
353 self.size,
354 PAGE_EXECUTE,
355 old_prot.as_mut_ptr(),
356 )
357 };
358 assert_ne!(res, 0, "failed to change memory protection to executable");
359 }
360 pub unsafe fn as_mut_slice(&mut self) -> &mut [u8] {
363 unsafe { core::slice::from_raw_parts_mut(self.ptr, self.size) }
364 }
365 pub fn as_ptr(&self) -> *const u8 {
367 self.ptr
368 }
369 pub fn as_mut_ptr(&mut self) -> *mut u8 {
371 self.ptr
372 }
373 pub fn size(&self) -> usize {
375 self.size
376 }
377}
378impl Drop for Allocation {
379 fn drop(&mut self) {
380 unsafe {
381 let _ = VirtualFree(self.ptr.cast(), 0, MEM_RELEASE);
382 }
383 }
384}
385
386pub struct PreparedHook {
388 trampoline: Allocation,
389 overwritten_bytes: ArrayVec<u8, MAX_JUMPER_LEN>,
390 fn_addr: usize,
391 hook_to_addr: usize,
392 jumper_code: JumperBytes,
393}
394impl PreparedHook {
395 pub fn trampoline(&self) -> &Allocation {
397 &self.trampoline
398 }
399 pub fn hook(self) -> Hook {
401 write_current_process_memory(self.fn_addr, &self.jumper_code);
402 Hook {
403 trampoline: self.trampoline,
404 overwritten_bytes: self.overwritten_bytes,
405 fn_addr: self.fn_addr,
406 hook_to_addr: self.hook_to_addr,
407 }
408 }
409}
410
411pub struct Hook {
413 trampoline: Allocation,
414 overwritten_bytes: ArrayVec<u8, MAX_JUMPER_LEN>,
415 fn_addr: usize,
416 hook_to_addr: usize,
417}
418impl Hook {
419 pub fn original_addr(&self) -> usize {
421 self.trampoline.ptr as usize
422 }
423 pub unsafe fn original<F: Copy>(&self) -> F {
430 assert!(
432 core::mem::size_of::<F>() == core::mem::size_of::<usize>()
433 && core::mem::align_of::<F>() == core::mem::align_of::<usize>(),
434 "provided function signature type {} is not a function pointer",
435 core::any::type_name::<F>()
436 );
437 let trampoline_ptr = self.trampoline.ptr;
438 core::mem::transmute_copy(&trampoline_ptr)
439 }
440 pub fn fn_addr(&self) -> usize {
442 self.fn_addr
443 }
444 pub fn hook_to_addr(&self) -> usize {
446 self.hook_to_addr
447 }
448 pub fn trampoline(&self) -> &Allocation {
450 &self.trampoline
451 }
452 pub fn into_trampoline(self) -> Allocation {
454 self.trampoline
455 }
456 pub fn remove(self) {
458 write_current_process_memory(self.fn_addr, &self.overwritten_bytes)
459 }
460}
461
462#[derive(Debug, Error)]
464#[error("winapi error code 0x{0:x}")]
465pub struct WinapiError(pub u32);
466impl WinapiError {
467 pub fn last_error() -> Self {
469 Self(unsafe { GetLastError() })
470 }
471}
472
473#[derive(Debug, Error)]
475pub enum Error {
476 #[error("failed to get module information")]
477 FailedToGetModuleInformation(#[source] WinapiError),
478
479 #[error("failed to load library")]
480 FailedToLoadLibrary(#[source] WinapiError),
481
482 #[error("no function with the provided name exists in the specified library")]
483 NoFunctionWithThatName,
484
485 #[error("failed to generate hook info")]
486 FailedToGenHookInfo(
487 #[source]
488 #[from]
489 hooker::HookError,
490 ),
491}
492
493pub type Result<T> = core::result::Result<T, Error>;