wraith/manipulation/inline_hook/hook/
vmt.rs1#[cfg(all(not(feature = "std"), feature = "alloc"))]
26use alloc::{boxed::Box, format, string::String, vec, vec::Vec};
27
28#[cfg(feature = "std")]
29use std::{boxed::Box, format, string::String, vec, vec::Vec};
30
31use crate::error::{Result, WraithError};
32use crate::util::memory::ProtectionGuard;
33use core::marker::PhantomData;
34
35const PAGE_READWRITE: u32 = 0x04;
36
37pub struct VmtHook {
42 vtable_entry: usize,
44 original: usize,
46 detour: usize,
48 active: bool,
50 auto_restore: bool,
52}
53
54impl VmtHook {
55 pub unsafe fn new(object: *const (), index: usize, detour: usize) -> Result<Self> {
73 if object.is_null() {
74 return Err(WraithError::NullPointer { context: "object" });
75 }
76
77 let vptr = unsafe { *(object as *const usize) };
79 if vptr == 0 {
80 return Err(WraithError::NullPointer { context: "vptr" });
81 }
82
83 Self::new_at_vtable(vptr, index, detour)
84 }
85
86 pub fn new_at_vtable(vtable: usize, index: usize, detour: usize) -> Result<Self> {
93 if vtable == 0 {
94 return Err(WraithError::NullPointer { context: "vtable" });
95 }
96
97 let ptr_size = core::mem::size_of::<usize>();
98 let vtable_entry = vtable + index * ptr_size;
99
100 let original = unsafe { *(vtable_entry as *const usize) };
103
104 let mut hook = Self {
105 vtable_entry,
106 original,
107 detour,
108 active: false,
109 auto_restore: true,
110 };
111
112 hook.install()?;
113 Ok(hook)
114 }
115
116 pub fn install(&mut self) -> Result<()> {
118 if self.active {
119 return Ok(());
120 }
121
122 write_vtable_entry(self.vtable_entry, self.detour)?;
123 self.active = true;
124
125 Ok(())
126 }
127
128 pub fn uninstall(&mut self) -> Result<()> {
130 if !self.active {
131 return Ok(());
132 }
133
134 write_vtable_entry(self.vtable_entry, self.original)?;
135 self.active = false;
136
137 Ok(())
138 }
139
140 pub fn is_active(&self) -> bool {
142 self.active
143 }
144
145 pub fn original(&self) -> usize {
147 self.original
148 }
149
150 pub fn detour(&self) -> usize {
152 self.detour
153 }
154
155 pub fn vtable_entry(&self) -> usize {
157 self.vtable_entry
158 }
159
160 pub fn set_auto_restore(&mut self, restore: bool) {
162 self.auto_restore = restore;
163 }
164
165 pub fn leak(mut self) {
167 self.auto_restore = false;
168 core::mem::forget(self);
169 }
170
171 pub fn restore(mut self) -> Result<()> {
173 self.uninstall()?;
174 self.auto_restore = false;
175 Ok(())
176 }
177}
178
179impl Drop for VmtHook {
180 fn drop(&mut self) {
181 if self.auto_restore && self.active {
182 let _ = self.uninstall();
183 }
184 }
185}
186
187unsafe impl Send for VmtHook {}
189unsafe impl Sync for VmtHook {}
190
191pub struct ShadowVmt<T: ?Sized = ()> {
197 object: *mut (),
199 original_vtable: usize,
201 shadow_vtable: Box<[usize]>,
203 hooks: Vec<(usize, usize)>,
205 auto_restore: bool,
207 _marker: PhantomData<T>,
209}
210
211impl<T: ?Sized> ShadowVmt<T> {
212 pub unsafe fn new(object: *mut (), vtable_size: usize) -> Result<Self> {
234 if object.is_null() {
235 return Err(WraithError::NullPointer { context: "object" });
236 }
237
238 if vtable_size == 0 {
239 return Err(WraithError::InvalidPeFormat {
240 reason: "vtable_size cannot be 0".into(),
241 });
242 }
243
244 let original_vtable = unsafe { *(object as *const usize) };
246 if original_vtable == 0 {
247 return Err(WraithError::NullPointer { context: "vptr" });
248 }
249
250 let mut shadow = Vec::with_capacity(vtable_size);
252 for i in 0..vtable_size {
253 let entry_addr = original_vtable + i * core::mem::size_of::<usize>();
254 let entry = unsafe { *(entry_addr as *const usize) };
256 shadow.push(entry);
257 }
258 let shadow_vtable = shadow.into_boxed_slice();
259
260 unsafe {
263 *(object as *mut usize) = shadow_vtable.as_ptr() as usize;
264 }
265
266 Ok(Self {
267 object,
268 original_vtable,
269 shadow_vtable,
270 hooks: Vec::new(),
271 auto_restore: true,
272 _marker: PhantomData,
273 })
274 }
275
276 pub fn hook(&mut self, index: usize, detour: usize) -> Result<()> {
282 if index >= self.shadow_vtable.len() {
283 return Err(WraithError::InvalidPeFormat {
284 reason: format!(
285 "vtable index {} out of bounds (size {})",
286 index,
287 self.shadow_vtable.len()
288 ),
289 });
290 }
291
292 if !self.hooks.iter().any(|(i, _)| *i == index) {
294 self.hooks.push((index, self.shadow_vtable[index]));
295 }
296
297 self.shadow_vtable[index] = detour;
299
300 Ok(())
301 }
302
303 pub fn unhook(&mut self, index: usize) -> Result<()> {
305 if let Some(pos) = self.hooks.iter().position(|(i, _)| *i == index) {
306 let (_, original) = self.hooks.remove(pos);
307 if index < self.shadow_vtable.len() {
308 self.shadow_vtable[index] = original;
309 }
310 }
311 Ok(())
312 }
313
314 pub fn unhook_all(&mut self) {
316 for (index, original) in self.hooks.drain(..) {
317 if index < self.shadow_vtable.len() {
318 self.shadow_vtable[index] = original;
319 }
320 }
321 }
322
323 pub fn original(&self, index: usize) -> Option<usize> {
325 for (i, original) in &self.hooks {
327 if *i == index {
328 return Some(*original);
329 }
330 }
331 self.shadow_vtable.get(index).copied()
333 }
334
335 pub fn original_vtable(&self) -> usize {
337 self.original_vtable
338 }
339
340 pub fn shadow_vtable(&self) -> usize {
342 self.shadow_vtable.as_ptr() as usize
343 }
344
345 pub fn vtable_size(&self) -> usize {
347 self.shadow_vtable.len()
348 }
349
350 pub fn is_hooked(&self, index: usize) -> bool {
352 self.hooks.iter().any(|(i, _)| *i == index)
353 }
354
355 pub fn hook_count(&self) -> usize {
357 self.hooks.len()
358 }
359
360 pub fn set_auto_restore(&mut self, restore: bool) {
362 self.auto_restore = restore;
363 }
364
365 pub fn restore(mut self) -> Result<()> {
367 self.restore_internal()?;
368 self.auto_restore = false;
369 Ok(())
370 }
371
372 fn restore_internal(&mut self) -> Result<()> {
373 unsafe {
376 *(self.object as *mut usize) = self.original_vtable;
377 }
378 Ok(())
379 }
380}
381
382impl<T: ?Sized> Drop for ShadowVmt<T> {
383 fn drop(&mut self) {
384 if self.auto_restore {
385 let _ = self.restore_internal();
386 }
387 }
388}
389
390unsafe impl<T: ?Sized> Send for ShadowVmt<T> {}
392unsafe impl<T: ?Sized> Sync for ShadowVmt<T> {}
393
394pub type VmtHookGuard = VmtHook;
396
397pub unsafe fn get_vtable(object: *const ()) -> Result<usize> {
402 if object.is_null() {
403 return Err(WraithError::NullPointer { context: "object" });
404 }
405
406 let vptr = unsafe { *(object as *const usize) };
408 if vptr == 0 {
409 return Err(WraithError::NullPointer { context: "vptr" });
410 }
411
412 Ok(vptr)
413}
414
415pub unsafe fn get_vtable_entry(vtable: usize, index: usize) -> Result<usize> {
420 if vtable == 0 {
421 return Err(WraithError::NullPointer { context: "vtable" });
422 }
423
424 let entry_addr = vtable + index * core::mem::size_of::<usize>();
425 let entry = unsafe { *(entry_addr as *const usize) };
427
428 Ok(entry)
429}
430
431pub unsafe fn estimate_vtable_size(vtable: usize, max_scan: usize) -> usize {
436 if vtable == 0 {
437 return 0;
438 }
439
440 let mut count = 0;
441 for i in 0..max_scan {
442 let entry_addr = vtable + i * core::mem::size_of::<usize>();
443
444 let entry = unsafe { *(entry_addr as *const usize) };
446
447 if entry == 0 {
450 break;
451 }
452
453 #[cfg(target_arch = "x86_64")]
455 {
456 if entry < 0x10000 || entry > 0x7FFF_FFFF_FFFF {
457 break;
458 }
459 }
460
461 #[cfg(target_arch = "x86")]
462 {
463 if entry < 0x10000 {
464 break;
465 }
466 }
467
468 count = i + 1;
469 }
470
471 count
472}
473
474fn write_vtable_entry(entry: usize, value: usize) -> Result<()> {
476 let _guard = ProtectionGuard::new(entry, core::mem::size_of::<usize>(), PAGE_READWRITE)?;
477
478 unsafe {
480 *(entry as *mut usize) = value;
481 }
482
483 Ok(())
484}
485
486pub trait VmtObject {
488 fn vtable(&self) -> usize {
490 unsafe { *(self as *const Self as *const usize) }
492 }
493}
494
495pub struct VmtHookBuilder {
497 object: Option<*const ()>,
498 vtable: Option<usize>,
499 index: Option<usize>,
500 detour: Option<usize>,
501}
502
503impl VmtHookBuilder {
504 pub fn new() -> Self {
506 Self {
507 object: None,
508 vtable: None,
509 index: None,
510 detour: None,
511 }
512 }
513
514 pub unsafe fn object(mut self, object: *const ()) -> Self {
519 self.object = Some(object);
520 self
521 }
522
523 pub fn vtable(mut self, vtable: usize) -> Self {
525 self.vtable = Some(vtable);
526 self
527 }
528
529 pub fn index(mut self, index: usize) -> Self {
531 self.index = Some(index);
532 self
533 }
534
535 pub fn detour(mut self, detour: usize) -> Self {
537 self.detour = Some(detour);
538 self
539 }
540
541 pub fn build(self) -> Result<VmtHook> {
543 let vtable = if let Some(vt) = self.vtable {
544 vt
545 } else if let Some(obj) = self.object {
546 unsafe { get_vtable(obj)? }
547 } else {
548 return Err(WraithError::NullPointer {
549 context: "neither object nor vtable set",
550 });
551 };
552
553 let index = self.index.ok_or(WraithError::NullPointer {
554 context: "index not set",
555 })?;
556
557 let detour = self.detour.ok_or(WraithError::NullPointer {
558 context: "detour not set",
559 })?;
560
561 VmtHook::new_at_vtable(vtable, index, detour)
562 }
563}
564
565impl Default for VmtHookBuilder {
566 fn default() -> Self {
567 Self::new()
568 }
569}
570
571#[cfg(test)]
572mod tests {
573 use super::*;
574
575 #[repr(C)]
577 struct TestVtable {
578 func1: usize,
579 func2: usize,
580 func3: usize,
581 }
582
583 #[repr(C)]
584 struct TestObject {
585 vptr: *const TestVtable,
586 }
587
588 extern "C" fn test_func1() -> i32 {
589 1
590 }
591 extern "C" fn test_func2() -> i32 {
592 2
593 }
594 extern "C" fn test_func3() -> i32 {
595 3
596 }
597
598 #[test]
599 fn test_get_vtable() {
600 static VTABLE: TestVtable = TestVtable {
601 func1: test_func1 as usize,
602 func2: test_func2 as usize,
603 func3: test_func3 as usize,
604 };
605
606 let obj = TestObject {
607 vptr: &VTABLE,
608 };
609
610 let vptr = unsafe { get_vtable(&obj as *const _ as *const ()) }
611 .expect("should get vtable");
612
613 assert_eq!(vptr, &VTABLE as *const _ as usize);
614 }
615
616 #[test]
617 fn test_get_vtable_entry() {
618 static VTABLE: TestVtable = TestVtable {
619 func1: test_func1 as usize,
620 func2: test_func2 as usize,
621 func3: test_func3 as usize,
622 };
623
624 let vtable = &VTABLE as *const _ as usize;
625
626 let entry0 = unsafe { get_vtable_entry(vtable, 0) }.expect("should get entry");
627 let entry1 = unsafe { get_vtable_entry(vtable, 1) }.expect("should get entry");
628
629 assert_eq!(entry0, test_func1 as usize);
630 assert_eq!(entry1, test_func2 as usize);
631 }
632
633 #[test]
634 fn test_estimate_vtable_size() {
635 static VTABLE: [usize; 5] = [
636 test_func1 as usize,
637 test_func2 as usize,
638 test_func3 as usize,
639 0, 0,
641 ];
642
643 let size = unsafe { estimate_vtable_size(VTABLE.as_ptr() as usize, 10) };
644 assert_eq!(size, 3);
645 }
646}