1use super::checksum;
14use crate::config;
15use crate::memory::info::{code_cave, symbol};
16use crate::memory::{image, patch, protection, thread};
17#[cfg(feature = "dev_release")]
18use crate::utils::logger;
19use once_cell::sync::Lazy;
20use parking_lot::Mutex;
21use std::collections::HashMap;
22use std::ffi::c_void;
23use std::ptr;
24use thiserror::Error;
25
26const PAGE_SIZE: usize = 0x4000;
27const TRAMPOLINE_SIZE: usize = 4096;
28const MAX_STOLEN_BYTES: usize = 16;
29const B_RANGE: isize = 128 * 1024 * 1024;
30
31const SAFE_REGS: [u32; 9] = [9, 10, 11, 12, 13, 14, 15, 16, 17];
33
34const MAX_JUNK_INSTRS: usize = 4;
36
37struct HookEntry {
39 target: usize,
41 original: Vec<u8>,
43 trampoline: usize,
45 stolen_size: usize,
47}
48
49static REGISTRY: Lazy<Mutex<HashMap<usize, HookEntry>>> = Lazy::new(|| Mutex::new(HashMap::new()));
50
51#[derive(Error, Debug)]
52pub enum HookError {
54 #[error("Hook exists: {0:#x}")]
56 AlreadyExists(usize),
57 #[error("Image not found: {0}")]
59 ImageBaseNotFound(#[from] crate::memory::info::image::ImageError),
60 #[error("Alloc failed")]
62 AllocationFailed,
63 #[error("Protection failed: {0}")]
65 ProtectionFailed(i32),
66 #[error("Patch failed")]
68 PatchFailed,
69 #[error("Relocation failed")]
71 RelocationFailed,
72 #[error("Thread error: {0}")]
74 ThreadError(#[from] crate::memory::platform::thread::ThreadError),
75 #[error("Symbol error: {0}")]
77 SymbolError(#[from] crate::memory::info::symbol::SymbolError),
78}
79
80pub struct Hook {
82 target: usize,
84 trampoline: usize,
86}
87
88impl Hook {
89 #[inline]
90 pub fn trampoline(&self) -> usize {
95 self.trampoline
96 }
97 #[inline]
98 pub unsafe fn trampoline_as<F>(&self) -> F
106 where
107 F: Copy,
108 {
109 unsafe { std::mem::transmute_copy(&self.trampoline) }
110 }
111 pub fn remove(self) {
113 unsafe {
114 remove_at_address(self.target);
115 }
116 }
117
118 #[inline]
119 pub unsafe fn call_original<T, F, R>(&self, callback: F) -> R
132 where
133 T: Copy,
134 F: FnOnce(T) -> R,
135 {
136 unsafe {
137 let orig: T = std::mem::transmute_copy(&self.trampoline);
138 callback(orig)
139 }
140 }
141
142 #[inline]
147 pub fn verify_integrity(&self) -> bool {
148 checksum::verify(self.target).unwrap_or(false)
149 }
150
151 #[inline]
153 pub fn target(&self) -> usize {
154 self.target
155 }
156}
157
158pub unsafe fn install(rva: usize, replacement: usize) -> Result<Hook, HookError> {
167 unsafe {
168 let image_name = config::get_target_image_name().ok_or_else(|| {
169 crate::memory::info::image::ImageError::NotFound("call mem_init first".to_string())
170 })?;
171 let base = image::get_image_base(&image_name)?;
172 let target = base + rva;
173 let trampoline = install_at_address(target, replacement)?;
174 Ok(Hook { target, trampoline })
175 }
176}
177
178pub unsafe fn hook_symbol(symbol_name: &str, replacement: usize) -> Result<Hook, HookError> {
187 unsafe {
188 let target = symbol::resolve_symbol(symbol_name)?;
189 let trampoline = install_at_address(target, replacement)?;
190 Ok(Hook { target, trampoline })
191 }
192}
193
194pub unsafe fn install_at_address(target: usize, replacement: usize) -> Result<usize, HookError> {
203 unsafe {
204 if REGISTRY.lock().contains_key(&target) {
205 #[cfg(feature = "dev_release")]
206 logger::warning("Hook already exists at target");
207 return Err(HookError::AlreadyExists(target));
208 }
209
210 let first_instr = super::rw::read::<u32>(target).map_err(|_| HookError::PatchFailed)?;
211 if is_b_instruction(first_instr) {
212 #[cfg(feature = "dev_release")]
213 logger::debug("Detected thunk, using short hook");
214 return install_thunk_hook(target, replacement, first_instr);
215 }
216 install_regular_hook(target, replacement)
217 }
218}
219
220fn is_b_instruction(instr: u32) -> bool {
222 (instr >> 26) & 0x3F == 0x05
223}
224
225fn decode_b_target(instr: u32, pc: usize) -> usize {
227 let imm26 = instr & 0x03FFFFFF;
228 let mut offset = (imm26 << 2) as i32;
229 if (offset & (1 << 27)) != 0 {
230 offset |= !0x0FFFFFFF_u32 as i32;
231 }
232 (pc as isize).wrapping_add(offset as isize) as usize
233}
234
235fn encode_b_instruction(from: usize, to: usize) -> Option<u32> {
237 let offset = (to as isize) - (from as isize);
238 if !(-B_RANGE..B_RANGE).contains(&offset) {
239 return None;
240 }
241 Some(0x14000000 | (((offset >> 2) as u32) & 0x03FFFFFF))
242}
243
244fn gen_branch_bytes(dest: usize) -> [u8; 16] {
248 let rand = unsafe { libc::arc4random() };
249 let reg = SAFE_REGS[(rand as usize) % SAFE_REGS.len()];
250 if (rand >> 16) & 1 == 0 {
251 gen_variant_ldr(dest, reg)
252 } else {
253 gen_variant_mov(dest, reg)
254 }
255}
256
257fn gen_variant_ldr(dest: usize, reg: u32) -> [u8; 16] {
259 let mut buf = [0u8; 16];
260 let ldr = 0x58000040u32 | reg; let br = 0xD61F0000u32 | (reg << 5); buf[0..4].copy_from_slice(&ldr.to_le_bytes());
263 buf[4..8].copy_from_slice(&br.to_le_bytes());
264 buf[8..16].copy_from_slice(&dest.to_le_bytes());
265 buf
266}
267
268fn gen_variant_mov(dest: usize, reg: u32) -> [u8; 16] {
270 let mut buf = [0u8; 16];
271 let d = dest as u64;
272 let movz = 0xD2800000u32 | (((d & 0xFFFF) as u32) << 5) | reg;
273 let movk16 = 0xF2A00000u32 | ((((d >> 16) & 0xFFFF) as u32) << 5) | reg;
274 let movk32 = 0xF2C00000u32 | ((((d >> 32) & 0xFFFF) as u32) << 5) | reg;
275 let br = 0xD61F0000u32 | (reg << 5);
276 buf[0..4].copy_from_slice(&movz.to_le_bytes());
277 buf[4..8].copy_from_slice(&movk16.to_le_bytes());
278 buf[8..12].copy_from_slice(&movk32.to_le_bytes());
279 buf[12..16].copy_from_slice(&br.to_le_bytes());
280 buf
281}
282
283#[allow(dead_code)]
286fn gen_junk_instruction() -> u32 {
287 let rand = unsafe { libc::arc4random() };
288 let reg = SAFE_REGS[(rand as usize) % SAFE_REGS.len()];
289
290 match (rand >> 8) % 6 {
291 0 => 0xD503201F,
293 1 => 0xAA0003E0 | reg | (reg << 16),
295 2 => 0x91000000 | reg | (reg << 5),
297 3 => 0xD1000000 | reg | (reg << 5),
299 4 => 0x8A000000 | reg | (reg << 5) | (reg << 16),
302 _ => 0xAA000000 | reg | (reg << 5) | (reg << 16),
304 }
305}
306
307#[allow(dead_code)]
310fn gen_junk_sled() -> (Vec<u8>, usize) {
311 let rand = unsafe { libc::arc4random() };
312 let count = 1 + ((rand as usize) % MAX_JUNK_INSTRS);
313 let mut bytes = Vec::with_capacity(count * 4);
314
315 for _ in 0..count {
316 bytes.extend_from_slice(&gen_junk_instruction().to_le_bytes());
317 }
318
319 (bytes, count)
320}
321
322#[allow(dead_code)]
326fn gen_opaque_always_true() -> [u8; 8] {
327 let mut buf = [0u8; 8];
328 let cmp = 0xEB1F03FF_u32;
330 let bne = 0x54000041_u32;
332 buf[0..4].copy_from_slice(&cmp.to_le_bytes());
333 buf[4..8].copy_from_slice(&bne.to_le_bytes());
334 buf
335}
336
337#[allow(dead_code)]
340fn gen_opaque_always_false() -> [u8; 8] {
341 let mut buf = [0u8; 8];
342 let cmp = 0xEB1F03FF_u32;
344 let beq = 0x54000040_u32;
346 buf[0..4].copy_from_slice(&cmp.to_le_bytes());
347 buf[4..8].copy_from_slice(&beq.to_le_bytes());
348 buf
349}
350
351#[allow(dead_code)]
359fn gen_obfuscated_branch(dest: usize) -> Vec<u8> {
360 let mut bytes = Vec::with_capacity(64);
361 let rand = unsafe { libc::arc4random() };
362
363 let (junk, _) = gen_junk_sled();
364 bytes.extend_from_slice(&junk);
365
366 if (rand >> 16) & 1 == 1 {
367 bytes.extend_from_slice(&gen_opaque_always_true());
368 }
369
370 let branch = gen_branch_bytes(dest);
371 bytes.extend_from_slice(&branch);
372
373 bytes
374}
375
376#[inline]
379unsafe fn emit_obfuscated_branch(addr: usize, dest: usize) -> usize {
380 unsafe {
381 let bytes = gen_obfuscated_branch(dest);
382 let len = bytes.len();
383 ptr::copy_nonoverlapping(bytes.as_ptr(), addr as *mut u8, len);
384 len
385 }
386}
387
388unsafe fn install_thunk_hook(
390 target: usize,
391 replacement: usize,
392 first_instr: u32,
393) -> Result<usize, HookError> {
394 unsafe {
395 let suspended = thread::suspend_other_threads()?;
396 let original_target = decode_b_target(first_instr, target);
397 let trampoline = alloc_trampoline_near(target).ok_or(HookError::AllocationFailed)?;
398 let trampoline_base = trampoline as usize;
399
400 if encode_b_instruction(target, trampoline_base).is_none() {
401 libc::munmap(trampoline, TRAMPOLINE_SIZE);
402 thread::resume_threads(&suspended);
403 return install_regular_hook(target, replacement);
404 }
405
406 let can_direct = encode_b_instruction(target, replacement).is_some();
407 let first_len = emit_obfuscated_branch(trampoline_base, original_target);
408 if !can_direct {
409 emit_obfuscated_branch(trampoline_base + first_len, replacement);
410 }
411
412 if protection::protect(
413 trampoline_base,
414 TRAMPOLINE_SIZE,
415 protection::PageProtection::read_execute(),
416 )
417 .is_err()
418 {
419 libc::munmap(trampoline, TRAMPOLINE_SIZE);
420 thread::resume_threads(&suspended);
421 return Err(HookError::ProtectionFailed(0));
422 }
423 patch::invalidate_icache(trampoline, TRAMPOLINE_SIZE);
424
425 let mut original = vec![0u8; 4];
426 ptr::copy_nonoverlapping(target as *const u8, original.as_mut_ptr(), 4);
427
428 let b_instr = if can_direct {
429 encode_b_instruction(target, replacement).unwrap()
430 } else {
431 encode_b_instruction(target, trampoline_base + 16).unwrap()
432 };
433
434 if !patch_short(target, b_instr) {
435 libc::munmap(trampoline, TRAMPOLINE_SIZE);
436 thread::resume_threads(&suspended);
437 return Err(HookError::PatchFailed);
438 }
439
440 REGISTRY.lock().insert(
441 target,
442 HookEntry {
443 target,
444 original,
445 trampoline: trampoline_base,
446 stolen_size: 4,
447 },
448 );
449 let _ = checksum::register(target, 4);
450 thread::resume_threads(&suspended);
451 #[cfg(feature = "dev_release")]
452 logger::debug("Thunk hook installed");
453 Ok(trampoline_base)
454 }
455}
456
457unsafe fn install_regular_hook(target: usize, replacement: usize) -> Result<usize, HookError> {
459 unsafe {
460 let suspended = thread::suspend_other_threads()?;
461 let trampoline = alloc_trampoline().ok_or(HookError::AllocationFailed)?;
462 if trampoline.is_null() {
463 thread::resume_threads(&suspended);
464 return Err(HookError::AllocationFailed);
465 }
466 let trampoline_base = trampoline as usize;
467
468 let mut original = vec![0u8; MAX_STOLEN_BYTES];
469 ptr::copy_nonoverlapping(target as *const u8, original.as_mut_ptr(), MAX_STOLEN_BYTES);
470
471 let mut trampoline_offset = 0;
472 for i in 0..4 {
473 let instr = super::rw::read::<u32>(target + i * 4).unwrap_or(0);
474 if let Some(size) =
475 relocate_instruction(instr, target + i * 4, trampoline_base + trampoline_offset)
476 {
477 trampoline_offset += size;
478 } else {
479 libc::munmap(trampoline, TRAMPOLINE_SIZE);
480 thread::resume_threads(&suspended);
481 return Err(HookError::RelocationFailed);
482 }
483 }
484
485 emit_obfuscated_branch(
486 trampoline_base + trampoline_offset,
487 target + MAX_STOLEN_BYTES,
488 );
489
490 if protection::protect(
491 trampoline_base,
492 TRAMPOLINE_SIZE,
493 protection::PageProtection::read_execute(),
494 )
495 .is_err()
496 {
497 libc::munmap(trampoline, TRAMPOLINE_SIZE);
498 thread::resume_threads(&suspended);
499 return Err(HookError::ProtectionFailed(0));
500 }
501 patch::invalidate_icache(trampoline, TRAMPOLINE_SIZE);
502
503 if !patch_code(target, replacement) {
504 libc::munmap(trampoline, TRAMPOLINE_SIZE);
505 thread::resume_threads(&suspended);
506 return Err(HookError::PatchFailed);
507 }
508
509 REGISTRY.lock().insert(
510 target,
511 HookEntry {
512 target,
513 original,
514 trampoline: trampoline_base,
515 stolen_size: MAX_STOLEN_BYTES,
516 },
517 );
518 let _ = checksum::register(target, MAX_STOLEN_BYTES);
519 thread::resume_threads(&suspended);
520 #[cfg(feature = "dev_release")]
521 logger::debug("Hook installed");
522 Ok(trampoline_base)
523 }
524}
525
526pub unsafe fn remove(rva: usize) -> bool {
534 unsafe {
535 let image_name = match config::get_target_image_name() {
536 Some(n) => n,
537 None => return false,
538 };
539 match image::get_image_base(&image_name) {
540 Ok(base) => remove_at_address(base + rva),
541 Err(_) => false,
542 }
543 }
544}
545
546pub unsafe fn remove_at_address(target: usize) -> bool {
554 unsafe {
555 let entry = match REGISTRY.lock().remove(&target) {
556 Some(e) => e,
557 None => return false,
558 };
559
560 let suspended = match thread::suspend_other_threads() {
561 Ok(s) => s,
562 Err(_) => return false,
563 };
564
565 let _ = patch::stealth_write(entry.target, &entry.original[..entry.stolen_size]);
566
567 libc::munmap(entry.trampoline as *mut c_void, TRAMPOLINE_SIZE);
568 checksum::unregister(target);
569 thread::resume_threads(&suspended);
570 #[cfg(feature = "dev_release")]
571 logger::debug("Hook removed");
572 true
573 }
574}
575
576pub fn restore_hook_bytes(target: usize) -> bool {
587 let registry = REGISTRY.lock();
588 let entry = match registry.get(&target) {
589 Some(e) => e,
590 None => return false,
591 };
592
593 let trampoline = entry.trampoline;
594 let stolen_size = entry.stolen_size;
595 drop(registry);
596
597 unsafe {
598 if stolen_size == 4 {
599 let offset = (trampoline as isize) - (target as isize);
600 if (-B_RANGE..B_RANGE).contains(&offset) {
601 let b_instr = 0x14000000 | (((offset >> 2) as u32) & 0x03FFFFFF);
602 return patch::stealth_write(target, &b_instr.to_le_bytes()).is_ok();
603 }
604 } else {
605 let bytes = gen_branch_bytes(trampoline + 16);
606 return patch::stealth_write(target, &bytes).is_ok();
607 }
608 }
609
610 false
611}
612
613#[inline]
615unsafe fn alloc_trampoline() -> Option<*mut c_void> {
616 unsafe {
617 let ptr = libc::mmap(
618 ptr::null_mut(),
619 TRAMPOLINE_SIZE,
620 libc::PROT_READ | libc::PROT_WRITE,
621 libc::MAP_PRIVATE | libc::MAP_ANON,
622 -1,
623 0,
624 );
625 if ptr == libc::MAP_FAILED {
626 None
627 } else {
628 Some(ptr)
629 }
630 }
631}
632
633#[inline]
635unsafe fn alloc_trampoline_near(target: usize) -> Option<*mut c_void> {
636 unsafe {
637 let search_range = B_RANGE as usize - TRAMPOLINE_SIZE;
638 for offset in (0..search_range).step_by(0x100000) {
639 let hint = target.saturating_sub(offset);
640 let hint_aligned = (hint & !(PAGE_SIZE - 1)) as *mut c_void;
641 let ptr = libc::mmap(
642 hint_aligned,
643 TRAMPOLINE_SIZE,
644 libc::PROT_READ | libc::PROT_WRITE,
645 libc::MAP_PRIVATE | libc::MAP_ANON,
646 -1,
647 0,
648 );
649 if ptr != libc::MAP_FAILED && (ptr as isize - target as isize).abs() < B_RANGE {
650 return Some(ptr);
651 }
652 if ptr != libc::MAP_FAILED {
653 libc::munmap(ptr, TRAMPOLINE_SIZE);
654 }
655 }
656 for offset in (0..search_range).step_by(0x100000) {
657 let hint = target.saturating_add(offset);
658 let hint_aligned = (hint & !(PAGE_SIZE - 1)) as *mut c_void;
659 let ptr = libc::mmap(
660 hint_aligned,
661 TRAMPOLINE_SIZE,
662 libc::PROT_READ | libc::PROT_WRITE,
663 libc::MAP_PRIVATE | libc::MAP_ANON,
664 -1,
665 0,
666 );
667 if ptr != libc::MAP_FAILED && (ptr as isize - target as isize).abs() < B_RANGE {
668 return Some(ptr);
669 }
670 if ptr != libc::MAP_FAILED {
671 libc::munmap(ptr, TRAMPOLINE_SIZE);
672 }
673 }
674 alloc_trampoline()
675 }
676}
677
678unsafe fn patch_short(target: usize, instr: u32) -> bool {
680 unsafe { patch::stealth_write(target, &instr.to_le_bytes()).is_ok() }
681}
682
683unsafe fn patch_code(target: usize, dest: usize) -> bool {
685 unsafe {
686 let bytes = gen_branch_bytes(dest);
687 patch::stealth_write(target, &bytes).is_ok()
688 }
689}
690
691#[inline]
693unsafe fn emit_branch(addr: usize, dest: usize) {
694 unsafe {
695 let bytes = gen_branch_bytes(dest);
696 ptr::copy_nonoverlapping(bytes.as_ptr(), addr as *mut u8, 16);
697 }
698}
699
700unsafe fn relocate_instruction(instr: u32, pc: usize, tramp: usize) -> Option<usize> {
702 unsafe {
703 let op24 = (instr >> 24) & 0x9F;
704 let op26 = (instr >> 26) & 0x3F;
705 let rd = (instr & 0x1F) as u8;
706
707 let is_adr = op24 == 0x10;
708 if is_adr || op24 == 0x90 {
709 let immlo = (instr >> 29) & 0x3;
710 let immhi = (instr >> 5) & 0x7FFFF;
711 let mut imm = (immhi << 2) | immlo;
712 if (imm & (1 << 20)) != 0 {
713 imm |= !0xFFFFF;
714 }
715 let target_val = if is_adr {
716 (pc as isize).wrapping_add(imm as isize) as usize
717 } else {
718 let pc_page = pc & !0xFFF;
719 (pc_page as isize).wrapping_add((imm as isize) << 12) as usize
720 };
721 ptr::write(tramp as *mut u32, 0x58000040 | (rd as u32));
722 ptr::write((tramp + 4) as *mut u32, 0x14000003);
723 ptr::write((tramp + 8) as *mut usize, target_val);
724 return Some(16);
725 }
726
727 let op_check = instr & 0x3B000000;
728 if op_check == 0x18000000 || op_check == 0x58000000 || op_check == 0x98000000 {
729 let imm19 = (instr >> 5) & 0x7FFFF;
730 let mut offset = imm19 << 2;
731 if (offset & (1 << 20)) != 0 {
732 offset |= !0xFFFFF;
733 }
734 let target_addr = (pc as isize).wrapping_add(offset as isize) as usize;
735 let ldr_reg_opcode = if op_check == 0x18000000 {
736 0xB9400000 | (rd as u32)
737 } else if op_check == 0x58000000 {
738 0xF9400000 | (rd as u32)
739 } else {
740 0xB9800000 | (rd as u32)
741 };
742 ptr::write(tramp as *mut u32, 0x58000071);
743 ptr::write((tramp + 4) as *mut u32, ldr_reg_opcode | (17 << 5));
744 ptr::write((tramp + 8) as *mut u32, 0x14000003);
745 ptr::write((tramp + 12) as *mut usize, target_addr);
746 return Some(20);
747 }
748
749 if op26 == 0x05 || op26 == 0x25 {
750 let imm26 = instr & 0x03FFFFFF;
751 let mut offset = imm26 << 2;
752 if (offset & (1 << 27)) != 0 {
753 offset |= !0x0FFFFFFF;
754 }
755 let target_addr = (pc as isize).wrapping_add(offset as isize) as usize;
756 if op26 == 0x25 {
757 ptr::write(tramp as *mut u32, 0x100000BE);
758 emit_branch(tramp + 4, target_addr);
759 return Some(20);
760 } else {
761 emit_branch(tramp, target_addr);
762 return Some(16);
763 }
764 }
765
766 let op_byte = (instr >> 24) & 0xFF;
767 let is_b_cond = op_byte == 0x54;
768 let is_cbz_cbnz = matches!(op_byte, 0x34 | 0xB4 | 0x35 | 0xB5);
769 let is_tbz_tbnz = matches!(op_byte, 0x36 | 0xB6 | 0x37 | 0xB7);
770
771 if is_b_cond || is_cbz_cbnz || is_tbz_tbnz {
772 let target_addr = if is_b_cond || is_cbz_cbnz {
773 let imm19 = (instr >> 5) & 0x7FFFF;
774 let offset = if (imm19 & (1 << 18)) != 0 {
775 ((imm19 | 0xFFF80000) as i32) as isize
776 } else {
777 imm19 as isize
778 };
779 (pc as isize).wrapping_add(offset * 4) as usize
780 } else {
781 let imm14 = (instr >> 5) & 0x3FFF;
782 let offset = if (imm14 & (1 << 13)) != 0 {
783 ((imm14 | 0xFFFFC000) as i32) as isize
784 } else {
785 imm14 as isize
786 };
787 (pc as isize).wrapping_add(offset * 4) as usize
788 };
789 let inverted = if is_b_cond {
790 ((instr & 0xFF00000F) ^ 1) | (5 << 5)
791 } else if is_cbz_cbnz {
792 ((instr & 0xFF00001F) ^ (1 << 24)) | (5 << 5)
793 } else {
794 ((instr & 0xFFF8001F) ^ (1 << 24)) | (5 << 5)
795 };
796 ptr::write(tramp as *mut u32, inverted);
797 ptr::write((tramp + 4) as *mut u32, 0x58000051);
798 ptr::write((tramp + 8) as *mut u32, 0xD61F0220);
799 ptr::write((tramp + 12) as *mut usize, target_addr);
800 return Some(20);
801 }
802
803 ptr::write(tramp as *mut u32, instr);
804 Some(4)
805 }
806}
807
808pub unsafe fn install_in_cave(rva: usize, replacement: usize) -> Result<Hook, HookError> {
817 unsafe {
818 let image_name = config::get_target_image_name().ok_or_else(|| {
819 crate::memory::info::image::ImageError::NotFound("call mem_init first".to_string())
820 })?;
821 let base = image::get_image_base(&image_name)?;
822 let target = base + rva;
823 install_in_cave_at_address(target, replacement)
824 }
825}
826
827pub unsafe fn install_in_cave_at_address(
836 target: usize,
837 replacement: usize,
838) -> Result<Hook, HookError> {
839 unsafe {
840 if REGISTRY.lock().contains_key(&target) {
841 return Err(HookError::AlreadyExists(target));
842 }
843
844 let cave =
845 code_cave::allocate_cave_near(target, 256).map_err(|_| HookError::AllocationFailed)?;
846
847 let trampoline_base = (cave.address + 3) & !3;
848 let suspended = thread::suspend_other_threads()?;
849
850 let mut original = vec![0u8; MAX_STOLEN_BYTES];
851 ptr::copy_nonoverlapping(target as *const u8, original.as_mut_ptr(), MAX_STOLEN_BYTES);
852
853 let mut tramp_buf = vec![0u8; 256];
854 let buf_base = tramp_buf.as_mut_ptr() as usize;
855
856 let mut cave_offset = 0;
857 for i in 0..4 {
858 let instr = super::rw::read::<u32>(target + i * 4).unwrap_or(0);
859 if let Some(size) = relocate_instruction(instr, target + i * 4, buf_base + cave_offset)
860 {
861 cave_offset += size;
862 } else {
863 code_cave::free_cave(cave.address).ok();
864 thread::resume_threads(&suspended);
865 return Err(HookError::RelocationFailed);
866 }
867 }
868
869 cave_offset += emit_obfuscated_branch(buf_base + cave_offset, target + MAX_STOLEN_BYTES);
870
871 if patch::stealth_write(trampoline_base, &tramp_buf[..cave_offset]).is_err() {
872 code_cave::free_cave(cave.address).ok();
873 thread::resume_threads(&suspended);
874 return Err(HookError::PatchFailed);
875 }
876
877 if !patch_code(target, replacement) {
878 code_cave::free_cave(cave.address).ok();
879 thread::resume_threads(&suspended);
880 return Err(HookError::PatchFailed);
881 }
882
883 REGISTRY.lock().insert(
884 target,
885 HookEntry {
886 target,
887 original,
888 trampoline: trampoline_base,
889 stolen_size: MAX_STOLEN_BYTES,
890 },
891 );
892 let _ = checksum::register(target, MAX_STOLEN_BYTES);
893
894 thread::resume_threads(&suspended);
895 #[cfg(feature = "dev_release")]
896 logger::debug("Cave hook installed");
897
898 Ok(Hook {
899 target,
900 trampoline: trampoline_base,
901 })
902 }
903}
904
905pub fn hook_count() -> usize {
910 REGISTRY.lock().len()
911}
912
913pub fn list_hooks() -> Vec<usize> {
918 REGISTRY.lock().keys().copied().collect()
919}
920
921pub fn is_hooked(target: usize) -> bool {
929 REGISTRY.lock().contains_key(&target)
930}